diff --git a/.changeset/fresh-weeks-trade.md b/.changeset/fresh-weeks-trade.md new file mode 100644 index 0000000000..224db4c896 --- /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 d94e20dd3e..e8cbe22718 100644 --- a/packages/svelte/src/internal/client/operations.js +++ b/packages/svelte/src/internal/client/operations.js @@ -1,46 +1,109 @@ 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 {Node} */ +var node_prototype; + +/** @type {Element} */ +var element_prototype; + +/** @type {Text} */ +var text_prototype; + +/** @type {Map} */ +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; + +/** + * 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; + } + + 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; + map_set_method = map_prototype.set; + map_get_method = map_prototype.get; + map_delete_method = map_prototype.delete; + + $window = window; + $document = document; + + // the following assignments improve perf of lookups on DOM nodes + // @ts-expect-error + element_prototype.__click = undefined; + // @ts-expect-error + text_prototype.__nodeValue = ' '; + // @ts-expect-error + element_prototype.__className = ''; + + 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 +254,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 d7671618d5..68f9ffdb31 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);