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

Use import maps for better caching #4482

Open
Rich-Harris opened this issue Apr 1, 2022 · 4 comments · May be fixed by #11615
Open

Use import maps for better caching #4482

Rich-Harris opened this issue Apr 1, 2022 · 4 comments · May be fixed by #11615
Labels
feature / enhancement New feature or request
Milestone

Comments

@Rich-Harris
Copy link
Member

Describe the problem

Suppose you have three pages, /one, /two and /three, that each depend on Widget.svelte. Because that component is used by multiple pages, Vite will create a chunk for it with a hashed name: chunks/Widget-abc123.js.

Since routes are also code-split, the pages will have their own chunks — pages/one-def234.js, pages/two-ghi345.js and pages/three-jkl456.js. Each will have an import declaration like this:

import Widget from '../chunks/Widget-abc123.js';

The hashing allows us to treat these assets as immutable, which means that repeat visitors won't have to redownload those chunks as long as they don't change.

But.

One day we change Widget.svelte without changing /one, /two or /three. Because the hashes are based on content, chunks/Widget-abc123.js is now chunks/Widget-mno567.js. That's fine — we want users to redownload that chunk — but it means that the import declaration now looks like this...

import Widget from '../chunks/Widget-mno567.js';

...which means that the page chunk hashes must also change, even though they're otherwise identical. Suddenly, the user must redownload four chunks (more, in fact, since the main entry point containing the route manifest is also tainted) even though only one module changed.

Obviously this problem isn't unique to SvelteKit, but it's a problem we're well-placed to solve.

Describe the proposed solution

Import maps solve this problem. By mapping stable identifiers to the hashed assets...

<script type="importmap">
{
  "imports": {
    "chunks/Widget": "/_app/chunks/Widget-abc123.js",
    "pages/one": "/_app/pages/one-def234.js",
    ...
  }
}
</script>

...we can import chunks like so:

import Widget from 'chunks/Widget';

Now, changes to Widget.svelte won't taint its consumers — all we need to do is update the import map.

Wrinkles:

  • Import maps aren't yet supported in all browsers. Happily, there's a production-ready solution: https://github.com/guybedford/es-module-shims
  • Generating import declarations with stable identifiers while still generating hashed assets might be tricky — I'm not sure how to do that with Vite. I'm confident we could figure it out though
  • The import map could conceivably grow quite large, perhaps even to the point where the trade-off isn't worth it

Alternatives considered

No response

Importance

nice to have

Additional Information

No response

@bluwy
Copy link
Member

bluwy commented Apr 2, 2022

Maybe related: vitejs/vite#2483

@Rich-Harris Rich-Harris added this to the post-1.0 milestone Apr 4, 2022
@Rich-Harris Rich-Harris added the feature / enhancement New feature or request label Apr 4, 2022
@jacekkarczmarczyk
Copy link

jacekkarczmarczyk commented Nov 9, 2022

Proof of concept for Vite+Rollup: vitejs/vite#6773 (comment)
There's also SystemJS approach in rollup/rollup#3407

@JReinhold
Copy link

With the release of Safari 16.4, import maps are now supported across all major browsers.

https://caniuse.com/import-maps

Safari 16.4 isn't widely adopted yet though.

@KonnorRogers
Copy link

KonnorRogers commented Apr 1, 2023

I actually managed to scaffold a prototype of this. I haven't had too much time to play around with it, but the repo can be found here:

https://github.com/konnorRogers/asset-mapper

Theres an example SvelteKit app in there that I used to generate an example manifest that looks like this:

