Skip to content

Commit 73e8820

Browse files
trueadmdummdidummRich-Harris
authored
chore: make DOM operations lazily init (#9468)
* chore: make DOM operations lazyily init * cleanup types * cleanup types * cleanup types * Update packages/svelte/src/internal/client/operations.js Co-authored-by: Simon H <[email protected]> * single line annotations * remove unnecessary coercion * group statements by type --------- Co-authored-by: Simon H <[email protected]> Co-authored-by: Rich Harris <[email protected]>
1 parent 640dd48 commit 73e8820

File tree

3 files changed

+110
-44
lines changed

3 files changed

+110
-44
lines changed

.changeset/fresh-weeks-trade.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
chore: make operations lazy

packages/svelte/src/internal/client/operations.js

Lines changed: 103 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,109 @@
11
import { current_hydration_fragment, get_hydration_fragment } from './hydration.js';
22
import { get_descriptor } from './utils.js';
33

4-
/** This file is also loaded in server environments, so we need guard against eagerly accessing browser globals */
5-
const has_browser_globals = typeof window !== 'undefined';
6-
7-
// We cache the Node and Element prototype methods, so that subsequent calls-sites are monomorphic rather
8-
// than megamorphic.
9-
const node_prototype = /** @type {Node} */ (has_browser_globals ? Node.prototype : {});
10-
const element_prototype = /** @type {Element} */ (has_browser_globals ? Element.prototype : {});
11-
const text_prototype = /** @type {Text} */ (has_browser_globals ? Text.prototype : {});
12-
const map_prototype = Map.prototype;
13-
const append_child_method = node_prototype.appendChild;
14-
const clone_node_method = node_prototype.cloneNode;
15-
const map_set_method = map_prototype.set;
16-
const map_get_method = map_prototype.get;
17-
const map_delete_method = map_prototype.delete;
18-
// @ts-expect-error improve perf of expando on DOM events
19-
element_prototype.__click = undefined;
20-
// @ts-expect-error improve perf of expando on DOM text updates
21-
text_prototype.__nodeValue = ' ';
22-
// @ts-expect-error improve perf of expando on DOM className updates
23-
element_prototype.__className = '';
24-
25-
const first_child_get = /** @type {(this: Node) => ChildNode | null} */ (
26-
// @ts-ignore
27-
has_browser_globals ? get_descriptor(node_prototype, 'firstChild').get : null
28-
);
29-
30-
const next_sibling_get = /** @type {(this: Node) => ChildNode | null} */ (
31-
// @ts-ignore
32-
has_browser_globals ? get_descriptor(node_prototype, 'nextSibling').get : null
33-
);
34-
35-
const text_content_set = /** @type {(this: Node, text: string ) => void} */ (
36-
// @ts-ignore
37-
has_browser_globals ? get_descriptor(node_prototype, 'textContent').set : null
38-
);
39-
40-
const class_name_set = /** @type {(this: Element, class_name: string) => void} */ (
41-
// @ts-ignore
42-
has_browser_globals ? get_descriptor(element_prototype, 'className').set : null
43-
);
4+
// We cache the Node and Element prototype methods, so that we can avoid doing
5+
// expensive prototype chain lookups.
6+
7+
/** @type {Node} */
8+
var node_prototype;
9+
10+
/** @type {Element} */
11+
var element_prototype;
12+
13+
/** @type {Text} */
14+
var text_prototype;
15+
16+
/** @type {Map<any, any>} */
17+
var map_prototype;
18+
19+
/** @type {typeof Node.prototype.appendChild} */
20+
var append_child_method;
21+
22+
/** @type {typeof Node.prototype.cloneNode} */
23+
var clone_node_method;
24+
25+
/** @type {typeof Map.prototype.set} */
26+
var map_set_method;
27+
28+
/** @type {typeof Map.prototype.get} */
29+
var map_get_method;
30+
31+
/** @type {typeof Map.prototype.delete} */
32+
var map_delete_method;
33+
34+
/** @type {(this: Node) => ChildNode | null} */
35+
var first_child_get;
36+
37+
/** @type {(this: Node) => ChildNode | null} */
38+
var next_sibling_get;
39+
40+
/** @type {(this: Node, text: string ) => void} */
41+
var text_content_set;
42+
43+
/** @type {(this: Element, class_name: string) => void} */
44+
var class_name_set;
45+
46+
// export these for reference in the compiled code, making global name deduplication unnecessary
47+
/**
48+
* @type {Window}
49+
*/
50+
export var $window;
51+
/**
52+
* @type {Document}
53+
*/
54+
export var $document;
55+
56+
/**
57+
* Initialize these lazily to avoid issues when using the runtime in a server context
58+
* where these globals are not available while avoiding a separate server entry point
59+
*/
60+
export function init_operations() {
61+
if (node_prototype !== undefined) {
62+
return;
63+
}
64+
65+
node_prototype = Node.prototype;
66+
element_prototype = Element.prototype;
67+
text_prototype = Text.prototype;
68+
map_prototype = Map.prototype;
69+
70+
append_child_method = node_prototype.appendChild;
71+
clone_node_method = node_prototype.cloneNode;
72+
map_set_method = map_prototype.set;
73+
map_get_method = map_prototype.get;
74+
map_delete_method = map_prototype.delete;
75+
76+
$window = window;
77+
$document = document;
78+
79+
// the following assignments improve perf of lookups on DOM nodes
80+
// @ts-expect-error
81+
element_prototype.__click = undefined;
82+
// @ts-expect-error
83+
text_prototype.__nodeValue = ' ';
84+
// @ts-expect-error
85+
element_prototype.__className = '';
86+
87+
first_child_get = /** @type {(this: Node) => ChildNode | null} */ (
88+
// @ts-ignore
89+
get_descriptor(node_prototype, 'firstChild').get
90+
);
91+
92+
next_sibling_get = /** @type {(this: Node) => ChildNode | null} */ (
93+
// @ts-ignore
94+
get_descriptor(node_prototype, 'nextSibling').get
95+
);
96+
97+
text_content_set = /** @type {(this: Node, text: string ) => void} */ (
98+
// @ts-ignore
99+
get_descriptor(node_prototype, 'textContent').set
100+
);
101+
102+
class_name_set = /** @type {(this: Element, class_name: string) => void} */ (
103+
// @ts-ignore
104+
get_descriptor(element_prototype, 'className').set
105+
);
106+
}
44107

45108
/**
46109
* @template {Element} E
@@ -191,7 +254,3 @@ function capture_fragment_from_node(node) {
191254
}
192255
return node;
193256
}
194-
195-
// export these for reference in the compiled code, making global name deduplication unnecessary
196-
export const $window = has_browser_globals ? window : undefined;
197-
export const $document = has_browser_globals ? document : undefined;

packages/svelte/src/internal/client/render.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
child,
55
clone_node,
66
create_element,
7+
init_operations,
78
map_get,
89
map_set,
910
set_class_name
@@ -3173,6 +3174,7 @@ export function createRoot(component, options) {
31733174
* @returns {[Exports, () => void]}
31743175
*/
31753176
export function mount(component, options) {
3177+
init_operations();
31763178
const registered_events = new Set();
31773179
const container = options.target;
31783180
const block = create_root_block(container, options.intro || false);

0 commit comments

Comments
 (0)