diff --git a/packages/svelte/package.json b/packages/svelte/package.json index c875fe1b16..5ef2e1b52a 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -71,6 +71,9 @@ "./internal/flags/tracing": { "default": "./src/internal/flags/tracing.js" }, + "./internal/flags/custom-renderer": { + "default": "./src/internal/flags/custom-renderer.js" + }, "./internal/server": { "default": "./src/internal/server/index.js" }, diff --git a/packages/svelte/scripts/check-treeshakeability.js b/packages/svelte/scripts/check-treeshakeability.js index d66b4c062a..47c26ed7a6 100644 --- a/packages/svelte/scripts/check-treeshakeability.js +++ b/packages/svelte/scripts/check-treeshakeability.js @@ -59,6 +59,7 @@ for (const key in pkg.exports) { if (key === './internal/disclose-version') continue; if (key === './internal/flags/legacy') continue; if (key === './internal/flags/tracing') continue; + if (key === './internal/flags/custom-renderer') continue; if (key === './internal/init-operations') continue; for (const type of ['browser', 'default']) { @@ -149,6 +150,7 @@ function check_bundle(case_name, ...strings) { check_bundle('Hydration code', 'hydrate_node', 'hydrate_next'); check_bundle('Legacy code', 'component_context.l'); check_bundle('$inspect.trace', `'CreatedAt'`); +check_bundle('Custom renderer code', 'push_renderer', 'insertForeign', 'renderer_missing_foreign'); if (failed) { // eslint-disable-next-line no-console diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 92360f1a3b..aca1db33d3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -555,6 +555,10 @@ export function client_component(analysis, options) { if (options.discloseVersion) { body.unshift(b.imports([], 'svelte/internal/disclose-version')); } + // this needs to be !== undefined because we want to enable the flag even if the custom renderer is null (which is a valid value meaning "no renderer") + if (custom_renderer !== undefined) { + body.unshift(b.imports([], 'svelte/internal/flags/custom-renderer')); + } if (custom_renderer) { body.unshift(b.imports([['$renderer', '$renderer', true]], custom_renderer)); diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index dffc8ddad2..2da74f5356 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -49,6 +49,7 @@ import { import { defer_effect } from '../../reactivity/utils.js'; import { set_signal_status } from '../../reactivity/status.js'; import { push_renderer } from '../../custom-renderer/state.js'; +import { custom_renderers_flag } from '../../../flags/index.js'; /** * @typedef {{ @@ -230,7 +231,9 @@ export class Boundary { this.#pending_effect = branch(() => pending(this.#anchor)); queue_micro_task(() => { - var pop_renderer = push_renderer(this.#effect.r, this.#effect.pr); + var pop_renderer = custom_renderers_flag + ? push_renderer(this.#effect.r, this.#effect.pr) + : undefined; var fragment = (this.#offscreen_fragment = create_fragment()); var anchor = create_text(); @@ -324,7 +327,9 @@ export class Boundary { set_active_reaction(this.#effect); set_component_context(this.#effect.ctx); - var pop_renderer = push_renderer(this.#effect.r, this.#effect.pr); + var pop_renderer = custom_renderers_flag + ? push_renderer(this.#effect.r, this.#effect.pr) + : undefined; try { Batch.ensure(); @@ -368,7 +373,9 @@ export class Boundary { } if (this.#offscreen_fragment) { - var pop_renderer = push_renderer(this.#effect.r, this.#effect.pr); + var pop_renderer = custom_renderers_flag + ? push_renderer(this.#effect.r, this.#effect.pr) + : undefined; insert_before(this.#anchor, this.#offscreen_fragment); this.#offscreen_fragment = null; pop_renderer?.(); diff --git a/packages/svelte/src/internal/client/dom/blocks/branches.js b/packages/svelte/src/internal/client/dom/blocks/branches.js index 6c43f52e72..1b97773ac6 100644 --- a/packages/svelte/src/internal/client/dom/blocks/branches.js +++ b/packages/svelte/src/internal/client/dom/blocks/branches.js @@ -24,9 +24,9 @@ import { push_renderer, current_renderer, parent_renderer, - set_parent_renderer, - set_renderer + set_parent_renderer } from '../../custom-renderer/state.js'; +import { custom_renderers_flag } from '../../../flags/index.js'; /** * @typedef {{ effect: Effect, fragment: DocumentFragment }} Branch @@ -108,14 +108,18 @@ export class BranchManager { #create_branch(fn) { // we push current renderer twice because branches will always // append to the current renderer - var pop_renderer = push_renderer(this.#renderer, this.#renderer); + var pop_renderer = custom_renderers_flag + ? push_renderer(this.#renderer, this.#renderer) + : undefined; try { return branch(fn); } finally { pop_renderer?.(); // we restore the parent_renderer so that an append after a // branch will append to the correct renderer - set_parent_renderer(this.#parent_renderer); + if (custom_renderers_flag) { + set_parent_renderer(this.#parent_renderer); + } } } @@ -126,7 +130,9 @@ export class BranchManager { // if this batch was made obsolete, bail if (!this.#batches.has(batch)) return; - var pop_renderer = push_renderer(this.#renderer, this.#parent_renderer); + var pop_renderer = custom_renderers_flag + ? push_renderer(this.#renderer, this.#parent_renderer) + : undefined; var key = /** @type {Key} */ (this.#batches.get(batch)); diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 7aea6b95a8..2517f4f162 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -56,6 +56,7 @@ import { parent_renderer, set_parent_renderer } from '../../custom-renderer/state.js'; +import { custom_renderers_flag } from '../../../flags/index.js'; // When making substantive changes to this file, validate them with the each block stress test: // https://svelte.dev/playground/1972b2cf46564476ad8c8c6405b23b7b @@ -272,7 +273,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f return; } - var pop_renderer = push_renderer(renderer, parent); + var pop_renderer = custom_renderers_flag ? push_renderer(renderer, parent) : undefined; state.pending.delete(batch); @@ -309,7 +310,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f // we push current renderer twice because branches will always // append to the current renderer - var pop_renderer = push_renderer(renderer, renderer); + var pop_renderer = custom_renderers_flag ? push_renderer(renderer, renderer) : undefined; var effect = block(() => { array = /** @type {V[]} */ (get(each_array)); @@ -447,7 +448,9 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f pop_renderer?.(); // we restore the parent_renderer so that an append after a // branch will append to the correct renderer - set_parent_renderer(parent); + if (custom_renderers_flag) { + set_parent_renderer(parent); + } /** @type {EachState} */ var state = { effect, flags, items, pending, outrogroups: null, fallback }; diff --git a/packages/svelte/src/internal/client/dom/blocks/snippet.js b/packages/svelte/src/internal/client/dom/blocks/snippet.js index 401d7cfc9c..d6270e0f66 100644 --- a/packages/svelte/src/internal/client/dom/blocks/snippet.js +++ b/packages/svelte/src/internal/client/dom/blocks/snippet.js @@ -18,6 +18,7 @@ import { prevent_snippet_stringification } from '../../../shared/validate.js'; import { has_own_property } from '../../../shared/utils.js'; import { BranchManager } from './branches.js'; import { current_renderer, push_renderer } from '../../custom-renderer/state.js'; +import { custom_renderers_flag } from '../../../flags/index.js'; /** * @template {(node: TemplateNode, ...args: any[]) => void} SnippetFn @@ -40,16 +41,18 @@ export function snippet(node, get_snippet, ...args) { snippet, snippet && ((anchor) => { - var renderer = /** @type {any} */ (snippet).__renderer; - var has_renderer = has_own_property.call(/** @type {any} */ (snippet), '__renderer'); - - if (has_renderer) { - var pop_renderer = push_renderer(renderer, renderer); - - try { - return snippet(anchor, ...args); - } finally { - pop_renderer(); + if (custom_renderers_flag) { + var renderer = /** @type {any} */ (snippet).__renderer; + var has_renderer = has_own_property.call(/** @type {any} */ (snippet), '__renderer'); + + if (has_renderer) { + var pop_renderer = push_renderer(renderer, renderer); + + try { + return snippet(anchor, ...args); + } finally { + pop_renderer(); + } } } diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index fca13e208e..427cd53181 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -16,6 +16,7 @@ import { TEMPLATE_USE_SVG } from '../../../constants.js'; import { current_renderer, parent_renderer } from '../custom-renderer/state.js'; +import { custom_renderers_flag } from '../../flags/index.js'; import { active_effect } from '../runtime.js'; import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from './hydration.js'; import { @@ -75,6 +76,7 @@ export function assign_nodes(start, end) { */ function should_segment_nodes(effect) { return ( + custom_renderers_flag && (current_renderer !== effect.r || parent_renderer !== effect.pr) && (current_renderer?.foreign != null || parent_renderer?.foreign != null) ); diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js index 03afc6c644..87d57b3a9b 100644 --- a/packages/svelte/src/internal/client/reactivity/async.js +++ b/packages/svelte/src/internal/client/reactivity/async.js @@ -15,6 +15,7 @@ import { set_parent_renderer, set_renderer } from '../custom-renderer/state.js'; +import { custom_renderers_flag } from '../../flags/index.js'; import { invoke_error_boundary } from '../error-handling.js'; import { active_effect, @@ -138,8 +139,8 @@ export function capture() { var previous_reaction = active_reaction; var previous_component_context = component_context; var previous_batch = /** @type {Batch} */ (current_batch); - var previous_renderer = current_renderer; - var previous_parent_renderer = parent_renderer; + var previous_renderer = custom_renderers_flag ? current_renderer : null; + var previous_parent_renderer = custom_renderers_flag ? parent_renderer : null; if (DEV) { var previous_dev_stack = dev_stack; @@ -150,8 +151,10 @@ export function capture() { set_active_reaction(previous_reaction); set_component_context(previous_component_context); - set_renderer(previous_renderer); - set_parent_renderer(previous_parent_renderer); + if (custom_renderers_flag) { + set_renderer(previous_renderer); + set_parent_renderer(previous_parent_renderer); + } if (activate_batch && (previous_effect.f & DESTROYED) === 0) { // TODO we only need optional chaining here because `{#await ...}` blocks @@ -283,8 +286,10 @@ export function unset_context(deactivate_batch = true) { set_active_effect(null); set_active_reaction(null); set_component_context(null); - set_renderer(null); - set_parent_renderer(null); + if (custom_renderers_flag) { + set_renderer(null); + set_parent_renderer(null); + } if (deactivate_batch) current_batch?.deactivate(); if (DEV) { diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index ad2fb4ce27..9dd6ae9827 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -46,6 +46,7 @@ import { flatten } from './async.js'; import { without_reactive_context } from '../dom/elements/bindings/shared.js'; import { set_signal_status } from './status.js'; import { push_renderer, current_renderer, parent_renderer } from '../custom-renderer/state.js'; +import { custom_renderers_flag } from '../../flags/index.js'; /** * @param {'$effect' | '$effect.pre' | '$inspect'} rune @@ -514,7 +515,7 @@ export function destroy_block_effect_children(signal) { export function destroy_effect(effect, remove_dom = true) { var removed = false; - var pop_renderer = push_renderer(effect.r, effect.pr); + var pop_renderer = custom_renderers_flag ? push_renderer(effect.r, effect.pr) : undefined; if ( (remove_dom || (effect.f & HEAD_EFFECT) !== 0) && @@ -600,7 +601,7 @@ function remove_effect_nodes(effect) { for (var i = segments.length - 1; i >= 0; i--) { var segment = segments[i]; - var pop_renderer = push_renderer(segment.r, segment.pr); + var pop_renderer = custom_renderers_flag ? push_renderer(segment.r, segment.pr) : undefined; remove_effect_dom(segment.start, /** @type {TemplateNode} */ (segment.end)); pop_renderer?.(); } @@ -760,7 +761,7 @@ export function aborted(effect = /** @type {Effect} */ (active_effect)) { export function move_effect(effect, fragment) { if (!effect.nodes) return; - var pop_renderer = push_renderer(effect.r, effect.pr); + var pop_renderer = custom_renderers_flag ? push_renderer(effect.r, effect.pr) : undefined; /** @type {TemplateNode | null} */ var node = effect.nodes.start; diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 3390fe78fd..f0dfe74367 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -34,6 +34,7 @@ import { is_passive_event } from '../../utils.js'; import { COMMENT_NODE, STATE_SYMBOL, TEXT_CACHE } from './constants.js'; import { boundary } from './dom/blocks/boundary.js'; import { push_renderer } from './custom-renderer/state.js'; +import { custom_renderers_flag } from '../flags/index.js'; /** * This is normally true — block effects should run their intro transitions — @@ -167,7 +168,7 @@ const listeners = new Map(); * @returns {Exports} */ function _mount(Component, options) { - if (options.renderer) { + if (custom_renderers_flag && options.renderer) { var pop_renderer = push_renderer(options.renderer, options.renderer); try { diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index b5de8032e9..693856032d 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -34,7 +34,7 @@ import { unfreeze_derived_effects, update_derived } from './reactivity/deriveds.js'; -import { async_mode_flag, tracing_mode_flag } from '../flags/index.js'; +import { async_mode_flag, tracing_mode_flag, custom_renderers_flag } from '../flags/index.js'; import { tracing_expressions } from './dev/tracing.js'; import { get_error } from '../shared/dev.js'; import { @@ -447,7 +447,7 @@ export function update_effect(effect) { active_effect = effect; is_updating_effect = true; - var pop_renderer = push_renderer(effect.r, effect.pr); + var pop_renderer = custom_renderers_flag ? push_renderer(effect.r, effect.pr) : undefined; if (DEV) { var previous_component_fn = dev_current_component_function; diff --git a/packages/svelte/src/internal/flags/custom-renderer.js b/packages/svelte/src/internal/flags/custom-renderer.js new file mode 100644 index 0000000000..31e2898ff6 --- /dev/null +++ b/packages/svelte/src/internal/flags/custom-renderer.js @@ -0,0 +1,3 @@ +import { enable_custom_renderers_flag } from './index.js'; + +enable_custom_renderers_flag(); diff --git a/packages/svelte/src/internal/flags/index.js b/packages/svelte/src/internal/flags/index.js index 5d4054975f..46e762db69 100644 --- a/packages/svelte/src/internal/flags/index.js +++ b/packages/svelte/src/internal/flags/index.js @@ -4,6 +4,8 @@ export let async_mode_flag = false; export let legacy_mode_flag = false; /** True if $inspect.trace is used */ export let tracing_mode_flag = false; +/** True if custom renderers are used */ +export let custom_renderers_flag = false; export function enable_async_mode_flag() { async_mode_flag = true; @@ -21,3 +23,7 @@ export function enable_legacy_mode_flag() { export function enable_tracing_mode_flag() { tracing_mode_flag = true; } + +export function enable_custom_renderers_flag() { + custom_renderers_flag = true; +} diff --git a/packages/svelte/src/renderer/index.js b/packages/svelte/src/renderer/index.js index 3377f7f1cb..dfee2a2892 100644 --- a/packages/svelte/src/renderer/index.js +++ b/packages/svelte/src/renderer/index.js @@ -1 +1,2 @@ +import '../internal/flags/custom-renderer.js'; export { createRenderer } from '../internal/client/custom-renderer/index.js';