Skip to content
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

More efficient fragment creation #3898

Closed
Rich-Harris opened this issue Nov 11, 2019 · 34 comments
Closed

More efficient fragment creation #3898

Rich-Harris opened this issue Nov 11, 2019 · 34 comments
Labels
compiler Changes relating to the compiler perf popular more than 20 upthumbs

Comments

@Rich-Harris
Copy link
Member

At 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 just svelte)
  • Haven't really thought about how this'd work with hydration
@halfnelson
Copy link
Contributor

This might upset svelte-native unless it is only run on html namespace (and not svg or TNS), otherwise I guess I could add a runtime html parser

@vipero07
Copy link

This could possibly be further optimized using a tagged template.

@tivac
Copy link
Contributor

tivac commented Dec 5, 2019

I experimented with benchmarking a simple example.

http://jsben.ch/Tg1Yx

Feel free to point out anywhere I got this wrong, or if there's a better JS/DOM benchmarking site you prefer.

My desktop running Chrome shows that the current imperative code may be faster?

image

Modified version of Rich's proposal so it worked
 const render = make_renderer(
-  `<section bind><h1 bind>Hello <sveltetext/>!</h1> <p bind></p></section>`
+  `<section bind><h1 bind>Hello <sveltetext/>!</h1> <p bind><sveltetext/></p></section>`
 );
 
 function create_fragment(ctx) {
   let section;
   let t0;
   let t1;
 
   return {
     c() {
       [section, { childNodes: [, t0] }, { childNodes: [t1] }] = render();
-      t0 = text(ctx.name);
-      t1 = text(ctx.description);
+      set_data(t0, ctx.name);
+      set_data(t1, ctx.description);
     },
     m(target, anchor) {
       insert(target, section, anchor);
     },
     p:(changed, ctx) {
       if (changed.name) set_data(t0, ctx.name);
       if (changed.description) set_data(t1, ctx.description);
     },
     i: noop,
     o: noop,
     d(detaching) {
       if (detaching) detach(section);
     }
   };
 }
Benchmark Setup JS
// SVELTE INTERNALS
function noop() { }
function append(target, node) {
    target.appendChild(node);
}
function insert(target, node, anchor) {
    target.insertBefore(node, anchor || null);
}
function detach(node) {
    node.parentNode.removeChild(node);
}
function element(name) {
    return document.createElement(name);
}
function text(data) {
    return document.createTextNode(data);
}
function space() {
    return text(' ');
}
function set_data(text, data) {
    data = '' + data;
    if(text.data !== data)
        text.data = data;
}

// END SVELTE INTERNALS

function create_fragment_imperative(ctx) {
    let section;
    let h1;
    let t3;
    let p;

    return {
        c() {
            section = element("section");
            h1 = element("h1");
            h1.textContent = `Hello ${ctx.name}!`;
            t3 = space();
            p = element("p");
            p.textContent = `${ctx.description}`;
        },
        m(target, anchor) {
            insert(target, section, anchor);
            append(section, h1);
            append(section, t3);
            append(section, p);
        },
        p: noop,
        i: noop,
        o: noop,
        d(detaching) {
            if(detaching) detach(section);
        }
    };
}

function 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]');
    };
}


const render = make_renderer(
    `<section bind><h1 bind>Hello <sveltetext/>!</h1> <p bind><sveltetext/></p></section>`
);

function create_fragment_template(ctx) {
    let section;
    let t0;
    let t1;

    return {
        c() {
            [ section, { childNodes: [ , t0 ] }, { childNodes: [ t1 ] } ] = render();

            t0 = set_data(t0, ctx.name);
            t1 = set_data(t1, ctx.description);
        },
        m(target, anchor) {
            insert(target, section, anchor);
        },
        i: noop,
        o: noop,
        d(detaching) {
            if(detaching) detach(section);
        }
    };
}

// IMPERATIVE TEST CASE
function imperative() {
    let name = "world";
    let description = "gogogo";

    const frag = create_fragment_imperative({ name, description });

    frag.c();
    frag.m(document.body);
}

