From 3f4248204d5de44fe3d503bf0ab713e0ff56e0fb Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 15 Nov 2023 14:23:07 +0000 Subject: [PATCH 1/8] chore: make DOM operations lazyily init --- .changeset/fresh-weeks-trade.md | 5 + .../svelte/src/internal/client/operations.js | 150 +++++++++++++----- packages/svelte/src/internal/client/render.js | 2 + 3 files changed, 113 insertions(+), 44 deletions(-) create mode 100644 .changeset/fresh-weeks-trade.md diff --git a/.changeset/fresh-weeks-trade.md b/.changeset/fresh-weeks-trade.md new file mode 100644 index 000000000000..224db4c8960e --- /dev/null +++ b/.changeset/fresh-weeks-trade.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: make operations lazy diff --git a/packages/svelte/src/internal/client/operations.js b/packages/svelte/src/internal/client/operations.js index d94e20dd3e17..134540b5b756 100644 --- a/packages/svelte/src/internal/client/operations.js +++ b/packages/svelte/src/internal/client/operations.js @@ -1,46 +1,112 @@ import { current_hydration_fragment, get_hydration_fragment } from './hydration.js'; import { get_descriptor } from './utils.js'; -/** This file is also loaded in server environments, so we need guard against eagerly accessing browser globals */ -const has_browser_globals = typeof window !== 'undefined'; - -// We cache the Node and Element prototype methods, so that subsequent calls-sites are monomorphic rather -// than megamorphic. -const node_prototype = /** @type {Node} */ (has_browser_globals ? Node.prototype : {}); -const element_prototype = /** @type {Element} */ (has_browser_globals ? Element.prototype : {}); -const text_prototype = /** @type {Text} */ (has_browser_globals ? Text.prototype : {}); -const map_prototype = Map.prototype; -const append_child_method = node_prototype.appendChild; -const clone_node_method = node_prototype.cloneNode; -const map_set_method = map_prototype.set; -const map_get_method = map_prototype.get; -const map_delete_method = map_prototype.delete; -// @ts-expect-error improve perf of expando on DOM events -element_prototype.__click = undefined; -// @ts-expect-error improve perf of expando on DOM text updates -text_prototype.__nodeValue = ' '; -// @ts-expect-error improve perf of expando on DOM className updates -element_prototype.__className = ''; - -const first_child_get = /** @type {(this: Node) => ChildNode | null} */ ( - // @ts-ignore - has_browser_globals ? get_descriptor(node_prototype, 'firstChild').get : null -); - -const next_sibling_get = /** @type {(this: Node) => ChildNode | null} */ ( - // @ts-ignore - has_browser_globals ? get_descriptor(node_prototype, 'nextSibling').get : null -); - -const text_content_set = /** @type {(this: Node, text: string ) => void} */ ( - // @ts-ignore - has_browser_globals ? get_descriptor(node_prototype, 'textContent').set : null -); - -const class_name_set = /** @type {(this: Element, class_name: string) => void} */ ( - // @ts-ignore - has_browser_globals ? get_descriptor(element_prototype, 'className').set : null -); +// We cache the Node and Element prototype methods, so that we can avoid doing +// expensive prototype chain lookups. + +/** + * @type {typeof Node.prototype} + */ +var node_prototype; +/** + * @type {typeof Element.prototype} + */ +var element_prototype; +/** + * @type {typeof Text.prototype} + */ +var text_prototype; +var map_prototype; +/** + * @type {typeof Node.prototype.appendChild} + */ +var append_child_method; +/** + * @type {typeof Node.prototype.cloneNode} + */ +var clone_node_method; +/** + * @type {typeof Map.prototype.set} + */ +var map_set_method; +/** + * @type {typeof Map.prototype.get} + */ +var map_get_method; +/** + * @type {typeof Map.prototype.delete} + */ +var map_delete_method; +/** + * @type {(this: Node) => ChildNode | null} + */ +var first_child_get; +/** + * @type {(this: Node) => ChildNode | null} + */ +var next_sibling_get; +/** + * @type {(this: Node, text: string ) => void} + */ +var text_content_set; + +/** + * @type {(this: Element, class_name: string) => void} + */ +var class_name_set; + +// export these for reference in the compiled code, making global name deduplication unnecessary +/** + * @type {Window} + */ +export var $window; +/** + * @type {Document} + */ +export var $document; + +export function init_operations() { + if (node_prototype !== undefined) { + return; + } + node_prototype = /** @type {Node} */ (Node.prototype); + element_prototype = /** @type {Element} */ (Element.prototype); + text_prototype = /** @type {Text} */ (Text.prototype); + map_prototype = Map.prototype; + append_child_method = node_prototype.appendChild; + clone_node_method = node_prototype.cloneNode; + map_set_method = map_prototype.set; + map_get_method = map_prototype.get; + map_delete_method = map_prototype.delete; + // @ts-expect-error improve perf of expando on DOM events + element_prototype.__click = undefined; + // @ts-expect-error improve perf of expando on DOM text updates + text_prototype.__nodeValue = ' '; + // @ts-expect-error improve perf of expando on DOM className updates + element_prototype.__className = ''; + $window = window; + $document = document; + + first_child_get = /** @type {(this: Node) => ChildNode | null} */ ( + // @ts-ignore + get_descriptor(node_prototype, 'firstChild').get + ); + + next_sibling_get = /** @type {(this: Node) => ChildNode | null} */ ( + // @ts-ignore + get_descriptor(node_prototype, 'nextSibling').get + ); + + text_content_set = /** @type {(this: Node, text: string ) => void} */ ( + // @ts-ignore + get_descriptor(node_prototype, 'textContent').set + ); + + class_name_set = /** @type {(this: Element, class_name: string) => void} */ ( + // @ts-ignore + get_descriptor(element_prototype, 'className').set + ); +} /** * @template {Element} E @@ -191,7 +257,3 @@ function capture_fragment_from_node(node) { } return node; } - -// export these for reference in the compiled code, making global name deduplication unnecessary -export const $window = has_browser_globals ? window : undefined; -export const $document = has_browser_globals ? document : undefined; diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index d7671618d560..68f9ffdb317e 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -4,6 +4,7 @@ import { child, clone_node, create_element, + init_operations, map_get, map_set, set_class_name @@ -3173,6 +3174,7 @@ export function createRoot(component, options) { * @returns {[Exports, () => void]} */ export function mount(component, options) { + init_operations(); const registered_events = new Set(); const container = options.target; const block = create_root_block(container, options.intro || false); From 10a3cbe2166fef175fcee39ab142fe33d56eefa3 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 15 Nov 2023 14:25:39 +0000 Subject: [PATCH 2/8] cleanup types --- packages/svelte/src/internal/client/operations.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/svelte/src/internal/client/operations.js b/packages/svelte/src/internal/client/operations.js index 134540b5b756..f177badd221d 100644 --- a/packages/svelte/src/internal/client/operations.js +++ b/packages/svelte/src/internal/client/operations.js @@ -5,15 +5,15 @@ import { get_descriptor } from './utils.js'; // expensive prototype chain lookups. /** - * @type {typeof Node.prototype} + * @type {Node} */ var node_prototype; /** - * @type {typeof Element.prototype} + * @type {Element} */ var element_prototype; /** - * @type {typeof Text.prototype} + * @type {Text} */ var text_prototype; var map_prototype; From 1e422ed432743042dc9830e5c5ff9ece3a054412 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 15 Nov 2023 14:25:52 +0000 Subject: [PATCH 3/8] cleanup types --- packages/svelte/src/internal/client/operations.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/svelte/src/internal/client/operations.js b/packages/svelte/src/internal/client/operations.js index f177badd221d..1020e7bc0416 100644 --- a/packages/svelte/src/internal/client/operations.js +++ b/packages/svelte/src/internal/client/operations.js @@ -16,6 +16,9 @@ var element_prototype; * @type {Text} */ var text_prototype; +/** + * @type {Map} + */ var map_prototype; /** * @type {typeof Node.prototype.appendChild} From 52d0855fd761c9e27fc10cf31f8a332a9d920f9e Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 15 Nov 2023 14:26:14 +0000 Subject: [PATCH 4/8] cleanup types --- packages/svelte/src/internal/client/operations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/operations.js b/packages/svelte/src/internal/client/operations.js index 1020e7bc0416..bfe8e13ab2df 100644 --- a/packages/svelte/src/internal/client/operations.js +++ b/packages/svelte/src/internal/client/operations.js @@ -17,7 +17,7 @@ var element_prototype; */ var text_prototype; /** - * @type {Map} + * @type {Map} */ var map_prototype; /** From a05dfedba702a20fbfb67c002a6ce7c57f5f5b81 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 15 Nov 2023 14:30:29 +0000 Subject: [PATCH 5/8] Update packages/svelte/src/internal/client/operations.js Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- packages/svelte/src/internal/client/operations.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/svelte/src/internal/client/operations.js b/packages/svelte/src/internal/client/operations.js index bfe8e13ab2df..2ecc230d707e 100644 --- a/packages/svelte/src/internal/client/operations.js +++ b/packages/svelte/src/internal/client/operations.js @@ -68,6 +68,10 @@ export var $window; */ export var $document; +/** + * Initialize these lazily to avoid issues when using the runtime in a server context + * where these globals are not available while avoiding a separate server entry point + */ export function init_operations() { if (node_prototype !== undefined) { return; From 4d628439e7031db113c9c748cdf97551cdb83ff6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 15 Nov 2023 11:12:25 -0500 Subject: [PATCH 6/8] single line annotations --- .../svelte/src/internal/client/operations.js | 63 +++++++------------ 1 file changed, 24 insertions(+), 39 deletions(-) diff --git a/packages/svelte/src/internal/client/operations.js b/packages/svelte/src/internal/client/operations.js index 2ecc230d707e..f1a7e9608295 100644 --- a/packages/svelte/src/internal/client/operations.js +++ b/packages/svelte/src/internal/client/operations.js @@ -4,58 +4,43 @@ import { get_descriptor } from './utils.js'; // We cache the Node and Element prototype methods, so that we can avoid doing // expensive prototype chain lookups. -/** - * @type {Node} - */ +/** @type {Node} */ var node_prototype; -/** - * @type {Element} - */ + +/** @type {Element} */ var element_prototype; -/** - * @type {Text} - */ + +/** @type {Text} */ var text_prototype; -/** - * @type {Map} - */ + +/** @type {Map} */ var map_prototype; -/** - * @type {typeof Node.prototype.appendChild} - */ + +/** @type {typeof Node.prototype.appendChild} */ var append_child_method; -/** - * @type {typeof Node.prototype.cloneNode} - */ + +/** @type {typeof Node.prototype.cloneNode} */ var clone_node_method; -/** - * @type {typeof Map.prototype.set} - */ + +/** @type {typeof Map.prototype.set} */ var map_set_method; -/** - * @type {typeof Map.prototype.get} - */ + +/** @type {typeof Map.prototype.get} */ var map_get_method; -/** - * @type {typeof Map.prototype.delete} - */ + +/** @type {typeof Map.prototype.delete} */ var map_delete_method; -/** - * @type {(this: Node) => ChildNode | null} - */ + +/** @type {(this: Node) => ChildNode | null} */ var first_child_get; -/** - * @type {(this: Node) => ChildNode | null} - */ + +/** @type {(this: Node) => ChildNode | null} */ var next_sibling_get; -/** - * @type {(this: Node, text: string ) => void} - */ + +/** @type {(this: Node, text: string ) => void} */ var text_content_set; -/** - * @type {(this: Element, class_name: string) => void} - */ +/** @type {(this: Element, class_name: string) => void} */ var class_name_set; // export these for reference in the compiled code, making global name deduplication unnecessary From c735f2b7b7ebc8fbf0cc74243d9d541cce78ca3a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 15 Nov 2023 11:15:08 -0500 Subject: [PATCH 7/8] remove unnecessary coercion --- packages/svelte/src/internal/client/operations.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/svelte/src/internal/client/operations.js b/packages/svelte/src/internal/client/operations.js index f1a7e9608295..764c33b7dac9 100644 --- a/packages/svelte/src/internal/client/operations.js +++ b/packages/svelte/src/internal/client/operations.js @@ -61,9 +61,10 @@ export function init_operations() { if (node_prototype !== undefined) { return; } - node_prototype = /** @type {Node} */ (Node.prototype); - element_prototype = /** @type {Element} */ (Element.prototype); - text_prototype = /** @type {Text} */ (Text.prototype); + + node_prototype = Node.prototype; + element_prototype = Element.prototype; + text_prototype = Text.prototype; map_prototype = Map.prototype; append_child_method = node_prototype.appendChild; clone_node_method = node_prototype.cloneNode; From 001ef3445dc35d3b1bda3bc81cb062d3128f97f4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 15 Nov 2023 11:17:36 -0500 Subject: [PATCH 8/8] group statements by type --- packages/svelte/src/internal/client/operations.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/internal/client/operations.js b/packages/svelte/src/internal/client/operations.js index 764c33b7dac9..e8cbe2271874 100644 --- a/packages/svelte/src/internal/client/operations.js +++ b/packages/svelte/src/internal/client/operations.js @@ -66,19 +66,23 @@ export function init_operations() { element_prototype = Element.prototype; text_prototype = Text.prototype; map_prototype = Map.prototype; + append_child_method = node_prototype.appendChild; clone_node_method = node_prototype.cloneNode; map_set_method = map_prototype.set; map_get_method = map_prototype.get; map_delete_method = map_prototype.delete; - // @ts-expect-error improve perf of expando on DOM events + + $window = window; + $document = document; + + // the following assignments improve perf of lookups on DOM nodes + // @ts-expect-error element_prototype.__click = undefined; - // @ts-expect-error improve perf of expando on DOM text updates + // @ts-expect-error text_prototype.__nodeValue = ' '; - // @ts-expect-error improve perf of expando on DOM className updates + // @ts-expect-error element_prototype.__className = ''; - $window = window; - $document = document; first_child_get = /** @type {(this: Node) => ChildNode | null} */ ( // @ts-ignore