Skip to content

Commit 6106c70

Browse files
feat(runtime): skip initial task queue to improve first time rendering (#6331)
* fix(runtime): skip task schedule for inital render phase * fix: use microtask to queue initial dispatch * test: added update-component tests to verify call order --------- Co-authored-by: Christian Bromann <[email protected]>
1 parent fd4b892 commit 6106c70

File tree

2 files changed

+81
-1
lines changed

2 files changed

+81
-1
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { Component, h, State } from '@stencil/core';
2+
import { newSpecPage } from '@stencil/core/testing';
3+
4+
describe('update-component', () => {
5+
describe('scheduleUpdate - initial load with queueMicrotask', () => {
6+
@Component({
7+
tag: 'test-cmp',
8+
})
9+
class TestCmp {
10+
@State() count = 0;
11+
12+
render() {
13+
return h('div', null, `Count: ${this.count}`);
14+
}
15+
}
16+
17+
it('should use queueMicrotask for initial load dispatch', async () => {
18+
const queueMicrotaskSpy = jest.spyOn(global, 'queueMicrotask');
19+
20+
const page = await newSpecPage({
21+
components: [TestCmp],
22+
html: `<test-cmp></test-cmp>`,
23+
});
24+
25+
expect(queueMicrotaskSpy).toHaveBeenCalled();
26+
expect(page.root.textContent).toContain('Count: 0');
27+
28+
queueMicrotaskSpy.mockRestore();
29+
});
30+
31+
it('should not interfere with following render dispatch events', async () => {
32+
let componentWillRender = 0;
33+
const queueMicrotaskSpy = jest.spyOn(global, 'queueMicrotask');
34+
35+
@Component({
36+
tag: 'update-test-cmp',
37+
})
38+
class UpdateTestCmp {
39+
@State() count = 0;
40+
41+
increment() {
42+
this.count++;
43+
}
44+
45+
componentWillRender() {
46+
componentWillRender++;
47+
}
48+
49+
render() {
50+
return h('div', null, `Count: ${this.count}`);
51+
}
52+
}
53+
54+
const page = await newSpecPage({
55+
components: [UpdateTestCmp],
56+
html: `<update-test-cmp></update-test-cmp>`,
57+
});
58+
59+
expect(page.root.textContent).toBe('Count: 0');
60+
expect(componentWillRender).toBe(1);
61+
62+
page.rootInstance.increment();
63+
await page.waitForChanges();
64+
65+
expect(page.root.textContent).toContain('Count: 1');
66+
expect(queueMicrotaskSpy).toHaveBeenCalledTimes(1);
67+
expect(componentWillRender).toBe(2);
68+
69+
queueMicrotaskSpy.mockRestore();
70+
});
71+
});
72+
});

src/runtime/update-component.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const attachToAncestor = (hostRef: d.HostRef, ancestorComponent?: d.HostE
2323
}
2424
};
2525

26-
export const scheduleUpdate = (hostRef: d.HostRef, isInitialLoad: boolean) => {
26+
export const scheduleUpdate = (hostRef: d.HostRef, isInitialLoad: boolean): Promise<void> | void => {
2727
if (BUILD.taskQueue && BUILD.updatable) {
2828
hostRef.$flags$ |= HOST_FLAGS.isQueuedForUpdate;
2929
}
@@ -37,6 +37,14 @@ export const scheduleUpdate = (hostRef: d.HostRef, isInitialLoad: boolean) => {
3737
// has already fired off its lifecycle update then
3838
// fire off the initial update
3939
const dispatch = () => dispatchHooks(hostRef, isInitialLoad);
40+
41+
if (isInitialLoad) {
42+
queueMicrotask(() => {
43+
dispatch();
44+
});
45+
return;
46+
}
47+
4048
return BUILD.taskQueue ? writeTask(dispatch) : dispatch();
4149
};
4250

0 commit comments

Comments
 (0)