More efficient fragment creation
See original GitHub issueAt present, fragments are built up programmatically one at a time. For a template like this…
<section>
<h1>Hello {name}!</h1>
<p>{description}</p>
</section>
…we get this:
function create_fragment(ctx) {
let section;
let h1;
let t0;
let t1;
let t2;
let t3;
let p;
let t4;
return {
c() {
section = element("section");
h1 = element("h1");
t0 = text("Hello ");
t1 = text(ctx.name);
t2 = text("!");
t3 = space();
p = element("p");
t4 = text(ctx.description);
},
m(target, anchor) {
insert(target, section, anchor);
append(section, h1);
append(h1, t0);
append(h1, t1);
append(h1, t2);
append(section, t3);
append(section, p);
append(p, t4);
},
p(changed, ctx) {
if (changed.name) set_data(t1, ctx.name);
if (changed.description) set_data(t4, ctx.description);
},
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(section);
}
};
}
One way we could potentially improve this is by using <template>
and cloneNode
to create the entire fragment in one go. This suggestion came from @martypdx, who co-authored Ractive (Svelte’s predecessor): https://gist.github.com/martypdx/96b0a0900d2769a982ba36a066c1e38a. Riffing on the ideas in that gist, imagine we had a helper like this…
const make_renderer = html => {
const template = document.createElement('template');
template.innerHTML = html;
const text = template.content.querySelectorAll('sveltetext');
for (let i = 0; i < text.length; i += 1) {
text[i].replaceWith(document.createTextNode());
}
return () => {
const fragment = template.content.cloneNode(true);
return fragment.querySelectorAll('[bind]');
};
};
…we could then generate this code instead:
+const render = make_renderer(
+ `<section bind><h1 bind>Hello <sveltetext/>!</h1> <p bind></p></section>`
+);
+
function create_fragment(ctx) {
let section;
+ let t0;
+ let t1;
- let h1;
- let t0;
- let t1;
- let t2;
- let t3;
- let p;
- let t4;
return {
c() {
+ [section, { childNodes: [, t0] }, { childNodes: [t1] }] = render();
+ t0 = text(ctx.name);
+ t1 = text(ctx.description);
- section = element("section");
- h1 = element("h1");
- t0 = text("Hello ");
- t1 = text(ctx.name);
- t2 = text("!");
- t3 = space();
- p = element("p");
- t4 = text(ctx.description);
},
m(target, anchor) {
insert(target, section, anchor);
- append(section, h1);
- append(h1, t0);
- append(h1, t1);
- append(h1, t2);
- append(section, t3);
- append(section, p);
- append(p, t4);
},
p(changed, ctx) {
+ if (changed.name) set_data(t0, ctx.name);
+ if (changed.description) set_data(t1, ctx.description);
- if (changed.name) set_data(t1, ctx.name);
- if (changed.description) set_data(t4, ctx.description);
},
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(section);
}
};
}
This is less code (notwithstanding the cost of the helper), and also very possibly less work (though I haven’t benchmarked anything yet).
There’s also potential for further benefits in future — by putting markup in <template>
elements that are on the page (generated during SSR), or in HTML modules if we ever get those. This would mean less JavaScript, and we can take advantage of the browser’s parser (which is fast!).
A few things to consider:
- Need to be able to insert placeholders for (if/each/etc) blocks (though this could be the same mechanism as for text)
- The
bind
attributes, used for selecting nodes we need references to, would stick around in the DOM. Not the worst, but not ideal. Also, maybe we need to pick a name that’s less likely to conflict with something that gets added to the DOM in future (maybe justsvelte
) - Haven’t really thought about how this’d work with hydration
Issue Analytics
- State:
- Created 4 years ago
- Reactions:52
- Comments:29 (17 by maintainers)
I was interested in this issue, so I created an experimental implementation using
<template>
andcloneNode
.https://github.com/tomoam/svelte/tree/efficient-fragment-creation
The implementation is only makeshift, but passes the tests. The tests have not been changed except for the test cases in the
test/js
directory and a very few test cases. The implementation ofhydration
andnamespace
is quite messy.Here is the keyed result of running krausest/js-framework-benchmark with this implementation.
svelte-experimental
is the benchmark score using this implementation.It is worth noting that the scores for
create rows
,replace all rows
,create many rows
, andappend rows to large table
are much improved.If we are going to improve the performance of Svelte in the future, the use of
<template>
andcloneNode
is worth considering.@ryansolid Thanks for the advice. I ran the benchmark in a different environment (Macbook Pro 2016, 32GB Memory) and got the following results. As you said, the score is in
Inferno
range.Keyed results
Startup Metrics
For
Startup Metrics
, the score of this implementation remained as good as the current Svelte.Memory allocation
For
Memory allocation
, there was a slight improvement.