import { defineConfig, normalizePath, type PluginOption } from "vite"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; import externalGlobals from "rollup-plugin-external-globals"; import { resolve, dirname, relative } from "path"; import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const normalizedRoot = normalizePath(__dirname); const createWordPressPhpFullReloadPlugin = (): PluginOption => ({ name: "b3-wvs-wordpress-php-full-reload", apply: "serve", configureServer(server) { const phpWatchGlobs = [ normalizePath(resolve(__dirname, "*.php")), normalizePath(resolve(__dirname, "includes/**/*.php")), ]; const ignoredSegments = ["/vendor/", "/node_modules/", "/dist/", "/tmp/"]; const shouldReload = (file: string) => { const normalizedFile = normalizePath(file); return ( normalizedFile.startsWith(`${normalizedRoot}/`) && normalizedFile.endsWith(".php") && !ignoredSegments.some((segment) => normalizedFile.includes(segment)) ); }; const triggerReload = (file: string) => { if (!shouldReload(file)) { return; } server.ws.send({ type: "full-reload", path: "*", }); const changedPath = relative(__dirname, file) || file; server.config.logger.info( `[b3-wvs] WordPress change detected, reloading page: ${normalizePath(changedPath)}`, { clear: false }, ); }; server.watcher.add(phpWatchGlobs); server.watcher.on("add", triggerReload); server.watcher.on("change", triggerReload); server.watcher.on("unlink", triggerReload); }, }); export default defineConfig(({ mode }) => ({ // WordPress plugins are not served from site root, so chunk URLs must be relative. base: "./", plugins: [ tailwindcss(), react({ jsxRuntime: "classic" }), createWordPressPhpFullReloadPlugin(), ], define: { // Define __DEV__ for conditional debug logging __DEV__: mode === "development", }, build: { // Keep manifest at a non-hidden path so packaging/deployment processors // do not drop it (some strip dot-directories like .vite). manifest: "manifest.json", outDir: "dist", // Free builds are self-contained, so stale chunks must never survive. emptyOutDir: true, // Keep the established terser output stable across direct free/source/Pro builds. minify: "terser", terserOptions: { compress: { // Drop console and debugger in production (replaces esbuild.drop). drop_console: mode === "production", drop_debugger: mode === "production", }, }, rollupOptions: { // Externalize React — WordPress provides these via the wp-element script handle. external: ["react", "react-dom", "react-dom/client", "@wordpress/i18n"], input: { admin: "src/admin/main.tsx", frontend: "src/frontend/main.tsx", "admin-product-swatches": "src/legacy/admin-product-swatches.js", "admin-term-meta": "src/legacy/admin-term-meta.js", }, output: { entryFileNames: "assets/[name]-[hash].js", chunkFileNames: "assets/[name]-[hash].js", assetFileNames: "assets/[name]-[hash].[ext]", // Map externalized imports to WordPress-provided globals. globals: { react: "React", "react-dom": "ReactDOM", "react-dom/client": "ReactDOM", "@wordpress/i18n": "wp.i18n", }, }, plugins: [ // Rewrite bare imports to global variable lookups in the output bundle. externalGlobals({ react: "React", "react-dom": "ReactDOM", "react-dom/client": "ReactDOM", "@wordpress/i18n": "wp.i18n", }), ], }, target: "es2018", // Enable source map only in development sourcemap: false, // Reduce chunk size warnings threshold chunkSizeWarningLimit: 150, }, resolve: { alias: { "@": resolve(__dirname, "src"), }, }, }));