Skip to content

Instantly share code, notes, and snippets.

@kl0tl
Last active June 6, 2021 11:20
Show Gist options
  • Save kl0tl/4043ac052d4a3978e759 to your computer and use it in GitHub Desktop.
Save kl0tl/4043ac052d4a3978e759 to your computer and use it in GitHub Desktop.
DOM diffing inside a `Worker` with `virtual-dom`
var VDOMWorkerRenderer = require('./vdom-worker-renderer');
var renderer = new VDOMWorkerRenderer(function (h) {
return function ui(state) {
return counter(state.counter)
};
function counter(value) {
return h('div', { className: 'counter' }, [String(value)]);
}
}, { counter: 0 });
document.querySelector('#ui-root').appendChild(renderer.node);
setInterval(function inc() {
renderer.state.counter += 1;
}, 1000);
requestAnimationFrame(function main() {
requestAnimationFrame(main);
renderer.render();
});
function observe(object, listener) {
var observed = {};
Object.keys(object).forEach(function (key) {
var descriptor = Object.getOwnPropertyDescriptor(object, key);
var value = object[key];
if (typeof value === 'object' && value !== null) {
value = observe(value, whenSubPropertiesChanged);
}
Object.defineProperty(observed, key, {
get: function () {
return value;
},
set: function (newValue) {
if ('value' in descriptor ? !descriptor.writable : false) return;
if (value === newValue) return;
if (typeof newValue === 'object' && newValue !== null) {
newValue = observe(newValue, whenSubPropertiesChanged);
}
listener([key], newValue, value);
value = newValue;
},
enumerable: descriptor.enumerable,
configurable: descriptor.configurable
});
function whenSubPropertiesChanged(path, newValue, oldValue) {
listener([key].concat(path), newValue, oldValue);
}
});
return Object.seal(observed);
}
module.exports = observe;
function VDOMRenderer(definition, state) {
var self = this;
self.node = document.create('div');
self.state = observe(state, function () {
var vtree = self._component(self.state);
var patches = vdom.diff(self._vtree, vtree);
self._vtree = vtree;
if (Object.keys(patches).length > 1) {
self._queue.push(patches);
}
});
self._component = definition(vdom.h);
self._vtree = vdom.h();
self._queue = [];
}
Object.defineProperty(VDOMRenderer.prototype, 'dirty', {
get: function () {
return Boolean(this._queue.length);
},
enumerable: true,
configurable: false
})
VDOMRenderer.prototype.render = function () {
if (!this.dirty) return;
var patches = null;
while ((patches = this._queue.shift())) {
this.node = vdom.patch(this.node, patches);
}
};
module.exports = VDOMRenderer;
var vdom = require('virtual-dom');
var observe = require('./observe');
var vdomWorkerScript = document.querySelector('script[type="text/vdom-worker"]');
var vdomWorkerSource = (vdomWorkerScript.textContent || vdomWorkerScript.innerText)
.replace('${ origin }', location.origin);
function VDOMWorkerRenderer(definition, state) {
var self = this;
var source = vdomWorkerSource
.replace('${ state }', JSON.stringify(state))
.replace('${ definition }', definition.toString());
var blob = new Blob([source]);
var url = URL.createObjectURL(blob);
self.node = document.createElement('div');
self.worker = Object.defineProperty(new Worker(url), 'url', {
get: function () { return url },
enumerable: true,
configurable: false
});
self.worker.addEventListener('message', function (event) {
var message = event.data;
if (message.type === 'render') {
self.node = vdom.patch(self.node, JSON.parse(message.patches));
}
}, false);
self.state = observe(state, function (path, newValue) {
self.worker.postMessage({ type: 'state:changed', path: path, value: newValue });
});
}
VDOMWorkerRenderer.prototype.render = function () {
this.worker.postMessage({ type: 'render' });
};
VDOMWorkerRenderer.prototype.dispose = function () {
this.worker.terminate();
URL.revokeObjectURL(this.worker.url);
};
module.exports = VDOMWorkerRenderer;
importScripts('${ origin }/dist/vdom.js');
(function () {
var oldVTree = vdom.h();
var state = ${ state };
var render = (${ definition }(vdom.h));
self.addEventListener('message', function (event) {
var message = event.data;
if (message.type === 'state:changed') {
update(state, message.path, message.value);
} else if (message.type === 'render') {
var newVTree = render(state);
var patches = vdom.diff(oldVTree, newVTree);
oldVTree = newVTree;
if (Object.keys(patches).length > 1) {
self.postMessage({ type: 'render', patches: JSON.stringify(patches) });
}
}
});
function update(dest, path, value) {
for (var i = 0, length = path.length - 1; i < length; i += 1) {
dest = dest[path[i]];
}
dest[path[i]] = value;
}
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment