Skip to content
Permalink
Browse files

Pause initial render for lazy loaded widgets while performing a merge (

…#312)

* Pause rendering for a lazily loaded widget when merging

* Make test more complicated for pausing the initial rendering

* Only retry resolving the registry item if there is no available constructor
  • Loading branch information...
agubler committed Apr 10, 2019
1 parent 860910a commit d4fc3417d7b57c3adfa466ee07f4098cd71114b6
Showing with 107 additions and 11 deletions.
  1. +42 −11 src/widget-core/vdom.ts
  2. +65 −0 tests/widget-core/unit/vdom.ts
@@ -402,6 +402,7 @@ export function renderer(renderer: () => WNode | VNode): Renderer {
};
let _invalidationQueue: InvalidationQueueItem[] = [];
let _processQueue: (ProcessItem | DetachApplication | AttachApplication)[] = [];
let _deferredProcessQueue: (ProcessItem | DetachApplication | AttachApplication)[] = [];
let _applicationQueue: ApplicationInstruction[] = [];
let _eventMap = new WeakMap<Function, EventListener>();
let _instanceToWrapperMap = new WeakMap<WidgetBase, WNodeWrapper>();
@@ -828,12 +829,8 @@ export function renderer(renderer: () => WNode | VNode): Renderer {
meta: { mergeNodes: arrayFrom(domNode.childNodes) }
});
_runProcessQueue();
let mergedNode: Node | undefined;
while ((mergedNode = _allMergedNodes.pop())) {
mergedNode.parentNode && mergedNode.parentNode.removeChild(mergedNode);
}
_cleanUpMergedNodes();
_runDomInstructionQueue();
_mountOptions.merge = false;
_insertBeforeMap = undefined;
_runCallbacks();
}
@@ -893,14 +890,29 @@ export function renderer(renderer: () => WNode | VNode): Renderer {
if (item) {
_processQueue.push(item);
instance && _instanceToWrapperMap.set(instance, next);
if (_deferredProcessQueue.length) {
_processQueue = [..._processQueue, ..._deferredProcessQueue];
_deferredProcessQueue = [];
}
_runProcessQueue();
}
}
}
_cleanUpMergedNodes();
_runDomInstructionQueue();
_runCallbacks();
}

function _cleanUpMergedNodes() {
if (_deferredProcessQueue.length === 0) {
let mergedNode: Node | undefined;
while ((mergedNode = _allMergedNodes.pop())) {
mergedNode.parentNode && mergedNode.parentNode.removeChild(mergedNode);
}
_mountOptions.merge = false;
}
}

function _runProcessQueue() {
let item: DetachApplication | AttachApplication | ProcessItem | undefined;
while ((item = _processQueue.pop())) {
@@ -996,7 +1008,9 @@ export function renderer(renderer: () => WNode | VNode): Renderer {
item.current.instance = undefined;
}
}
_nodeToInstanceMap = new WeakMap();
if (_deferredProcessQueue.length === 0) {
_nodeToInstanceMap = new WeakMap();
}
}

function _runCallbacks() {
@@ -1086,14 +1100,27 @@ export function renderer(renderer: () => WNode | VNode): Renderer {
}

for (let i = 0; i < instructions.length; i++) {
const { item, dom, widget } = _processOne(instructions[i]);
const result = _processOne(instructions[i]);
if (result === false) {
if (_mountOptions.merge && mergeNodes.length) {
if (newIndex < nextLength) {
_processQueue.pop();
}
_processQueue.push({ next, current, meta });
_deferredProcessQueue = _processQueue;
_processQueue = [];
break;
}
continue;
}
const { widget, item, dom } = result;
widget && _processQueue.push(widget);
item && _processQueue.push(item);
dom && _applicationQueue.push(dom);
}
}

function _processOne({ current, next }: Instruction): ProcessResult {
function _processOne({ current, next }: Instruction): ProcessResult | false {
if (current !== next) {
if (!current && next) {
if (isVNodeWrapper(next)) {
@@ -1118,14 +1145,18 @@ export function renderer(renderer: () => WNode | VNode): Renderer {
return {};
}

function _createWidget({ next }: CreateWidgetInstruction): ProcessResult {
function _createWidget({ next }: CreateWidgetInstruction): ProcessResult | false {
let {
node: { widgetConstructor }
} = next;
let { registry } = _mountOptions;
const Constructor = next.registryItem || widgetConstructor;
let Constructor = next.registryItem || widgetConstructor;
if (!isWidgetBaseConstructor(Constructor)) {
return {};
resolveRegistryItem(next);
if (!next.registryItem) {
return false;
}
Constructor = next.registryItem;
}
const instance = new Constructor() as WidgetBase;
if (registry) {
@@ -461,6 +461,71 @@ jsdomDescribe('vdom', () => {
});
});

it('Should pause rendering while merging to allow lazily loaded widgets to be loaded', () => {
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.contentDocument.write(`<div><span>54321</span><span>98765</span><span>12345</span></div>`);
iframe.contentDocument.close();

const root = iframe.contentDocument.body.firstChild as HTMLElement;
const lazyFooSpan = root.childNodes[0] as HTMLSpanElement;
const lazyBarSpan = root.childNodes[1] as HTMLSpanElement;
const span = root.childNodes[2] as HTMLSpanElement;
const registry = new Registry();

class Bar extends WidgetBase {
render() {
return v('span', ['98765']);
}
}

let barResolver: any;
const barPromise = new Promise<any>((resolve) => {
barResolver = resolve;
});

class Foo extends WidgetBase {
render() {
return [v('span', ['54321']), w({ label: 'bar', registryItem: () => barPromise }, {})];
}
}

let fooResolver: any;
const fooPromise = new Promise<any>((resolve) => {
fooResolver = resolve;
});

class App extends WidgetBase {
render() {
return v('div', [
w(
{
label: 'foo',
registryItem: () => fooPromise
},
{}
),
v('span', ['12345'])
]);
}
}

const r = renderer(() => w(App, {}));
r.mount({ registry, domNode: iframe.contentDocument.body, sync: true });
fooResolver(Foo);
return fooPromise.then(() => {
assert.strictEqual(root.childNodes[2], span);
assert.strictEqual(root.childNodes[1], lazyBarSpan);
assert.strictEqual(root.childNodes[0], lazyFooSpan);
barResolver(Bar);
return barPromise.then(() => {
assert.strictEqual(root.childNodes[2], span);
assert.strictEqual(root.childNodes[1], lazyBarSpan);
assert.strictEqual(root.childNodes[0], lazyFooSpan);
});
});
});

it('registry items', () => {
let resolver = () => {};
const registry = new Registry();

0 comments on commit d4fc341

Please sign in to comment.
You can’t perform that action at this time.