[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
1import { createHash } from 'node:crypto'
2import { mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync, mkdirSync, globSync } from 'node:fs'
3import { fileURLToPath } from 'node:url'
4import { join } from 'node:path'
5import { afterAll, beforeAll, describe, expect, it } from 'vitest'
6import { build } from 'vite'
7import { cosPlugin } from '../src/vite'
8
9// Build inside the project tree so the fixture resolves `vue` from the project
10// node_modules rather than a detached temp dir.
11const scratchRoot = fileURLToPath(new URL('./.plugin-scratch', import.meta.url))
12const nodeModules = fileURLToPath(new URL('../node_modules', import.meta.url))
13const vueEntry = globSync('.pnpm/vue@*/node_modules/vue/dist/vue.runtime.esm-bundler.js', { cwd: nodeModules })[0]!
14
15describe('cosPlugin (standalone vite build)', () => {
16 let root: string
17 let outDir: string
18 let assetsDir: string
19
20 beforeAll(async () => {
21 mkdirSync(scratchRoot, { recursive: true })
22 root = mkdtempSync(join(scratchRoot, 'app-'))
23 outDir = join(root, 'dist')
24 assetsDir = join(outDir, 'assets')
25 mkdirSync(join(root, 'src'), { recursive: true })
26 writeFileSync(
27 join(root, 'index.html'),
28 '<!doctype html><html><head></head><body><script type="module" src="/src/main.js"></script></body></html>',
29 )
30 writeFileSync(join(root, 'src/main.js'), 'import { ref } from "vue"\ndocument.body.dataset.count = String(ref(0).value)\n')
31
32 await build({
33 root,
34 logLevel: 'error',
35 // The fixture lives in a scratch dir; point bare `vue` at the project copy.
36 resolve: { alias: { vue: join(nodeModules, vueEntry) } },
37 plugins: [cosPlugin({ packages: [/^(?:vue$|@vue\/)/] })],
38 build: { outDir, emptyOutDir: true, rollupOptions: { input: join(root, 'index.html') } },
39 })
40 }, 120_000)
41
42 afterAll(() => {
43 rmSync(scratchRoot, { recursive: true, force: true })
44 })
45
46 function cosChunks(): string[] {
47 return readdirSync(assetsDir).filter(f => /^[a-f0-9]{64}\.js$/.test(f))
48 }
49
50 it('emits content-addressed chunks whose names match their bytes', () => {
51 expect(cosChunks().length).toBeGreaterThanOrEqual(1)
52 for (const file of cosChunks()) {
53 const hash = createHash('sha256').update(readFileSync(join(assetsDir, file))).digest('hex')
54 expect(hash).toBe(file.replace('.js', ''))
55 }
56 })
57
58 it('rewrites managed imports to content-addressed specifiers', () => {
59 for (const file of cosChunks()) {
60 const code = readFileSync(join(assetsDir, file), 'utf8')
61 const specifiers = [...code.matchAll(/(?:from|import)\s*["']([^"']+)["']/g)].map(m => m[1]!)
62 for (const specifier of specifiers) {
63 expect(specifier).toMatch(/^cos1:[a-f0-9]{64}$/)
64 }
65 }
66 })
67
68 it('injects the loader into index.html and removes the default entry script', () => {
69 const html = readFileSync(join(outDir, 'index.html'), 'utf8')
70 expect(html).toContain('<script id="cos-loader">')
71 expect(html).toMatch(/cos1:[a-f0-9]{64}/)
72 expect(html).not.toMatch(/<script type="module"[^>]*src="[^"]*\.js"/)
73 })
74
75 it('derives the base path from the vite config', () => {
76 const html = readFileSync(join(outDir, 'index.html'), 'utf8')
77 expect(html).toMatch(/"base":"\/assets\/"/)
78 })
79})