// TEMPLATE TEST CASE
function template() {
    let name = "world";
    let description = "gogogo";

    const frag = create_fragment_template({ name, description });

    frag.c();
    frag.m(document.body);
}

@benmccann
Copy link
Member

benmccann commented May 7, 2020

domc is one of the fastest libraries in the js-frameworks-benchmark (the benchmark is mentioned in #1260). It clones elements rather than using createElement and says it's much faster. There's a full explanation on their readme of what they do.

stage0, by the same author, is also much faster than Svelte. It looks to primarily use template with innerHTML. Though the author suggests you may be able to just use a string with innerHTML for IE11 support.

Also interesting is that they said they got some ideas from morphdom. On the morphdom readme it says that they render to HTML string when doing SSR and perform 10x faster than React as a result, so that might be another idea worth pursuing.

@benmccann
Copy link
Member

@tivac thanks for running this benchmark. It was a really helpful starting point. I've been playing around with it quite a bit and think I've discovered a number of issues:

  • jsben.ch seems to give very different results from other benchmarking solutions in at least some cases according to posts I've seen online 1 2
  • The results are very highly variable. jsben.ch does not print the standard deviation, but I've tried running numerous times on other sites and the standard deviation can sometimes be extremely high
  • The benchmark appends a new element to the dom, but never removes it. This makes the test get slower over time. On many of the sites I tested on, this means that the second test that runs is at a disadvantage. This is usually the template test since it was entered second

I got a really significant speedup on js-framework-benchmark using both the approach suggested here as well as using stage0 to implement a similar approach #1260 (comment). It was about 15% faster on my machine in both cases. I tend to trust js-framework-benchmark more than jsben.ch because I know some people that are highly experienced with JS benchmarking are looking at it, it tests script load and parse times as well, and it returns more consistent results for me.

The solution from the original post here creates extra bind attributes. Because Svelte is a compiler, I feel like that's probably not necessary. E.g. consider what domc does.

@benmccann
Copy link
Member

Both the benchmark included above as well as js-framework-benchmark create the same fragment numerous times, which seems to be where template + cloneNode approaches like the one Rich posted above and the stage0 solution (#1260 (comment)) would shine. It'd be interesting to test on a real-world example which creates a whole HTML page with a mix of fragments being created just once, fragments being created in an each, etc. but I'm not sure anyone has the time to create such tooling. @krausest, @leeoniya, and @ryansolid do you guys have any thoughts from your experience on whether template + cloneNode might still perform better than naive js dom creation if you're creating a fragment only once or in an overall real world example?

@ryansolid
Copy link

ryansolid commented Jun 8, 2020

I've spent a decent amount of time here.

The problem is you need to get the nodes to clone in the first place. This means generally innerHTML it at some point. Usually on file load. I haven't found that too problematic but it's something. Once you have the nodes cloning is generally more performant once you are creating more than a couple at once. I use the same cloning technique in Solid, as does Mikado, Sinuous. I converted the Vanilla implementations to do it too. Basically the whole top tier of JS Frameworks benchmark use this cloning technique as it improves creation time and actually lowers memory usage. That being said Surplus does not, nor does Fidan and they are still up there. I like using these as a baseline since they very similar internals o Solid. We are talking about 5ms on 1000 rows and maybe like 50ms on 10k.

But some important details. We can't discount the cost of traversing the nodes to attach bindings or the method of doing so. I don't know what that querySelector is doing in the jsbench example but depending a simple directed walk can be more performant. Also Document Fragments are intrinsically more expensive to attach to the DOM than single elements. I don't know exactly how under the hood, but it's like there is an internal traversal. With a single child fragment probably unnoticeable anyway. Again small gains but I generally clone the element rather than the template.content if possible.

All that being said. Realworld I doubt it makes a big difference. But I like the hydration story. What you find is now the creation code resembles the hydration code. In one case the server-rendered the template and the other you cloned it. Your work then is more or less the same.

@benmccann
Copy link
Member

Thanks for taking the time to provide such a detailed response Ryan!

@martypdx
Copy link

martypdx commented Jun 9, 2020

@ryansolid:

The problem is you need to get the nodes to clone in the first place. This means generally innerHTML it at some point

The goal is to move the html creation out of js land and into the browser load. No innerHTML, no JS to parse and evaluate. Just let them browser load html in template tags. I think this is the part of the approach people are missing here. No JavaScript will always be faster than some JavaScript.

We can't discount the cost of traversing the nodes to attach bindings or the method of doing so. I don't know what that querySelector is doing in the jsbench example but depending a simple directed walk can be more performant

Turns out querySelector is 3-5 times faster. I did my benchmarks a few years ago, but I doubt that has changed. I tried using childNode indexes, firstChild, etc., but no contest. I suspect the browser does some indexing on creation to support css. It may even make the process linear, but I haven't tested that. And this doesn't even take into account that there is no js to parse and evaluate

Also Document Fragments are intrinsically more expensive to attach to the DOM than single elements

If you know your template fragment only has one child (which you would in a pre-processed framework) you can always optimize by acting on the firstChild in lieu of the fragment. I'd like to see some more benchmarks on this though.

@ryansolid
Copy link

ryansolid commented Jun 9, 2020

For the HTML creation I was thinking about that with SSR techniques. I imagine for pure clientside Svelte that still isn't an option unless we are talking HTML imports. That would be interesting.

You've convinced me to revisit QuerySelector. I use QuerySelector to grab templates out for hydration since it is much faster than TreeWalker etc, but I am interested to test whether adding an extra attribute on each element and query selecting versus having a pre-calculated traversal path from compilation using firstChild/nextSibling has differences. That would be interesting because all those libraries use the firstChild approach and I'm always looking for an edge.

As for fragments. I was testing an idea of batch creation,. https://jsperf.com/scaling-clonenode A little bit different but the idea was if you knew you were going generally make 100 rows per page couldn't we just do all 100 in one clone operations. In this test I try creating rows 1, 10, 100, and 1000 at a time. In the process I noticed some interesting results. Compare biggestDiv with biggestBlock, this is 1000 rows in a fragment vs 1000 in a div. Second compare oneBlock and oneTr which comes a single row cloning the fragment and a single row cloning the Tr. I use oneTr effectively in Solid but I was looking at different ways to optimize.

Finally I should mention, which I missed in my first response. We can't forget with cloning in general there is the overhead of markers for text that wraps dynamic expressions. They will be joined as a single text node which will screw up insertion points. I use comment blocks in Solid that I don't remove. I just insert before them. In this example they are "sveltetext" text being replaced at creation time. I imagine this might even save a little initial creation time. But it's worth pointing out that there are gotcha's like this more prevalent in the hydration story when you go this way versus per element creation.

@benmccann
Copy link
Member

At least in the Svelte js-framework-benchmark, I didn't see a big difference between query selector vs referencing the node via children. The really big difference was in adopting cloneNode to avoid rebuilding the rows from scratch

@ryansolid
Copy link

children is super expensive as is childNodes they basically convert something that is a link list in the underlying implementation into an array. The second you access it you take a hit. If you want to compare use firstChild or nextSibling

@ryansolid
Copy link

ryansolid commented Jun 11, 2020

You guys had me testing my sanity but turns out the methods I've been using are still the fastest. Here's the improved benchmark:

https://jsben.ch/16Wsq

Template should be much faster now.

EDIT:
And here is the version that leaves the QuerySelector which is slightly slower:
https://jsben.ch/8oVCY

As you can see in this case walking the tree manually is faster.

@tivac
Copy link
Contributor

tivac commented Jun 11, 2020

I see template only being 65-80% as fast as imperative in that benchmark, depending on whether I use Firefox (65.34%) or Chrome (82.56%). Multiple test runs flopped results around, but imperative was always faster?

@ryansolid
Copy link

I ran it multiple times. I see the opposite in Chrome 83. Imperative being around 50% of Template. But Chrome 85 does flip it again although it's much closer. Interesting. I wonder how much this varies version to version.

@halfnelson
Copy link
Contributor

halfnelson commented Jun 11, 2020

image

Firefox 78

@halfnelson
Copy link
Contributor

I would love this as a renderer toggle, otherwise poor old svelte-native is going to need a heck of a lot of work to adapt

@ryansolid
Copy link

ryansolid commented Jun 11, 2020

I don't know how much I trust this test runner, the more I run it the more all over the place the results seem. I tried in Firefox 78.0b5 and every other run seems to switch.

To be fair JS Frameworks Benchmark is only ran in Chrome so that could explain it favoring certain methods. Templates were definitely faster at the time I changed the vanilla implementation of the JS Framework Benchmark.

I tried porting the test to other test runners which confirm imperative still seems to be faster for this test although it's closer. Not sure if I'm still just missing something, but this deserves closer examination.

EDIT: Compared both methods with VanillaJS version of JS Framework Benchmark and template is still faster. There might be something around this example/implementation that leads to different results. I guess the next thing to try is different variations of handling textnodes.

@benmccann
Copy link
Member

I do not trust jsben.ch at all. See my comment above: #3898 (comment)

@halfnelson I'm not that familiar with svelte-native. How would it be impacted?

@benmccann
Copy link
Member

I realized that the SSR code already uses template strings. It might be interesting to test and see if that's faster on the client-side. It could potentially reduce the amount of code to maintain if that were the only code that was necessary and we no longer needed a lot of the DOM code

@ryansolid
Copy link

I mean the templates still become DOM nodes that get traversed and bindings added. You do get to save a number of create/appends calls, but the server/client code ends up being still very different. However where the real advantage comes in is unifying hydration. It isn't hard to write the node traversal code to be identical for the render and hydration case since they walk over the same nodes either created from cloneNode or rendered from the server. This means that Svelte which I believe currently ships 2 separate paths could ship a single path potentially reducing code size considerably in this case, even more than the reductions from the pure render case as you'd have those reductions as well.

The best way to handle this though is the way Solid currently does I think which is still generate separate code for the hydratable case so that the client only render case can be the most optimal still. Hydration still requires walking over dynamic parts to gather nodes which can be completely avoided in the optimal client render approach.

As to transfering the Templates in the SSR rendered code.. it depends. On first render I think this has a lot of merit but I wonder if the cacheability of the JavaScript might trump having to send the templates each time. Assuming dynamic content the HTML is not cached but the JS is. But to be fair there are a lot of things that could affect this once you start comparing cache behavior. If our goal is just to prove the smallest JS bundles this is definitely the way to go without much contest.

@pngwn pngwn added popular more than 20 upthumbs compiler Changes relating to the compiler and removed internals labels Jun 26, 2021
@tomoam
Copy link
Contributor

tomoam commented Sep 4, 2021

I was interested in this issue, so I created an experimental implementation using <template> and cloneNode.

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 of hydration and namespace 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.

js-framework-benchmark keyed results

Note: The scores for partial update, select row, swap rows, and clear rows are not helpful. This is because in my environment (M1 MacBook Air, 16GB Memory), the scores for these four test cases are random and vary widely each time these are run, no matter which framework.

It is worth noting that the scores for create rows, replace all rows, create many rows, and append rows to large table are much improved.

If we are going to improve the performance of Svelte in the future, the use of <template> and cloneNode is worth considering.

@frederikhors
Copy link

@tomoam this is impressive! Thanks! Svelte is getting better, day after day! 😄

@ryansolid
Copy link

@tomoam if you remove cpu throttle from the benchmark(there is a line setting it you can comment out) you can get more stable results. M1 has some issue with cpu throttling. Those results are all over due to that.

But yeah as I said a year ago this is definitely good for about 7% improvement on the official setup. If Svelte implements this it won't be in Solid range but it should be in Inferno range in the benchmark. But that isn't even the biggest benefit. Id expect considerable code reduction in the hydration case.

@tomoam
Copy link
Contributor

tomoam commented Sep 9, 2021

@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

keyed results

Startup Metrics

For Startup Metrics, the score of this implementation remained as good as the current Svelte.

Startup metrics

Memory allocation

For Memory allocation, there was a slight improvement.

Memory allocation

@tomoam
Copy link
Contributor

tomoam commented Oct 28, 2021

after the previous experiment(#3898 (comment), #3898 (comment)), I came up with an idea to reduce the bundle size by extending this implementation.

The implementation is here.

I tried to build site to test it, and the bundle size was reduced.
I compared the total size of the files in the site/build/assets/_app directory, excluding the css files and codemirror.

  • v3.44.0
minified(KiB) gzip(KiB)
303.64 104.05
  • this impl
minified(KiB) gzip(KiB)
276.71 96.89

minified size is 8.87% smaller, and gzip compressed size is 6.88% smaller.

comparing only the files in the site/build/assets/_app/pages directory, minified size is 13.67% smaller and gzip compressed size is 8.13% smaller.

details
file v3.44.0 (KiB) v3.44.0 gzip(KiB) this impl (KiB) this impl gzip(KiB) diff (%) diff gzip(%)
_app/chunks/config-n.js 0.29 0.24 0.29 0.24 0.00% 0.00%
_app/chunks/examples-n.js 0.32 0.18 0.32 0.18 0.00% 0.00%
_app/chunks/InputOutputToggle-n.js 1.20 0.65 0.93 0.52 -22.50% -20.00%
_app/chunks/ReplWidget-n.js 3.24 1.66 3.17 1.64 -2.16% -1.20%
_app/chunks/ScreenToggle-n.js 1.42 0.75 1.41 0.75 -0.70% 0.00%
_app/chunks/singletons-n.js 0.05 0.06 0.05 0.06 0.00% 0.00%
_app/chunks/stores-n.js 0.64 0.33 0.64 0.33 0.00% 0.00%
_app/chunks/vendor-n.js 179.3 54.83 165.64 50.84 -7.62% -7.28%
_app/pages/__error.svelte-n.js 3.87 1.55 2.96 1.36 -23.51% -12.26%
_app/pages/__layout.svelte-n.js 7.16 2.41 7.06 2.48 -1.40% 2.90%
_app/pages/apps/index.svelte-n.js 4.96 2.21 3.90 1.81 -21.37% -18.10%
_app/pages/blog/[slug].svelte-n.js 2.70 1.21 2.24 1.00 -17.04% -17.36%
_app/pages/blog/index.svelte-n.js 3.03 1.38 2.46 1.18 -18.81% -14.49%
_app/pages/docs/index.svelte-n.js 1.83 0.96 1.64 0.90 -10.38% -6.25%
_app/pages/examples/index.svelte-n.js 7.78 3.22 7.21 3.08 -7.33% -4.35%
_app/pages/faq/index.svelte-n.js 2.85 1.31 2.31 1.13 -18.95% -13.74%
_app/pages/index.svelte-n.js 30.70 11.46 24.66 10.29 -19.67% -10.21%
_app/pages/repl/[id]/index.svelte-n.js 14.63 5.70 13.58 5.51 -7.18% -3.33%
_app/pages/repl/embed.svelte-n.js 1.91 0.98 1.78 0.96 -6.81% -2.04%
_app/pages/repl/index.svelte-n.js 0.46 0.35 0.46 0.34 0.00% -2.86%
_app/pages/tutorial/__layout.svelte-n.js 0.74 0.45 0.73 0.45 -1.35% 0.00%
_app/pages/tutorial/[slug]/index.svelte-n.js 10.91 4.36 9.72 3.99 -10.91% -8.49%
_app/pages/tutorial/index.svelte-n.js 0.22 0.19 0.22 0.19 0.00% 0.00%
_app/start-n.js 23.43 7.61 23.33 7.66 -0.43% 0.66%
sum 303.64 104.05 276.71 96.89 -8.87% -6.88%

my implementation code is a messy, so I don't think I should submit a PR right now.

if the core team is interested, I will try to write a detailed description (maybe, to svelte/rfcs?).

@benmccann
Copy link
Member

@tomoam thank you so much for this work! The team talked about your efforts today and we're definitely interested and see the value in this. That being said, it looks like a rather large change with a bit more chance of introducing issues than a usual change. We'll at some point in the (possibly distant) future be doing a compiler overhaul and the feeling was that it'd be best to try to make this change then. So let's definitely keep this code around and we welcome experiments to find these performance improvements and edge cases, but maybe wait until Svelte 4 to PR it.

@tomoam
Copy link
Contributor

tomoam commented Jan 9, 2022

@benmccann Thank you for replying and for bringing up this topic at the maintainer's meeting.

That being said, it looks like a rather large change with a bit more chance of introducing issues than a usual change. We'll at some point in the (possibly distant) future be doing a compiler overhaul and the feeling was that it'd be best to try to make this change then.

I agree with you. The code generated by this experimental compiler is quite different from that of the current Svelte, and has some problems (for example, it may work in production mode, but not in development mode. Also, the code is not maintainable ). If the results of this experiment could be of any help in making Svelte better, I would be happy.

I listened to the JS Party Podcast (to be exact, I translated and read the transcript), where Rich mentioned error boundaries, along with a solution to bundle size. That's not included in my experimental implementation, so perhaps Rich has some other great idea. I'm looking forward to seeing it come to fruition.

@ghost
Copy link

ghost commented Oct 19, 2022

I hope I can help out here. I've been dissecting Svelte's generated JS output for a while, and I have been thinking if the functionality of create_fragment could be moved to helpers (e.g. text(), element(), etc.), the compiler could be simplified at the cost of bundling the helpers. I have a miniature version of that concept as a private and experimental library; however, the current attribute and event listener diffing is not as efficient as in Svelte or Solid, and my library does not even have conditional or list rendering yet. I can make my repository public if anyone wants to dissect my library or take it for a test run. :)

@thet0ast3r
Copy link

You guys had me testing my sanity but turns out the methods I've been using are still the fastest. Here's the improved benchmark:

https://jsben.ch/16Wsq

Template should be much faster now.

EDIT: And here is the version that leaves the QuerySelector which is slightly slower: https://jsben.ch/8oVCY

As you can see in this case walking the tree manually is faster.

I am sorry to reply here, and this is probably kinda offtopic: but i clicked one of the jsbench links and it forwarded me to a mac notification center scam website. is this "jsben.ch" website legit in any way?

@benmccann
Copy link
Member

This will be addressed as part of Svelte 5: https://twitter.com/Rich_Harris/status/1688581184018583558

@martypdx
Copy link

martypdx commented Feb 2, 2024

Will Svelte 5 include the build option to bundle templates into the .html page?

@benmccann
Copy link
Member

It's not exactly clear what you're referring to, but check out SvelteKit's prerendering, which sounds like what you're looking for

I'm going to close this issue as completed

@martypdx
Copy link

martypdx commented Feb 2, 2024

No, I was talking about general Svelte 5 (which is looking awsome btw). You got all the way to:

var frag = $.template(`<div>Hello World <button> </button></div>`);

Which uses js to create a dom template to create the fragment, but...

...shame to stop there when you could do:

<!-- index.html -->
<template id="abc123"><div>Hello World <button> </button></div></template>
var frag = $.templateById('abc123');

And offload that to the browser (really good with html) and get all of those kb of html out of your javascript at load time.

🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler Changes relating to the compiler perf popular more than 20 upthumbs
Projects
None yet
Development

No branches or pull requests