Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,48 @@ When diffing and patching, the behaviour is much the same. If you're diffing rea
When patching event listeners, the previous one will be completely unbound and the new one will be bound. This prevents handlers from stacking.

When patching properties, they're simply just set if the source and destination values aren't the same.

### Web workers

You can tell the differ to do its work in a web worker simply by passing a `done` callback option to any of the three major entry functions (`diff()`, `merge()`, `render()`).

#### `diff()`

In the case of `diff()`, it's called once the diffing algorithm has finished in the worker and passed the `instructions`. The patch `instructions` are the only argument passed into the callback.

```js
function done (instructions) {
patch(instructions);
}
diff({ source, destination, done });
```

#### `merge()`

For `done()`, it's passed in the same exact way. The only difference is that it's called after the patch is performed but it's still passed the instructions that were performed by the patch algorithm.

```js
function done (instructions) {
// The DOM has been updated, do what you want here.
}
merge({ source, destination, done });
```

#### `render()`

And for `render()`, it is the same as the `merge()` function. So once the vDOM is rendered and DOM is patched, `done()` is called with the instructions that were performed.

```js
function done (instructions) {
// Renering and patching is done...
}
const root = document.createElement('div');
const doRender = render(function (root) {
return sd.vdom.element('div', null, root.test);
});

div.test = 'initial text';
doRender(div, done);
div.test = 'updated text';
doRender(div, done);
```
1 change: 0 additions & 1 deletion gulpfile.js

