Skip to content

Commit 29bdd8f

Browse files
authored
fix(): update lifecycles respect hierarchy (#1924)
1 parent b8d71a4 commit 29bdd8f

File tree

13 files changed

+173
-109
lines changed

13 files changed

+173
-109
lines changed

src/declarations/host-element.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,6 @@ export interface HostElement extends HTMLElement {
2525
*/
2626
['s-cr']?: RenderNode;
2727

28-
/**
29-
* Is Active Loading:
30-
* Set of child host elements that are actively loading.
31-
*/
32-
['s-al']?: Set<HostElement>;
33-
3428
/**
3529
* Lifecycle ready
3630
*/
@@ -49,13 +43,6 @@ export interface HostElement extends HTMLElement {
4943
*/
5044
['s-sc']?: string;
5145

52-
/**
53-
* Component Initial Load:
54-
* The component has fully loaded, instance creatd,
55-
* and has rendered. Method is on the host element prototype.
56-
*/
57-
['s-init']?: () => void;
58-
5946
/**
6047
* Hot Module Replacement, dev mode only
6148
*/
@@ -66,6 +53,8 @@ export interface HostElement extends HTMLElement {
6653
*/
6754
['s-hmr-load']?: () => void;
6855

56+
['s-p']?: Promise<void>[];
57+
6958
componentOnReady?: () => Promise<this>;
7059
}
7160

src/declarations/runtime.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export interface HostRef {
9898
$onReadyResolve$?: (elm: any) => void;
9999
$onInstancePromise$?: Promise<any>;
100100
$onInstanceResolve$?: (elm: any) => void;
101+
$onRenderResolve$?: () => void;
101102
$vnode$?: VNode;
102103
$queuedListeners$?: [string, any][];
103104
$rmListeners$?: () => void;

src/hydrate/platform/bootstrap-hydrate.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as d from '../../declarations';
2-
import { doc, getComponent, getHostRef, plt } from '@platform';
2+
import { doc, getComponent, plt } from '@platform';
33
import { hydrateComponent } from './hydrate-component';
4-
import { insertVdomAnnotations, postUpdateComponent } from '@runtime';
4+
import { insertVdomAnnotations } from '@runtime';
55

66

77
export function bootstrapHydrate(win: Window, opts: d.HydrateDocumentOptions, done: (results: BootstrapHydrateResults) => void) {
@@ -20,13 +20,6 @@ export function bootstrapHydrate(win: Window, opts: d.HydrateDocumentOptions, do
2020
connectElements(win, opts, results, this, connectedElements, waitPromises);
2121
};
2222

23-
const patchedComponentInit = function patchedComponentInit(this: d.HostElement) {
24-
const hostRef = getHostRef(this);
25-
if (hostRef != null) {
26-
postUpdateComponent(this, hostRef);
27-
}
28-
};
29-
3023
const patchComponent = function(elm: d.HostElement) {
3124
const tagName = elm.nodeName.toLowerCase();
3225
if (elm.tagName.includes('-')) {
@@ -37,10 +30,11 @@ export function bootstrapHydrate(win: Window, opts: d.HydrateDocumentOptions, do
3730
elm.connectedCallback = patchedConnectedCallback;
3831
}
3932

40-
if (typeof elm['s-init'] !== 'function') {
41-
elm['s-rc'] = [];
42-
elm['s-init'] = patchedComponentInit;
43-
}
33+
elm['s-p'] = [];
34+
elm['s-rc'] = [];
35+
// if (typeof elm['s-init'] !== 'function') {
36+
// elm['s-init'] = patchedComponentInit;
37+
// }
4438
}
4539
}
4640
};

src/runtime/bootstrap-lazy.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { BUILD } from '@build-conditionals';
88
import { doc, getHostRef, plt, registerHost, supportsShadowDom, win } from '@platform';
99
import { hmrStart } from './hmr-component';
1010
import { HYDRATE_ID, PLATFORM_FLAGS, PROXY_FLAGS } from './runtime-constants';
11-
import { appDidLoad, forceUpdate, postUpdateComponent } from './update-component';
11+
import { appDidLoad, forceUpdate } from './update-component';
1212

1313

1414
export const bootstrapLazy = (lazyBundles: d.LazyBundlesRuntimeData, options: d.CustomElementsDefineOptions = {}) => {
@@ -66,7 +66,7 @@ export const bootstrapLazy = (lazyBundles: d.LazyBundlesRuntimeData, options: d.
6666
const tagName = cmpMeta.$tagName$;
6767
const HostElement = class extends HTMLElement {
6868

69-
['s-lr']: boolean;
69+
['s-p']: Promise<void>[];
7070
['s-rc']: (() => void)[];
7171

7272
// StencilLazyHost
@@ -75,10 +75,8 @@ export const bootstrapLazy = (lazyBundles: d.LazyBundlesRuntimeData, options: d.
7575
super(self);
7676
self = this;
7777

78-
if (BUILD.lifecycle) {
79-
this['s-lr'] = false;
80-
this['s-rc'] = [];
81-
}
78+
this['s-p'] = [];
79+
this['s-rc'] = [];
8280

8381
registerHost(self);
8482
if (BUILD.shadowDom && cmpMeta.$flags$ & CMP_FLAGS.shadowDomEncapsulation) {
@@ -105,13 +103,6 @@ export const bootstrapLazy = (lazyBundles: d.LazyBundlesRuntimeData, options: d.
105103
plt.jmp(() => disconnectedCallback(this));
106104
}
107105

108-
's-init'() {
109-
const hostRef = getHostRef(this);
110-
if (hostRef.$lazyInstance$) {
111-
postUpdateComponent(this, hostRef);
112-
}
113-
}
114-
115106
's-hmr'(hmrVersionId: string) {
116107
if (BUILD.hotModuleReplacement) {
117108
hmrStart(this, cmpMeta, hmrVersionId);

src/runtime/connected-callback.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { doc, getHostRef, nextTick, plt, supportsShadowDom } from '@platform';
77
import { HYDRATE_ID, NODE_TYPE, PLATFORM_FLAGS } from './runtime-constants';
88
import { initializeClientHydrate } from './client-hydrate';
99
import { initializeComponent } from './initialize-component';
10-
import { safeCall } from './update-component';
10+
import { safeCall, attachToAncestor } from './update-component';
1111

1212
export const fireConnectedCallback = (instance: any) => {
1313
if (BUILD.lazyLoad && BUILD.connectedCallback) {
@@ -57,22 +57,21 @@ export const connectedCallback = (elm: d.HostElement, cmpMeta: d.ComponentRuntim
5757
}
5858
}
5959

60-
if (BUILD.lifecycle && BUILD.lazyLoad) {
60+
if (BUILD.lifecycle || BUILD.lazyLoad) {
6161
// find the first ancestor component (if there is one) and register
6262
// this component as one of the actively loading child components for its ancestor
6363
let ancestorComponent = elm;
6464

6565
while ((ancestorComponent = (ancestorComponent.parentNode as any || ancestorComponent.host as any))) {
6666
// climb up the ancestors looking for the first
6767
// component that hasn't finished its lifecycle update yet
68-
if ((BUILD.hydrateClientSide && ancestorComponent.nodeType === NODE_TYPE.ElementNode && ancestorComponent.hasAttribute('s-id')) || (ancestorComponent['s-init'] && ancestorComponent['s-lr'] === false)) {
68+
if (
69+
(BUILD.hydrateClientSide && ancestorComponent.nodeType === NODE_TYPE.ElementNode && ancestorComponent.hasAttribute('s-id')) ||
70+
(ancestorComponent['s-p'])
71+
) {
6972
// we found this components first ancestor component
7073
// keep a reference to this component's ancestor component
71-
hostRef.$ancestorComponent$ = ancestorComponent;
72-
73-
// ensure there is an array to contain a reference to each of the child components
74-
// and set this component as one of the ancestor's child components it should wait on
75-
(ancestorComponent['s-al'] = ancestorComponent['s-al'] || new Set()).add(elm);
74+
attachToAncestor(hostRef, (hostRef.$ancestorComponent$ = ancestorComponent));
7675
break;
7776
}
7877
}

src/runtime/initialize-component.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,6 @@ export const initializeComponent = async (elm: d.HostElement, hostRef: d.HostRef
7272
if (BUILD.watchCallback) {
7373
hostRef.$flags$ |= HOST_FLAGS.isWatchReady;
7474
}
75-
if (BUILD.method) {
76-
hostRef.$onInstanceResolve$(elm);
77-
}
7875
fireConnectedCallback(hostRef.$lazyInstance$);
7976

8077
} else {
@@ -103,7 +100,7 @@ export const initializeComponent = async (elm: d.HostElement, hostRef: d.HostRef
103100
const ancestorComponent = hostRef.$ancestorComponent$;
104101
const schedule = () => scheduleUpdate(elm, hostRef, cmpMeta, true);
105102

106-
if (BUILD.lifecycle && BUILD.lazyLoad && ancestorComponent && ancestorComponent['s-lr'] === false && ancestorComponent['s-rc']) {
103+
if (BUILD.lifecycle && BUILD.lazyLoad && ancestorComponent && ancestorComponent['s-rc']) {
107104
// this is the intial load and this component it has an ancestor component
108105
// but the ancestor component has NOT fired its will update lifecycle yet
109106
// so let's just cool our jets and wait for the ancestor to continue first

src/runtime/set-value.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const setValue = (ref: d.RuntimeRef, propName: string, newVal: any, cmpMe
4646
}
4747
}
4848

49-
if (BUILD.updatable && (flags & (HOST_FLAGS.isActiveRender | HOST_FLAGS.hasRendered | HOST_FLAGS.isQueuedForUpdate)) === HOST_FLAGS.hasRendered) {
49+
if (BUILD.updatable && (flags & (HOST_FLAGS.hasRendered | HOST_FLAGS.isQueuedForUpdate)) === HOST_FLAGS.hasRendered) {
5050
if (BUILD.cmpShouldUpdate && instance.componentShouldUpdate) {
5151
if (instance.componentShouldUpdate(newVal, oldVal, propName) === false) {
5252
return;

src/runtime/test/lifecycle-sync.spec.tsx

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, Prop, Watch, h } from '@stencil/core';
1+
import { Component, Element, Host, Prop, Watch, h } from '@stencil/core';
22
import { newSpecPage } from '@stencil/core/testing';
33

44

@@ -193,4 +193,93 @@ describe('lifecycle sync', () => {
193193
expect(rootInstance.renders).toBe(2);
194194
});
195195

196+
describe('childrens', () => {
197+
it ('sync', async () => {
198+
199+
const log: string[] = [];
200+
@Component({
201+
tag: 'cmp-a'
202+
})
203+
class CmpA {
204+
@Prop() prop: string;
205+
componentWillLoad() {
206+
log.push('componentWillLoad a');
207+
}
208+
componentDidLoad() {
209+
log.push('componentDidLoad a');
210+
}
211+
componentWillUpdate() {
212+
log.push('componentWillUpdate a');
213+
}
214+
componentDidUpdate() {
215+
log.push('componentDidUpdate a');
216+
}
217+
render() {
218+
return (
219+
<Host>
220+
<cmp-b id='b1' prop={this.prop}>
221+
<cmp-b id='b2' prop={this.prop}>
222+
<cmp-b id='b3' prop={this.prop}></cmp-b>
223+
</cmp-b>
224+
</cmp-b>
225+
</Host>
226+
);
227+
}
228+
}
229+
@Component({
230+
tag: 'cmp-b'
231+
})
232+
class CmpB {
233+
@Element() el: HTMLElement;
234+
@Prop() prop: string;
235+
componentWillLoad() {
236+
log.push(`componentWillLoad ${this.el.id}`);
237+
}
238+
componentDidLoad() {
239+
log.push(`componentDidLoad ${this.el.id}`);
240+
}
241+
componentWillUpdate() {
242+
log.push(`componentWillUpdate ${this.el.id}`);
243+
}
244+
componentDidUpdate() {
245+
log.push(`componentDidUpdate ${this.el.id}`);
246+
}
247+
}
248+
const {root, waitForChanges} = await newSpecPage({
249+
components: [CmpA, CmpB],
250+
template: () => <cmp-a></cmp-a>
251+
});
252+
expect(log).toEqual([
253+
'componentWillLoad a',
254+
'componentWillLoad b1',
255+
'componentWillLoad b2',
256+
'componentWillLoad b3',
257+
'componentDidLoad b3',
258+
'componentDidLoad b2',
259+
'componentDidLoad b1',
260+
'componentDidLoad a',
261+
]);
262+
log.length = 0;
263+
root.forceUpdate();
264+
await waitForChanges();
265+
expect(log).toEqual([
266+
'componentWillUpdate a',
267+
'componentDidUpdate a',
268+
]);
269+
270+
log.length = 0;
271+
root.prop = 'something else';
272+
await waitForChanges();
273+
expect(log).toEqual([
274+
'componentWillUpdate a',
275+
'componentWillUpdate b1',
276+
'componentWillUpdate b2',
277+
'componentWillUpdate b3',
278+
'componentDidUpdate b3',
279+
'componentDidUpdate b2',
280+
'componentDidUpdate b1',
281+
'componentDidUpdate a',
282+
]);
283+
});
284+
});
196285
});

0 commit comments

Comments
 (0)