Skip to content

chore: simplify templates #10954

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 10 commits into from
Mar 27, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import {
EACH_IS_STRICT_EQUALS,
EACH_ITEM_REACTIVE,
EACH_KEYED,
TEMPLATE_FRAGMENT,
TEMPLATE_USE_IMPORT_NODE,
TRANSITION_GLOBAL,
TRANSITION_IN,
TRANSITION_OUT
Expand Down Expand Up @@ -929,9 +931,9 @@ function serialize_bind_this(bind_this, context, node) {
* const block_name = $.template(`...`);
*
* // for the main block:
* const id = $.open(block_name);
* const id = block_name();
* // init stuff and possibly render effect
* $.close(id);
* $.append($$anchor, id);
* ```
* Adds the hoisted parts to `context.state.hoisted` and returns the statements of the main block.
* @param {import('#compiler').SvelteNode} parent
Expand Down Expand Up @@ -1001,25 +1003,19 @@ function create_block(parent, name, nodes, context) {
node: id
});

context.state.hoisted.push(
b.var(
template_name,
b.call(
get_template_function(namespace, state),
b.template([b.quasi(state.template.join(''), true)], [])
)
)
);

/** @type {import('estree').Expression[]} */
const args = [template_name];
const args = [b.template([b.quasi(state.template.join(''), true)], [])];

if (state.metadata.context.template_needs_import_node) {
args.push(b.false);
args.push(b.literal(TEMPLATE_USE_IMPORT_NODE));
}

body.push(b.var(id, b.call('$.open', ...args)), ...state.before_init, ...state.init);
close = b.stmt(b.call('$.close', b.id('$$anchor'), id));
context.state.hoisted.push(
b.var(template_name, b.call(get_template_function(namespace, state), ...args))
);

body.push(b.var(id, b.call(template_name)), ...state.before_init, ...state.init);
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
} else if (is_single_child_not_needing_template) {
context.visit(trimmed[0], state);
body.push(...state.before_init, ...state.init);
Expand All @@ -1040,7 +1036,7 @@ function create_block(parent, name, nodes, context) {
});

body.push(b.var(id, b.call('$.text', b.id('$$anchor'))), ...state.before_init, ...state.init);
close = b.stmt(b.call('$.close', b.id('$$anchor'), id));
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
} else {
/** @type {(is_text: boolean) => import('estree').Expression} */
const expression = (is_text) =>
Expand All @@ -1054,30 +1050,29 @@ function create_block(parent, name, nodes, context) {
// special case — we can use `$.comment` instead of creating a unique template
body.push(b.var(id, b.call('$.comment')));
} else {
let flags = TEMPLATE_FRAGMENT;

if (state.metadata.context.template_needs_import_node) {
flags |= TEMPLATE_USE_IMPORT_NODE;
}

state.hoisted.push(
b.var(
template_name,
b.call(
get_template_function(namespace, state),
b.template([b.quasi(state.template.join(''), true)], []),
b.true
b.literal(flags)
)
)
);

/** @type {import('estree').Expression[]} */
const args = [template_name];

if (state.metadata.context.template_needs_import_node) {
args.push(b.false);
}

body.push(b.var(id, b.call('$.open_frag', ...args)));
body.push(b.var(id, b.call(template_name)));
}

body.push(...state.before_init, ...state.init);

close = b.stmt(b.call('$.close_frag', b.id('$$anchor'), id));
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
}
} else {
body.push(...state.before_init, ...state.init);
Expand All @@ -1093,12 +1088,6 @@ function create_block(parent, name, nodes, context) {
// It's important that close is the last statement in the block, as any previous statements
// could contain element insertions into the template, which the close statement needs to
// know of when constructing the list of current inner elements.

if (context.path.length > 0) {
// this is a block — return DOM so it can be attached directly to the effect
close = b.return(close.expression);
}

body.push(close);
}

Expand Down Expand Up @@ -1566,7 +1555,7 @@ function serialize_template_literal(values, visit) {
/** @type {import('../types').ComponentVisitors} */
export const template_visitors = {
Fragment(node, context) {
const body = create_block(node, 'frag', node.nodes, context);
const body = create_block(node, 'root', node.nodes, context);
return b.block(body);
},
Comment(node, context) {
Expand Down
3 changes: 3 additions & 0 deletions packages/svelte/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export const TRANSITION_IN = 1;
export const TRANSITION_OUT = 1 << 1;
export const TRANSITION_GLOBAL = 1 << 2;

export const TEMPLATE_FRAGMENT = 1;
export const TEMPLATE_USE_IMPORT_NODE = 1 << 1;

/** List of Element events that will be delegated */
export const DelegatedEvents = [
'beforeinput',
Expand Down
18 changes: 16 additions & 2 deletions packages/svelte/src/internal/client/dom/blocks/each.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '../../../../constants.js';
import { hydrate_anchor, hydrate_nodes, hydrating, set_hydrating } from '../hydration.js';
import { empty } from '../operations.js';
import { insert, remove } from '../reconciler.js';
import { remove } from '../reconciler.js';
import { untrack } from '../../runtime.js';
import {
block,
Expand Down Expand Up @@ -389,7 +389,7 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {

if (moved && index !== LIS_ITEM) {
if (last_item !== undefined) anchor = get_first_child(last_item);
insert(/** @type {import('#client').Dom} */ (item.e.dom), anchor);
move(/** @type {import('#client').Dom} */ (item.e.dom), anchor);
}
}

Expand Down Expand Up @@ -560,3 +560,17 @@ function create_item(anchor, value, key, index, render_fn, flags) {
current_each_item = previous_each_item;
}
}

/**
* @param {import('#client').Dom} current
* @param {Text | Element | Comment} anchor
*/
function move(current, anchor) {
if (is_array(current)) {
for (var i = 0; i < current.length; i++) {
anchor.before(current[i]);
}
} else {
anchor.before(current);
}
}
18 changes: 5 additions & 13 deletions packages/svelte/src/internal/client/dom/blocks/snippet.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { render_effect } from '../../reactivity/effects.js';
import { remove } from '../reconciler.js';
import { untrack } from '../../runtime.js';
import { branch, render_effect } from '../../reactivity/effects.js';

/**
* @template {(node: import('#client').TemplateNode, ...args: any[]) => import('#client').Dom} SnippetFn
Expand All @@ -11,19 +9,13 @@ import { untrack } from '../../runtime.js';
*/
export function snippet(get_snippet, node, ...args) {
/** @type {SnippetFn | null | undefined} */
var snippet_fn;
var snippet;

render_effect(() => {
if (snippet_fn === (snippet_fn = get_snippet())) return;
if (snippet === (snippet = get_snippet())) return;

if (snippet_fn) {
// Untrack so we only rerender when the snippet function itself changes,
// not when an eagerly-read prop inside the snippet function changes
var dom = untrack(() => /** @type {SnippetFn} */ (snippet_fn)(node, ...args));

if (dom !== undefined) {
return () => remove(dom);
}
if (snippet) {
branch(() => /** @type {SnippetFn} */ (snippet)(node, ...args));
}
});
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createClassComponent } from '../../../../legacy/legacy-client.js';
import { destroy_effect, render_effect } from '../../reactivity/effects.js';
import { open, close } from '../template.js';
import { append } from '../template.js';
import { define_property } from '../../utils.js';

/**
Expand Down Expand Up @@ -98,14 +98,10 @@ if (typeof HTMLElement === 'function') {
* @param {Element} anchor
*/
return (anchor) => {
const node = open(() => {
const slot = document.createElement('slot');
if (name !== 'default') {
slot.name = name;
}
return slot;
});
close(anchor, /** @type {Element} */ (node));
const slot = document.createElement('slot');
if (name !== 'default') slot.name = name;

append(anchor, slot);
};
}
/** @type {Record<string, any>} */
Expand Down
35 changes: 0 additions & 35 deletions packages/svelte/src/internal/client/dom/reconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,6 @@ export function create_fragment_from_html(html) {
return elem.content;
}

/**
* Creating a document fragment from HTML that contains script tags will not execute
* the scripts. We need to replace the script tags with new ones so that they are executed.
* @param {string} html
*/
export function create_fragment_with_script_from_html(html) {
var content = create_fragment_from_html(html);
var scripts = content.querySelectorAll('script');
for (const script of scripts) {
var new_script = document.createElement('script');
for (var i = 0; i < script.attributes.length; i++) {
new_script.setAttribute(script.attributes[i].name, script.attributes[i].value);
}
new_script.textContent = script.textContent;
/** @type {Node} */ (script.parentNode).replaceChild(new_script, script);
}
return content;
}

/**
* @param {import('#client').Dom} current
* @param {Text | Element | Comment} sibling
*/
export function insert(current, sibling) {
if (!current) return sibling;

if (is_array(current)) {
for (var i = 0; i < current.length; i++) {
sibling.before(/** @type {Node} */ (current[i]));
}
} else {
sibling.before(/** @type {Node} */ (current));
}
}

/**
* @param {import('#client').Dom} current
*/
Expand Down
Loading