This file was deleted.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"custom-event-polyfill": "^0.2.1",
"cz-conventional-changelog": "^1.2.0",
"semantic-release": "^6.3.2",
"skatejs-build": "^12.1.0"
"skatejs-build": "^12.1.0",
"worker-loader": "^0.7.1"
},
"scripts": {
"build": "sk-build",
Expand Down
10 changes: 5 additions & 5 deletions src/compare/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import * as types from '../types';
import { getAccessor } from '../util/accessor';

export default function (src, dst) {
let srcAttrs = src.attributes;
let dstAttrs = dst.attributes;
let srcAttrsLen = (srcAttrs || 0) && srcAttrs.length;
let dstAttrsLen = (dstAttrs || 0) && dstAttrs.length;
let instructions = [];
const srcAttrs = src.attributes;
const dstAttrs = dst.attributes;
const srcAttrsLen = (srcAttrs || 0) && srcAttrs.length;
const dstAttrsLen = (dstAttrs || 0) && dstAttrs.length;
const instructions = [];

// Bail early if possible.
if (!srcAttrsLen && !dstAttrsLen) {
Expand Down
75 changes: 75 additions & 0 deletions src/diff-main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as types from './types';
import compareNode from './compare/node';
import realNode from './util/real-node';
import realNodeMap from './util/real-node-map';

function diffNode (source, destination) {
let nodeInstructions = compareNode(source, destination);

// If there are instructions (even an empty array) it means the node can be
// diffed and doesn't have to be replaced. If the instructions are falsy
// it means that the nodes are not similar (cannot be changed) and must be
// replaced instead.
if (nodeInstructions) {
return nodeInstructions.concat(diff({ source, destination }));
}

return [{
destination,
source,
type: types.REPLACE_CHILD
}];
}

export default function diff (opts) {
const src = opts.source;
const dst = opts.destination;

if (!src || !dst) {
return [];
}

let instructions = opts.root ? diffNode(src, dst) : [];

const srcChs = src.childNodes;
const dstChs = dst.childNodes;
const srcChsLen = srcChs ? srcChs.length : 0;
const dstChsLen = dstChs ? dstChs.length : 0;

for (let a = 0; a < dstChsLen; a++) {
const curSrc = srcChs[a];
const curDst = dstChs[a];

// If there is no matching destination node it means we need to remove the
// current source node from the source.
if (!curSrc) {
instructions.push({
destination: dstChs[a],
source: src,
type: types.APPEND_CHILD
});
continue;
} else {
// Ensure the real node is carried over even if the destination isn't used.
// This is used in the render() function to keep track of the real node
// that corresponds to a virtual node if a virtual tree is being used.
if (typeof curDst.__id !== 'undefined') {
realNodeMap.set(curDst.__id, realNode(curSrc));
}
}

instructions = instructions.concat(diffNode(curSrc, curDst));
}

if (dstChsLen < srcChsLen) {
for (let a = dstChsLen; a < srcChsLen; a++) {
instructions.push({
destination: srcChs[a],
source: src,
type: types.REMOVE_CHILD
});
}
}

return instructions;
}
6 changes: 6 additions & 0 deletions src/diff-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import diff from './diff-main';

self.addEventListener('message', e => {
const instructions = diff(e.data);
self.postMessage(instructions);
});
81 changes: 11 additions & 70 deletions src/diff.js
Original file line number Diff line number Diff line change
@@ -1,77 +1,18 @@
import * as types from './types';
import compareNode from './compare/node';
import realNode from './util/real-node';
import realNodeMap from './util/real-node-map';
import diffMain from './diff-main';
import DiffWorker from 'worker-loader!./diff-worker';

const { Node } = window;

function diffNode (source, destination) {
let nodeInstructions = compareNode(source, destination);

// If there are instructions (even an empty array) it means the node can be
// diffed and doesn't have to be replaced. If the instructions are falsy
// it means that the nodes are not similar (cannot be changed) and must be
// replaced instead.
if (nodeInstructions) {
return nodeInstructions.concat(diff({ source, destination }));
}

return [{
destination,
source,
type: types.REPLACE_CHILD
}];
function diffWorker (opts) {
const worker = new DiffWorker();
const { done } = opts;
worker.addEventListener('message', e => done(e.data));
delete opts.done;
worker.postMessage(opts);
}

export default function diff (opts = {}) {
const src = opts.source;
const dst = opts.destination;

if (!src || !dst) {
return [];
}

let instructions = opts.root ? diffNode(src, dst) : [];

const srcChs = src.childNodes;
const dstChs = dst.childNodes;
const srcChsLen = srcChs ? srcChs.length : 0;
const dstChsLen = dstChs ? dstChs.length : 0;

for (let a = 0; a < dstChsLen; a++) {
const curSrc = srcChs[a];
const curDst = dstChs[a];

// If there is no matching destination node it means we need to remove the
// current source node from the source.
if (!curSrc) {
instructions.push({
destination: dstChs[a],
source: src,
type: types.APPEND_CHILD
});
continue;
} else {
// Ensure the real node is carried over even if the destination isn't used.
// This is used in the render() function to keep track of the real node
// that corresponds to a virtual node if a virtual tree is being used.
if (!(curDst instanceof Node)) {
realNodeMap.set(curDst, realNode(curSrc));
}
}

instructions = instructions.concat(diffNode(curSrc, curDst));
}

if (dstChsLen < srcChsLen) {
for (let a = dstChsLen; a < srcChsLen; a++) {
instructions.push({
destination: srcChs[a],
source: src,
type: types.REMOVE_CHILD
});
}
}

return instructions;
const { source, destination, done } = opts;
const canDiffInWorker = done && !(source instanceof Node && destination instanceof Node);
return canDiffInWorker ? diffWorker(opts) : diffMain(opts);
}
4 changes: 1 addition & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import patch from './patch';
import render from './render';
import * as types from './types';
import vdom from './vdom';
import version from './version';

export default {
diff,
merge,
patch,
render,
types,
vdom,
version
vdom
};
19 changes: 15 additions & 4 deletions src/merge.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import diff from './diff';
import patch from './patch';

export default function (opts) {
var inst = diff(opts);
patch(inst);
return inst;
export default function (opts = {}) {
const { source, destination, done } = opts;
if (done) {
return diff({
source,
destination,
done (instructions) {
patch(instructions);
done(instructions);
}
});
}
const instructions = diff(opts);
patch(instructions);
return instructions;
}
8 changes: 6 additions & 2 deletions src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { Node } = window;
const oldTreeMap = new WeakMap();

export default function (render) {
return function (elem) {
return function (elem, done) {
elem = elem instanceof Node ? elem : this;

if (!(elem instanceof Node)) {
Expand All @@ -21,10 +21,14 @@ export default function (render) {
if (oldTree) {
merge({
destination: newTree,
source: oldTree
source: oldTree,
done
});
} else {
mount(elem, newTree.childNodes);
if (typeof done === 'function') {
done();
}
}

oldTreeMap.set(elem, newTree);
Expand Down
13 changes: 11 additions & 2 deletions src/util/real-node-map.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
import WeakMap from './weak-map';
export default new WeakMap();
// import WeakMap from './weak-map';
// export default new WeakMap();
const map = [];
export default {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't use weakmaps if we're using scalar values as keys.

get (id) {
return map[id];
},
set (id, node) {
map[id] = node;
}
};
5 changes: 3 additions & 2 deletions src/util/real-node.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import realNodeMap from './real-node-map';

const { Node } = window;
const isWindow = typeof window !== 'undefined';
const { Node } = isWindow ? window : self;

export default function (node) {
return node instanceof Node ? node : realNodeMap.get(node);
return isWindow && node instanceof Node ? node : realNodeMap.get(node.__id);
}
2 changes: 1 addition & 1 deletion src/vdom/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@ export default function render (el) {
return frag;
}
const realNode = el.tagName ? createElement(el) : createText(el);
realNodeMap.set(el, realNode);
realNodeMap.set(el.__id, realNode);
return realNode;
}
2 changes: 2 additions & 0 deletions src/vdom/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,12 @@ function translateFromReact (item) {
return item;
}

let count = 0;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely better ways, but this gets the initial spike working.

export default function element (name, attrs = {}, ...chren) {
const isAttrsNode = isChildren(attrs);
const data = separateData(isAttrsNode ? {} : attrs);
const node = data.node;
node.__id = ++count;
node.nodeType = 1;
node.tagName = ensureTagName(name);
node.attributes = data.attrs;
Expand Down
1 change: 0 additions & 1 deletion src/version.js

This file was deleted.

Loading