Skip to content

Commit 4add75c

Browse files
authored
refactor(runtime): make WeakMap obsolete (#6156)
* poc: use hostrefs inside nodes * Add cleanup logic of vnode * fix typings * remove vdome cleanup to void stable refs * remove dev log * fix return types of delete * use get function to resolve hostref * remove not needed function * change type of childNode * remove WeapMap implementation from hydration and testing
1 parent 3b47de6 commit 4add75c

File tree

9 files changed

+40
-90
lines changed

9 files changed

+40
-90
lines changed

src/client/client-host-ref.ts

+12-31
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,19 @@ import { reWireGetterSetter } from '@utils/es2022-rewire-class-members';
33

44
import type * as d from '../declarations';
55

6-
/**
7-
* A WeakMap mapping runtime component references to their corresponding host reference
8-
* instances.
9-
*
10-
* **Note**: If we're in an HMR context we need to store a reference to this
11-
* value on `window` in order to maintain the mapping of {@link d.RuntimeRef}
12-
* to {@link d.HostRef} across HMR updates.
13-
*
14-
* This is necessary because when HMR updates for a component are processed by
15-
* the browser-side dev server client the JS bundle for that component is
16-
* re-fetched. Since the module containing {@link hostRefs} is included in
17-
* that bundle, if we do not store a reference to it the new iteration of the
18-
* component will not have access to the previous hostRef map, leading to a
19-
* bug where the new version of the component cannot properly initialize.
20-
*/
21-
const hostRefs: WeakMap<d.RuntimeRef, d.HostRef> = /*@__PURE__*/ BUILD.hotModuleReplacement
22-
? ((window as any).__STENCIL_HOSTREFS__ ||= new WeakMap())
23-
: new WeakMap();
24-
25-
/**
26-
* Given a {@link d.RuntimeRef} remove the corresponding {@link d.HostRef} from
27-
* the {@link hostRefs} WeakMap.
28-
*
29-
* @param ref the runtime ref of interest
30-
* @returns — true if the element was successfully removed, or false if it was not present.
31-
*/
32-
export const deleteHostRef = (ref: d.RuntimeRef) => hostRefs.delete(ref);
33-
346
/**
357
* Given a {@link d.RuntimeRef} retrieve the corresponding {@link d.HostRef}
368
*
379
* @param ref the runtime ref of interest
3810
* @returns the Host reference (if found) or undefined
3911
*/
40-
export const getHostRef = (ref: d.RuntimeRef): d.HostRef | undefined => hostRefs.get(ref);
12+
export const getHostRef = (ref: d.RuntimeRef): d.HostRef | undefined => {
13+
if (ref.__stencil__getHostRef) {
14+
return ref.__stencil__getHostRef();
15+
}
16+
17+
return undefined;
18+
};
4119

4220
/**
4321
* Register a lazy instance with the {@link hostRefs} object so it's
@@ -47,7 +25,9 @@ export const getHostRef = (ref: d.RuntimeRef): d.HostRef | undefined => hostRefs
4725
* @param hostRef that instances `HostRef` object
4826
*/
4927
export const registerInstance = (lazyInstance: any, hostRef: d.HostRef) => {
50-
hostRefs.set((hostRef.$lazyInstance$ = lazyInstance), hostRef);
28+
lazyInstance.__stencil__getHostRef = () => hostRef;
29+
hostRef.$lazyInstance$ = lazyInstance;
30+
5131
if (BUILD.modernPropertyDecls && (BUILD.state || BUILD.prop)) {
5232
reWireGetterSetter(lazyInstance, hostRef);
5333
}
@@ -81,7 +61,8 @@ export const registerHost = (hostElement: d.HostElement, cmpMeta: d.ComponentRun
8161
hostElement['s-rc'] = [];
8262
}
8363

84-
const ref = hostRefs.set(hostElement, hostRef);
64+
const ref = hostRef;
65+
hostElement.__stencil__getHostRef = () => ref;
8566

8667
if (!BUILD.lazyLoad && BUILD.modernPropertyDecls && (BUILD.state || BUILD.prop)) {
8768
reWireGetterSetter(hostElement, hostRef);

src/declarations/stencil-private.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1088,6 +1088,8 @@ export interface HostElement extends HTMLElement {
10881088
host?: Element;
10891089
forceUpdate?: () => void;
10901090

1091+
__stencil__getHostRef?: () => HostRef;
1092+
10911093
// "s-" prefixed properties should not be property renamed
10921094
// and should be common between all versions of stencil
10931095

@@ -1724,7 +1726,7 @@ export type ComponentRuntimeReflectingAttr = [string, string | undefined];
17241726
* keys in a `WeakMap` which maps {@link HostElement} instances to their
17251727
* associated {@link HostRef} instance.
17261728
*/
1727-
export type RuntimeRef = HostElement | {};
1729+
export type RuntimeRef = HostElement | { __stencil__getHostRef?: () => HostRef };
17281730

17291731
/**
17301732
* Interface used to track an Element, it's virtual Node (`VNode`), and other data

src/hydrate/platform/index.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,22 @@ export const supportsListenerOptions = false;
127127

128128
export const supportsConstructableStylesheets = false;
129129

130-
const hostRefs: WeakMap<d.RuntimeRef, d.HostRef> = new WeakMap();
130+
export const getHostRef = (ref: d.RuntimeRef) => {
131+
if (ref.__stencil__getHostRef) {
132+
return ref.__stencil__getHostRef();
133+
}
131134

132-
export const getHostRef = (ref: d.RuntimeRef) => hostRefs.get(ref);
133-
export const deleteHostRef = (ref: d.RuntimeRef) => hostRefs.delete(ref);
135+
return undefined;
136+
};
134137

135138
export const registerInstance = (lazyInstance: any, hostRef: d.HostRef) => {
136-
const ref = hostRefs.set((hostRef.$lazyInstance$ = lazyInstance), hostRef);
139+
lazyInstance.__stencil__getHostRef = () => hostRef;
140+
hostRef.$lazyInstance$ = lazyInstance;
141+
137142
if (BUILD.modernPropertyDecls && (BUILD.state || BUILD.prop)) {
138143
reWireGetterSetter(lazyInstance, hostRef);
139144
}
140-
return ref;
145+
return hostRef;
141146
};
142147

143148
export const registerHost = (elm: d.HostElement, cmpMeta: d.ComponentRuntimeMeta) => {
@@ -152,7 +157,9 @@ export const registerHost = (elm: d.HostElement, cmpMeta: d.ComponentRuntimeMeta
152157
hostRef.$onReadyPromise$ = new Promise((r) => (hostRef.$onReadyResolve$ = r));
153158
elm['s-p'] = [];
154159
elm['s-rc'] = [];
155-
return hostRefs.set(elm, hostRef);
160+
elm.__stencil__getHostRef = () => hostRef;
161+
162+
return hostRef;
156163
};
157164

158165
export const Build: d.UserBuildConditionals = {

src/runtime/bootstrap-custom-element.ts

+1-24
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
import { BUILD } from '@app-data';
2-
import {
3-
addHostEventListeners,
4-
deleteHostRef,
5-
forceUpdate,
6-
getHostRef,
7-
plt,
8-
registerHost,
9-
styles,
10-
supportsShadow,
11-
} from '@platform';
2+
import { addHostEventListeners, forceUpdate, getHostRef, registerHost, styles, supportsShadow } from '@platform';
123
import { CMP_FLAGS } from '@utils';
134

145
import type * as d from '../declarations';
@@ -102,20 +93,6 @@ export const proxyCustomElement = (Cstr: any, compactMeta: d.ComponentRuntimeMet
10293
if (BUILD.disconnectedCallback && originalDisconnectedCallback) {
10394
originalDisconnectedCallback.call(this);
10495
}
105-
106-
/**
107-
* Clean up Node references lingering around in `hostRef` objects
108-
* to ensure GC can clean up the memory.
109-
*/
110-
plt.raf(() => {
111-
const hostRef = getHostRef(this);
112-
if (hostRef?.$vnode$?.$elm$ instanceof Node && !hostRef.$vnode$.$elm$.isConnected) {
113-
delete hostRef.$vnode$;
114-
}
115-
if (this instanceof Node && !this.isConnected) {
116-
deleteHostRef(this);
117-
}
118-
});
11996
},
12097
__attachShadow() {
12198
if (supportsShadow) {

src/runtime/vdom/vdom-annotations.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ const parseVNodeAnnotations = (
128128
*/
129129
const childNodes = [...Array.from(node.childNodes), ...Array.from(node.shadowRoot?.childNodes || [])];
130130
childNodes.forEach((childNode) => {
131-
const hostRef = getHostRef(childNode);
131+
const hostRef = getHostRef(childNode as d.RuntimeRef);
132132
if (hostRef != null && !docData.staticComponents.has(childNode.nodeName.toLowerCase())) {
133133
const cmpData: CmpData = {
134134
nodeIds: 0,

src/testing/platform/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export { Build } from './testing-build';
22
export { modeResolutionChain, styles } from './testing-constants';
3-
export { deleteHostRef, getHostRef, registerHost, registerInstance } from './testing-host-ref';
3+
export { getHostRef, registerHost, registerInstance } from './testing-host-ref';
44
export { consoleDevError, consoleDevInfo, consoleDevWarn, consoleError, setErrorHandler } from './testing-log';
55
export {
66
isMemberInElement,

src/testing/platform/testing-constants.ts

-5
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,3 @@ export const queuedLoadModules: QueuedLoadModule[] = [];
2121
* A collection of errors that were detected to surface during the rendering process
2222
*/
2323
export const caughtErrors: Error[] = [];
24-
/**
25-
* A mapping of runtime references to HTML elements to the data structure Stencil uses to track the element alongside
26-
* additional metadata
27-
*/
28-
export const hostRefs = new Map<d.RuntimeRef, d.HostRef>();
+8-19
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,26 @@
11
import type * as d from '@stencil/core/internal';
22

3-
import { hostRefs } from './testing-constants';
4-
53
/**
64
* Retrieve the data structure tracking the component by its runtime reference
75
* @param elm the reference to the element
86
* @returns the corresponding Stencil reference data structure, or undefined if one cannot be found
97
*/
108
export const getHostRef = (elm: d.RuntimeRef | undefined): d.HostRef | undefined => {
11-
return hostRefs.get(elm);
12-
};
9+
if (elm.__stencil__getHostRef) {
10+
return elm.__stencil__getHostRef();
11+
}
1312

14-
/**
15-
* Given a {@link d.RuntimeRef} remove the corresponding {@link d.HostRef} from
16-
* the {@link hostRefs} WeakMap.
17-
*
18-
* @param ref the runtime ref of interest
19-
* @returns — true if the element was successfully removed, or false if it was not present.
20-
*/
21-
export const deleteHostRef = (ref: d.RuntimeRef) => hostRefs.delete(ref);
13+
return undefined;
14+
};
2215

2316
/**
2417
* Add the provided `hostRef` instance to the global {@link hostRefs} map, using the provided `lazyInstance` as a key.
2518
* @param lazyInstance a Stencil component instance
2619
* @param hostRef an optional reference to Stencil's tracking data for the component. If none is provided, one will be created.
27-
* @returns the updated `hostRefs` data structure
2820
* @throws if the provided `lazyInstance` coerces to `null`, or if the `lazyInstance` does not have a `constructor`
2921
* property
3022
*/
31-
export const registerInstance = (
32-
lazyInstance: any,
33-
hostRef: d.HostRef | null | undefined,
34-
): Map<d.RuntimeRef, d.HostRef> => {
23+
export const registerInstance = (lazyInstance: any, hostRef: d.HostRef | null | undefined) => {
3524
if (lazyInstance == null || lazyInstance.constructor == null) {
3625
throw new Error(`Invalid component constructor`);
3726
}
@@ -44,8 +33,8 @@ export const registerInstance = (
4433
hostRef = getHostRef(elm);
4534
}
4635

36+
lazyInstance.__stencil__getHostRef = () => hostRef;
4737
hostRef.$lazyInstance$ = lazyInstance;
48-
return hostRefs.set(lazyInstance, hostRef);
4938
};
5039

5140
/**
@@ -65,5 +54,5 @@ export const registerHost = (elm: d.HostElement, cmpMeta: d.ComponentRuntimeMeta
6554
hostRef.$onReadyPromise$ = new Promise((r) => (hostRef.$onReadyResolve$ = r));
6655
elm['s-p'] = [];
6756
elm['s-rc'] = [];
68-
hostRefs.set(elm, hostRef);
57+
elm.__stencil__getHostRef = () => hostRef;
6958
};

src/testing/platform/testing-platform.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type * as d from '@stencil/core/internal';
22

3-
import { cstrs, hostRefs, moduleLoaded, styles } from './testing-constants';
3+
import { cstrs, moduleLoaded, styles } from './testing-constants';
44
import { flushAll, resetTaskQueue } from './testing-task-queue';
55
import { win } from './testing-window';
66

@@ -54,7 +54,6 @@ export function resetPlatform(defaults: Partial<d.PlatformRuntime> = {}) {
5454
win.close();
5555
}
5656

57-
hostRefs.clear();
5857
styles.clear();
5958
plt.$flags$ = 0;
6059
Object.assign(plt, defaults);

0 commit comments

Comments
 (0)