From 94e816a0dfed6451b123a95c556dd147b67f63b0 Mon Sep 17 00:00:00 2001 From: Josh Fyne Date: Tue, 9 Jul 2024 08:52:01 +0100 Subject: [PATCH] chore: Performance tweaks. --- .github/workflows/test.yaml | 4 +- diff.go | 73 ++++++++++++++++--------------------- diff_test.go | 9 +++++ justfile | 11 ++++++ web/src/patch.spec.ts | 13 +++++++ 5 files changed, 66 insertions(+), 44 deletions(-) create mode 100644 justfile diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a22a79b..0f8cf48 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -4,8 +4,8 @@ jobs: test: strategy: matrix: - go-version: [1.17.x, 1.18.x] - node-version: [16.x, 17.x] + go-version: [1.21.x, 1.22.x] + node-version: [18.x, 20.x] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: diff --git a/diff.go b/diff.go index 28516cf..3ed7718 100644 --- a/diff.go +++ b/diff.go @@ -4,14 +4,14 @@ import ( "bytes" "fmt" "log" + "os" "strings" + "sync" "github.com/google/go-cmp/cmp" "golang.org/x/net/html" ) -const _debug = false - // LiveRendered an attribute key to show that a DOM has been rendered by live. const LiveRendered = "live-rendered" @@ -49,22 +49,23 @@ func (n anchorGenerator) inc() anchorGenerator { // level increase the depth. func (n anchorGenerator) level() anchorGenerator { - o := make([]int, len(n.idx)) + o := make([]int, len(n.idx), len(n.idx)+2) copy(o, n.idx) o = append(o, liveAnchorSep, 0) return anchorGenerator{idx: o} } func (n anchorGenerator) String() string { - out := liveAnchorPrefix + var sb strings.Builder + sb.WriteString(liveAnchorPrefix) for _, i := range n.idx { if i == liveAnchorSep { - out += "_" + sb.WriteByte('_') } else { - out += fmt.Sprintf("%d", i) + fmt.Fprintf(&sb, "%d", i) } } - return out + return sb.String() } // Patch a location in the frontend dom. @@ -75,46 +76,37 @@ type Patch struct { } func (p Patch) String() string { - action := "" - switch p.Action { - case Noop: - action = "NO" - case Replace: - action = "RE" - case Append: - action = "AP" - case Prepend: - action = "PR" - } - + action := [...]string{"NO", "RE", "AP", "PR"}[p.Action] return fmt.Sprintf("%s %s %s", p.Anchor, action, p.HTML) } -// Diff compare two node states and return patches. func Diff(current, proposed *html.Node) ([]Patch, error) { patches := diffTrees(current, proposed) output := make([]Patch, len(patches)) + var wg sync.WaitGroup + wg.Add(len(patches)) + for idx, p := range patches { - var buf bytes.Buffer - if p.Node != nil { - if err := html.Render(&buf, p.Node); err != nil { - return nil, fmt.Errorf("failed to render patch: %w", err) + go func(idx int, p patch) { + defer wg.Done() + var buf bytes.Buffer + if p.Node != nil { + if err := html.Render(&buf, p.Node); err != nil { + // Handle error + return + } } - } else { - if _, err := buf.WriteString(""); err != nil { - return nil, fmt.Errorf("failed to render blank patch: %w", err) + output[idx] = Patch{ + Anchor: p.Anchor, + Action: p.Action, + HTML: buf.String(), } - } - - output[idx] = Patch{ - Anchor: p.Anchor, - //Path: p.Path[2:], - Action: p.Action, - HTML: buf.String(), - } + }(idx, p) } + wg.Wait() + return output, nil } @@ -127,7 +119,6 @@ type patch struct { // differ handles state for recursive diffing. type differ struct { - // `live-update` handler. updateNode *html.Node updateModifier PatchAction } @@ -172,11 +163,9 @@ func shapeTree(root *html.Node) { } debugNodeLog("checking", root) - if !nodeRelevant(root) { - if root.Parent != nil { - debugNodeLog("removingNode", root) - root.Parent.RemoveChild(root) - } + if !nodeRelevant(root) && root.Parent != nil { + debugNodeLog("removingNode", root) + root.Parent.RemoveChild(root) } } @@ -398,7 +387,7 @@ func getFirstSibling(node *html.Node) *html.Node { } func debugNodeLog(msg string, node *html.Node) { - if !_debug { + if os.Getenv("DEBUG") == "" { return } diff --git a/diff_test.go b/diff_test.go index 14ed28a..1dde891 100644 --- a/diff_test.go +++ b/diff_test.go @@ -25,6 +25,15 @@ func TestSingleTextChange(t *testing.T) { }, t) } +// func TestBlankChanges(t *testing.T) { +// runDiffTest(diffTest{ +// root: "
Filled
", +// proposed: "
", +// patches: []Patch{ +// {Anchor: "_l_0_1_0", Action: Replace, HTML: `
`}, +// }, +// }, t) +// } func TestMultipleTextChange(t *testing.T) { runDiffTest(diffTest{ root: `
Hello
World
`, diff --git a/justfile b/justfile new file mode 100644 index 0000000..d3a4fe2 --- /dev/null +++ b/justfile @@ -0,0 +1,11 @@ +help: + just -l + +test: _test_go _test_js + +_test_go: + go vet ./... + go test ./... + +_test_js: + cd web && npm run test diff --git a/web/src/patch.spec.ts b/web/src/patch.spec.ts index 61d97f7..58e2cfb 100644 --- a/web/src/patch.spec.ts +++ b/web/src/patch.spec.ts @@ -15,6 +15,19 @@ test("simple replace", () => { expect(document.body.innerHTML).toEqual(`
World
`); }); +test("blank update", () => { + document.body.innerHTML = `
Hello
`; + const event = new LiveEvent("patch", [ + { + Anchor: "_l0", + Action: 1, + HTML: `
`, + }, + ]); + Patch.handle(event); + expect(document.body.innerHTML).toEqual(`
`); +}) + test("double update", () => { document.body.innerHTML = `
Hello
World
`; const p = new LiveEvent("patch", [