chore: sprinkle comments here and there

custom-render-shim-dom
paoloricciuti 6 months ago
parent 4daa63a8c9
commit 72f93e361f

@ -167,7 +167,7 @@ export function client_component(analysis, options) {
in_constructor: false, in_constructor: false,
instance_level_snippets: [], instance_level_snippets: [],
module_level_snippets: [], module_level_snippets: [],
is_functional_template_mode: options.templatingMode === 'functional', is_functional_template_mode: true, //options.templatingMode === 'functional',
// these are set inside the `Fragment` visitor, and cannot be used until then // these are set inside the `Fragment` visitor, and cannot be used until then
init: /** @type {any} */ (null), init: /** @type {any} */ (null),

@ -57,15 +57,20 @@ export function template_to_functions(items, namespace) {
*/ */
let last_current_element; let last_current_element;
// if the first item is a comment we need to add another comment for effect.start
if (items[0].kind === 'create_anchor') { if (items[0].kind === 'create_anchor') {
items.unshift({ kind: 'create_anchor' }); items.unshift({ kind: 'create_anchor' });
} }
for (let instruction of items) { for (let instruction of items) {
// on push element we add the element to the stack, from this moment on every insert will
// happen on the last element in the stack
if (instruction.kind === 'push_element' && last_current_element) { if (instruction.kind === 'push_element' && last_current_element) {
elements_stack.push(last_current_element); elements_stack.push(last_current_element);
continue; continue;
} }
// we closed one element, we remove it from the stack and eventually revert back
// the namespace to the previous one
if (instruction.kind === 'pop_element') { if (instruction.kind === 'pop_element') {
const removed = elements_stack.pop(); const removed = elements_stack.pop();
if (removed?.namespaced) { if (removed?.namespaced) {
@ -77,6 +82,8 @@ export function template_to_functions(items, namespace) {
continue; continue;
} }
// if the inserted node is in the svg/mathml we push the namespace to the stack because we need to
// create with createElementNS
if (instruction.metadata?.svg || instruction.metadata?.mathml) { if (instruction.metadata?.svg || instruction.metadata?.mathml) {
namespace_stack.push(instruction.metadata.svg ? NAMESPACE_SVG : NAMESPACE_MATHML); namespace_stack.push(instruction.metadata.svg ? NAMESPACE_SVG : NAMESPACE_MATHML);
} }
@ -84,7 +91,12 @@ export function template_to_functions(items, namespace) {
// @ts-expect-error we can't be here if `swap_current_element` but TS doesn't know that // @ts-expect-error we can't be here if `swap_current_element` but TS doesn't know that
const value = map[instruction.kind]( const value = map[instruction.kind](
...[ ...[
// for set prop we need to send the last element (not the one in the stack since
// it get's added to the stack only after the push_element instruction)...for all the rest
// the first prop is a the scope to generate the name of the variable
...(instruction.kind === 'set_prop' ? [last_current_element] : [scope]), ...(instruction.kind === 'set_prop' ? [last_current_element] : [scope]),
// for create element we also need to add the namespace...namespaces in the stack get's precedence over
// the "global" namespace (and if we are in a foreignObject we default to html)
...(instruction.kind === 'create_element' ...(instruction.kind === 'create_element'
? [ ? [
foreign_object_count > 0 foreign_object_count > 0
@ -102,9 +114,12 @@ export function template_to_functions(items, namespace) {
); );
if (value) { if (value) {
// this will compose the body of the function
body.push(value.call); body.push(value.call);
} }
// with set_prop we don't need to do anything else, in all other cases we also need to
// append the element/node/anchor to the current active element or push it in the elements array
if (instruction.kind !== 'set_prop') { if (instruction.kind !== 'set_prop') {
if (elements_stack.length >= 1 && value) { if (elements_stack.length >= 1 && value) {
const { call } = map.insert(/** @type {Element} */ (elements_stack.at(-1)), value); const { call } = map.insert(/** @type {Element} */ (elements_stack.at(-1)), value);
@ -112,6 +127,7 @@ export function template_to_functions(items, namespace) {
} else if (value) { } else if (value) {
elements.push(b.id(value.name)); elements.push(b.id(value.name));
} }
// keep track of the last created element (it will be pushed to the stack after the props are set)
if (instruction.kind === 'create_element') { if (instruction.kind === 'create_element') {
last_current_element = /** @type {Element} */ (value); last_current_element = /** @type {Element} */ (value);
if (last_current_element.element === 'foreignObject') { if (last_current_element.element === 'foreignObject') {
@ -120,6 +136,7 @@ export function template_to_functions(items, namespace) {
} }
} }
} }
// every function needs to return a fragment so we create one and push all the elements there
const fragment = scope.generate('fragment'); const fragment = scope.generate('fragment');
body.push(b.var(fragment, b.call('document.createDocumentFragment'))); body.push(b.var(fragment, b.call('document.createDocumentFragment')));
body.push(b.call(fragment + '.append', ...elements)); body.push(b.call(fragment + '.append', ...elements));
@ -159,6 +176,11 @@ function create_element(scope, namespace, element) {
} }
const call = b.var(name, b.call(fn, ...args)); const call = b.var(name, b.call(fn, ...args));
/** /**
* if there's an "is" attribute we can't just add it as a property, it needs to be
* specified on creation like this `document.createElement('button', { is: 'my-button' })`
*
* Since the props are appended after the creation we change the generated call arguments and we push
* the is attribute later on on `set_prop`
* @param {string} value * @param {string} value
*/ */
function add_is(value) { function add_is(value) {
@ -208,6 +230,7 @@ function create_text(scope, value) {
* @param {string} value * @param {string} value
*/ */
function set_prop(el, prop, value) { function set_prop(el, prop, value) {
// see comment above about the "is" attribute
if (prop === 'is') { if (prop === 'is') {
el.add_is(value); el.add_is(value);
return; return;
@ -217,6 +240,7 @@ function set_prop(el, prop, value) {
let fn = namespace !== prop ? '.setAttributeNS' : '.setAttribute'; let fn = namespace !== prop ? '.setAttributeNS' : '.setAttribute';
let args = [b.literal(fix_attribute_casing(prop)), b.literal(value ?? '')]; let args = [b.literal(fix_attribute_casing(prop)), b.literal(value ?? '')];
// attributes like `xlink:href` need to be set with the `xlink` namespace
if (namespace === 'xlink') { if (namespace === 'xlink') {
args.unshift(b.literal('http://www.w3.org/1999/xlink')); args.unshift(b.literal('http://www.w3.org/1999/xlink'));
} }
@ -235,6 +259,7 @@ function set_prop(el, prop, value) {
function insert(el, child, anchor) { function insert(el, child, anchor) {
return { return {
call: b.call( call: b.call(
// if we have a template element we need to push into it's content rather than the element itself
el.name + (el.element === 'template' ? '.content' : '') + '.insertBefore', el.name + (el.element === 'template' ? '.content' : '') + '.insertBefore',
b.id(child.name), b.id(child.name),
b.id(anchor?.name ?? 'undefined') b.id(anchor?.name ?? 'undefined')

@ -20,10 +20,13 @@ export function template_to_string(items) {
let last_current_element; let last_current_element;
for (let instruction of items) { for (let instruction of items) {
// on push element we add the element to the stack, from this moment on every insert will
// happen on the last element in the stack
if (instruction.kind === 'push_element' && last_current_element) { if (instruction.kind === 'push_element' && last_current_element) {
elements_stack.push(last_current_element); elements_stack.push(last_current_element);
continue; continue;
} }
// we closed one element, we remove it from the stack
if (instruction.kind === 'pop_element') { if (instruction.kind === 'pop_element') {
elements_stack.pop(); elements_stack.pop();
continue; continue;
@ -34,16 +37,21 @@ export function template_to_string(items) {
// @ts-expect-error we can't be here if `swap_current_element` but TS doesn't know that // @ts-expect-error we can't be here if `swap_current_element` but TS doesn't know that
const value = map[instruction.kind]( const value = map[instruction.kind](
...[ ...[
// for set prop we need to send the last element (not the one in the stack since
// it get's added to the stack only after the push_element instruction)
...(instruction.kind === 'set_prop' ? [last_current_element] : []), ...(instruction.kind === 'set_prop' ? [last_current_element] : []),
...(instruction.args ?? []) ...(instruction.args ?? [])
] ]
); );
// with set_prop we don't need to do anything else, in all other cases we also need to
// append the element/node/anchor to the current active element or push it in the elements array
if (instruction.kind !== 'set_prop') { if (instruction.kind !== 'set_prop') {
if (elements_stack.length >= 1 && value) { if (elements_stack.length >= 1 && value) {
map.insert(/** @type {Element} */ (elements_stack.at(-1)), value); map.insert(/** @type {Element} */ (elements_stack.at(-1)), value);
} else if (value) { } else if (value) {
elements.push(value); elements.push(value);
} }
// keep track of the last created element (it will be pushed to the stack after the props are set)
if (instruction.kind === 'create_element') { if (instruction.kind === 'create_element') {
last_current_element = /** @type {Element} */ (value); last_current_element = /** @type {Element} */ (value);
} }
@ -141,7 +149,9 @@ let map = {
function stringify(el) { function stringify(el) {
let str = ``; let str = ``;
if (el.kind === 'element') { if (el.kind === 'element') {
// we create the <tagname part
str += `<${el.element}`; str += `<${el.element}`;
// we concatenate all the prop to it
for (let [prop, value] of Object.entries(el.props ?? {})) { for (let [prop, value] of Object.entries(el.props ?? {})) {
if (value == null) { if (value == null) {
str += ` ${prop}`; str += ` ${prop}`;
@ -149,10 +159,13 @@ function stringify(el) {
str += ` ${prop}="${value}"`; str += ` ${prop}="${value}"`;
} }
} }
// then we close the opening tag
str += `>`; str += `>`;
// we stringify all the children and concatenate them
for (let child of el.children ?? []) { for (let child of el.children ?? []) {
str += stringify(child); str += stringify(child);
} }
// if it's not void we also add the closing tag
if (!is_void(el.element)) { if (!is_void(el.element)) {
str += `</${el.element}>`; str += `</${el.element}>`;
} }

@ -124,6 +124,8 @@ export function RegularElement(node, context) {
kind: 'set_prop', kind: 'set_prop',
args: [ args: [
'is', 'is',
// if we are using the functional template mode we don't want to escape since we will
// create a text node from it which is already escaped
context.state.is_functional_template_mode context.state.is_functional_template_mode
? value.value ? value.value
: escape_html(value.value, true) : escape_html(value.value, true)
@ -312,7 +314,9 @@ export function RegularElement(node, context) {
: [ : [
value === true value === true
? '' ? ''
: context.state.is_functional_template_mode : // if we are using the functional template mode we don't want to escape since we will
// create a text node from it which is already escaped
context.state.is_functional_template_mode
? value ? value
: escape_html(value, true) : escape_html(value, true)
] ]

@ -427,11 +427,13 @@ export function build_component(node, component_name, context, anchor = context.
*/ */
const template_operations = []; const template_operations = [];
if (context.state.metadata.namespace === 'svg') { if (context.state.metadata.namespace === 'svg') {
// this boils down to <g><!></g>
template_operations.push({ kind: 'create_element', args: ['g'] }); template_operations.push({ kind: 'create_element', args: ['g'] });
template_operations.push({ kind: 'push_element' }); template_operations.push({ kind: 'push_element' });
template_operations.push({ kind: 'create_anchor' }); template_operations.push({ kind: 'create_anchor' });
template_operations.push({ kind: 'pop_element' }); template_operations.push({ kind: 'pop_element' });
} else { } else {
// this boils down to <svelte-css-wrapper style='display: contents'><!></svelte-css-wrapper>
template_operations.push({ kind: 'create_element', args: ['svelte-css-wrapper'] }); template_operations.push({ kind: 'create_element', args: ['svelte-css-wrapper'] });
template_operations.push({ kind: 'set_prop', args: ['style', 'display: contents'] }); template_operations.push({ kind: 'set_prop', args: ['style', 'display: contents'] });
template_operations.push({ kind: 'push_element' }); template_operations.push({ kind: 'push_element' });

@ -276,12 +276,16 @@ export function clean_nodes(
// initial newline inside a `<pre>` is disregarded, if not followed by another newline // initial newline inside a `<pre>` is disregarded, if not followed by another newline
if ( if (
parent.type === 'RegularElement' && parent.type === 'RegularElement' &&
// we also want to do the replacement on the textarea if we are in functional template mode because createTextNode behave differently
// then template.innerHTML
(parent.name === 'pre' || (is_functional_template_mode && parent.name === 'textarea')) && (parent.name === 'pre' || (is_functional_template_mode && parent.name === 'textarea')) &&
first?.type === 'Text' first?.type === 'Text'
) { ) {
const text = first.data.replace(regex_starts_with_newline, ''); const text = first.data.replace(regex_starts_with_newline, '');
if (text !== first.data) { if (text !== first.data) {
const tmp = text.replace(regex_starts_with_newline, ''); const tmp = text.replace(regex_starts_with_newline, '');
// do an extra replacement if we are in functional template mode because createTextNode behave differently
// then template.innerHTML
if (text === tmp || is_functional_template_mode) { if (text === tmp || is_functional_template_mode) {
first.data = text; first.data = text;
first.raw = first.raw.replace(regex_starts_with_newline, ''); first.raw = first.raw.replace(regex_starts_with_newline, '');

Loading…
Cancel
Save