[READ-ONLY] Mirror of https://github.com/danielroe/cross-origin-storage. Load shared dependencies from Cross-Origin Storage (COS).
cross-origin-storage
experimental
nuxt
vite
vite-plugin
1# vite-plugin-cross-origin-storage
2
3> [!WARNING]
4> Experimental. The [Cross-Origin Storage API](https://github.com/WICG/cross-origin-storage) is an early-stage proposal with no native browser support yet, and this plugin's chunk format is not stable. Do not depend on it in production.
5
6A Vite plugin that extracts shared dependencies (such as `vue`) into **content-addressed** chunks that can be loaded from [Cross-Origin Storage (COS)](https://github.com/WICG/cross-origin-storage). When two sites build the same dependency at the same version, they produce byte-identical chunks with the same SHA-256, so a browser that supports COS can serve the chunk from a shared store instead of fetching it again per origin.
7
8This builds on [Thomas Steiner](https://github.com/tomayac)'s original [`vite-plugin-cross-origin-storage`](https://github.com/tomayac/vite-plugin-cross-origin-storage), and is intended as an update of it. It explores a content-addressed chunking and decentralised (registry-free) sharing model on top of the loader and import-rewriting approach Thomas established. The aim is to merge these changes back upstream.
9
10## How it works
11
12At build time, for each package matched by `packages`:
13
141. The package is externalised from the app graph and re-bundled on its own with `rolldown`, **preserving every export** (no tree-shaking). This makes a chunk's bytes depend only on the package, never on which parts of it the app happened to import, so it is identical across sites.
152. Its dependencies are discovered and bundled too, recursively, so managing one package implicitly manages its whole import subgraph (e.g. `vue` pulls in `@vue/*`). Shared dependencies become their own chunks rather than being duplicated, which also preserves singletons like `@vue/reactivity`.
163. Chunks are hashed **bottom-up**: each chunk imports its dependencies by their content hash (`cos1:<sha256>`), so a chunk can only be hashed once its dependencies are. The result is purely a function of the source plus a pinned build recipe.
174. A runtime loader is injected. It looks each managed chunk up in COS by hash, falling back to the network and storing the fetched chunk for next time, then wires everything together through an import map.
18
19The `cos1:` prefix is a **recipe version**. Chunks are only byte-identical across builds that use the same recipe (the same `rolldown` version and options); the prefix is bumped when the recipe changes so chunks built under different recipes can never collide on the same hash.
20
21## Installation
22
23```bash
24npm install -D vite-plugin-cross-origin-storage
25```
26
27## Usage
28
29```ts
30// vite.config.ts
31import { defineConfig } from 'vite'
32import { cosPlugin } from 'vite-plugin-cross-origin-storage'
33
34export default defineConfig({
35 plugins: [
36 cosPlugin({
37 packages: [/^(?:vue$|@vue\/)/],
38 }),
39 ],
40})
41```
42
43For a plain client build, the plugin injects the loader into `index.html` and removes the default entry `<script>` automatically.
44
45## Trying it out
46
47The plugin only runs at build time, so verify against a production build, not the dev server:
48
49```bash
50vite build && vite preview
51```
52
53Then check, in your `dist/<assetsDir>/`, that the managed packages are emitted as content-hashed chunks (a 64-character hex filename like `a1b2c3...e4f5.js`), and that opening the preview URL still loads the app normally. The chunks import each other by `cos1:<hash>` specifiers, resolved at runtime through an injected `<script type="importmap">`.
54
55Without a COS-capable browser the loader fetches each chunk over the network, so this is the network-fallback path: it confirms the chunking and loader work, but not sharing.
56
57To see real Cross-Origin Storage, install the [extension](https://chromewebstore.google.com/detail/cross-origin-storage/denpnpcgjgikjpoglpjefakmdcbmlgih), then:
58
591. Open the preview URL. On the first load the chunks are fetched and stored in COS (the extension's toolbar popup shows the activity).
602. Reload, or open a **different** site that ships the same dependency at the same version. In DevTools -> Network, the managed hashed `.js` chunks are no longer fetched; they come from the shared store instead.
61
62## Options
63
64| Option | Type | Default | Description |
65| --- | --- | --- | --- |
66| `packages` | `Array<string \| RegExp>` | (required) | Packages to extract into COS chunks. Matched against the imported specifier; a plain string is an exact match. Transitive dependencies are collected automatically. |
67| `base` | `string` | Vite's `base` + `build.assetsDir` | Public path the chunks are served from. |
68| `loaderEntry` | `string` | bundled loader | Path to a custom runtime loader entry. |
69| `onGenerated` | `(scriptContent: string) => void` | (unset) | Receives the loader `<script>` body once chunks are emitted. SSR frameworks inject it into their own rendered HTML; when omitted the plugin injects into `index.html`. |
70
71## Browser support
72
73The [Cross-Origin Storage API](https://github.com/WICG/cross-origin-storage) is not yet implemented in any browser. You can try it today with the [Cross-Origin Storage browser extension](https://github.com/web-ai-community/cross-origin-storage-extension). Without it, the loader falls back to ordinary network requests, so the build still works everywhere; it just doesn't share chunks.
74
75## Limitations
76
77- **Managed packages must be self-contained.** A package whose source imports build-time virtuals (e.g. `#build/*`, `#imports`) cannot be bundled standalone and is rejected with a clear error. It also wouldn't be shareable, since its output would differ per app.
78- **Single-entry builds.** The loader wires up one entry chunk; multi-page builds with several HTML entries are not yet supported.
79- **The app entry is never COS-shared.** It is app-specific and is loaded from the network.
80- **Determinism is recipe-scoped.** Sharing only happens between builds on the same package version *and* the same recipe (`cos1:`).
81
82## Credits
83
84Original plugin and the COS loader / import-rewriting approach by [Thomas Steiner](https://github.com/tomayac) ([`tomayac/vite-plugin-cross-origin-storage`](https://github.com/tomayac/vite-plugin-cross-origin-storage)).
85
86## License
87
88MIT