Skip to content

Commit

Permalink
chore: make DOM operations lazily init (#9468)
Browse files Browse the repository at this point in the history
* 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 <5968653+dummdidumm@users.noreply.github.com>

* single line annotations

* remove unnecessary coercion

* group statements by type

---------

Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Co-authored-by: Rich Harris <rich.harris@vercel.com>
  • Loading branch information
3 people committed Nov 15, 2023
1 parent 640dd48 commit 73e8820
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 44 deletions.
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

1 comment on commit 73e8820

@vercel
Copy link

@vercel vercel bot commented on 73e8820 Nov 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

svelte-5-preview – ./sites/svelte-5-preview

svelte-5-preview-svelte.vercel.app
svelte-5-preview-git-main-svelte.vercel.app
svelte-5-preview.vercel.app
svelte-octane.vercel.app

Please sign in to comment.