Skip to content

Commit 3e36a5e

Browse files
committed
feat: Add experimental web worker support.
Implements #113
1 parent aaa321b commit 3e36a5e

File tree

10 files changed

+291
-107
lines changed

10 files changed

+291
-107
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"custom-event-polyfill": "^0.2.1",
1212
"cz-conventional-changelog": "^1.2.0",
1313
"semantic-release": "^6.3.2",
14-
"skatejs-build": "^12.1.0"
14+
"skatejs-build": "^12.1.0",
15+
"worker-loader": "^0.7.1"
1516
},
1617
"scripts": {
1718
"build": "sk-build",

src/diff-main.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import * as types from './types';
2+
import compareNode from './compare/node';
3+
import realNode from './util/real-node';
4+
import realNodeMap from './util/real-node-map';
5+
6+
function diffNode (source, destination) {
7+
let nodeInstructions = compareNode(source, destination);
8+
9+
// If there are instructions (even an empty array) it means the node can be
10+
// diffed and doesn't have to be replaced. If the instructions are falsy
11+
// it means that the nodes are not similar (cannot be changed) and must be
12+
// replaced instead.
13+
if (nodeInstructions) {
14+
return nodeInstructions.concat(diff({ source, destination }));
15+
}
16+
17+
return [{
18+
destination,
19+
source,
20+
type: types.REPLACE_CHILD
21+
}];
22+
}
23+
24+
export default function diff (opts) {
25+
const src = opts.source;
26+
const dst = opts.destination;
27+
28+
if (!src || !dst) {
29+
return [];
30+
}
31+
32+
let instructions = opts.root ? diffNode(src, dst) : [];
33+
34+
const srcChs = src.childNodes;
35+
const dstChs = dst.childNodes;
36+
const srcChsLen = srcChs ? srcChs.length : 0;
37+
const dstChsLen = dstChs ? dstChs.length : 0;
38+
39+
for (let a = 0; a < dstChsLen; a++) {
40+
const curSrc = srcChs[a];
41+
const curDst = dstChs[a];
42+
43+
// If there is no matching destination node it means we need to remove the
44+
// current source node from the source.
45+
if (!curSrc) {
46+
instructions.push({
47+
destination: dstChs[a],
48+
source: src,
49+
type: types.APPEND_CHILD
50+
});
51+
continue;
52+
} else {
53+
// Ensure the real node is carried over even if the destination isn't used.
54+
// This is used in the render() function to keep track of the real node
55+
// that corresponds to a virtual node if a virtual tree is being used.
56+
if (typeof curDst.__id === 'undefined') {
57+
realNodeMap.set(curDst.__id, realNode(curSrc));
58+
}
59+
}
60+
61+
instructions = instructions.concat(diffNode(curSrc, curDst));
62+
}
63+
64+
if (dstChsLen < srcChsLen) {
65+
for (let a = dstChsLen; a < srcChsLen; a++) {
66+
instructions.push({
67+
destination: srcChs[a],
68+
source: src,
69+
type: types.REMOVE_CHILD
70+
});
71+
}
72+
}
73+
74+
return instructions;
75+
}

src/diff-worker.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import diff from './diff-main';
2+
3+
self.addEventListener('message', e => {
4+
const instructions = diff(e.data);
5+
self.postMessage(instructions);
6+
});

src/diff.js

Lines changed: 11 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,18 @@
1-
import * as types from './types';
2-
import compareNode from './compare/node';
3-
import realNode from './util/real-node';
4-
import realNodeMap from './util/real-node-map';
1+
import diffMain from './diff-main';
2+
import DiffWorker from 'worker-loader!./diff-worker';
53

64
const { Node } = window;
75

8-
function diffNode (source, destination) {
9-
let nodeInstructions = compareNode(source, destination);
10-
11-
// If there are instructions (even an empty array) it means the node can be
12-
// diffed and doesn't have to be replaced. If the instructions are falsy
13-
// it means that the nodes are not similar (cannot be changed) and must be
14-
// replaced instead.
15-
if (nodeInstructions) {
16-
return nodeInstructions.concat(diff({ source, destination }));
17-
}
18-
19-
return [{
20-
destination,
21-
source,
22-
type: types.REPLACE_CHILD
23-
}];
6+
function diffWorker (opts) {
7+
const worker = new DiffWorker();
8+
const { done } = opts;
9+
worker.addEventListener('message', e => done(e.data));
10+
delete opts.done;
11+
worker.postMessage(opts);
2412
}
2513

2614
export default function diff (opts = {}) {
27-
const src = opts.source;
28-
const dst = opts.destination;
29-
30-
if (!src || !dst) {
31-
return [];
32-
}
33-
34-
let instructions = opts.root ? diffNode(src, dst) : [];
35-
36-
const srcChs = src.childNodes;
37-
const dstChs = dst.childNodes;
38-
const srcChsLen = srcChs ? srcChs.length : 0;
39-
const dstChsLen = dstChs ? dstChs.length : 0;
40-
41-
for (let a = 0; a < dstChsLen; a++) {
42-
const curSrc = srcChs[a];
43-
const curDst = dstChs[a];
44-
45-
// If there is no matching destination node it means we need to remove the
46-
// current source node from the source.
47-
if (!curSrc) {
48-
instructions.push({
49-
destination: dstChs[a],
50-
source: src,
51-
type: types.APPEND_CHILD
52-
});
53-
continue;
54-
} else {
55-
// Ensure the real node is carried over even if the destination isn't used.
56-
// This is used in the render() function to keep track of the real node
57-
// that corresponds to a virtual node if a virtual tree is being used.
58-
if (!(curDst instanceof Node)) {
59-
realNodeMap.set(curDst, realNode(curSrc));
60-
}
61-
}
62-
63-
instructions = instructions.concat(diffNode(curSrc, curDst));
64-
}
65-
66-
if (dstChsLen < srcChsLen) {
67-
for (let a = dstChsLen; a < srcChsLen; a++) {
68-
instructions.push({
69-
destination: srcChs[a],
70-
source: src,
71-
type: types.REMOVE_CHILD
72-
});
73-
}
74-
}
75-
76-
return instructions;
15+
const { source, destination, done } = opts;
16+
const canDiffInWorker = done && !(source instanceof Node && destination instanceof Node);
17+
return canDiffInWorker ? diffWorker(opts) : diffMain(opts);
7718
}

src/util/real-node-map.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
1-
import WeakMap from './weak-map';
2-
export default new WeakMap();
1+
// import WeakMap from './weak-map';
2+
// export default new WeakMap();
3+
const map = [];
4+
export default {
5+
get (id) {
6+
return map[id];
7+
},
8+
set (id, node) {
9+
map[id] = node;
10+
}
11+
};

src/util/real-node.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import realNodeMap from './real-node-map';
22

3-
const { Node } = window;
3+
const isWindow = typeof window !== 'undefined';
4+
const { Node } = isWindow ? window : self;
45

56
export default function (node) {
6-
return node instanceof Node ? node : realNodeMap.get(node);
7+
return isWindow && node instanceof Node ? node : realNodeMap.get(node.__id);
78
}

src/vdom/dom.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,6 @@ export default function render (el) {
5656
return frag;
5757
}
5858
const realNode = el.tagName ? createElement(el) : createText(el);
59-
realNodeMap.set(el, realNode);
59+
realNodeMap.set(el.__id, realNode);
6060
return realNode;
6161
}

src/vdom/element.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,12 @@ function translateFromReact (item) {
6666
return item;
6767
}
6868

69+
let count = 0;
6970
export default function element (name, attrs = {}, ...chren) {
7071
const isAttrsNode = isChildren(attrs);
7172
const data = separateData(isAttrsNode ? {} : attrs);
7273
const node = data.node;
74+
node.__id = ++count;
7375
node.nodeType = 1;
7476
node.tagName = ensureTagName(name);
7577
node.attributes = data.attrs;

test/unit.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,3 +465,22 @@ describe('render', function () {
465465
});
466466
});
467467
});
468+
469+
describe('diff worker', function () {
470+
it('should do work in a worker if opts.done is specified', done => {
471+
const source = sd.vdom.element('div', null, 'text 1');
472+
const destination = sd.vdom.element('div', null, 'text 2');
473+
const realDom = sd.vdom.dom(source);
474+
475+
sd.diff({
476+
destination,
477+
source,
478+
done (instructions) {
479+
assert.ok(Array.isArray(instructions));
480+
sd.patch(instructions);
481+
assert.ok(realDom.textContent === 'text 2');
482+
done();
483+
}
484+
});
485+
});
486+
});

0 commit comments

Comments
 (0)