From 37e3773d88cf08d9c9155d29a30c8895911855ab Mon Sep 17 00:00:00 2001 From: Ben Word Date: Mon, 20 Jan 2025 15:57:09 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=A9=B9=20Dev=20server:=20fix=20WP=20depen?= =?UTF-8?q?dency=20handling=20(#3207)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "🎨 Simplify WordPress dependency handling" (#3202) * 🩹 Fix dev server error --- resources/js/build/wordpress.js | 182 ++++++++++++++++++++++++-------- vite.config.js | 12 ++- 2 files changed, 149 insertions(+), 45 deletions(-) diff --git a/resources/js/build/wordpress.js b/resources/js/build/wordpress.js index 00b82d37bb..bac9f05ae5 100644 --- a/resources/js/build/wordpress.js +++ b/resources/js/build/wordpress.js @@ -1,61 +1,159 @@ import resolveConfig from 'tailwindcss/resolveConfig' -import { defaultRequestToExternal, defaultRequestToHandle } from '@wordpress/dependency-extraction-webpack-plugin/lib/util' +import { + defaultRequestToExternal, + defaultRequestToHandle, +} from '@wordpress/dependency-extraction-webpack-plugin/lib/util' import fs from 'fs' import path from 'path' /** - * WordPress dependency extraction for Vite. + * Vite plugin that handles WordPress dependencies and generates a dependency manifest. * - * This plugin configures Vite to exclude WordPress packages from your bundle - * and instead load them from the `window.wp` global. It also generates a - * dependency file that is read by Sage's setup.php to enqueue required - * WordPress scripts. + * This plugin: + * 1. Transforms @wordpress/* imports into global wp.* references + * 2. Tracks WordPress script dependencies + * 3. Generates an editor.deps.json file listing all WordPress dependencies * - * @returns {import('vite').Plugin} + * @returns {import('vite').Plugin} Vite plugin */ +export function wordpressPlugin() { + const dependencies = new Set() + + // Helper functions for import handling + function extractNamedImports(imports) { + const match = imports.match(/{([^}]+)}/) + if (!match) return [] + return match[1].split(',').map((s) => s.trim()) + } + + function handleNamedReplacement(namedImports, external) { + return namedImports + .map((imports) => { + const [name, alias = name] = imports + .split(' as ') + .map((script) => script.trim()) + return `const ${alias} = ${external.join('.')}.${name};` + }) + .join('\n') + } -const externalMap = new Map() + function handleReplacements(imports, external) { + const importStr = Array.isArray(imports) ? imports[0] : imports + + if (importStr.includes('{')) { + const namedImports = extractNamedImports(importStr) + return handleNamedReplacement(namedImports, external) + } + + if (importStr.includes('* as')) { + const match = importStr.match(/\*\s+as\s+(\w+)/) + if (!match) return '' + const alias = match[1] + return `const ${alias} = ${external.join('.')};` + } + + const name = importStr.trim() + return `const ${name} = ${external.join('.')};` + } -export function extractWordPressDependencies() { return { - name: 'wordpress-dependencies', - config() { + name: 'wordpress-plugin', + enforce: 'pre', + config(config) { return { - build: { - rollupOptions: { - external(id) { - const result = defaultRequestToExternal(id) - if (result) { - externalMap.set(id, result) - return true - } - return false - }, - output: { - globals(id) { - const global = externalMap.get(id) - return Array.isArray(global) ? global.join('.') : global - } - } + ...config, + resolve: { + ...config.resolve, + alias: { + ...config.resolve?.alias, + } + }, + } + }, + resolveId(id) { + if (id.startsWith('@wordpress/')) { + const pkg = id.replace('@wordpress/', '') + const external = defaultRequestToExternal(id) + const handle = defaultRequestToHandle(id) + + if (external && handle) { + dependencies.add(handle) + return { + id, + external: true, } } } }, - generateBundle(options, bundle) { - const deps = Object.values(bundle) - .filter(chunk => chunk.type === 'chunk' && chunk.isEntry) - .flatMap(chunk => chunk.imports) - .map(defaultRequestToHandle) - .filter(Boolean) - - if (deps.length) { - this.emitFile({ - type: 'asset', - fileName: 'editor.deps.json', - source: JSON.stringify(deps, null, 2) - }) + transform(code, id) { + if (!id.endsWith('.js')) return + + const imports = [ + ...(code.match(/^import .+ from ['"]@wordpress\/[^'"]+['"]/gm) || []), + ...(code.match(/^import ['"]@wordpress\/[^'"]+['"]/gm) || []), + ] + + imports.forEach((statement) => { + const match = + statement.match(/^import (.+) from ['"]@wordpress\/([^'"]+)['"]/) || + statement.match(/^import ['"]@wordpress\/([^'"]+)['"]/) + + if (!match) return + + const [, imports, pkg] = match + if (!pkg) return + + const external = defaultRequestToExternal(`@wordpress/${pkg}`) + const handle = defaultRequestToHandle(`@wordpress/${pkg}`) + + if (external && handle) { + dependencies.add(handle) + const replacement = imports + ? handleReplacements(imports, external) + : `const ${pkg.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())} = ${external.join('.')};` + + code = code.replace(statement, replacement) + } + }) + + return { code, map: null } + }, + generateBundle() { + this.emitFile({ + type: 'asset', + fileName: 'editor.deps.json', + source: JSON.stringify([...dependencies]), + }) + }, + } +} + +/** + * Rollup plugin that configures external WordPress dependencies. + * + * This plugin: + * 1. Marks all @wordpress/* packages as external dependencies + * 2. Maps external @wordpress/* imports to wp.* global variables + * + * This prevents WordPress core libraries from being bundled and ensures + * they are loaded from WordPress's global scope instead. + * + * @returns {import('rollup').Plugin} Rollup plugin + */ +export function wordpressRollupPlugin() { + return { + name: 'wordpress-rollup-plugin', + options(opts) { + opts.external = (id) => id.startsWith('@wordpress/') + opts.output = opts.output || {} + opts.output.globals = (id) => { + if (id.startsWith('@wordpress/')) { + const packageName = id.replace('@wordpress/', '') + return `wp.${packageName.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())}` + } } - } + return opts + }, } } @@ -74,7 +172,7 @@ export function extractWordPressDependencies() { * @param {boolean} [options.disableTailwindFontSizes=false] - Disable including Tailwind font sizes in theme.json * @returns {import('vite').Plugin} Vite plugin */ -export function processThemeJson({ +export function wordpressThemeJson({ tailwindConfig, disableTailwindColors = false, disableTailwindFonts = false, diff --git a/vite.config.js b/vite.config.js index d084780341..cc8fcd6e32 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,6 +1,10 @@ import { defineConfig } from 'vite' import laravel from 'laravel-vite-plugin' -import { extractWordPressDependencies, processThemeJson } from './resources/js/build/wordpress' +import { + wordpressPlugin, + wordpressRollupPlugin, + wordpressThemeJson, +} from './resources/js/build/wordpress' import tailwindConfig from './tailwind.config.js' export default defineConfig({ @@ -15,11 +19,13 @@ export default defineConfig({ ], refresh: true, }), - extractWordPressDependencies(), + + wordpressPlugin(), + wordpressRollupPlugin(), // Generate the theme.json file in the public/build/assets directory // based on the Tailwind config and the theme.json file from base theme folder - processThemeJson({ + wordpressThemeJson({ tailwindConfig, disableTailwindColors: false, disableTailwindFonts: false,