import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import path from 'path'; const ADMIN_BUNDLE_BANNER = '/*! LLMagnet AI SEO Optimizer — compiled admin bundle (Vite). Human-readable TypeScript/React source ships in this plugin under src/. Rebuild: npm ci && npm run build. See readme.txt → Source code. */'; /** * Dedicated build pass for the Gutenberg editor entry (adoption plan F2.2, * Phase E Lane E1). Run AFTER the main build: * * vite build && vite build --config vite.editor.config.ts * * Why a separate config instead of another input in vite.config.ts: * the editor bundle must reuse the editor's own React (window.React / * window.ReactDOM / window.ReactJSXRuntime, provided by the `react`, * `react-dom` and `react-jsx-runtime` core script handles) instead of * bundling a duplicate copy. Rollup `external`/aliases are build-global — * they cannot be scoped to one entry — and aliasing react to the window * shims in the main config would break every other entry (the plugin's * admin pages bundle their own React and don't load wp deps). A second * tiny build pass is the only way to externalize React per-entry. * * Output lands next to the main build (emptyOutDir: false): js/editor.js. * The @wordpress/* aliases mirror vite.config.ts (Phase 0.5 shims). */ export default defineConfig(({ mode }) => ({ base: './', plugins: [ react(), { name: 'banner-css-chunks', generateBundle(_options, bundle) { const banner = `${ADMIN_BUNDLE_BANNER}\n`; for (const file of Object.values(bundle)) { if (file.type === 'chunk' && typeof file.code === 'string') { const without = file.code.split(ADMIN_BUNDLE_BANNER).join(''); file.code = banner + without.replace(/^\n+/, ''); } if ( file.type === 'asset' && file.fileName.endsWith('.css') && typeof file.source === 'string' && !file.source.startsWith(ADMIN_BUNDLE_BANNER) ) { file.source = banner + file.source; } } }, }, ], resolve: { alias: [ // WordPress-bundled React — keep the editor bundle duplicate-free. // Exact-match regexes so `react-dom/client` / `react/jsx-runtime` // don't get mangled by the bare `react` / `react-dom` entries. { find: /^react$/, replacement: path.resolve(__dirname, './src/wp-externals/react.ts') }, { find: /^react\/jsx-runtime$/, replacement: path.resolve(__dirname, './src/wp-externals/react-jsx-runtime.ts') }, { find: /^react\/jsx-dev-runtime$/, replacement: path.resolve(__dirname, './src/wp-externals/react-jsx-runtime.ts') }, { find: /^react-dom$/, replacement: path.resolve(__dirname, './src/wp-externals/react-dom.ts') }, { find: /^react-dom\/client$/, replacement: path.resolve(__dirname, './src/wp-externals/react-dom-client.ts') }, // @wordpress/* → window.wp.* shims (same set as vite.config.ts). { find: '@wordpress/plugins', replacement: path.resolve(__dirname, './src/wp-externals/plugins.ts') }, { find: '@wordpress/data', replacement: path.resolve(__dirname, './src/wp-externals/data.ts') }, { find: '@wordpress/components', replacement: path.resolve(__dirname, './src/wp-externals/components.ts') }, { find: '@wordpress/edit-post', replacement: path.resolve(__dirname, './src/wp-externals/edit-post.ts') }, { find: '@wordpress/editor', replacement: path.resolve(__dirname, './src/wp-externals/editor.ts') }, { find: '@wordpress/core-data', replacement: path.resolve(__dirname, './src/wp-externals/core-data.ts') }, { find: '@wordpress/element', replacement: path.resolve(__dirname, './src/wp-externals/element.ts') }, { find: '@wordpress/i18n', replacement: path.resolve(__dirname, './src/wp-externals/i18n.ts') }, { find: '@wordpress/api-fetch', replacement: path.resolve(__dirname, './src/wp-externals/api-fetch.ts') }, { find: '@', replacement: path.resolve(__dirname, './src') }, ], }, build: { outDir: 'assets/react-build', // The main vite.config.ts build owns (and empties) the output dir; // this pass only adds js/editor.js next to it. emptyOutDir: false, sourcemap: mode === 'development', rollupOptions: { input: { editor: path.resolve(__dirname, 'src/editor-main.tsx'), }, output: { banner: `${ADMIN_BUNDLE_BANNER}\n`, entryFileNames: 'js/[name].js', chunkFileNames: 'js/[name]-[hash].js', assetFileNames: ({ name }) => { if (/\.(css)$/.test(name ?? '')) { return 'css/[name][extname]'; } return 'assets/[name]-[hash][extname]'; }, }, }, }, }));