-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathvite.config.ts
More file actions
163 lines (146 loc) · 5.69 KB
/
Copy pathvite.config.ts
File metadata and controls
163 lines (146 loc) · 5.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import { cpSync, readFileSync, writeFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import tailwindcss from '@tailwindcss/vite'
import vue from '@vitejs/plugin-vue'
import { build as viteBuild, defineConfig, type PluginOption } from 'vite'
import { buildManifest, type ExtensionTarget } from './manifest.config'
const __dirname = dirname(fileURLToPath(import.meta.url))
const srcDir = resolve(__dirname, 'src')
/** Require an explicit target so a bare build cannot emit the wrong browser bundle. */
function extensionTarget(): ExtensionTarget {
const requested = process.env.EXTENSION_TARGET
if (requested !== 'chrome' && requested !== 'firefox') {
throw new Error(
`EXTENSION_TARGET must be "chrome" or "firefox", got ${requested ? `"${requested}"` : 'nothing'}. ` +
'Run "pnpm build:chrome" or "pnpm build:firefox".',
)
}
return requested
}
const target = extensionTarget()
// Both targets build from the same sources, so each one gets its own directory named after it.
const distDir = resolve(__dirname, `dist-${target}`)
// Static directories that ship as-is; Vite copies them into the build untouched.
const STATIC_ASSETS = ['icons']
// Scripts the manifest loads by a fixed name, so they stay unhashed at the build root.
const CONTENT_SCRIPTS = ['content-script', 'page-world']
/**
* Copy the icons into the build directory and write the target's manifest once Vite finishes.
*/
function emitStaticAssets(): PluginOption {
return {
name: 'inertia-devtools-static-assets',
apply: 'build',
closeBundle() {
for (const asset of STATIC_ASSETS) {
cpSync(resolve(__dirname, asset), resolve(distDir, asset), { recursive: true })
}
writeFileSync(resolve(distDir, 'manifest.json'), `${JSON.stringify(buildManifest(target), null, 2)}\n`)
},
}
}
/** Bundle one classic script for Chrome's worker and Firefox's event page. */
function buildServiceWorker(mode: string): PluginOption {
return {
name: 'inertia-devtools-service-worker',
apply: 'build',
async closeBundle() {
await viteBuild({
configFile: false,
root: srcDir,
publicDir: false,
mode,
build: {
outDir: distDir,
emptyOutDir: false,
minify: mode === 'production',
sourcemap: mode !== 'production',
lib: {
entry: resolve(srcDir, 'background.ts'),
formats: ['es'],
fileName: () => 'background.js',
},
// Disable code splitting so a stray import() can't split off a chunk.
rollupOptions: { output: { codeSplitting: false } },
},
})
// The worker must stay self-contained, so fail the build if an import slipped back in.
const code = readFileSync(resolve(distDir, 'background.js'), 'utf8')
if (/\bimport\s*[({]|\bimport\s+['"]|\bfrom\s*['"]|\bexport[\s{]/.test(code)) {
throw new Error(
'Background script (background.js) is not self-contained: it contains import/export syntax. ' +
'The Chrome MV3 worker and the Firefox event page both need a single dependency-free ' +
'classic file.',
)
}
},
}
}
/** Bundle content scripts as IIFEs to avoid collisions with page-level bindings. */
function buildContentScripts(mode: string): PluginOption {
return {
name: 'inertia-devtools-content-scripts',
apply: 'build',
async closeBundle() {
for (const entry of CONTENT_SCRIPTS) {
await viteBuild({
configFile: false,
root: srcDir,
publicDir: false,
mode,
build: {
outDir: distDir,
emptyOutDir: false,
minify: mode === 'production',
sourcemap: mode !== 'production',
lib: {
entry: resolve(srcDir, `${entry}.ts`),
formats: ['iife'],
name: 'inertiaDevtools',
fileName: () => `${entry}.js`,
},
// Disable code splitting: a content script cannot follow an ES module chunk import.
rollupOptions: { output: { codeSplitting: false } },
},
})
// Guard the property that actually matters, since nothing else in the build enforces it.
const code = readFileSync(resolve(distDir, `${entry}.js`), 'utf8').trim()
if (!code.startsWith('(')) {
throw new Error(
`Content script (${entry}.js) does not open as an IIFE, so its top-level declarations ` +
'leak into the world it runs in. It must stay wrapped.',
)
}
}
},
}
}
export default defineConfig(({ mode }) => ({
root: srcDir,
publicDir: false,
plugins: [vue(), tailwindcss(), buildServiceWorker(mode), buildContentScripts(mode), emitStaticAssets()],
build: {
outDir: distDir,
emptyOutDir: true,
sourcemap: mode !== 'production',
minify: mode === 'production',
// Extension pages run in an evergreen browser, so the module-preload polyfill is dead weight.
modulePreload: false,
rollupOptions: {
input: {
devtools: resolve(srcDir, 'devtools.html'),
panel: resolve(srcDir, 'panel/panel.html'),
popup: resolve(srcDir, 'popup/popup.html'),
},
output: {
// Keep constants and guards in one stable chunk shared by the extension pages. The
// content scripts import nothing and the worker is built separately, so neither uses it.
manualChunks: (id) => (/\/src\/(constants|guards)\.ts$/.test(id) ? 'shared' : undefined),
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name][extname]',
},
},
},
}))