From aa553ef125bfa590e77b80226c7662ccd2a0b782 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 26 Jun 2024 20:33:24 -0400 Subject: [PATCH] progress --- .../3-transform/client/visitors/template.js | 29 ++++++++++---- packages/svelte/src/constants.js | 1 + .../src/internal/client/dom/template.js | 38 +++++++++++++++---- .../src/internal/client/reactivity/effects.js | 14 +++++-- .../src/internal/client/reactivity/types.d.ts | 2 +- playgrounds/demo/vite.config.js | 2 +- 6 files changed, 66 insertions(+), 20 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 6a426db0d4..fda50b864a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -36,6 +36,7 @@ import { EACH_KEYED, is_capture_event, TEMPLATE_FRAGMENT, + TEMPLATE_UNSET_START, TEMPLATE_USE_IMPORT_NODE, TRANSITION_GLOBAL, TRANSITION_IN, @@ -1680,18 +1681,32 @@ export const template_visitors = { process_children(trimmed, expression, false, { ...context, state }); + let flags = TEMPLATE_FRAGMENT; + + if (state.metadata.context.template_needs_import_node) { + flags |= TEMPLATE_USE_IMPORT_NODE; + } + + if (trimmed[0].type === 'Component') { + flags |= TEMPLATE_UNSET_START; + } + + if (trimmed[0].type === 'RenderTag') { + const callee = unwrap_optional(trimmed[0].expression).callee; + const is_reactive = + callee.type !== 'Identifier' || context.state.scope.get(callee.name)?.kind !== 'normal'; + + if (!is_reactive) { + flags |= TEMPLATE_UNSET_START; + } + } + const use_comment_template = state.template.length === 1 && state.template[0] === ''; if (use_comment_template) { // special case — we can use `$.comment` instead of creating a unique template - body.push(b.var(id, b.call('$.comment'))); + body.push(b.var(id, b.call('$.comment', flags !== 0 && b.literal(flags)))); } else { - let flags = TEMPLATE_FRAGMENT; - - if (state.metadata.context.template_needs_import_node) { - flags |= TEMPLATE_USE_IMPORT_NODE; - } - add_template(template_name, [ b.template([b.quasi(state.template.join(''), true)], []), b.literal(flags) diff --git a/packages/svelte/src/constants.js b/packages/svelte/src/constants.js index a8963d6854..5c22be5f5d 100644 --- a/packages/svelte/src/constants.js +++ b/packages/svelte/src/constants.js @@ -18,6 +18,7 @@ export const TRANSITION_GLOBAL = 1 << 2; export const TEMPLATE_FRAGMENT = 1; export const TEMPLATE_USE_IMPORT_NODE = 1 << 1; +export const TEMPLATE_UNSET_START = 1 << 2; export const HYDRATION_START = '['; export const HYDRATION_END = ']'; diff --git a/packages/svelte/src/internal/client/dom/template.js b/packages/svelte/src/internal/client/dom/template.js index 55d9f424ac..24ba7f0d28 100644 --- a/packages/svelte/src/internal/client/dom/template.js +++ b/packages/svelte/src/internal/client/dom/template.js @@ -2,7 +2,11 @@ import { hydrate_nodes, hydrate_start, hydrating } from './hydration.js'; import { empty } from './operations.js'; import { create_fragment_from_html } from './reconciler.js'; import { current_effect } from '../runtime.js'; -import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants.js'; +import { + TEMPLATE_FRAGMENT, + TEMPLATE_UNSET_START, + TEMPLATE_USE_IMPORT_NODE +} from '../../../constants.js'; import { is_array } from '../utils.js'; import { queue_micro_task } from './task.js'; @@ -34,7 +38,7 @@ export function push_template_node( /** * - * @param {import('#client').TemplateNode | null} start + * @param {import('#client').TemplateNode | null | undefined} start * @param {import('#client').TemplateNode} end */ function assign_nodes(start, end) { @@ -43,7 +47,9 @@ function assign_nodes(start, end) { if (effect.nodes === null) { effect.nodes = { start, end }; } else { - effect.nodes.end = end; + if (effect.nodes.start === undefined) { + effect.nodes.start = start; + } } } @@ -61,10 +67,14 @@ export function template(content, flags) { var node; var has_start = !content.startsWith(''); + var unset = (flags & TEMPLATE_UNSET_START) !== 0; return () => { if (hydrating) { - assign_nodes(has_start ? hydrate_nodes[0] : null, hydrate_nodes[hydrate_nodes.length - 1]); + assign_nodes( + has_start ? hydrate_nodes[0] : unset ? undefined : null, + hydrate_nodes[hydrate_nodes.length - 1] + ); push_template_node(is_fragment ? hydrate_nodes : hydrate_start); return hydrate_start; @@ -80,7 +90,9 @@ export function template(content, flags) { var start = is_fragment ? has_start ? /** @type {import('#client').TemplateNode} */ (clone.firstChild) - : null + : unset + ? undefined + : null : /** @type {import('#client').TemplateNode} */ (clone); var end = /** @type {import('#client').TemplateNode} */ (is_fragment ? clone.lastChild : clone); @@ -146,8 +158,8 @@ export function ns_template(content, flags, ns = 'svg') { } if (!node) { - var fragment = /** @type {Element} */ (create_fragment_from_html(wrapped)); - var root = fragment.firstChild; + var fragment = /** @type {DocumentFragment} */ (create_fragment_from_html(wrapped)); + var root = /** @type {Element} */ (fragment.firstChild); if (is_fragment) { node = document.createDocumentFragment(); @@ -271,9 +283,17 @@ export function text(anchor) { return node; } -export function comment() { +/** + * @param {number} [flags] + */ +export function comment(flags = 0) { // we're not delegating to `template` here for performance reasons if (hydrating) { + assign_nodes( + (flags & TEMPLATE_UNSET_START) !== 0 ? undefined : null, + hydrate_nodes[hydrate_nodes.length - 1] + ); + push_template_node(hydrate_nodes); return hydrate_start; } @@ -281,6 +301,8 @@ export function comment() { var frag = document.createDocumentFragment(); var anchor = empty(); frag.append(anchor); + + assign_nodes((flags & TEMPLATE_UNSET_START) !== 0 ? undefined : null, anchor); push_template_node([anchor]); return frag; diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 5ee9bab180..896888f159 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -71,6 +71,8 @@ export function push_effect(effect, parent_effect) { } } +var uid = 1; + /** * @param {number} type * @param {null | (() => void | (() => void))} fn @@ -82,6 +84,7 @@ function create_effect(type, fn, sync) { /** @type {import('#client').Effect} */ var effect = { + id: uid++, ctx: current_component_context, deps: null, dom: null, @@ -388,13 +391,18 @@ export function destroy_effect(effect, remove_dom = true) { */ function get_first_node(effect) { if (effect.nodes !== null) { - if (effect.nodes.start !== null) { + if (effect.nodes.start != null) { return effect.nodes.start; } } - if (effect.first !== null) { - return get_first_node(effect.first); + var child = effect.first; + while (child && (child.f & (BLOCK_EFFECT | BRANCH_EFFECT)) === 0) { + child = child.next; + } + + if (child !== null) { + return get_first_node(child); } return null; diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts index b4204dc205..8dffe7001e 100644 --- a/packages/svelte/src/internal/client/reactivity/types.d.ts +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -37,7 +37,7 @@ export interface Derived extends Value, Reaction { export interface Effect extends Reaction { parent: Effect | null; dom: Dom | null; - nodes: null | { start: null | TemplateNode; end: TemplateNode }; + nodes: null | { start: undefined | null | TemplateNode; end: TemplateNode }; /** The associated component context */ ctx: null | ComponentContext; /** The effect function */ diff --git a/playgrounds/demo/vite.config.js b/playgrounds/demo/vite.config.js index 0feb57810d..bcb0ca358d 100644 --- a/playgrounds/demo/vite.config.js +++ b/playgrounds/demo/vite.config.js @@ -6,7 +6,7 @@ export default defineConfig({ build: { minify: false }, - plugins: [inspect(), svelte()], + plugins: [inspect(), svelte({ compilerOptions: { hmr: false }})], optimizeDeps: { // svelte is a local workspace package, optimizing it would require dev server restarts with --force for every change exclude: ['svelte']