Skip to content

Svelte "hello world" increased by 4.37kB (~59%) between Svelte 3.37.0 and 3.38.0 #6462

Closed
@nolanlawson

Description

@nolanlawson

Describe the bug

First off, thank you for maintaining Svelte. It's one of my favorite JavaScript frameworks for its ease-of-use, small bundle size, and runtime performance.

Unfortunately it seems that the baseline bundle size has increased significantly in recent versions. I wrote a small repro using Rollup, rollup-plugin-svelte, and the Hello World example from the REPL. Here are the bundle sizes reported by bundlesize for recent Svelte versions:

3.37.0 3.38.3 Delta
minified 7.42KB 11.79KB +58.89%
min+gz 2.27KB 3.58KB +36.59%

Here is a diff of the JavaScript bundle:

Click to see diff
19a20,126
> 
> // Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM
> // at the end of hydration without touching the remaining nodes.
> let is_hydrating = false;
> function start_hydrating() {
>     is_hydrating = true;
> }
> function end_hydrating() {
>     is_hydrating = false;
> }
> function upper_bound(low, high, key, value) {
>     // Return first index of value larger than input value in the range [low, high)
>     while (low < high) {
>         const mid = low + ((high - low) >> 1);
>         if (key(mid) <= value) {
>             low = mid + 1;
>         }
>         else {
>             high = mid;
>         }
>     }
>     return low;
> }
> function init_hydrate(target) {
>     if (target.hydrate_init)
>         return;
>     target.hydrate_init = true;
>     // We know that all children have claim_order values since the unclaimed have been detached
>     const children = target.childNodes;
>     /*
>     * Reorder claimed children optimally.
>     * We can reorder claimed children optimally by finding the longest subsequence of
>     * nodes that are already claimed in order and only moving the rest. The longest
>     * subsequence subsequence of nodes that are claimed in order can be found by
>     * computing the longest increasing subsequence of .claim_order values.
>     *
>     * This algorithm is optimal in generating the least amount of reorder operations
>     * possible.
>     *
>     * Proof:
>     * We know that, given a set of reordering operations, the nodes that do not move
>     * always form an increasing subsequence, since they do not move among each other
>     * meaning that they must be already ordered among each other. Thus, the maximal
>     * set of nodes that do not move form a longest increasing subsequence.
>     */
>     // Compute longest increasing subsequence
>     // m: subsequence length j => index k of smallest value that ends an increasing subsequence of length j
>     const m = new Int32Array(children.length + 1);
>     // Predecessor indices + 1
>     const p = new Int32Array(children.length);
>     m[0] = -1;
>     let longest = 0;
>     for (let i = 0; i < children.length; i++) {
>         const current = children[i].claim_order;
>         // Find the largest subsequence length such that it ends in a value less than our current value
>         // upper_bound returns first greater value, so we subtract one
>         const seqLen = upper_bound(1, longest + 1, idx => children[m[idx]].claim_order, current) - 1;
>         p[i] = m[seqLen] + 1;
>         const newLen = seqLen + 1;
>         // We can guarantee that current is the smallest value. Otherwise, we would have generated a longer sequence.
>         m[newLen] = i;
>         longest = Math.max(newLen, longest);
>     }
>     // The longest increasing subsequence of nodes (initially reversed)
>     const lis = [];
>     // The rest of the nodes, nodes that will be moved
>     const toMove = [];
>     let last = children.length - 1;
>     for (let cur = m[longest] + 1; cur != 0; cur = p[cur - 1]) {
>         lis.push(children[cur - 1]);
>         for (; last >= cur; last--) {
>             toMove.push(children[last]);
>         }
>         last--;
>     }
>     for (; last >= 0; last--) {
>         toMove.push(children[last]);
>     }
>     lis.reverse();
>     // We sort the nodes being moved to guarantee that their insertion order matches the claim order
>     toMove.sort((a, b) => a.claim_order - b.claim_order);
>     // Finally, we move the nodes
>     for (let i = 0, j = 0; i < toMove.length; i++) {
>         while (j < lis.length && toMove[i].claim_order >= lis[j].claim_order) {
>             j++;
>         }
>         const anchor = j < lis.length ? lis[j] : null;
>         target.insertBefore(toMove[i], anchor);
>     }
> }
> function append(target, node) {
>     if (is_hydrating) {
>         init_hydrate(target);
>         if ((target.actual_end_child === undefined) || ((target.actual_end_child !== null) && (target.actual_end_child.parentElement !== target))) {
>             target.actual_end_child = target.firstChild;
>         }
>         if (node !== target.actual_end_child) {
>             target.insertBefore(node, target.actual_end_child);
>         }
>         else {
>             target.actual_end_child = node.nextSibling;
>         }
>     }
>     else if (node.parentNode !== target) {
>         target.appendChild(node);
>     }
> }
21c128,133
<     target.insertBefore(node, anchor || null);
---
>     if (is_hydrating && !anchor) {
>         append(target, node);
>     }
>     else if (node.parentNode !== target || (anchor && node.nextSibling !== anchor)) {
>         target.insertBefore(node, anchor || null);
>     }
189a302
>             start_hydrating();
201a315
>         end_hydrating();
232c346
< /* index.svelte generated by Svelte v3.37.0 */
---
> /* index.svelte generated by Svelte v3.38.3 */

Most of the size increase seems to have come from 10e3e3d and 04bc37d, which are related to hydration.

I'm not completely sure, but it seems like potentially this new code could be omitted for components compiled with hydratable: false? This would help a lot for use cases like mine, where I'm distributing a small standalone web component built with Svelte, with no SSR or hydration needed, and I'd ideally like it to be as small as possible.

Thank you in advance for considering this potential performance improvement!

Reproduction

git clone git@gist.github.com:0fd1d597cd18bd861a60abf035466a17.git repro
cd repro
npm i
npm t

Logs

No response

System Info

Ubuntu 20.04.2

Severity

annoyance

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions