ai / size-limit

Calculate the real cost to run your JS app or lib to keep good performance. Show error in pull request if the cost exceeds the limit.
MIT License
6.54k stars 1.82k forks source link

Inaccurate bundle size when using dynamic imports #351

Closed glitch-txs closed 7 months ago

glitch-txs commented 7 months ago

I have a small project setup where I'm testing tools to measure bundle size:

I have two files (index.js & store.js), one file has a function createStore which creates a basic JavaScript store, the index file exports a returnCreateStore function that simply returns the createStore function from store.js, I created this same library twice but just added a dynamic import to one of them for the createStore.

Library A:

import { createStore } from './store.js'

export async function returnCreateStore(){
  return createStore
}

Library B

export async function returnCreateStore(){
  const { createStore } = await import('./store.js')
  return createStore
}

With the following config:

[
  {
    "path": "dist/index.js",
    "import" : "{ returnCreateStore }"
  }
]

Library A: Size: 223 B Library B: Size: 1.13 kB

When both should have around the same size

Repository: https://github.com/glitch-txs/bundlephobia-poc

ai commented 7 months ago

Very likely that 1 KB of JS is esbuild’s code for import().

Can you try what bundle will create Vite with your library?

glitch-txs commented 7 months ago

Can you try what bundle will create Vite with your library?

Sorry I didn't understand your question, what should I try? Do you mean bundling it with Vite first?

ai commented 7 months ago

Do you mean bundling it with Vite first?

Yes, and see the real bundle of library A and B.

glitch-txs commented 7 months ago

yeah it creates a lot of extra code:

this is just a return function with a dynamic import inside:

const h = "modulepreload",
  m = function (o) {
    return "/" + o;
  },
  c = {},
  v = function (u, l, f) {
    let a = Promise.resolve();
    if (l && l.length > 0) {
      const r = document.getElementsByTagName("link");
      a = Promise.all(
        l.map((e) => {
          if (((e = m(e)), e in c)) return;
          c[e] = !0;
          const n = e.endsWith(".css"),
            d = n ? '[rel="stylesheet"]' : "";
          if (!!f)
            for (let s = r.length - 1; s >= 0; s--) {
              const i = r[s];
              if (i.href === e && (!n || i.rel === "stylesheet")) return;
            }
          else if (document.querySelector(`link[href="${e}"]${d}`)) return;
          const t = document.createElement("link");
          if (
            ((t.rel = n ? "stylesheet" : h),
            n || ((t.as = "script"), (t.crossOrigin = "")),
            (t.href = e),
            document.head.appendChild(t),
            n)
          )
            return new Promise((s, i) => {
              t.addEventListener("load", s),
                t.addEventListener("error", () =>
                  i(new Error(`Unable to preload CSS for ${e}`))
                );
            });
        })
      );
    }
    return a
      .then(() => u())
      .catch((r) => {
        const e = new Event("vite:preloadError", { cancelable: !0 });
        if (((e.payload = r), window.dispatchEvent(e), !e.defaultPrevented))
          throw r;
      });
  };
async function E() {
  const { createStore: o } = await v(
    () => import("./store-DcLibnQz.js"),
    __vite__mapDeps([])
  );
  return o;
}
E();
function __vite__mapDeps(indexes) {
  if (!__vite__mapDeps.viteFileDeps) {
    __vite__mapDeps.viteFileDeps = [];
  }
  return indexes.map((i) => __vite__mapDeps.viteFileDeps[i]);
}
ai commented 7 months ago

So, the Size Limit is right.

The true cost of library B will be higher in real JS bundles.

glitch-txs commented 7 months ago

got it, thanks!

glitch-txs commented 7 months ago

Hi @ai ! I have another case where dynamic import is not working as expected, could you check on this please?

I created two packages that import an external package, this external package is a direct dependency, if I use dynamic import it won't calculate the external package size, if I use nomal import it will, even when my configuration is like this:

[
  {
    "path": "dist/*.js",
    "import": "*"
  }
]

This is the repo, is very minimal: https://github.com/glitch-txs/size-limit-dynamic-import

Results

split file

  Size:         1.3 kB with all dependencies, minified and brotlied
  Loading time: 26 ms  on slow 3G
  Running time: 48 ms  on Snapdragon 410
  Total time:   73 ms

no-split file

  Size:         81.44 kB with all dependencies, minified and brotlied
  Loading time: 1.6 s    on slow 3G
  Running time: 976 ms   on Snapdragon 410
  Total time:   2.6 s
glitch-txs commented 7 months ago

ah small lib seems to do the work, since the big one measures the execution time and the function is not being executed, this is why it's omitted I assume

glitch-txs commented 7 months ago

does the small package apply minification?

ai commented 7 months ago

if I use dynamic import it won't calculate the external package size

Unfortunately, it is how import() works. It is dynamic, so the content of import() is not part of a current bundle.

import() is used for code splitting and it is expected behavior to not see loaded part in a current bundle size.