import { cp, mkdir, readdir, readFile, rm, stat, writeFile, } from "node:fs/promises" import path from "node:path" import { fileURLToPath } from "node:url" import { defineConfig, type Plugin } from "vite" // Build du thème : un bundle IIFE unique enregistrant tous les blocs côté éditeur // Gutenberg (en EXTERNALISANT @wordpress/* + React, fournis par WordPress), et la // copie de chaque block.json (sans editorScript/Style) + render.php vers build/blocks. // Le thème PARENT wp-reactor-base découvre build/blocks et enqueue build/editor. const themeRoot = fileURLToPath(new URL(".", import.meta.url)) const srcRoot = path.join(themeRoot, "src") const blocksRoot = path.join(srcRoot, "blocks") const buildRoot = path.join(themeRoot, "build") const editorOutDir = path.join(buildRoot, "editor") const builtBlocksRoot = path.join(buildRoot, "blocks") const editorEntry = path.join(srcRoot, "index.tsx") const wordpressEditorGlobals: Record = { "@wordpress/api-fetch": "wp.apiFetch", "@wordpress/block-editor": "wp.blockEditor", "@wordpress/blocks": "wp.blocks", "@wordpress/components": "wp.components", "@wordpress/element": "wp.element", "@wordpress/rich-text": "wp.richText", react: "React", "react/jsx-runtime": "ReactJSXRuntime", "react-dom": "ReactDOM", "react-dom/client": "ReactDOM", } async function pathExists(p: string) { try { await stat(p) return true } catch { return false } } async function getBlockDirectories() { if (!(await pathExists(blocksRoot))) return [] const entries = await readdir(blocksRoot, { withFileTypes: true }) const dirs: string[] = [] for (const entry of entries) { if (!entry.isDirectory()) continue if (await pathExists(path.join(blocksRoot, entry.name, "block.json"))) { dirs.push(entry.name) } } return dirs.sort() } /** Copie block.json (sans editorScript/Style) + render.php vers build/blocks. */ async function copyBlockArtifacts(blockName: string) { const sourceDir = path.join(blocksRoot, blockName) const outputDir = path.join(builtBlocksRoot, blockName) const meta = JSON.parse( await readFile(path.join(sourceDir, "block.json"), "utf8"), ) as Record delete meta.editorScript delete meta.editorStyle await mkdir(outputDir, { recursive: true }) await writeFile( path.join(outputDir, "block.json"), `${JSON.stringify(meta, null, "\t")}\n`, "utf8", ) const renderPath = path.join(sourceDir, "render.php") if (await pathExists(renderPath)) { await cp(renderPath, path.join(outputDir, "render.php")) } } function themeBuildPlugin(): Plugin { return { name: "wp-reactor-theme-build", apply: "build", async buildStart() { await rm(buildRoot, { recursive: true, force: true }) }, generateBundle(_, bundle) { const entryChunk = Object.values(bundle).find( (chunk) => chunk.type === "chunk" && chunk.isEntry, ) if (!entryChunk || entryChunk.type !== "chunk") { this.error("Unable to find the built editor entry chunk.") } const cssAssets = entryChunk.viteMetadata?.importedCss ? Array.from(entryChunk.viteMetadata.importedCss) : [] this.emitFile({ type: "asset", fileName: ".vite/manifest.json", source: `${JSON.stringify( { "src/index.tsx": { file: entryChunk.fileName, name: entryChunk.name, src: "src/index.tsx", isEntry: true, css: cssAssets, }, }, null, 2, )}\n`, }) }, async closeBundle() { await mkdir(builtBlocksRoot, { recursive: true }) for (const blockName of await getBlockDirectories()) { await copyBlockArtifacts(blockName) } }, } } export default defineConfig({ root: themeRoot, // Le build en mode lib/IIFE ne remplace pas `process.env.NODE_ENV` (contrairement // au mode app). TipTap/ProseMirror le lisent au runtime → `process is not defined`. define: { "process.env.NODE_ENV": JSON.stringify("production"), }, build: { outDir: editorOutDir, emptyOutDir: false, cssCodeSplit: false, lib: { entry: editorEntry, name: "WpReactorThemeEditor", formats: ["iife"], }, rollupOptions: { external: Object.keys(wordpressEditorGlobals), output: { entryFileNames: "assets/[name]-[hash].js", assetFileNames: "assets/[name]-[hash][extname]", globals: wordpressEditorGlobals, // Certaines deps CJS (ex. use-sync-external-store via @tiptap/react) // gardent un `require("react")` que Rollup ne mappe pas sur le global // en sortie IIFE. Ce shim, scopé dans l'IIFE, le résout sans polluer // le scope global (sinon : `require is not defined`). intro: 'function require(id){if(id==="react")return window.React;if(id==="react-dom")return window.ReactDOM;throw new Error("Unexpected CJS require in editor bundle: "+id);}', }, }, }, plugins: [themeBuildPlugin()], })