JSON output
// .svelte-kit/output/client/asset-mapper-manifest.json
{
  "_app/immutable/assets/svelte-logo.svg": "_app/immutable/assets/svelte-logo-87df40b8fbb4afb4.svg",
  "_app/immutable/assets/github.svg": "_app/immutable/assets/github-1ea8d62ee9f90811.svg",
  "_app/immutable/assets/fira-mono-greek-ext-400-normal.woff2": "_app/immutable/assets/fira-mono-greek-ext-400-normal-9e2fe623052dabee.woff2",
  "_app/immutable/assets/fira-mono-greek-400-normal.woff2": "_app/immutable/assets/fira-mono-greek-400-normal-a8be01cef8d13a05.woff2",
  "_app/immutable/assets/svelte-welcome.webp": "_app/immutable/assets/svelte-welcome-c18bcf5a7a25fc1d.webp",
  "_app/immutable/assets/fira-mono-cyrillic-ext-400-normal.woff2": "_app/immutable/assets/fira-mono-cyrillic-ext-400-normal-3df7909e625102bb.woff2",
  "_app/immutable/assets/fira-mono-cyrillic-400-normal.woff2": "_app/immutable/assets/fira-mono-cyrillic-400-normal-c7d433fd8d89f891.woff2",
  "_app/immutable/assets/fira-mono-latin-ext-400-normal.woff2": "_app/immutable/assets/fira-mono-latin-ext-400-normal-6bfabd307779c11b.woff2",
  "_app/immutable/entry/app.js": "_app/immutable/entry/app-f338ff0a3089d9ab.js",
  "_app/immutable/assets/fira-mono-all-400-normal.woff": "_app/immutable/assets/fira-mono-all-400-normal-1e3b098cc2d20d35.woff",
  "_app/immutable/assets/svelte-welcome.png": "_app/immutable/assets/svelte-welcome-6c300099eb59ca6c.png",
  "_app/immutable/entry/start.js": "_app/immutable/entry/start-d0598b62ed40ea9b.js",
  "_app/immutable/entry/_layout.svelte.js": "_app/immutable/entry/_layout.svelte-de54b5df8c99a43e.js",
  "_app/immutable/entry/_page.svelte.js": "_app/immutable/entry/_page.svelte-cae5b5b27bafd7da.js",
  "_app/immutable/entry/error.svelte.js": "_app/immutable/entry/error.svelte-e622c1032969775e.js",
  "_app/immutable/entry/about-page.svelte.js": "_app/immutable/entry/about-page.svelte-d99e7b253ffd1e16.js",
  "_app/immutable/assets/fira-mono-latin-400-normal.woff2": "_app/immutable/assets/fira-mono-latin-400-normal-e43b3538e39a85a0.woff2",
  "_app/immutable/entry/sverdle-how-to-play-page.svelte.js": "_app/immutable/entry/sverdle-how-to-play-page.svelte-a5d1d2c678b115ed.js",
  "_app/immutable/entry/about-page.js.js": "_app/immutable/entry/about-page.js-bf64cf6e88336878.js",
  "_app/immutable/entry/sverdle-how-to-play-page.js.js": "_app/immutable/entry/sverdle-how-to-play-page.js-bf4e30b459881312.js",
  "_app/immutable/entry/sverdle-page.svelte.js": "_app/immutable/entry/sverdle-page.svelte-63f64dcadd55dac5.js",
  "_app/immutable/entry/_page.js.js": "_app/immutable/entry/_page.js-9f067d6c4e964d6d.js",
  "_app/immutable/chunks/index.js": "_app/immutable/chunks/index-90a53736ddfa4113.js",
  "_app/immutable/chunks/singletons.js": "_app/immutable/chunks/singletons-b51f803857cdde4d.js",
  "_app/immutable/chunks/1.js": "_app/immutable/chunks/1-35de6d94f03ca829.js",
  "_app/immutable/chunks/parse.js": "_app/immutable/chunks/parse-300c9616221d5453.js",
  "_app/immutable/chunks/3.js": "_app/immutable/chunks/3-52195e7b83748fe8.js",
  "_app/immutable/chunks/4.js": "_app/immutable/chunks/4-cf47d3ec2700026c.js",
  "_app/immutable/chunks/2.js": "_app/immutable/chunks/2-a00365ffa7eb34d9.js",
  "_app/immutable/chunks/0.js": "_app/immutable/chunks/0-01514bbbcf4ecad9.js",
  "_app/immutable/chunks/stores.js": "_app/immutable/chunks/stores-45f9f67b65489a5d.js",
  "_app/immutable/chunks/5.js": "_app/immutable/chunks/5-ede663e6a4df8158.js",
  "_app/immutable/chunks/_page2.js": "_app/immutable/chunks/_page2-100d4dff2b757243.js",
  "_app/immutable/chunks/index2.js": "_app/immutable/chunks/index2-3506a163e244535d.js",
  "_app/immutable/chunks/_page.js": "_app/immutable/chunks/_page-1c6540c96d702a52.js",
  "_app/immutable/chunks/environment.js": "_app/immutable/chunks/environment-6b4c8de700c75d5f.js",
  "_app/immutable/chunks/_page3.js": "_app/immutable/chunks/_page3-100d4dff2b757243.js",
  "_app/immutable/assets/_page.css": "_app/immutable/assets/_page-89a9e780fc0f784e.css",
  "_app/immutable/assets/_page2.css": "_app/immutable/assets/_page2-265a38f05eb328b2.css",
  "_app/immutable/assets/_layout.css": "_app/immutable/assets/_layout-746118a189509aa7.css",
  "_app/immutable/assets/_page3.css": "_app/immutable/assets/_page3-9d50104935bef3f8.css",
  "_app/version.json": "_app/version-fa2e8ab57482a8c7.json"
}

So while it doesn't directly write the importmaps, a simple server side helper could write the importmap. The benefit of course being the manifest can be used for other things if needed by having a logical reference to a hashed path. This is roughly how Rails asset pipeline works for anyone familiar.

<%= asset_path("app.js") %>
# => app-[hash].js 

The obvious caveats are is I really don't know how well collided filenames get handled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature / enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants