Skip to content

Commit

Permalink
🩹 Dev server: fix WP dependency handling (#3207)
Browse files Browse the repository at this point in the history
* Revert "🎨 Simplify WordPress dependency handling" (#3202)
* 🩹 Fix dev server error
  • Loading branch information
retlehs authored Jan 20, 2025
1 parent 2ff215a commit 37e3773
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 45 deletions.
182 changes: 140 additions & 42 deletions resources/js/build/wordpress.js
Original file line number Diff line number Diff line change
@@ -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
},
}
}

Expand All @@ -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,
Expand Down
12 changes: 9 additions & 3 deletions vite.config.js
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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,
Expand Down

0 comments on commit 37e3773

Please sign in to comment.