Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Performance tweaks. #71

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
73 changes: 31 additions & 42 deletions diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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.
Expand All @@ -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
}

Expand All @@ -127,7 +119,6 @@ type patch struct {

// differ handles state for recursive diffing.
type differ struct {
// `live-update` handler.
updateNode *html.Node
updateModifier PatchAction
}
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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
}

Expand Down
9 changes: 9 additions & 0 deletions diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ func TestSingleTextChange(t *testing.T) {
}, t)
}

// func TestBlankChanges(t *testing.T) {
// runDiffTest(diffTest{
// root: "<div>Filled</div>",
// proposed: "<div></div>",
// patches: []Patch{
// {Anchor: "_l_0_1_0", Action: Replace, HTML: `<div _l_0_1_0=""></div>`},
// },
// }, t)
// }
func TestMultipleTextChange(t *testing.T) {
runDiffTest(diffTest{
root: `<div>Hello</div><div>World</div>`,
Expand Down
11 changes: 11 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions web/src/patch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ test("simple replace", () => {
expect(document.body.innerHTML).toEqual(`<div _l0="">World</div>`);
});

test("blank update", () => {
document.body.innerHTML = `<div _l0="">Hello</div>`;
const event = new LiveEvent("patch", [
{
Anchor: "_l0",
Action: 1,
HTML: `<div _l0=""></div>`,
},
]);
Patch.handle(event);
expect(document.body.innerHTML).toEqual(`<div _l0=""></div>`);
})

test("double update", () => {
document.body.innerHTML = `<div _l0="">Hello</div><div _l1="">World</div>`;
const p = new LiveEvent("patch", [
Expand Down
Loading