-
-
Notifications
You must be signed in to change notification settings - Fork 840
Expand file tree
/
Copy pathinitialize-component.ts
More file actions
222 lines (206 loc) · 8.79 KB
/
initialize-component.ts
File metadata and controls
222 lines (206 loc) · 8.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
import { BUILD } from '@app-data';
import { consoleError, loadModule, needsScopedSSR, styles } from '@platform';
import type * as d from '../declarations';
import { CMP_FLAGS, HOST_FLAGS } from '../utils/constants';
import { expandPartSelectors, scopeCss } from '../utils/shadow-css';
import { computeMode } from './mode';
import { normalizeWatchers } from './normalize-watchers';
import { createTime, uniqueTime } from './profile';
import { proxyComponent } from './proxy-component';
import { PROXY_FLAGS } from './runtime-constants';
import { getScopeId, registerStyle } from './styles';
import { safeCall, scheduleUpdate } from './update-component';
/**
* Initialize a Stencil component given a reference to its host element, its
* runtime bookkeeping data structure, runtime metadata about the component,
* and (optionally) an HMR version ID.
*
* @param elm a host element
* @param hostRef the element's runtime bookkeeping object
* @param cmpMeta runtime metadata for the Stencil component
* @param hmrVersionId an (optional) HMR version ID
*/
export const initializeComponent = async (
elm: d.HostElement,
hostRef: d.HostRef,
cmpMeta: d.ComponentRuntimeMeta,
hmrVersionId?: string,
) => {
let Cstr: d.ComponentConstructor | undefined;
// initializeComponent
try {
if ((hostRef.$flags$ & HOST_FLAGS.hasInitializedComponent) === 0) {
// Let the runtime know that the component has been initialized
hostRef.$flags$ |= HOST_FLAGS.hasInitializedComponent;
const bundleId = cmpMeta.$lazyBundleId$;
if (BUILD.lazyLoad && bundleId) {
// lazy loaded components
// request the component's implementation to be
// wired up with the host element
const CstrImport = loadModule(cmpMeta, hostRef, hmrVersionId);
if (CstrImport && 'then' in CstrImport) {
// Await creates a micro-task avoid if possible
const endLoad = uniqueTime(
`st:load:${cmpMeta.$tagName$}:${hostRef.$modeName$}`,
`[Stencil] Load module for <${cmpMeta.$tagName$}>`,
);
Cstr = await CstrImport;
endLoad();
} else {
Cstr = CstrImport as d.ComponentConstructor | undefined;
}
if (!Cstr) {
throw new Error(`Constructor for "${cmpMeta.$tagName$}#${hostRef.$modeName$}" was not found`);
}
if (BUILD.member && !Cstr.isProxied) {
// we've never proxied this Constructor before
// let's add the getters/setters to its prototype before
// the first time we create an instance of the implementation
if (BUILD.propChangeCallback) {
cmpMeta.$watchers$ = normalizeWatchers(Cstr.watchers);
cmpMeta.$serializers$ = Cstr.serializers;
cmpMeta.$deserializers$ = Cstr.deserializers;
}
proxyComponent(Cstr, cmpMeta, PROXY_FLAGS.proxyState);
Cstr.isProxied = true;
}
const endNewInstance = createTime('createInstance', cmpMeta.$tagName$);
// ok, time to construct the instance
// but let's keep track of when we start and stop
// so that the getters/setters don't incorrectly step on data
if (BUILD.member) {
hostRef.$flags$ |= HOST_FLAGS.isConstructingInstance;
}
// construct the lazy-loaded component implementation
// passing the hostRef is very important during
// construction in order to directly wire together the
// host element and the lazy-loaded instance
try {
new (Cstr as any)(hostRef);
} catch (e) {
consoleError(e, elm);
}
if (BUILD.member) {
hostRef.$flags$ &= ~HOST_FLAGS.isConstructingInstance;
}
if (BUILD.propChangeCallback) {
hostRef.$flags$ |= HOST_FLAGS.isWatchReady;
}
endNewInstance();
// For components that relocate slots, defer connectedCallback until after first render
// so that slotted content is available
const needsDeferredCallback = BUILD.slotRelocation && cmpMeta.$flags$ & CMP_FLAGS.hasSlotRelocation;
if (!needsDeferredCallback) {
fireConnectedCallback(hostRef.$lazyInstance$, elm);
} else {
hostRef.$deferredConnectedCallback$ = true;
}
} else {
// sync constructor component
Cstr = elm.constructor as any;
/**
* Instead of using e.g. `cmpMeta.$tagName$` we use `elm.localName` to get the tag name of the component.
* This is because we can't guarantee that the component class is actually registered with the tag name
* defined in the component class as users can very well also do this:
*
* ```html
* <script type="module">
* import { MyComponent } from 'my-stencil-component-library';
* customElements.define('my-other-component', MyComponent);
* </script>
* ```
*/
const cmpTag = elm.localName;
// wait for the CustomElementRegistry to mark the component as ready before setting `isWatchReady`. Otherwise,
// watchers may fire prematurely if `customElements.get()`/`customElements.whenDefined()` resolves _before_
// Stencil has completed instantiating the component.
customElements.whenDefined(cmpTag).then(() => (hostRef.$flags$ |= HOST_FLAGS.isWatchReady));
}
if (BUILD.style && Cstr && Cstr.style) {
/**
* this component has styles but we haven't registered them yet
*/
let style: string | undefined;
if (typeof Cstr.style === 'string') {
/**
* in case the component has a `styleUrl` defined, e.g.
* ```ts
* @Component({
* tag: 'my-component',
* styleUrl: 'my-component.css'
* })
* ```
*/
style = Cstr.style;
} else if (BUILD.mode && typeof Cstr.style !== 'string') {
/**
* in case the component has a `styleUrl` object defined, e.g.
* ```ts
* @Component({
* tag: 'my-component',
* styleUrl: {
* ios: 'my-component.ios.css',
* md: 'my-component.md.css'
* }
* })
* ```
*/
hostRef.$modeName$ = computeMode(elm) as string | undefined;
if (hostRef.$modeName$) {
style = Cstr.style[hostRef.$modeName$];
}
if (BUILD.hydrateServerSide && hostRef.$modeName$) {
elm.setAttribute('s-mode', hostRef.$modeName$);
}
}
const scopeId = getScopeId(cmpMeta, hostRef.$modeName$);
// Always re-register styles during HMR to pick up inline style changes
if (!styles.has(scopeId) || (BUILD.hotModuleReplacement && hmrVersionId)) {
const endRegisterStyles = createTime('registerStyles', cmpMeta.$tagName$);
if (BUILD.hydrateServerSide && BUILD.shadowDom) {
if (cmpMeta.$flags$ & CMP_FLAGS.shadowNeedsScopedCss) {
style = scopeCss(style, scopeId, true);
} else if (needsScopedSSR()) {
style = expandPartSelectors(style);
}
}
registerStyle(scopeId, style, !!(cmpMeta.$flags$ & CMP_FLAGS.shadowDomEncapsulation));
endRegisterStyles();
}
}
}
// we've successfully created a lazy instance
const ancestorComponent = hostRef.$ancestorComponent$;
const schedule = () => scheduleUpdate(hostRef, true);
if (BUILD.asyncLoading && ancestorComponent && ancestorComponent['s-rc']) {
// this is the initial load and this component it has an ancestor component
// but the ancestor component has NOT fired its will update lifecycle yet
// so let's just cool our jets and wait for the ancestor to continue first
// this will get fired off when the ancestor component
// finally gets around to rendering its lazy self
// fire off the initial update
ancestorComponent['s-rc'].push(schedule);
} else {
schedule();
}
} catch (e) {
consoleError(e, elm);
// Ensure we release the parent component even if this child failed to initialize.
// Without this, a failed child would cause its parent to hang forever waiting
// for all children to resolve before completing hydration.
if (BUILD.asyncLoading && hostRef.$onRenderResolve$) {
hostRef.$onRenderResolve$();
hostRef.$onRenderResolve$ = undefined;
}
// Also resolve the component's ready promise so any code waiting on
// componentOnReady() doesn't hang forever
if (BUILD.asyncLoading && hostRef.$onReadyResolve$) {
hostRef.$onReadyResolve$(elm);
}
}
};
export const fireConnectedCallback = (instance: any, elm?: HTMLElement) => {
if (BUILD.lazyLoad) {
safeCall(instance, 'connectedCallback', undefined, elm);
}
};