pull/10954/head
Rich Harris 2 years ago
parent 9a4cd7e8d8
commit 6563ee2710

@ -33,6 +33,8 @@ import {
EACH_IS_STRICT_EQUALS, EACH_IS_STRICT_EQUALS,
EACH_ITEM_REACTIVE, EACH_ITEM_REACTIVE,
EACH_KEYED, EACH_KEYED,
TEMPLATE_FRAGMENT,
TEMPLATE_USE_IMPORT_NODE,
TRANSITION_GLOBAL, TRANSITION_GLOBAL,
TRANSITION_IN, TRANSITION_IN,
TRANSITION_OUT TRANSITION_OUT
@ -929,9 +931,9 @@ function serialize_bind_this(bind_this, context, node) {
* const block_name = $.template(`...`); * const block_name = $.template(`...`);
* *
* // for the main block: * // for the main block:
* const id = $.open(block_name); * const id = block_name();
* // init stuff and possibly render effect * // init stuff and possibly render effect
* $.close(id); * $.close($$anchor, id);
* ``` * ```
* Adds the hoisted parts to `context.state.hoisted` and returns the statements of the main block. * Adds the hoisted parts to `context.state.hoisted` and returns the statements of the main block.
* @param {import('#compiler').SvelteNode} parent * @param {import('#compiler').SvelteNode} parent
@ -1001,24 +1003,18 @@ function create_block(parent, name, nodes, context) {
node: id 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[]} */ /** @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) { 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); 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('$.close', b.id('$$anchor'), id)); close = b.stmt(b.call('$.close', b.id('$$anchor'), id));
} else if (is_single_child_not_needing_template) { } else if (is_single_child_not_needing_template) {
context.visit(trimmed[0], state); context.visit(trimmed[0], state);
@ -1054,25 +1050,24 @@ function create_block(parent, name, nodes, context) {
// special case — we can use `$.comment` instead of creating a unique 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')));
} else { } else {
let flags = TEMPLATE_FRAGMENT;
if (state.metadata.context.template_needs_import_node) {
flags |= TEMPLATE_USE_IMPORT_NODE;
}
state.hoisted.push( state.hoisted.push(
b.var( b.var(
template_name, template_name,
b.call( b.call(
get_template_function(namespace, state), get_template_function(namespace, state),
b.template([b.quasi(state.template.join(''), true)], []), b.template([b.quasi(state.template.join(''), true)], []),
b.true b.literal(flags)
) )
) )
); );
/** @type {import('estree').Expression[]} */ body.push(b.var(id, b.call(template_name)));
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(...state.before_init, ...state.init); body.push(...state.before_init, ...state.init);

@ -16,6 +16,9 @@ export const TRANSITION_IN = 1;
export const TRANSITION_OUT = 1 << 1; export const TRANSITION_OUT = 1 << 1;
export const TRANSITION_GLOBAL = 1 << 2; 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 */ /** List of Element events that will be delegated */
export const DelegatedEvents = [ export const DelegatedEvents = [
'beforeinput', 'beforeinput',

@ -1,6 +1,6 @@
import { createClassComponent } from '../../../../legacy/legacy-client.js'; import { createClassComponent } from '../../../../legacy/legacy-client.js';
import { destroy_effect, render_effect } from '../../reactivity/effects.js'; import { destroy_effect, render_effect } from '../../reactivity/effects.js';
import { open, close } from '../template.js'; import { close } from '../template.js';
import { define_property } from '../../utils.js'; import { define_property } from '../../utils.js';
/** /**
@ -98,14 +98,10 @@ if (typeof HTMLElement === 'function') {
* @param {Element} anchor * @param {Element} anchor
*/ */
return (anchor) => { return (anchor) => {
const node = open(() => { const slot = document.createElement('slot');
const slot = document.createElement('slot'); if (name !== 'default') slot.name = name;
if (name !== 'default') {
slot.name = name; close(anchor, slot);
}
return slot;
});
close(anchor, /** @type {Element} */ (node));
}; };
} }
/** @type {Record<string, any>} */ /** @type {Record<string, any>} */

@ -8,25 +8,6 @@ export function create_fragment_from_html(html) {
return elem.content; 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 {import('#client').Dom} current
* @param {Text | Element | Comment} sibling * @param {Text | Element | Comment} sibling

@ -1,121 +1,134 @@
import { hydrate_nodes, hydrating } from './hydration.js'; import { hydrate_nodes, hydrating } from './hydration.js';
import { child, clone_node, empty } from './operations.js'; import { clone_node, empty } from './operations.js';
import { import { create_fragment_from_html, insert } from './reconciler.js';
create_fragment_from_html,
create_fragment_with_script_from_html,
insert
} from './reconciler.js';
import { current_effect } from '../runtime.js'; import { current_effect } from '../runtime.js';
import { is_array } from '../utils.js'; import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants.js';
/** /**
* @param {string} html * @param {string} html
* @param {boolean} return_fragment * @param {number} flags
* @returns {() => Node} * @returns {() => Node | Node[]}
*/ */
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function template(html, return_fragment) { export function template(html, flags) {
/** @type {undefined | Node} */ var is_fragment = (flags & TEMPLATE_FRAGMENT) !== 0;
let cached_content; var use_import_node = (flags & TEMPLATE_USE_IMPORT_NODE) !== 0;
/** @type {Node} */
var node;
return () => { return () => {
if (cached_content === undefined) { if (hydrating) {
const content = create_fragment_from_html(html); return is_fragment ? hydrate_nodes : /** @type {Node} */ (hydrate_nodes[0]);
cached_content = return_fragment ? content : /** @type {Node} */ (child(content));
} }
return cached_content;
if (!node) {
node = create_fragment_from_html(html);
if (!is_fragment) node = /** @type {Node} */ (node.firstChild);
}
return use_import_node ? document.importNode(node, true) : clone_node(node, true);
}; };
} }
/** /**
* @param {string} html * @param {string} html
* @param {boolean} return_fragment * @param {number} flags
* @returns {() => Node} * @returns {() => Node | Node[]}
*/ */
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function template_with_script(html, return_fragment) { export function template_with_script(html, flags) {
/** @type {undefined | Node} */ var first = true;
let cached_content; var fn = template(html, flags);
return () => { return () => {
if (cached_content === undefined) { if (hydrating) return fn();
const content = create_fragment_with_script_from_html(html);
cached_content = return_fragment ? content : /** @type {Node} */ (child(content)); var node = /** @type {Element | DocumentFragment} */ (fn());
if (first) {
first = false;
run_scripts(node);
} }
return cached_content;
return node;
}; };
} }
/** /**
* @param {string} svg * @param {string} svg
* @param {boolean} return_fragment * @param {number} flags
* @returns {() => Node} * @returns {() => Node | Node[]}
*/ */
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function svg_template(svg, return_fragment) { export function svg_template(svg, flags) {
/** @type {undefined | Node} */ var fn = template(`<svg>${svg}</svg>`, flags & ~TEMPLATE_FRAGMENT);
let cached_content;
/** @type {Element | DocumentFragment} */
var node;
return () => { return () => {
if (cached_content === undefined) { if (hydrating) {
const content = /** @type {Node} */ (child(create_fragment_from_html(`<svg>${svg}</svg>`))); return fn();
cached_content = return_fragment ? content : /** @type {Node} */ (child(content)); }
if (!node) {
var element = /** @type {Element} */ (fn());
if ((flags & TEMPLATE_FRAGMENT) === 0) {
node = /** @type {Element} */ (element.firstChild);
} else {
node = document.createDocumentFragment();
while (element.firstChild) {
node.appendChild(element.firstChild);
}
}
} }
return cached_content;
return node;
}; };
} }
/** /**
* @param {string} svg * @param {string} svg
* @param {boolean} return_fragment * @param {number} flags
* @returns {() => Node} * @returns {() => Node | Node[]}
*/ */
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function svg_template_with_script(svg, return_fragment) { export function svg_template_with_script(svg, flags) {
/** @type {undefined | Node} */ var first = true;
let cached_content; var fn = svg_template(svg, flags);
return () => { return () => {
if (cached_content === undefined) { if (hydrating) return fn();
const content = /** @type {Node} */ (child(create_fragment_from_html(`<svg>${svg}</svg>`)));
cached_content = return_fragment ? content : /** @type {Node} */ (child(content));
}
return cached_content;
};
}
/** var node = /** @type {Element | DocumentFragment} */ (fn());
* @param {boolean} is_fragment
* @param {boolean} use_clone_node
* @param {() => Node} [template_element_fn]
* @returns {Element | DocumentFragment | Node[]}
*/
/*#__NO_SIDE_EFFECTS__*/
function open_template(is_fragment, use_clone_node, template_element_fn) {
if (hydrating) {
return is_fragment ? hydrate_nodes : /** @type {Element} */ (hydrate_nodes[0]);
}
return use_clone_node if (first) {
? clone_node(/** @type {() => Element} */ (template_element_fn)(), true) first = false;
: document.importNode(/** @type {() => Element} */ (template_element_fn)(), true); run_scripts(node);
} }
/** return node;
* @param {() => Node} template_element_fn };
* @param {boolean} [use_clone_node]
* @returns {Element}
*/
export function open(template_element_fn, use_clone_node = true) {
return /** @type {Element} */ (open_template(false, use_clone_node, template_element_fn));
} }
/** /**
* @param {() => Node} template_element_fn * Creating a document fragment from HTML that contains script tags will not execute
* @param {boolean} [use_clone_node] * the scripts. We need to replace the script tags with new ones so that they are executed.
* @returns {Element | DocumentFragment | Node[]} * @param {Element | DocumentFragment} node
*/ */
export function open_frag(template_element_fn, use_clone_node = true) { function run_scripts(node) {
return open_template(true, use_clone_node, template_element_fn); for (const script of node.querySelectorAll('script')) {
} var clone = document.createElement('script');
for (var attribute of script.attributes) {
clone.setAttribute(attribute.name, attribute.value);
}
const comment_template = template('<!>', true); clone.textContent = script.textContent;
script.replaceWith(clone);
}
}
/** /**
* @param {Text | Comment | Element} anchor * @param {Text | Comment | Element} anchor
@ -136,9 +149,7 @@ export function text(anchor) {
} }
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function comment() { export const comment = template('<!>', TEMPLATE_FRAGMENT);
return open_frag(comment_template);
}
/** /**
* Assign the created (or in hydration mode, traversed) dom elements to the current block * Assign the created (or in hydration mode, traversed) dom elements to the current block

Loading…
Cancel
Save