[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
1declare global {
2 interface Navigator {
3 crossOriginStorage?: {
4 requestFileHandles: (
5 descriptors: Array<{ algorithm: string, value: string }>,
6 options?: { create?: boolean },
7 ) => Promise<Array<{
8 getFile: () => Promise<File>
9 createWritable: () => Promise<{
10 write: (data: Blob) => Promise<void>
11 close: () => Promise<void>
12 }>
13 }>>
14 }
15 }
16}
17
18export interface CosManifest {
19 /** Public base path that managed chunks are served from, e.g. `/_nuxt/`. */
20 base: string
21 /**
22 * The entry chunk to import once the import map is ready. It is app-specific, so it is
23 * loaded straight from the network rather than stored in COS by a content hash.
24 */
25 entry: { specifier: string, file: string }
26 /** Map of content-addressed specifier to `{ file, hash }` for every COS-managed chunk. */
27 chunks: Record<string, { file: string, hash: string }>
28}
29
30export async function runCosLoader(manifest: CosManifest): Promise<void> {
31 const cos = navigator.crossOriginStorage
32 const imports: Record<string, string> = {}
33
34 async function resolveChunk(hash: string, file: string): Promise<string> {
35 if (cos) {
36 try {
37 const [handle] = await cos.requestFileHandles([{ algorithm: 'SHA-256', value: hash }])
38 if (handle) {
39 const blob = await handle.getFile()
40 return URL.createObjectURL(new Blob([blob], { type: 'text/javascript' }))
41 }
42 }
43 catch (error) {
44 if ((error as Error)?.name !== 'NotFoundError') {
45 console.error('[cos] lookup failed', error)
46 }
47 }
48 }
49
50 const response = await fetch(file)
51 const blob = new Blob([await response.blob()], { type: 'text/javascript' })
52
53 if (cos) {
54 try {
55 const [handle] = await cos.requestFileHandles([{ algorithm: 'SHA-256', value: hash }], { create: true })
56 if (handle) {
57 const writable = await handle.createWritable()
58 await writable.write(blob)
59 await writable.close()
60 }
61 }
62 catch (error) {
63 console.error('[cos] store failed', error)
64 }
65 }
66
67 return URL.createObjectURL(blob)
68 }
69
70 await Promise.all(
71 Object.entries(manifest.chunks).map(async ([specifier, { file, hash }]) => {
72 imports[specifier] = await resolveChunk(hash, manifest.base + file)
73 }),
74 )
75
76 imports[manifest.entry.specifier] = new URL(manifest.base + manifest.entry.file, location.origin).href
77
78 const script = document.createElement('script')
79 script.type = 'importmap'
80 script.textContent = JSON.stringify({ imports })
81 document.head.appendChild(script)
82
83 await new Promise(resolve => setTimeout(resolve, 0))
84 await import(/* @vite-ignore */ manifest.entry.specifier)
85}