Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

feat: functional template generation #15538

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 86 commits into from
May 22, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
de8a38b
feat: templateless template generation
paoloricciuti Mar 18, 2025
575908a
fix: auto import fumble
paoloricciuti Mar 18, 2025
8a737f1
feat: add option `preventTemplateCloning` and functions transformation
paoloricciuti Mar 18, 2025
7c7a857
feat: make all tests pass with functional templates
paoloricciuti Mar 20, 2025
8424395
chore: remove `prevent-template-cloning` test
paoloricciuti Mar 20, 2025
c75c429
chore: run test suite on both `functional` and `string` templating
paoloricciuti Mar 20, 2025
64219ed
chore: change options from `boolean` to `list` (templatingMode: "func…
paoloricciuti Mar 20, 2025
95ce311
chore: revert unneeded change to `package.json`
paoloricciuti Mar 20, 2025
59902cc
chore: simplify `process_children`
paoloricciuti Mar 20, 2025
132ea2b
Merge remote-tracking branch 'origin/main' into templateless-template…
paoloricciuti Mar 20, 2025
403b17e
chore: update snapshots
paoloricciuti Mar 20, 2025
ba237c7
chore: don't write different `rendered`
paoloricciuti Mar 20, 2025
4daa63a
fix: snapshot test runner
paoloricciuti Mar 20, 2025
72f93e3
chore: sprinkle comments here and there
paoloricciuti Mar 21, 2025
ad56847
fix: silly goose am i 🪿
paoloricciuti Mar 21, 2025
d8afd8e
fix (this broke the sandbox)
Rich-Harris Mar 21, 2025
a48df4a
Merge remote-tracking branch 'origin/main' into templateless-template…
paoloricciuti Mar 24, 2025
be39867
chore: alterative functional templating syntax (#15599)
paoloricciuti Apr 1, 2025
eaf2aaf
Merge branch 'main' into templateless-template-generation
Rich-Harris Apr 17, 2025
97817a9
fix snapshot tests
Rich-Harris Apr 17, 2025
15c6306
Merge remote-tracking branch 'origin/main' into templateless-template…
paoloricciuti Apr 22, 2025
084f7f9
chore: update purity snapshot
paoloricciuti Apr 22, 2025
084e313
chore: update purity snapshot, this time in the proper way
paoloricciuti Apr 22, 2025
d294a66
chore: add `each-index-non-null` snapshot
paoloricciuti Apr 22, 2025
2d0db55
Merge remote-tracking branch 'origin/main' into templateless-template…
paoloricciuti Apr 22, 2025
7f732bf
chore: remove export of `seen`
paoloricciuti May 21, 2025
d3ee992
fix: revert exporting `seen`
paoloricciuti May 21, 2025
2e24fdc
chore: use more sane to-string code
paoloricciuti May 21, 2025
67183ce
Merge branch 'main' into templateless-template-generation
Rich-Harris May 21, 2025
173a7f7
update snapshots
Rich-Harris May 21, 2025
d9d24a2
this is already copied, no need to do it explicitly
Rich-Harris May 21, 2025
5b1f5e5
do the escaping inside `template_to_string`
Rich-Harris May 21, 2025
1c4503c
Apply suggestions from code review
Rich-Harris May 21, 2025
e81c669
chore: refactor `to-functions` to be sane
paoloricciuti May 21, 2025
324bd8a
use proper discriminated unions (TODO replace args with node-specific…
Rich-Harris May 21, 2025
4a3fc9d
remove instruction.args
Rich-Harris May 21, 2025
76d05e0
make process_children unaware of templating mode - the less visitors …
Rich-Harris May 21, 2025
22cf914
generate functional output in sandbox
Rich-Harris May 21, 2025
a9f2d6f
tidy up
Rich-Harris May 21, 2025
36c01ca
fix
Rich-Harris May 21, 2025
68de6b4
no longer need to pass `is_functional_template_mode` to `clean_nodes`
Rich-Harris May 21, 2025
d9237e2
simplify state
Rich-Harris May 21, 2025
17f1b6a
WIP
Rich-Harris May 21, 2025
1e6d85b
fixes
Rich-Harris May 21, 2025
f677792
fix
Rich-Harris May 21, 2025
a59f18a
fix
Rich-Harris May 21, 2025
8c819e5
unused
Rich-Harris May 21, 2025
448cff1
remove indirection
Rich-Harris May 21, 2025
747d24a
lint
Rich-Harris May 21, 2025
60813bc
tweak
Rich-Harris May 21, 2025
3afd814
tweak
Rich-Harris May 21, 2025
9106533
doh
Rich-Harris May 21, 2025
310f82d
move stuff off `state.metadata.context` and onto `state.template`
Rich-Harris May 21, 2025
98fa3c0
simplify
Rich-Harris May 21, 2025
f36df5f
remove unused arg
Rich-Harris May 21, 2025
ab5f15c
tweak
Rich-Harris May 21, 2025
bcc11ce
tweak
Rich-Harris May 21, 2025
92cc175
merge main
Rich-Harris May 21, 2025
af78465
unused
Rich-Harris May 21, 2025
40be734
put locations on template, instead of on the side
Rich-Harris May 22, 2025
7edf0c2
simplify
Rich-Harris May 22, 2025
941f266
simplify
Rich-Harris May 22, 2025
faf1822
optimise for the common case
Rich-Harris May 22, 2025
d8637fb
tweak
Rich-Harris May 22, 2025
84f15d0
tweak
Rich-Harris May 22, 2025
1be9120
tweak
Rich-Harris May 22, 2025
07aed41
colocate
Rich-Harris May 22, 2025
4039f9e
remove test duplication
Rich-Harris May 22, 2025
546fa28
more
Rich-Harris May 22, 2025
93c4d84
tweak
Rich-Harris May 22, 2025
99b7afc
undo
Rich-Harris May 22, 2025
78e3405
undo
Rich-Harris May 22, 2025
0cb475e
undo
Rich-Harris May 22, 2025
f432305
one snapshot test is probably enough
Rich-Harris May 22, 2025
32d2b4c
add a runtime test, we can always add more later if we need to
Rich-Harris May 22, 2025
19fae20
push on create
Rich-Harris May 22, 2025
408955d
rename stuff
Rich-Harris May 22, 2025
7d1551b
use function composition
Rich-Harris May 22, 2025
fa8e1ef
simplify
Rich-Harris May 22, 2025
dfde459
deduplicate stuff
Rich-Harris May 22, 2025
4ba3853
update names
Rich-Harris May 22, 2025
5884b8f
small tweak
Rich-Harris May 22, 2025
a86e52b
XHTML compliance
Rich-Harris May 22, 2025
27848a5
update snapshots
Rich-Harris May 22, 2025
7ac936f
update sandbox
Rich-Harris May 22, 2025
5a748f9
fix tests
Rich-Harris May 22, 2025
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
Prev Previous commit
Next Next commit
optimise for the common case
  • Loading branch information
Rich-Harris committed May 22, 2025
commit faf18220e069f5a8765ce51f325620bd9da43b44
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ export function template_to_functions(items) {
function build(item) {
switch (item.type) {
case 'element': {
const element = b.object([b.prop('init', b.id('e'), b.literal(item.name))]);
const element = b.array([b.literal(item.name)]);

const attributes = b.prop('init', b.id('p'), b.object([]));
const attributes = b.object([]);

for (const key in item.attributes) {
const value = item.attributes[key];

attributes.value.properties.push(
attributes.properties.push(
b.prop(
'init',
b.key(fix_attribute_casing(key)),
Expand All @@ -35,13 +35,13 @@ function build(item) {
);
}

if (attributes.value.properties.length > 0) {
element.properties.push(attributes);
if (attributes.properties.length > 0 || item.children.length > 0) {
element.elements.push(attributes.properties.length > 0 ? attributes : b.null);
}

if (item.children.length > 0) {
const children = item.children.map(build);
element.properties.push(b.prop('init', b.id('c'), b.array(children)));
element.elements.push(...children);

// special case — strip leading newline from `<pre>` and `<textarea>`
if (item.name === 'pre' || item.name === 'textarea') {
Expand All @@ -59,7 +59,7 @@ function build(item) {
}

case 'anchor': {
return item.data ? b.array([b.literal(item.data)]) : null;
return item.data ? b.array([b.literal(`// ${item.data}`)]) : null;
}

case 'text': {
Expand Down
31 changes: 15 additions & 16 deletions 31 packages/svelte/src/internal/client/dom/template.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/** @import { Effect, TemplateNode } from '#client' */
/** @import { TemplateStructure } from './types' */
import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from './hydration.js';
import {
create_text,
Expand Down Expand Up @@ -76,46 +77,44 @@ export function template(content, flags) {
return clone;
};
}

/**
* @typedef {{e: string, p: Record<string, string>, c: Array<TemplateStructure>} | undefined | string | [string]} TemplateStructure
*/

/**
* @param {Array<TemplateStructure>} structure
* @param {TemplateStructure[]} structure
* @param {NAMESPACE_SVG | NAMESPACE_MATHML | undefined} [ns]
*/
function structure_to_fragment(structure, ns) {
var fragment = create_fragment();

for (var item of structure) {
if (item === undefined || Array.isArray(item)) {
fragment.append(create_comment(item ? item[0] : ''));
if (typeof item === 'string') {
fragment.append(create_text(item));
continue;
}

if (typeof item === 'string') {
fragment.append(create_text(item));
// if `preserveComments === true`, comments are represented as `['// <data>']`
if (item === undefined || item[0][0] === '/') {
fragment.append(create_comment(item ? item[0].slice(3) : ''));
continue;
}

const [name, attributes, ...children] = item;

/** @type {NAMESPACE_SVG | NAMESPACE_MATHML | undefined} */
let namespace = item.e === 'svg' ? NAMESPACE_SVG : item.e === 'math' ? NAMESPACE_MATHML : ns;
let namespace = name === 'svg' ? NAMESPACE_SVG : name === 'math' ? NAMESPACE_MATHML : ns;

var element = create_element(item.e, namespace, item.p?.is);
var element = create_element(name, namespace, attributes?.is);

for (var key in item.p) {
set_attribute(element, key, item.p[key]);
for (var key in attributes) {
set_attribute(element, key, attributes[key]);
}

if (item.c) {
if (children.length > 0) {
var target =
element.tagName === 'TEMPLATE'
? /** @type {HTMLTemplateElement} */ (element).content
: element;

target.append(
structure_to_fragment(item.c, element.tagName === 'foreignObject' ? undefined : namespace)
structure_to_fragment(children, element.tagName === 'foreignObject' ? undefined : namespace)
);
}

Expand Down
4 changes: 4 additions & 0 deletions 4 packages/svelte/src/internal/client/dom/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type TemplateStructure =
| string
| undefined
| [string, Record<string, string> | undefined, ...TemplateStructure[]];
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function increment(_, counter) {
counter.count += 1;
}

var root = $.template_fn([{ e: 'button', c: [' '] }, ' ', , ' '], 1);
var root = $.template_fn([['button', null, ' '], ' ', , ' '], 1);

export default function Await_block_scope($$anchor) {
let counter = $.proxy({ count: 0 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import * as $ from 'svelte/internal/client';

var root = $.template_fn(
[
{ e: 'div' },
['div'],
' ',
{ e: 'svg' },
['svg'],
' ',
{ e: 'custom-element' },
['custom-element'],
' ',
{ e: 'div' },
['div'],
' ',
{ e: 'svg' },
['svg'],
' ',
{ e: 'custom-element' }
['custom-element']
],
3
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version';
import 'svelte/internal/flags/legacy';
import * as $ from 'svelte/internal/client';

var root_1 = $.template_fn([{ e: 'p' }]);
var root_1 = $.template_fn([['p']]);

export default function Each_index_non_null($$anchor) {
var fragment = $.comment();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version';
import 'svelte/internal/flags/legacy';
import * as $ from 'svelte/internal/client';

var root = $.template_fn([{ e: 'h1', c: ['hello world'] }]);
var root = $.template_fn([['h1', null, 'hello world']]);

export default function Hello_world($$anchor) {
var h1 = root();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version';
import 'svelte/internal/flags/legacy';
import * as $ from 'svelte/internal/client';

var root = $.template_fn([{ e: 'h1', c: ['hello world'] }]);
var root = $.template_fn([['h1', null, 'hello world']]);

function Hmr($$anchor) {
var h1 = root();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ var on_click = (_, count) => $.update(count);

var root = $.template_fn(
[
{ e: 'h1' },
['h1'],
' ',
{ e: 'b' },
['b'],
' ',
{ e: 'button', c: [' '] },
['button', null, ' '],
' ',
{ e: 'h1' }
['h1']
],
1
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'svelte/internal/disclose-version';
import 'svelte/internal/flags/legacy';
import * as $ from 'svelte/internal/client';

var root = $.template_fn([{ e: 'p' }, ' ', { e: 'p' }, ' ', ,], 1);
var root = $.template_fn([['p'], ' ', ['p'], ' ', ,], 1);

export default function Purity($$anchor) {
var fragment = root();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,92 +3,77 @@ import * as $ from 'svelte/internal/client';

var root = $.template_fn(
[
{
e: 'header',
c: [
{
e: 'nav',
c: [
{ e: 'a', p: { href: '/' }, c: ['Home'] },
' ',
{
e: 'a',
p: { href: '/away' },
c: ['Away']
}
]
}
]
},
' ',
{
e: 'main',
c: [
{ e: 'h1', c: [' '] },
' ',
{
e: 'div',
p: { class: 'static' },
c: [
{
e: 'p',
c: ['we don\'t need to traverse these nodes']
}
]
},
' ',
{ e: 'p', c: ['or'] },
' ',
{ e: 'p', c: ['these'] },
' ',
{ e: 'p', c: ['ones'] },
' ',
,
[
'header',
null,
[
'nav',
null,
['a', { href: '/' }, 'Home'],
' ',
{ e: 'p', c: ['these'] },
' ',
{ e: 'p', c: ['trailing'] },
' ',
{ e: 'p', c: ['nodes'] },
' ',
{ e: 'p', c: ['can'] },
' ',
{ e: 'p', c: ['be'] },
' ',
{ e: 'p', c: ['completely'] },
' ',
{ e: 'p', c: ['ignored'] }
['a', { href: '/away' }, 'Away']
]
},
],
' ',
[
'main',
null,
['h1', null, ' '],
' ',
[
'div',
{ class: 'static' },
[
'p',
null,
'we don\'t need to traverse these nodes'
]
],
' ',
['p', null, 'or'],
' ',
['p', null, 'these'],
' ',
['p', null, 'ones'],
' ',
,
' ',
['p', null, 'these'],
' ',
['p', null, 'trailing'],
' ',
['p', null, 'nodes'],
' ',
['p', null, 'can'],
' ',
['p', null, 'be'],
' ',
['p', null, 'completely'],
' ',
['p', null, 'ignored']
],
' ',
{
e: 'cant-skip',
c: [{ e: 'custom-elements' }]
},
['cant-skip', null, ['custom-elements']],
' ',
{ e: 'div', c: [{ e: 'input' }] },
['div', null, ['input']],
' ',
{ e: 'div', c: [{ e: 'source' }] },
['div', null, ['source']],
' ',
{
e: 'select',
c: [{ e: 'option', c: ['a'] }]
},
['select', null, ['option', null, 'a']],
' ',
{
e: 'img',
p: { src: '...', alt: '', loading: 'lazy' }
},
[
'img',
{ src: '...', alt: '', loading: 'lazy' }
],
' ',
{
e: 'div',
c: [
{
e: 'img',
p: { src: '...', alt: '', loading: 'lazy' }
}
[
'div',
null,
[
'img',
{ src: '...', alt: '', loading: 'lazy' }
]
}
]
],
3
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ function reset(_, str, tpl) {

var root = $.template_fn(
[
{ e: 'input' },
['input'],
' ',
{ e: 'input' },
['input'],
' ',
{ e: 'button', c: ['reset'] }
['button', null, 'reset']
],
1
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'svelte/internal/disclose-version';
import * as $ from 'svelte/internal/client';

var root = $.template_fn([{ e: 'p', c: [' '] }]);
var root = $.template_fn([['p', null, ' ']]);

export default function Text_nodes_deriveds($$anchor) {
let count1 = 0;
Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.