@wordpress/build 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,327 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { writeFile, mkdir } from 'fs/promises';
5
+ import path from 'path';
6
+ import { camelCase } from 'change-case';
7
+
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ import { getPackageInfo } from './package-utils.mjs';
12
+
13
+ /**
14
+ * Create WordPress externals plugin for esbuild.
15
+ * This plugin handles WordPress package externals and vendor libraries,
16
+ * treating them as external dependencies available via global variables.
17
+ *
18
+ * @param {string} packageNamespace Custom package namespace (e.g., 'wordpress', 'my-plugin').
19
+ * @param {string|false} scriptGlobal Global variable name (e.g., 'wp', 'myPlugin') or false to disable globals.
20
+ * @param {Object} externalNamespaces Additional namespaces to externalize (e.g., { 'woo': { global: 'woo', handlePrefix: 'woocommerce' } }).
21
+ * @param {string} handlePrefix Handle prefix for main package (e.g., 'wp', 'mp'). Defaults to packageNamespace.
22
+ * @return {Function} Function that creates the esbuild plugin instance.
23
+ */
24
+ export function createWordpressExternalsPlugin(
25
+ packageNamespace,
26
+ scriptGlobal,
27
+ externalNamespaces = {},
28
+ handlePrefix
29
+ ) {
30
+ /**
31
+ * WordPress externals plugin for esbuild.
32
+ *
33
+ * @param {string} assetName Base name for the asset file (e.g., 'index.min').
34
+ * @param {string} buildFormat Build format: 'iife' for classic scripts, 'esm' for modules.
35
+ * @param {Array<string>} extraDependencies Additional dependencies to include in the asset file.
36
+ * @return {Object} esbuild plugin object.
37
+ */
38
+ return function wordpressExternalsPlugin(
39
+ assetName = 'index.min',
40
+ buildFormat = 'iife',
41
+ extraDependencies = []
42
+ ) {
43
+ return {
44
+ name: 'wordpress-externals',
45
+ /** @param {import('esbuild').PluginBuild} build */
46
+ setup( build ) {
47
+ const dependencies = new Set();
48
+ const moduleDependencies = new Map();
49
+
50
+ /**
51
+ * Check if a package import is a script module.
52
+ * A package is considered a script module if it has wpScriptModuleExports
53
+ * and the specific import path (root or subpath) is declared in wpScriptModuleExports.
54
+ *
55
+ * @param {import('./package-utils.mjs').PackageJson} packageJson Package.json object.
56
+ * @param {string|null} subpath Subpath after package name, or null for root import.
57
+ * @return {boolean} True if the import is a script module.
58
+ */
59
+ function isScriptModuleImport( packageJson, subpath ) {
60
+ const { wpScriptModuleExports } = packageJson;
61
+
62
+ if ( ! wpScriptModuleExports ) {
63
+ return false;
64
+ }
65
+
66
+ // Root import: @wordpress/package-name
67
+ if ( ! subpath ) {
68
+ if ( typeof wpScriptModuleExports === 'string' ) {
69
+ return true;
70
+ }
71
+ if (
72
+ typeof wpScriptModuleExports === 'object' &&
73
+ wpScriptModuleExports[ '.' ]
74
+ ) {
75
+ return true;
76
+ }
77
+ return false;
78
+ }
79
+
80
+ // Subpath import: @wordpress/package-name/subpath
81
+ if (
82
+ typeof wpScriptModuleExports === 'object' &&
83
+ wpScriptModuleExports[ `./${ subpath }` ]
84
+ ) {
85
+ return true;
86
+ }
87
+
88
+ return false;
89
+ }
90
+
91
+ // Map of vendor packages to their global variables and handles
92
+ const vendorExternals = {
93
+ react: { global: 'React', handle: 'react' },
94
+ 'react-dom': { global: 'ReactDOM', handle: 'react-dom' },
95
+ 'react/jsx-runtime': {
96
+ global: 'ReactJSXRuntime',
97
+ handle: 'react-jsx-runtime',
98
+ },
99
+ 'react/jsx-dev-runtime': {
100
+ global: 'ReactJSXRuntime',
101
+ handle: 'react-jsx-runtime',
102
+ },
103
+ moment: { global: 'moment', handle: 'moment' },
104
+ lodash: { global: 'lodash', handle: 'lodash' },
105
+ 'lodash-es': { global: 'lodash', handle: 'lodash' },
106
+ jquery: { global: 'jQuery', handle: 'jquery' },
107
+ };
108
+
109
+ // Build list of package namespace configurations
110
+ const packageExternals = [
111
+ {
112
+ namespace: 'wordpress',
113
+ pattern: /^@wordpress\//,
114
+ globalName: 'wp',
115
+ handlePrefix: 'wp',
116
+ },
117
+ ];
118
+
119
+ // Add custom namespace if different from wordpress and scriptGlobal is not false
120
+ if ( packageNamespace && packageNamespace !== 'wordpress' && scriptGlobal !== false ) {
121
+ packageExternals.push( {
122
+ namespace: packageNamespace,
123
+ pattern: new RegExp( `^@${ packageNamespace }/` ),
124
+ globalName: scriptGlobal,
125
+ handlePrefix: handlePrefix || packageNamespace,
126
+ } );
127
+ }
128
+
129
+ // Add additional external namespaces from configuration
130
+ for ( const [ namespace, config ] of Object.entries( externalNamespaces ) ) {
131
+ packageExternals.push( {
132
+ namespace,
133
+ pattern: new RegExp( `^@${ namespace }/` ),
134
+ globalName: config.global,
135
+ handlePrefix: config.handlePrefix || namespace,
136
+ } );
137
+ }
138
+
139
+ for ( const [ packageName, config ] of Object.entries(
140
+ vendorExternals
141
+ ) ) {
142
+ build.onResolve(
143
+ {
144
+ filter: new RegExp( `^${ packageName }$` ),
145
+ },
146
+ /** @param {import('esbuild').OnResolveArgs} args */
147
+ ( args ) => {
148
+ dependencies.add( config.handle );
149
+
150
+ return {
151
+ path: args.path,
152
+ namespace: 'vendor-external',
153
+ pluginData: { global: config.global },
154
+ };
155
+ }
156
+ );
157
+ }
158
+
159
+ // Handle package namespace externals (wordpress and custom)
160
+ for ( const externalConfig of packageExternals ) {
161
+ build.onResolve(
162
+ { filter: externalConfig.pattern },
163
+ /** @param {import('esbuild').OnResolveArgs} args */
164
+ ( args ) => {
165
+ // Extract package name and subpath from import
166
+ // e.g., '@wordpress/blocks/sub/path' → packageName='@wordpress/blocks', subpath='sub/path'
167
+ const parts = args.path.split( '/' );
168
+ let packageName = args.path;
169
+ let subpath = null;
170
+ if ( parts.length > 2 ) {
171
+ packageName = parts.slice( 0, 2 ).join( '/' );
172
+ subpath = parts.slice( 2 ).join( '/' );
173
+ }
174
+ const shortName = parts[ 1 ];
175
+ const handle = `${ externalConfig.handlePrefix }-${ shortName }`;
176
+
177
+ const packageJson = getPackageInfo( packageName );
178
+
179
+ if ( ! packageJson ) {
180
+ return undefined;
181
+ }
182
+
183
+ let isScriptModule = isScriptModuleImport(
184
+ packageJson,
185
+ subpath
186
+ );
187
+ let isScript = !! packageJson.wpScript;
188
+ if ( isScriptModule && isScript ) {
189
+ // If the package is both a script and a script module, rely on the format being built
190
+ isScript = buildFormat === 'iife';
191
+ isScriptModule = buildFormat === 'esm';
192
+ }
193
+
194
+ const kind =
195
+ args.kind === 'dynamic-import'
196
+ ? 'dynamic'
197
+ : 'static';
198
+
199
+ if ( isScriptModule ) {
200
+ if ( kind === 'static' ) {
201
+ moduleDependencies.set( args.path, 'static' );
202
+ } else if (
203
+ ! moduleDependencies.has( args.path )
204
+ ) {
205
+ moduleDependencies.set( args.path, 'dynamic' );
206
+ }
207
+
208
+ return {
209
+ path: args.path,
210
+ external: true,
211
+ };
212
+ }
213
+
214
+ if ( isScript ) {
215
+ dependencies.add( handle );
216
+
217
+ return {
218
+ path: args.path,
219
+ namespace: 'package-external',
220
+ pluginData: { globalName: externalConfig.globalName },
221
+ };
222
+ }
223
+
224
+ return undefined;
225
+ }
226
+ );
227
+ }
228
+
229
+ build.onLoad(
230
+ { filter: /.*/, namespace: 'vendor-external' },
231
+ /** @param {import('esbuild').OnLoadArgs} args */
232
+ ( args ) => {
233
+ const global = args.pluginData.global;
234
+
235
+ return {
236
+ contents: `module.exports = window.${ global };`,
237
+ loader: 'js',
238
+ };
239
+ }
240
+ );
241
+
242
+ build.onLoad(
243
+ { filter: /.*/, namespace: 'package-external' },
244
+ /** @param {import('esbuild').OnLoadArgs} args */
245
+ ( args ) => {
246
+ const globalName = args.pluginData.globalName;
247
+ // Extract package name after '@namespace/' prefix
248
+ // e.g., '@wordpress/blocks' or '@my-plugin/data'
249
+ const packagePath = args.path.split( '/' ).slice( 1 ).join( '/' );
250
+ const camelCasedName = camelCase( packagePath );
251
+
252
+ return {
253
+ contents: `module.exports = window.${ globalName }.${ camelCasedName };`,
254
+ loader: 'js',
255
+ };
256
+ }
257
+ );
258
+
259
+ build.onEnd(
260
+ /** @param {import('esbuild').BuildResult} result */
261
+ async ( result ) => {
262
+ if ( result.errors.length > 0 ) {
263
+ return;
264
+ }
265
+
266
+ // Merge discovered dependencies with extra dependencies
267
+ const allDependencies = new Set( [
268
+ ...dependencies,
269
+ ...extraDependencies,
270
+ ] );
271
+
272
+ const dependenciesString = Array.from( allDependencies )
273
+ .sort()
274
+ .map( ( dep ) => `'${ dep }'` )
275
+ .join( ', ' );
276
+
277
+ // Format module dependencies as array of arrays with 'id' and 'import' keys
278
+ const moduleDependenciesArray = Array.from(
279
+ moduleDependencies.entries()
280
+ )
281
+ .sort( ( [ a ], [ b ] ) => a.localeCompare( b ) )
282
+ .map(
283
+ ( [ dep, kind ] ) =>
284
+ `array('id' => '${ dep }', 'import' => '${ kind }')`
285
+ );
286
+
287
+ const moduleDependenciesString =
288
+ moduleDependenciesArray.length > 0
289
+ ? moduleDependenciesArray.join( ', ' )
290
+ : '';
291
+
292
+ const version = Date.now();
293
+
294
+ const parts = [
295
+ `'dependencies' => array(${ dependenciesString })`,
296
+ ];
297
+ if ( moduleDependenciesString ) {
298
+ parts.push(
299
+ `'module_dependencies' => array(${ moduleDependenciesString })`
300
+ );
301
+ }
302
+ parts.push( `'version' => '${ version }'` );
303
+ const assetContent = `<?php return array(${ parts.join(
304
+ ', '
305
+ ) });`;
306
+
307
+ const outputDir =
308
+ build.initialOptions.outdir ||
309
+ path.dirname(
310
+ build.initialOptions.outfile || 'build'
311
+ );
312
+
313
+ const assetFilePath = path.join(
314
+ outputDir,
315
+ `${ assetName }.asset.php`
316
+ );
317
+
318
+ await mkdir( path.dirname( assetFilePath ), {
319
+ recursive: true,
320
+ } );
321
+ await writeFile( assetFilePath, assetContent );
322
+ }
323
+ );
324
+ },
325
+ };
326
+ };
327
+ }
@@ -0,0 +1,31 @@
1
+ <?php
2
+ /**
3
+ * Main entry point for auto-generated asset registration.
4
+ * Do not edit this file manually.
5
+ *
6
+ * @package {{PREFIX}}
7
+ */
8
+
9
+ // Load version constant.
10
+ $version_file = __DIR__ . '/version.php';
11
+ if ( file_exists( $version_file ) ) {
12
+ require_once $version_file;
13
+ }
14
+
15
+ // Load script module registration.
16
+ $modules_file = __DIR__ . '/modules.php';
17
+ if ( file_exists( $modules_file ) ) {
18
+ require_once $modules_file;
19
+ }
20
+
21
+ // Load script registration.
22
+ $scripts_file = __DIR__ . '/scripts.php';
23
+ if ( file_exists( $scripts_file ) ) {
24
+ require_once $scripts_file;
25
+ }
26
+
27
+ // Load style registration.
28
+ $styles_file = __DIR__ . '/styles.php';
29
+ if ( file_exists( $styles_file ) ) {
30
+ require_once $styles_file;
31
+ }
@@ -0,0 +1,44 @@
1
+ <?php
2
+ /**
3
+ * Script module registration - Auto-generated by build process.
4
+ * Do not edit this file manually.
5
+ *
6
+ * @package {{PREFIX}}
7
+ */
8
+
9
+ if ( ! function_exists( '{{PREFIX}}_register_script_modules' ) ) {
10
+ /**
11
+ * Register all script modules.
12
+ */
13
+ function {{PREFIX}}_register_script_modules() {
14
+ $modules_dir = __DIR__ . '/modules';
15
+ $modules_file = $modules_dir . '/index.php';
16
+
17
+ if ( ! file_exists( $modules_file ) ) {
18
+ return;
19
+ }
20
+
21
+ $modules = require $modules_file;
22
+ $base_url = plugins_url( 'build/modules/', dirname( __FILE__ ) );
23
+ $extension = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '.js' : '.min.js';
24
+
25
+ foreach ( $modules as $module ) {
26
+ $asset_path = $modules_dir . '/' . $module['asset'];
27
+ $asset = file_exists( $asset_path ) ? require $asset_path : array();
28
+
29
+ wp_register_script_module(
30
+ $module['id'],
31
+ $base_url . $module['path'] . $extension,
32
+ $asset['module_dependencies'] ?? array(),
33
+ $asset['version'] ?? false,
34
+ array(
35
+ 'fetchpriority' => 'low',
36
+ 'in_footer' => true,
37
+ )
38
+ );
39
+ }
40
+ }
41
+
42
+ add_action( 'wp_default_scripts', '{{PREFIX}}_register_script_modules' );
43
+ remove_action( 'wp_default_scripts', 'wp_default_script_modules' );
44
+ }
@@ -0,0 +1,11 @@
1
+ <?php
2
+ /**
3
+ * Script module registry - Auto-generated by build process.
4
+ * Do not edit this file manually.
5
+ *
6
+ * @package {{PREFIX}}
7
+ */
8
+
9
+ return array(
10
+ {{MODULES}}
11
+ );
@@ -0,0 +1,87 @@
1
+ <?php
2
+ /**
3
+ * Script registration - Auto-generated by build process.
4
+ * Do not edit this file manually.
5
+ *
6
+ * @package {{PREFIX}}
7
+ */
8
+
9
+ if ( ! function_exists( '{{PREFIX}}_override_script' ) ) {
10
+ /**
11
+ * Registers a script according to `wp_register_script`. Honors this request by
12
+ * reassigning internal dependency properties of any script handle already
13
+ * registered by that name. It does not deregister the original script, to
14
+ * avoid losing inline scripts which may have been attached.
15
+ *
16
+ * @param WP_Scripts $scripts WP_Scripts instance.
17
+ * @param string $handle Name of the script. Should be unique.
18
+ * @param string $src Full URL of the script, or path of the script relative to the WordPress root directory.
19
+ * @param array $deps Optional. An array of registered script handles this script depends on. Default empty array.
20
+ * @param string|bool|null $ver Optional. String specifying script version number, if it has one, which is added to the URL
21
+ * as a query string for cache busting purposes. If version is set to false, a version
22
+ * number is automatically added equal to current installed WordPress version.
23
+ * If set to null, no version is added.
24
+ * @param bool $in_footer Optional. Whether to enqueue the script before </body> instead of in the <head>.
25
+ * Default 'false'.
26
+ */
27
+ function {{PREFIX}}_override_script( $scripts, $handle, $src, $deps = array(), $ver = false, $in_footer = false ) {
28
+ $script = $scripts->query( $handle, 'registered' );
29
+ if ( $script ) {
30
+ /*
31
+ * In many ways, this is a reimplementation of `wp_register_script` but
32
+ * bypassing consideration of whether a script by the given handle had
33
+ * already been registered.
34
+ */
35
+
36
+ // See: `_WP_Dependency::__construct` .
37
+ $script->src = $src;
38
+ $script->deps = $deps;
39
+ $script->ver = $ver;
40
+ $script->args = $in_footer ? 1 : null;
41
+ } else {
42
+ $scripts->add( $handle, $src, $deps, $ver, ( $in_footer ? 1 : null ) );
43
+ }
44
+
45
+ if ( in_array( 'wp-i18n', $deps, true ) ) {
46
+ $scripts->set_translations( $handle );
47
+ }
48
+ }
49
+ }
50
+
51
+ if ( ! function_exists( '{{PREFIX}}_register_package_scripts' ) ) {
52
+ /**
53
+ * Register all package scripts.
54
+ */
55
+ function {{PREFIX}}_register_package_scripts( $scripts ) {
56
+ $default_version = defined( '{{VERSION_CONSTANT}}' ) && ! SCRIPT_DEBUG ? {{VERSION_CONSTANT}} : time();
57
+ $extension = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '.js' : '.min.js';
58
+
59
+ $scripts_dir = __DIR__ . '/scripts';
60
+ $scripts_file = $scripts_dir . '/index.php';
61
+
62
+ if ( ! file_exists( $scripts_file ) ) {
63
+ return;
64
+ }
65
+
66
+ $scripts_data = require $scripts_file;
67
+ $plugin_dir = dirname( __FILE__ );
68
+
69
+ foreach ( $scripts_data as $script_data ) {
70
+ $asset_file = $scripts_dir . '/' . $script_data['asset'];
71
+ $asset = file_exists( $asset_file ) ? require $asset_file : array();
72
+ $dependencies = $asset['dependencies'] ?? array();
73
+ $version = $asset['version'] ?? $default_version;
74
+
75
+ {{PREFIX}}_override_script(
76
+ $scripts,
77
+ $script_data['handle'],
78
+ plugins_url( 'build/scripts/' . $script_data['path'] . $extension, $plugin_dir ),
79
+ $dependencies,
80
+ $version,
81
+ true
82
+ );
83
+ }
84
+ }
85
+
86
+ add_action( 'wp_default_scripts', '{{PREFIX}}_register_package_scripts' );
87
+ }
@@ -0,0 +1,11 @@
1
+ <?php
2
+ /**
3
+ * Script registry - Auto-generated by build process.
4
+ * Do not edit this file manually.
5
+ *
6
+ * @package {{PREFIX}}
7
+ */
8
+
9
+ return array(
10
+ {{SCRIPTS}}
11
+ );
@@ -0,0 +1,73 @@
1
+ <?php
2
+ /**
3
+ * Style registration - Auto-generated by build process.
4
+ * Do not edit this file manually.
5
+ *
6
+ * Note: These styles can be overridden by calling {{PREFIX}}_override_style()
7
+ * in lib/client-assets.php for complex cases (multiple files, conditional deps, etc.)
8
+ *
9
+ * @package {{PREFIX}}
10
+ */
11
+
12
+ if ( ! function_exists( '{{PREFIX}}_override_style' ) ) {
13
+ /**
14
+ * Registers a style according to `wp_register_style`. Honors this request by
15
+ * deregistering any style by the same handler before registration.
16
+ *
17
+ * @param WP_Styles $styles WP_Styles instance.
18
+ * @param string $handle Name of the stylesheet. Should be unique.
19
+ * @param string $src Full URL of the stylesheet, or path of the stylesheet relative to the WordPress root directory.
20
+ * @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
21
+ * @param string|bool|null $ver Optional. String specifying stylesheet version number, if it has one, which is added to the URL
22
+ * as a query string for cache busting purposes. If version is set to false, a version
23
+ * number is automatically added equal to current installed WordPress version.
24
+ * If set to null, no version is added.
25
+ * @param string $media Optional. The media for which this stylesheet has been defined.
26
+ * Default 'all'. Accepts media types like 'all', 'print' and 'screen', or media queries like
27
+ * '(orientation: portrait)' and '(max-width: 640px)'.
28
+ */
29
+ function {{PREFIX}}_override_style( $styles, $handle, $src, $deps = array(), $ver = false, $media = 'all' ) {
30
+ $style = $styles->query( $handle, 'registered' );
31
+ if ( $style ) {
32
+ $styles->remove( $handle );
33
+ }
34
+ $styles->add( $handle, $src, $deps, $ver, $media );
35
+ }
36
+ }
37
+
38
+ if ( ! function_exists( '{{PREFIX}}_register_package_styles' ) ) {
39
+ /**
40
+ * Register all package styles (simple cases only).
41
+ * Complex cases should be manually registered in lib/client-assets.php.
42
+ *
43
+ * @param WP_Styles $styles WP_Styles instance.
44
+ */
45
+ function {{PREFIX}}_register_package_styles( $styles ) {
46
+ $default_version = defined( '{{VERSION_CONSTANT}}' ) && ! SCRIPT_DEBUG ? {{VERSION_CONSTANT}} : time();
47
+
48
+ $styles_dir = __DIR__ . '/styles';
49
+ $styles_file = $styles_dir . '/index.php';
50
+
51
+ if ( ! file_exists( $styles_file ) ) {
52
+ return;
53
+ }
54
+
55
+ $styles_data = require $styles_file;
56
+ $plugin_dir = dirname( __FILE__ );
57
+
58
+ foreach ( $styles_data as $style_data ) {
59
+ {{PREFIX}}_override_style(
60
+ $styles,
61
+ $style_data['handle'],
62
+ plugins_url( 'build/styles/' . $style_data['path'] . '.css', $plugin_dir ),
63
+ $style_data['dependencies'],
64
+ $default_version
65
+ );
66
+
67
+ // Enable RTL support (WordPress automatically loads -rtl.css variant)
68
+ $styles->add_data( $style_data['handle'], 'rtl', 'replace' );
69
+ }
70
+ }
71
+
72
+ add_action( 'wp_default_styles', '{{PREFIX}}_register_package_styles' );
73
+ }
@@ -0,0 +1,11 @@
1
+ <?php
2
+ /**
3
+ * Style registry - Auto-generated by build process.
4
+ * Do not edit this file manually.
5
+ *
6
+ * @package {{PREFIX}}
7
+ */
8
+
9
+ return array(
10
+ {{STYLES}}
11
+ );
@@ -0,0 +1,11 @@
1
+ <?php
2
+ /**
3
+ * Plugin version constant - Auto-generated by build process.
4
+ * Do not edit this file manually.
5
+ *
6
+ * @package {{PREFIX}}
7
+ */
8
+
9
+ if ( ! defined( '{{VERSION_CONSTANT}}' ) ) {
10
+ define( '{{VERSION_CONSTANT}}', '{{VERSION}}' );
11
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig.json",
3
+ "extends": "../../tsconfig.base.json",
4
+ "compilerOptions": {
5
+ "module": "node16",
6
+ "moduleResolution": "node16",
7
+ "esModuleInterop": true,
8
+ "target": "es2021",
9
+ "lib": [ "es2021", "ES2020.string" ],
10
+ "rootDir": ".",
11
+ "types": [ "node" ],
12
+
13
+ // This package isn't published with types.
14
+ // Don't generate types, this is strictly for validation.
15
+ "incremental": true,
16
+ "declarationMap": false,
17
+ "emitDeclarationOnly": false,
18
+ "noEmit": true,
19
+ "outDir": ".cache"
20
+ },
21
+ "include": [ "." ],
22
+ "exclude": [
23
+ // Excluded to maintain consistency with bin/tsconfig.json
24
+ // where packages/build.mjs was also excluded
25
+ "src/build.mjs"
26
+ ]
27
+ }