-
Notifications
You must be signed in to change notification settings - Fork 22
Description
Note
This issue proposes adopting patterns from WordPress core's evolving build tooling. Sections marked with 🔮 are proposals for 10up-toolkit, not existing features.
Summary
WordPress core is evolving its build tooling from @wordpress/scripts (Webpack-based) to the new @wordpress/build package (esbuild-based). As part of this transition, they are deprecating the DependencyExtractionWebpackPlugin in favor of a more flexible, package.json metadata-driven externalization system.
This new approach offers significant advantages that 10up-toolkit should consider adopting, particularly the ability for individual packages to define their own externalization behavior through declarative configuration.
Background
The Current Approach (DependencyExtractionWebpackPlugin)
Currently, @wordpress/scripts and 10up-toolkit rely on @wordpress/dependency-extraction-webpack-plugin to:
- Automatically externalize
@wordpress/*packages towp.*globals - Generate
.asset.phpfiles with dependency arrays - Handle vendor externalization (React, lodash, jQuery, etc.)
While functional, this approach has limitations:
- Tightly coupled to Webpack
- Hardcoded externalization rules are inflexible
- Third-party packages cannot easily declare their own externalization behavior
- No straightforward way for plugins to consume other plugins' packages as externals
The New Approach (@wordpress/build)
WordPress core's new @wordpress/build package introduces a declarative, metadata-driven externalization system using package.json fields.
Package-Level Metadata
Individual packages can declare their build behavior via package.json:
{
"name": "@wordpress/data",
"wpScript": true,
"wpScriptModuleExports": "./build-module/index.js"
}Key fields:
| Field | Purpose |
|---|---|
wpScript |
When true, package is bundled and exposed via configured global (e.g., window.wp.data) |
wpScriptModuleExports |
Defines entry points for ES module script exports (string or object) |
Root-Level Plugin Configuration
Projects configure externalization behavior in their root package.json via a wpPlugin object:
{
"wpPlugin": {
"scriptGlobal": "wp",
"packageNamespace": "wordpress",
"handlePrefix": "wp",
"externalNamespaces": {
"woo": {
"global": "woo",
"handlePrefix": "woocommerce"
}
}
}
}Configuration options:
| Option | Purpose |
|---|---|
scriptGlobal |
Global variable namespace (e.g., "wp", "myPlugin"). Set to false to disable |
packageNamespace |
Package scope to match for global exposure (without @ prefix) |
handlePrefix |
Prefix for WordPress script handles in .asset.php files |
externalNamespaces |
Third-party namespaces to consume as externals |
The Key Innovation: externalNamespaces
The externalNamespaces configuration is particularly powerful. It allows projects to declare that imports from specific namespaces should be externalized without bundling:
{
"wpPlugin": {
"externalNamespaces": {
"woo": {
"global": "woo",
"handlePrefix": "woocommerce"
}
}
}
}With this configuration:
import { Cart } from '@woo/cart'→ resolves towindow.woo.cart- The dependency
woocommerce-cartis tracked in.asset.php - No bundling of WooCommerce packages occurs
This enables a powerful ecosystem where:
- WooCommerce can expose
@woo/*packages viawindow.woo.* - Any plugin can expose its own packages via a configured global
- Consumers can add those namespaces to
externalNamespacesand use them as externals without bundling
🔮 Proposal for 10up-toolkit
1. Modernize WordPress Externals Handling
The current approach relies on DependencyExtractionWebpackPlugin which is tightly coupled to Webpack and will be deprecated. We should update how 10up-toolkit handles WordPress externals to align with core's new direction:
- Read the
wpScriptmetadata from@wordpress/*packages to determine if they're bundled in core - Only externalize packages that are actually available as WordPress scripts
- Generate accurate
.asset.phpdependency arrays
2. Implement externalNamespaces Configuration
Adopt the externalNamespaces pattern to give projects flexibility in defining their own externalization rules:
// 10up-toolkit.config.js
module.exports = {
externalNamespaces: {
// WordPress packages (built-in default)
wordpress: {
global: 'wp',
handlePrefix: 'wp'
},
// Add WooCommerce support
woo: {
global: 'woo',
handlePrefix: 'woocommerce'
},
// Add support for any other ecosystem
acme: {
global: 'acme',
handlePrefix: 'acme-plugin'
}
}
};This allows projects to:
import { Cart } from '@woo/cart'→ resolves towindow.woo.cartwithwoocommerce-cartas a dependencyimport { Button } from '@acme/ui'→ resolves towindow.acme.uiwithacme-plugin-uias a dependency- Easily add/remove external namespaces without modifying toolkit internals
3. Pluggable Externalization Handlers (Optional Enhancement)
For even more flexibility, allow registering custom handler functions:
// 10up-toolkit.config.js
module.exports = {
externalHandlers: [
// Custom handler with full control
{
namespace: 'mycompany',
global: 'mycompany',
handlePrefix: 'mycompany',
// Optional: custom logic for edge cases
shouldExternalize: (packageName) => {
// Don't externalize internal-only packages
if (packageName.includes('/internal/')) return false;
return packageName.startsWith('@mycompany/');
}
}
]
};🔮 Benefits of Adoption
- Future-proofing: Aligns with WordPress core's direction as they deprecate the Webpack plugin
- Flexibility: Projects can easily add/remove externalization targets via configuration
- Ecosystem interop: Seamlessly consume packages from WooCommerce, other plugins, or internal libraries
- Build tool agnostic: The configuration approach works regardless of underlying bundler (Webpack, esbuild, etc.)
- Accurate dependencies: Reading package metadata ensures we only externalize what's actually available in WordPress
Tradeoffs
Package Installation Requirements
A key difference with this approach is that every @wordpress/* package you import must be installed in your project in order to read its metadata and determine externalization behavior.
Downsides:
- Slower installs: More packages to download and resolve, especially on fresh installs or CI
- NPM dependency headaches: More packages means more potential for version conflicts, peer dependency warnings, and resolution issues
- Larger
node_modules: Even though the packages aren't bundled, they still need to be present locally - Version management: Need to keep WordPress package versions in sync with the WordPress version you're targeting
Upsides:
- TypeScript support: Having packages installed means full type definitions are available out of the box
- Better IDE integration: Autocomplete, go-to-definition, and inline documentation work correctly
- Explicit dependencies: Your
package.jsonaccurately reflects what your code depends on - Version awareness: You can intentionally pin to specific versions rather than relying on whatever WordPress ships
This is a philosophical shift from "trust that WordPress has these globals" to "explicitly declare and type-check your dependencies." The tradeoff is worth considering based on project needs.
Questions to Consider
- Should
externalNamespacesbe configured via10up-toolkit.config.js,package.json, or both? - What should the default configuration include? (WordPress only, or also common ecosystems like WooCommerce?)
- How do we handle the transition period while
DependencyExtractionWebpackPluginis still supported but deprecated? - Should handlers be able to read package metadata (like
wpScript) to make smarter externalization decisions?