@cloudcatch/wp-esbuild 1.1.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.
- package/LICENSE +21 -0
- package/README.md +516 -0
- package/bin/wp-esbuild.mjs +36 -0
- package/lib/build-blocks-manifest.mjs +63 -0
- package/lib/build.mjs +111 -0
- package/lib/config.mjs +96 -0
- package/lib/define-config.mjs +51 -0
- package/lib/handlers/blocks.mjs +159 -0
- package/lib/handlers/copy.mjs +59 -0
- package/lib/handlers/index.mjs +44 -0
- package/lib/handlers/script.mjs +141 -0
- package/lib/handlers/scss.mjs +113 -0
- package/lib/load-config.mjs +103 -0
- package/lib/merge-esbuild-options.mjs +51 -0
- package/lib/normalize-config.mjs +267 -0
- package/lib/php-export.mjs +62 -0
- package/lib/postcss.mjs +82 -0
- package/lib/resolve-scss-outfile.mjs +75 -0
- package/lib/utils.mjs +29 -0
- package/lib/wordpress-externals-plugin.mjs +320 -0
- package/lib/write-asset-php.mjs +35 -0
- package/package.json +51 -0
package/lib/build.mjs
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build orchestrator for WordPress blocks, JS, modules, and SCSS assets.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import chokidar from 'chokidar';
|
|
7
|
+
import { loadProjectConfig } from './load-config.mjs';
|
|
8
|
+
import { getEntriesForChangedPath } from './normalize-config.mjs';
|
|
9
|
+
import { runEntry } from './handlers/index.mjs';
|
|
10
|
+
import { buildBlocksManifest } from './build-blocks-manifest.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} projectRoot
|
|
14
|
+
* @param {object} config
|
|
15
|
+
* @param {object[]} [entries]
|
|
16
|
+
* @return {Promise<void>}
|
|
17
|
+
*/
|
|
18
|
+
async function runUserPlugins( projectRoot, config, entries = config.entries ) {
|
|
19
|
+
for ( const plugin of config.plugins || [] ) {
|
|
20
|
+
if ( typeof plugin.build === 'function' ) {
|
|
21
|
+
await plugin.build( { projectRoot, config, entries } );
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} projectRoot
|
|
28
|
+
* @param {object} config
|
|
29
|
+
* @param {object[]} entries
|
|
30
|
+
* @return {Promise<void>}
|
|
31
|
+
*/
|
|
32
|
+
async function runEntries( projectRoot, config, entries ) {
|
|
33
|
+
const buildContext = {
|
|
34
|
+
minify: config.minify,
|
|
35
|
+
sourcemap: config.sourcemap,
|
|
36
|
+
projectRoot,
|
|
37
|
+
globalEsbuild: config.esbuild,
|
|
38
|
+
wordpressExternals: config.wordpressExternals,
|
|
39
|
+
postcss: config.postcss,
|
|
40
|
+
rtl: config.rtl,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
for ( const entry of entries ) {
|
|
44
|
+
await runEntry( projectRoot, entry, buildContext );
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const hasBlocksEntry = entries.some( ( entry ) => entry.type === 'blocks' );
|
|
48
|
+
if ( hasBlocksEntry && config.blocksManifest?.enabled ) {
|
|
49
|
+
await buildBlocksManifest( {
|
|
50
|
+
projectRoot,
|
|
51
|
+
inputDir: config.blocksManifest.input,
|
|
52
|
+
outputFile: config.blocksManifest.output,
|
|
53
|
+
} );
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await runUserPlugins( projectRoot, config, entries );
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {string} projectRoot
|
|
61
|
+
* @param {object} [options]
|
|
62
|
+
* @param {boolean} [options.watch]
|
|
63
|
+
* @return {Promise<void>}
|
|
64
|
+
*/
|
|
65
|
+
export async function build( projectRoot, options = {} ) {
|
|
66
|
+
const config = await loadProjectConfig( projectRoot );
|
|
67
|
+
|
|
68
|
+
const runBuild = async ( entries = config.entries ) => {
|
|
69
|
+
console.log( `Building ${ projectRoot }...` );
|
|
70
|
+
await runEntries( projectRoot, config, entries );
|
|
71
|
+
console.log( 'Build complete.' );
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
await runBuild();
|
|
75
|
+
|
|
76
|
+
if ( options.watch ) {
|
|
77
|
+
const watchPaths = config.watchPaths.map( ( watchPath ) =>
|
|
78
|
+
path.join( projectRoot, watchPath )
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
console.log( 'Watching for changes...' );
|
|
82
|
+
|
|
83
|
+
const watcher = chokidar.watch( watchPaths, {
|
|
84
|
+
ignoreInitial: true,
|
|
85
|
+
ignored: /(^|[/\\])\../,
|
|
86
|
+
} );
|
|
87
|
+
|
|
88
|
+
let debounceTimer;
|
|
89
|
+
const scheduleRebuild = ( changedPath ) => {
|
|
90
|
+
clearTimeout( debounceTimer );
|
|
91
|
+
debounceTimer = setTimeout( () => {
|
|
92
|
+
const entries = changedPath
|
|
93
|
+
? getEntriesForChangedPath( changedPath, projectRoot, config )
|
|
94
|
+
: config.entries;
|
|
95
|
+
|
|
96
|
+
runBuild( entries ).catch( ( error ) => {
|
|
97
|
+
console.error( error );
|
|
98
|
+
} );
|
|
99
|
+
}, 100 );
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
watcher.on( 'all', ( eventName, changedPath ) => {
|
|
103
|
+
scheduleRebuild( changedPath );
|
|
104
|
+
} );
|
|
105
|
+
|
|
106
|
+
await new Promise( () => {} );
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export { normalizeConfig } from './normalize-config.mjs';
|
|
111
|
+
export { defineConfig } from './define-config.mjs';
|
package/lib/config.mjs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default esbuild and project configuration for @cloudcatch/wp-esbuild.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const defaultWordpressBundledPackages = [
|
|
6
|
+
'@wordpress/admin-ui',
|
|
7
|
+
'@wordpress/dataviews',
|
|
8
|
+
'@wordpress/fields',
|
|
9
|
+
'@wordpress/global-styles-engine',
|
|
10
|
+
'@wordpress/global-styles-ui',
|
|
11
|
+
'@wordpress/grid',
|
|
12
|
+
'@wordpress/icons',
|
|
13
|
+
'@wordpress/image-cropper',
|
|
14
|
+
'@wordpress/interface',
|
|
15
|
+
'@wordpress/ui',
|
|
16
|
+
'@wordpress/views',
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export const defaultProjectConfig = {
|
|
20
|
+
srcDir: 'src',
|
|
21
|
+
outDir: 'build',
|
|
22
|
+
blocks: {
|
|
23
|
+
src: 'src/blocks',
|
|
24
|
+
out: 'build/blocks',
|
|
25
|
+
discover: '*/block.json',
|
|
26
|
+
copy: [ 'block.json', 'render.php' ],
|
|
27
|
+
},
|
|
28
|
+
js: {
|
|
29
|
+
src: 'src/js',
|
|
30
|
+
out: 'build/js',
|
|
31
|
+
},
|
|
32
|
+
modules: {
|
|
33
|
+
src: 'src/js/modules',
|
|
34
|
+
out: 'build/js/modules',
|
|
35
|
+
},
|
|
36
|
+
scss: {
|
|
37
|
+
src: 'src/scss',
|
|
38
|
+
out: 'build/css',
|
|
39
|
+
},
|
|
40
|
+
copy: [],
|
|
41
|
+
blocksManifest: {
|
|
42
|
+
enabled: true,
|
|
43
|
+
input: 'build/blocks',
|
|
44
|
+
output: 'build/blocks/blocks-manifest.php',
|
|
45
|
+
},
|
|
46
|
+
wordpressExternals: {
|
|
47
|
+
bundle: [],
|
|
48
|
+
external: [],
|
|
49
|
+
vendors: {},
|
|
50
|
+
},
|
|
51
|
+
postcss: true,
|
|
52
|
+
rtl: false,
|
|
53
|
+
esbuild: {},
|
|
54
|
+
plugins: [],
|
|
55
|
+
minify: process.env.NODE_ENV === 'production',
|
|
56
|
+
sourcemap: process.env.NODE_ENV !== 'production',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Default esbuild options for WordPress browser bundles.
|
|
61
|
+
*
|
|
62
|
+
* @param {object} options
|
|
63
|
+
* @param {boolean} options.minify
|
|
64
|
+
* @param {boolean} options.sourcemap
|
|
65
|
+
* @return {import('esbuild').BuildOptions}
|
|
66
|
+
*/
|
|
67
|
+
export function getDefaultEsbuildOptions( { minify, sourcemap } ) {
|
|
68
|
+
return {
|
|
69
|
+
bundle: true,
|
|
70
|
+
platform: 'browser',
|
|
71
|
+
target: 'es2018',
|
|
72
|
+
jsx: 'automatic',
|
|
73
|
+
jsxImportSource: 'react',
|
|
74
|
+
loader: {
|
|
75
|
+
'.js': 'jsx',
|
|
76
|
+
'.jsx': 'jsx',
|
|
77
|
+
'.ts': 'tsx',
|
|
78
|
+
'.tsx': 'tsx',
|
|
79
|
+
'.png': 'file',
|
|
80
|
+
'.jpg': 'file',
|
|
81
|
+
'.jpeg': 'file',
|
|
82
|
+
'.gif': 'file',
|
|
83
|
+
'.svg': 'file',
|
|
84
|
+
'.webp': 'file',
|
|
85
|
+
'.woff': 'file',
|
|
86
|
+
'.woff2': 'file',
|
|
87
|
+
'.ttf': 'file',
|
|
88
|
+
'.eot': 'file',
|
|
89
|
+
},
|
|
90
|
+
assetNames: 'assets/[name]-[hash]',
|
|
91
|
+
minify,
|
|
92
|
+
sourcemap,
|
|
93
|
+
logLevel: 'info',
|
|
94
|
+
define: {},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defineConfig helper with basic validation warnings.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const KNOWN_KEYS = new Set( [
|
|
6
|
+
'srcDir',
|
|
7
|
+
'outDir',
|
|
8
|
+
'entries',
|
|
9
|
+
'blocks',
|
|
10
|
+
'js',
|
|
11
|
+
'modules',
|
|
12
|
+
'scss',
|
|
13
|
+
'copy',
|
|
14
|
+
'blocksManifest',
|
|
15
|
+
'minify',
|
|
16
|
+
'sourcemap',
|
|
17
|
+
'esbuild',
|
|
18
|
+
'wordpressExternals',
|
|
19
|
+
'postcss',
|
|
20
|
+
'rtl',
|
|
21
|
+
'plugins',
|
|
22
|
+
] );
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {object} config
|
|
26
|
+
* @return {object}
|
|
27
|
+
*/
|
|
28
|
+
export function defineConfig( config ) {
|
|
29
|
+
if ( config && typeof config === 'object' ) {
|
|
30
|
+
for ( const key of Object.keys( config ) ) {
|
|
31
|
+
if ( ! KNOWN_KEYS.has( key ) ) {
|
|
32
|
+
console.warn( `[wp-esbuild] Unknown config key "${ key }".` );
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if ( Array.isArray( config.entries ) ) {
|
|
37
|
+
for ( const entry of config.entries ) {
|
|
38
|
+
if ( ! entry.type ) {
|
|
39
|
+
console.warn( '[wp-esbuild] entries item missing "type".' );
|
|
40
|
+
}
|
|
41
|
+
if ( entry.type !== 'copy' && ! entry.src && ! entry.from ) {
|
|
42
|
+
console.warn(
|
|
43
|
+
`[wp-esbuild] entry "${ entry.name || '(unnamed)' }" missing src/from.`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return config;
|
|
51
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gutenberg blocks handler.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import glob from 'fast-glob';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { cp, mkdir, readFile } from 'fs/promises';
|
|
8
|
+
import { fileExists } from '../utils.mjs';
|
|
9
|
+
import { buildScssFile } from './scss.mjs';
|
|
10
|
+
import { buildScriptFile } from './script.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} blockDir
|
|
14
|
+
* @param {string} projectRoot
|
|
15
|
+
* @param {object} entryConfig
|
|
16
|
+
* @return {Promise<object>}
|
|
17
|
+
*/
|
|
18
|
+
async function readBlockMetadata( blockDir ) {
|
|
19
|
+
const blockJsonPath = path.join( blockDir, 'block.json' );
|
|
20
|
+
if ( ! ( await fileExists( blockJsonPath ) ) ) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return JSON.parse( await readFile( blockJsonPath, 'utf8' ) );
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} projectRoot
|
|
29
|
+
* @param {object} entryConfig
|
|
30
|
+
* @return {Promise<string[]>}
|
|
31
|
+
*/
|
|
32
|
+
async function discoverBlocks( projectRoot, entryConfig ) {
|
|
33
|
+
const blocksSrc = path.join( projectRoot, entryConfig.src );
|
|
34
|
+
if ( ! ( await fileExists( blocksSrc ) ) ) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const discoverPattern = entryConfig.discover || '*/block.json';
|
|
39
|
+
const blockJsonFiles = await glob( discoverPattern, {
|
|
40
|
+
cwd: blocksSrc,
|
|
41
|
+
absolute: true,
|
|
42
|
+
} );
|
|
43
|
+
|
|
44
|
+
return blockJsonFiles.map( ( blockJsonPath ) => path.dirname( blockJsonPath ) );
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} blockDir
|
|
49
|
+
* @param {string} blocksOut
|
|
50
|
+
* @param {object} buildContext
|
|
51
|
+
* @param {object} entryConfig
|
|
52
|
+
* @return {Promise<void>}
|
|
53
|
+
*/
|
|
54
|
+
async function buildBlock( blockDir, blocksOut, buildContext, entryConfig ) {
|
|
55
|
+
const slug = path.basename( blockDir );
|
|
56
|
+
const outDir = path.join( blocksOut, slug );
|
|
57
|
+
const metadata = await readBlockMetadata( blockDir );
|
|
58
|
+
await mkdir( outDir, { recursive: true } );
|
|
59
|
+
|
|
60
|
+
const indexJs = path.join( blockDir, 'index.js' );
|
|
61
|
+
if ( await fileExists( indexJs ) ) {
|
|
62
|
+
await buildScriptFile( {
|
|
63
|
+
entry: indexJs,
|
|
64
|
+
outfile: path.join( outDir, 'index.js' ),
|
|
65
|
+
buildContext,
|
|
66
|
+
entryConfig: {
|
|
67
|
+
...entryConfig,
|
|
68
|
+
format: 'iife',
|
|
69
|
+
wordpressExternals: true,
|
|
70
|
+
assetPhp: true,
|
|
71
|
+
},
|
|
72
|
+
} );
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const styleScss = path.join( blockDir, 'style.scss' );
|
|
76
|
+
if ( await fileExists( styleScss ) ) {
|
|
77
|
+
await buildScssFile( {
|
|
78
|
+
entry: styleScss,
|
|
79
|
+
outfile: path.join( outDir, 'style-index.css' ),
|
|
80
|
+
buildContext,
|
|
81
|
+
entryConfig,
|
|
82
|
+
} );
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const editorScss = path.join( blockDir, 'editor.scss' );
|
|
86
|
+
if ( await fileExists( editorScss ) ) {
|
|
87
|
+
await buildScssFile( {
|
|
88
|
+
entry: editorScss,
|
|
89
|
+
outfile: path.join( outDir, 'index.css' ),
|
|
90
|
+
buildContext,
|
|
91
|
+
entryConfig,
|
|
92
|
+
} );
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const viewEntry = await findViewScript( blockDir );
|
|
96
|
+
if ( viewEntry ) {
|
|
97
|
+
const viewOut = path.join( outDir, 'view.js' );
|
|
98
|
+
const isModule = Boolean( metadata.viewScriptModule );
|
|
99
|
+
|
|
100
|
+
await buildScriptFile( {
|
|
101
|
+
entry: viewEntry,
|
|
102
|
+
outfile: viewOut,
|
|
103
|
+
buildContext,
|
|
104
|
+
entryConfig: {
|
|
105
|
+
...entryConfig,
|
|
106
|
+
format: isModule ? 'esm' : 'iife',
|
|
107
|
+
wordpressExternals: ! isModule,
|
|
108
|
+
assetPhp: true,
|
|
109
|
+
},
|
|
110
|
+
} );
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const copyList = entryConfig.copy || [ 'block.json', 'render.php' ];
|
|
114
|
+
for ( const fileName of copyList ) {
|
|
115
|
+
const source = path.join( blockDir, fileName );
|
|
116
|
+
if ( ! ( await fileExists( source ) ) ) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const destination = path.join( outDir, fileName );
|
|
121
|
+
await mkdir( path.dirname( destination ), { recursive: true } );
|
|
122
|
+
await cp( source, destination, { recursive: true, force: true } );
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {string} blockDir
|
|
128
|
+
* @return {Promise<string|null>}
|
|
129
|
+
*/
|
|
130
|
+
async function findViewScript( blockDir ) {
|
|
131
|
+
for ( const fileName of [ 'view.ts', 'view.tsx', 'view.js', 'view.mjs' ] ) {
|
|
132
|
+
const candidate = path.join( blockDir, fileName );
|
|
133
|
+
if ( await fileExists( candidate ) ) {
|
|
134
|
+
return candidate;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {string} projectRoot
|
|
143
|
+
* @param {object} entryConfig
|
|
144
|
+
* @param {object} buildContext
|
|
145
|
+
* @return {Promise<void>}
|
|
146
|
+
*/
|
|
147
|
+
export async function runBlocksHandler( projectRoot, entryConfig, buildContext ) {
|
|
148
|
+
const blocksOut = path.join( projectRoot, entryConfig.out );
|
|
149
|
+
const blockDirs = await discoverBlocks( projectRoot, entryConfig );
|
|
150
|
+
|
|
151
|
+
for ( const blockDir of blockDirs ) {
|
|
152
|
+
await buildBlock( blockDir, blocksOut, buildContext, entryConfig );
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export const blocksHandler = {
|
|
157
|
+
type: 'blocks',
|
|
158
|
+
run: runBlocksHandler,
|
|
159
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static copy handler.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import glob from 'fast-glob';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { cp, copyFile, mkdir } from 'fs/promises';
|
|
8
|
+
import { fileExists } from '../utils.mjs';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} projectRoot
|
|
12
|
+
* @param {object} entryConfig
|
|
13
|
+
* @return {Promise<void>}
|
|
14
|
+
*/
|
|
15
|
+
export async function runCopyHandler( projectRoot, entryConfig ) {
|
|
16
|
+
const fromPath = path.join( projectRoot, entryConfig.from );
|
|
17
|
+
const toPath = path.join( projectRoot, entryConfig.to );
|
|
18
|
+
|
|
19
|
+
if ( ! entryConfig.from ) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const hasGlob = /[*?[\]]/.test( entryConfig.from );
|
|
24
|
+
|
|
25
|
+
if ( ! hasGlob ) {
|
|
26
|
+
if ( ! ( await fileExists( fromPath ) ) ) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
await mkdir( path.dirname( toPath ), { recursive: true } );
|
|
31
|
+
await cp( fromPath, toPath, { recursive: true, force: true } );
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const matchedFiles = await glob( entryConfig.from, {
|
|
36
|
+
cwd: projectRoot,
|
|
37
|
+
absolute: true,
|
|
38
|
+
onlyFiles: true,
|
|
39
|
+
} );
|
|
40
|
+
|
|
41
|
+
const baseDir = path.dirname(
|
|
42
|
+
path.join( projectRoot, entryConfig.from.split( '*' )[ 0 ] )
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
for ( const sourceFile of matchedFiles ) {
|
|
46
|
+
const relativePath = path.relative( baseDir, sourceFile );
|
|
47
|
+
const destination = entryConfig.flatten
|
|
48
|
+
? path.join( toPath, path.basename( sourceFile ) )
|
|
49
|
+
: path.join( toPath, relativePath );
|
|
50
|
+
|
|
51
|
+
await mkdir( path.dirname( destination ), { recursive: true } );
|
|
52
|
+
await copyFile( sourceFile, destination );
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const copyHandler = {
|
|
57
|
+
type: 'copy',
|
|
58
|
+
run: runCopyHandler,
|
|
59
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler registry for wp-esbuild pipelines.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { blocksHandler } from './blocks.mjs';
|
|
6
|
+
import { copyHandler } from './copy.mjs';
|
|
7
|
+
import { scssHandler } from './scss.mjs';
|
|
8
|
+
import { scriptHandler } from './script.mjs';
|
|
9
|
+
|
|
10
|
+
/** @type {Record<string, { type: string, run: Function }>} */
|
|
11
|
+
export const handlersByType = {
|
|
12
|
+
blocks: blocksHandler,
|
|
13
|
+
script: scriptHandler,
|
|
14
|
+
scss: scssHandler,
|
|
15
|
+
copy: copyHandler,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {object} entry
|
|
20
|
+
* @return {{ type: string, run: Function }|undefined}
|
|
21
|
+
*/
|
|
22
|
+
export function getHandlerForEntry( entry ) {
|
|
23
|
+
return handlersByType[ entry.type ];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} projectRoot
|
|
28
|
+
* @param {object} entry
|
|
29
|
+
* @param {object} buildContext
|
|
30
|
+
* @return {Promise<void>}
|
|
31
|
+
*/
|
|
32
|
+
export async function runEntry( projectRoot, entry, buildContext ) {
|
|
33
|
+
const handler = getHandlerForEntry( entry );
|
|
34
|
+
if ( ! handler ) {
|
|
35
|
+
throw new Error( `Unknown entry type "${ entry.type }" for entry "${ entry.name }".` );
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if ( entry.type === 'copy' ) {
|
|
39
|
+
await handler.run( projectRoot, entry );
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await handler.run( projectRoot, entry, buildContext );
|
|
44
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script bundle handler (IIFE and ESM).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import esbuild from 'esbuild';
|
|
6
|
+
import glob from 'fast-glob';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { mkdir } from 'fs/promises';
|
|
9
|
+
import { mergeEsbuildOptions } from '../merge-esbuild-options.mjs';
|
|
10
|
+
import { fileExists } from '../utils.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} srcDir
|
|
14
|
+
* @param {string} outDir
|
|
15
|
+
* @param {string} entry
|
|
16
|
+
* @param {object} entryConfig
|
|
17
|
+
* @return {string}
|
|
18
|
+
*/
|
|
19
|
+
function resolveScriptOutfile( srcDir, outDir, entry, entryConfig ) {
|
|
20
|
+
const relativeEntryPath = path.relative( srcDir, entry );
|
|
21
|
+
const outRelativePath = relativeEntryPath.replace( /\.(tsx|ts|mjs|jsx|js)$/, '.js' );
|
|
22
|
+
const globPattern = entryConfig.glob || '*.{js,jsx,mjs,ts,tsx}';
|
|
23
|
+
const preserveStructure = globPattern.includes( '**' );
|
|
24
|
+
|
|
25
|
+
if ( entryConfig.outName === 'entry-dir' ) {
|
|
26
|
+
const parentDir = path.dirname( relativeEntryPath );
|
|
27
|
+
const baseName =
|
|
28
|
+
parentDir === '.'
|
|
29
|
+
? path.basename( outRelativePath, '.js' )
|
|
30
|
+
: parentDir.split( path.sep ).join( '-' );
|
|
31
|
+
return path.join( outDir, `${ baseName }.js` );
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if ( preserveStructure ) {
|
|
35
|
+
return path.join( outDir, outRelativePath );
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return path.join( outDir, `${ path.basename( outRelativePath, '.js' ) }.js` );
|
|
39
|
+
}
|
|
40
|
+
import {
|
|
41
|
+
extractStyleImportsPlugin,
|
|
42
|
+
stripStyleImportsPlugin,
|
|
43
|
+
wordpressExternalsPlugin,
|
|
44
|
+
wordpressModuleExternalsPlugin,
|
|
45
|
+
} from '../wordpress-externals-plugin.mjs';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {object} options
|
|
49
|
+
* @param {string} options.entry
|
|
50
|
+
* @param {string} options.outfile
|
|
51
|
+
* @param {object} options.buildContext
|
|
52
|
+
* @param {object} options.entryConfig
|
|
53
|
+
* @return {Promise<import('esbuild').BuildResult>}
|
|
54
|
+
*/
|
|
55
|
+
export async function buildScriptFile( { entry, outfile, buildContext, entryConfig } ) {
|
|
56
|
+
const {
|
|
57
|
+
minify,
|
|
58
|
+
sourcemap,
|
|
59
|
+
projectRoot,
|
|
60
|
+
globalEsbuild,
|
|
61
|
+
wordpressExternals: globalExternals,
|
|
62
|
+
} = buildContext;
|
|
63
|
+
|
|
64
|
+
const format = entryConfig.format || 'iife';
|
|
65
|
+
const extractCss = entryConfig.extractCss === true;
|
|
66
|
+
const assetPhp = entryConfig.assetPhp !== false;
|
|
67
|
+
const assetBaseName = path.basename( outfile, path.extname( outfile ) );
|
|
68
|
+
|
|
69
|
+
const stylePlugin = extractCss ? extractStyleImportsPlugin() : stripStyleImportsPlugin();
|
|
70
|
+
const plugins = [ stylePlugin, ...( entryConfig.esbuild?.plugins || [] ), ...( globalEsbuild?.plugins || [] ) ];
|
|
71
|
+
|
|
72
|
+
if ( assetPhp ) {
|
|
73
|
+
if ( format === 'esm' ) {
|
|
74
|
+
plugins.push(
|
|
75
|
+
wordpressModuleExternalsPlugin( assetBaseName, globalExternals )
|
|
76
|
+
);
|
|
77
|
+
} else if ( entryConfig.wordpressExternals !== false ) {
|
|
78
|
+
plugins.push(
|
|
79
|
+
wordpressExternalsPlugin( assetBaseName, [], globalExternals )
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await mkdir( path.dirname( outfile ), { recursive: true } );
|
|
85
|
+
|
|
86
|
+
const esbuildOptions = mergeEsbuildOptions( {
|
|
87
|
+
minify,
|
|
88
|
+
sourcemap,
|
|
89
|
+
globalEsbuild,
|
|
90
|
+
entryEsbuild: entryConfig.esbuild,
|
|
91
|
+
projectRoot,
|
|
92
|
+
} );
|
|
93
|
+
|
|
94
|
+
return esbuild.build( {
|
|
95
|
+
...esbuildOptions,
|
|
96
|
+
entryPoints: [ entry ],
|
|
97
|
+
outfile,
|
|
98
|
+
format,
|
|
99
|
+
plugins: [ ...plugins, ...( esbuildOptions.plugins || [] ) ].filter(
|
|
100
|
+
( plugin, index, all ) =>
|
|
101
|
+
all.findIndex( ( item ) => item.name === plugin.name ) === index
|
|
102
|
+
),
|
|
103
|
+
} );
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @param {string} projectRoot
|
|
108
|
+
* @param {object} entryConfig
|
|
109
|
+
* @param {object} buildContext
|
|
110
|
+
* @return {Promise<void>}
|
|
111
|
+
*/
|
|
112
|
+
export async function runScriptHandler( projectRoot, entryConfig, buildContext ) {
|
|
113
|
+
const srcDir = path.join( projectRoot, entryConfig.src );
|
|
114
|
+
const outDir = path.join( projectRoot, entryConfig.out );
|
|
115
|
+
|
|
116
|
+
if ( ! ( await fileExists( srcDir ) ) ) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const globPattern = entryConfig.glob || '*.{js,jsx,mjs,ts,tsx}';
|
|
121
|
+
const entries = await glob( globPattern, {
|
|
122
|
+
cwd: srcDir,
|
|
123
|
+
absolute: true,
|
|
124
|
+
} );
|
|
125
|
+
|
|
126
|
+
for ( const entry of entries ) {
|
|
127
|
+
const outfile = resolveScriptOutfile( srcDir, outDir, entry, entryConfig );
|
|
128
|
+
|
|
129
|
+
await buildScriptFile( {
|
|
130
|
+
entry,
|
|
131
|
+
outfile,
|
|
132
|
+
buildContext,
|
|
133
|
+
entryConfig,
|
|
134
|
+
} );
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const scriptHandler = {
|
|
139
|
+
type: 'script',
|
|
140
|
+
run: runScriptHandler,
|
|
141
|
+
};
|