import { defineConfig, build as viteBuild, type UserConfig } from 'vite'; import { resolve } from 'path'; import { readFileSync, writeFileSync, mkdirSync, cpSync, existsSync, rmSync } from 'fs'; /** * Vite build config for the Gogh Theme Engine Chrome Extension. * * Chrome MV3 content scripts CANNOT use ES module imports. * Service workers CAN if the manifest specifies "type": "module". * * Strategy: * • content.ts → IIFE bundle (no imports, self-contained) * • background.ts → ESM (manifest has "type": "module") * • options/ → Standard Vite HTML entry * * We solve the code-splitting problem by using a custom plugin that * rebuilds content.ts as a separate IIFE after the main build. */ function chromeExtensionPlugin() { return { name: 'chrome-extension-iife', async closeBundle() { // Rebuild content.ts as a standalone IIFE bundle console.log('[chrome-ext] Rebuilding content.js as IIFE...'); await viteBuild({ configFile: false, build: { outDir: resolve(__dirname, 'dist'), emptyOutDir: false, // Don't wipe the rest of the build lib: { entry: resolve(__dirname, 'src/content.ts'), name: 'GoghContent', formats: ['iife'], fileName: () => 'content.js', }, rollupOptions: { output: { // Ensure it overwrites the ESM version inlineDynamicImports: true, }, }, minify: false, sourcemap: false, target: 'chrome120', }, logLevel: 'warn', }); // Fix options HTML path const wrongPath = resolve(__dirname, 'dist/src/options/options.html'); const correctDir = resolve(__dirname, 'dist/options'); const correctPath = resolve(correctDir, 'options.html'); if (existsSync(wrongPath) && !existsSync(correctPath)) { mkdirSync(correctDir, { recursive: true }); const html = readFileSync(wrongPath, 'utf-8'); // Fix script paths — point to the options.js at dist root const fixedHtml = html .replace(/src="[^"]*options\.js"/g, 'src="../options.js"') .replace(/src="\.\.\/\.\.\/options\.js"/g, 'src="../options.js"'); writeFileSync(correctPath, fixedHtml); } // Clean up the misplaced src/ directory in dist const distSrc = resolve(__dirname, 'dist/src'); if (existsSync(distSrc)) { rmSync(distSrc, { recursive: true, force: true }); } // Clean up the chunks directory since content.js is now self-contained // and background.js still needs its chunk import console.log('[chrome-ext] Build complete.'); }, }; } export default defineConfig(({ mode }) => { const isProd = mode === 'production'; return { plugins: [chromeExtensionPlugin()], build: { outDir: 'dist', emptyOutDir: true, rollupOptions: { input: { background: resolve(__dirname, 'src/background.ts'), content: resolve(__dirname, 'src/content.ts'), options: resolve(__dirname, 'src/options/options.html'), }, output: { entryFileNames: '[name].js', chunkFileNames: 'chunks/[name].js', assetFileNames: 'assets/[name].[ext]', }, }, minify: isProd ? 'esbuild' : false, sourcemap: !isProd ? 'inline' : false, target: 'chrome120', }, resolve: { alias: { '@': resolve(__dirname, 'src'), }, }, }; });