Skip to content

chore: make DOM operations lazily init #9468

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fresh-weeks-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

chore: make operations lazy
147 changes: 103 additions & 44 deletions packages/svelte/src/internal/client/operations.js
Original file line number Diff line number Diff line change
@@ -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<any, any>} */
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
Expand Down Expand Up @@ -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;
2 changes: 2 additions & 0 deletions packages/svelte/src/internal/client/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
child,
clone_node,
create_element,
init_operations,
map_get,
map_set,
set_class_name
Expand Down Expand Up @@ -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);
Expand Down