@wordpress/build 0.4.1-next.8b30e05b0.0 → 0.4.1-next.8fd3f8831.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/src/build.mjs CHANGED
@@ -344,14 +344,7 @@ async function bundlePackage( packageName, options = {} ) {
344
344
  };
345
345
  }
346
346
 
347
- const bundlePlugins = [
348
- momentTimezoneAliasPlugin(),
349
- wordpressExternalsPlugin(
350
- 'index.min',
351
- 'iife',
352
- packageJson.wpScriptExtraDependencies || []
353
- ),
354
- ];
347
+ const baseBundlePlugins = [ momentTimezoneAliasPlugin() ];
355
348
 
356
349
  builds.push(
357
350
  esbuild.build( {
@@ -359,14 +352,30 @@ async function bundlePackage( packageName, options = {} ) {
359
352
  outfile: path.join( outputDir, 'index.min.js' ),
360
353
  minify: true,
361
354
  define: getDefine( false ),
362
- plugins: bundlePlugins,
355
+ plugins: [
356
+ ...baseBundlePlugins,
357
+ wordpressExternalsPlugin(
358
+ 'index.min',
359
+ 'iife',
360
+ packageJson.wpScriptExtraDependencies || [],
361
+ true // Generate asset file for minified build
362
+ ),
363
+ ],
363
364
  } ),
364
365
  esbuild.build( {
365
366
  ...baseConfig,
366
367
  outfile: path.join( outputDir, 'index.js' ),
367
368
  minify: false,
368
369
  define: getDefine( true ),
369
- plugins: bundlePlugins,
370
+ plugins: [
371
+ ...baseBundlePlugins,
372
+ wordpressExternalsPlugin(
373
+ 'index.min',
374
+ 'iife',
375
+ packageJson.wpScriptExtraDependencies || [],
376
+ false // Skip asset file for non-minified build
377
+ ),
378
+ ],
370
379
  } )
371
380
  );
372
381
 
@@ -397,9 +406,6 @@ async function bundlePackage( packageName, options = {} ) {
397
406
  : exportName.replace( /^\.\//, '' );
398
407
  const entryPoint = path.join( packageDir, exportPath );
399
408
  const baseFileName = path.basename( fileName );
400
- const modulePlugins = [
401
- wordpressExternalsPlugin( `${ baseFileName }.min`, 'esm' ),
402
- ];
403
409
 
404
410
  builds.push(
405
411
  esbuild.build( {
@@ -415,7 +421,14 @@ async function bundlePackage( packageName, options = {} ) {
415
421
  platform: 'browser',
416
422
  minify: true,
417
423
  define: getDefine( false ),
418
- plugins: modulePlugins,
424
+ plugins: [
425
+ wordpressExternalsPlugin(
426
+ `${ baseFileName }.min`,
427
+ 'esm',
428
+ [],
429
+ true // Generate asset file for minified build
430
+ ),
431
+ ],
419
432
  } ),
420
433
  esbuild.build( {
421
434
  entryPoints: [ entryPoint ],
@@ -430,7 +443,14 @@ async function bundlePackage( packageName, options = {} ) {
430
443
  platform: 'browser',
431
444
  minify: false,
432
445
  define: getDefine( true ),
433
- plugins: modulePlugins,
446
+ plugins: [
447
+ wordpressExternalsPlugin(
448
+ `${ baseFileName }.min`,
449
+ 'esm',
450
+ [],
451
+ false // Skip asset file for non-minified build
452
+ ),
453
+ ],
434
454
  } )
435
455
  );
436
456
 
@@ -603,7 +623,10 @@ async function bundlePackage( packageName, options = {} ) {
603
623
  ?.map( ( d ) => d.replace( /'/g, '' ) ) || [];
604
624
  }
605
625
 
606
- const styleDeps = await inferStyleDependencies( scriptDependencies );
626
+ const styleDeps = await inferStyleDependencies(
627
+ scriptDependencies,
628
+ packageName
629
+ );
607
630
 
608
631
  builtStyles.push( {
609
632
  handle: `${ handlePrefix }-${ packageName }`,
@@ -627,14 +650,17 @@ async function bundlePackage( packageName, options = {} ) {
627
650
  * 3. Actually have a built style.css file
628
651
  *
629
652
  * @param {string[]} scriptDependencies Array of script handles from asset file.
653
+ * @param {string} packageName Package name (short name) being bundled, for context-aware resolution.
630
654
  * @return {Promise<string[]>} Array of style handles to depend on.
631
655
  */
632
- async function inferStyleDependencies( scriptDependencies ) {
656
+ async function inferStyleDependencies( scriptDependencies, packageName ) {
633
657
  if ( ! scriptDependencies || scriptDependencies.length === 0 ) {
634
658
  return [];
635
659
  }
636
660
 
637
661
  const styleDeps = [];
662
+ // Get the resolve directory for context-aware package resolution
663
+ const resolveDir = path.join( PACKAGES_DIR, packageName );
638
664
 
639
665
  for ( const scriptHandle of scriptDependencies ) {
640
666
  // Skip non-package dependencies (like 'react', 'lodash', etc.)
@@ -642,13 +668,13 @@ async function inferStyleDependencies( scriptDependencies ) {
642
668
  continue;
643
669
  }
644
670
 
645
- // Convert handle to package name: 'wp-components' → 'components'
671
+ // Convert handle to package name: 'wp-components' → '@wordpress/components'
646
672
  const shortName = scriptHandle.replace( 'wp-', '' );
647
673
  const depPackageName = `@wordpress/${ shortName }`;
648
674
 
649
- // Read the dependency's package.json
675
+ // Read the dependency's package.json with context-aware resolution
650
676
  try {
651
- const depPackageJson = getPackageInfo( depPackageName );
677
+ const depPackageJson = getPackageInfo( depPackageName, resolveDir );
652
678
 
653
679
  if ( ! depPackageJson ) {
654
680
  continue;
@@ -1002,7 +1028,12 @@ async function transpilePackage( packageName ) {
1002
1028
  name: 'externalize-except-css',
1003
1029
  setup( build ) {
1004
1030
  // Externalize all non-CSS imports
1005
- build.onResolve( { filter: /.*/ }, ( args ) => {
1031
+ build.onResolve( { filter: /.*/ }, async ( args ) => {
1032
+ // Skip recursive calls.
1033
+ if ( args.pluginData?.__fromExternalize ) {
1034
+ return null;
1035
+ }
1036
+
1006
1037
  // Skip entry points
1007
1038
  if ( args.kind === 'entry-point' ) {
1008
1039
  return null;
@@ -1013,6 +1044,44 @@ async function transpilePackage( packageName ) {
1013
1044
  return null;
1014
1045
  }
1015
1046
 
1047
+ // Fully resolve local dependencies without file extension
1048
+ // and replace the extension with the target extension.
1049
+ if ( args.path.startsWith( '.' ) ) {
1050
+ const resolved = await build.resolve( args.path, {
1051
+ namespace: args.namespace,
1052
+ importer: args.importer,
1053
+ kind: args.kind,
1054
+ resolveDir: args.resolveDir,
1055
+ with: args.with,
1056
+ pluginData: {
1057
+ ...args.pluginData,
1058
+ __fromExternalize: true,
1059
+ },
1060
+ } );
1061
+
1062
+ if ( resolved.errors.length > 0 ) {
1063
+ return resolved;
1064
+ }
1065
+
1066
+ // Relativize path: make it relative to resolveDir with leading ./
1067
+ let relativePath = normalizePath(
1068
+ path.relative( args.resolveDir, resolved.path )
1069
+ );
1070
+ if ( ! relativePath.startsWith( '.' ) ) {
1071
+ relativePath = './' + relativePath;
1072
+ }
1073
+
1074
+ // Replace extension: make sure that file extension is always `.js` or `.cjs`.
1075
+ const newExt =
1076
+ build.initialOptions.format === 'cjs' ? '.cjs' : '.js';
1077
+ relativePath = relativePath.replace( /\.[jt]sx?$/, newExt );
1078
+
1079
+ return {
1080
+ path: relativePath,
1081
+ external: true,
1082
+ };
1083
+ }
1084
+
1016
1085
  // Externalize everything else (keep imports as-is)
1017
1086
  return { path: args.path, external: true };
1018
1087
  } );
@@ -1030,6 +1099,7 @@ async function transpilePackage( packageName ) {
1030
1099
  entryPoints: srcFiles,
1031
1100
  outdir: buildDir,
1032
1101
  outbase: srcDir,
1102
+ outExtension: { '.js': '.cjs' },
1033
1103
  bundle: true,
1034
1104
  platform: 'node',
1035
1105
  format: 'cjs',
@@ -1037,9 +1107,7 @@ async function transpilePackage( packageName ) {
1037
1107
  target,
1038
1108
  jsx: 'automatic',
1039
1109
  jsxImportSource: 'react',
1040
- loader: {
1041
- '.js': 'jsx',
1042
- },
1110
+ loader: { '.js': 'jsx' },
1043
1111
  plugins,
1044
1112
  } )
1045
1113
  );
@@ -1069,9 +1137,7 @@ async function transpilePackage( packageName ) {
1069
1137
  target,
1070
1138
  jsx: 'automatic',
1071
1139
  jsxImportSource: 'react',
1072
- loader: {
1073
- '.js': 'jsx',
1074
- },
1140
+ loader: { '.js': 'jsx' },
1075
1141
  plugins,
1076
1142
  } )
1077
1143
  );
@@ -1279,10 +1345,6 @@ async function buildRoute( routeName ) {
1279
1345
  } );
1280
1346
 
1281
1347
  if ( routeEntryPoints.length > 0 ) {
1282
- const routePlugins = [
1283
- wordpressExternalsPlugin( 'route.min', 'esm' ),
1284
- ];
1285
-
1286
1348
  // Build both minified and non-minified versions in parallel
1287
1349
  await Promise.all( [
1288
1350
  esbuild.build( {
@@ -1293,7 +1355,14 @@ async function buildRoute( routeName ) {
1293
1355
  target: browserslistToEsbuild(),
1294
1356
  minify: true,
1295
1357
  define: getDefine( false ),
1296
- plugins: routePlugins,
1358
+ plugins: [
1359
+ wordpressExternalsPlugin(
1360
+ 'route.min',
1361
+ 'esm',
1362
+ [],
1363
+ true // Generate asset file for minified build
1364
+ ),
1365
+ ],
1297
1366
  } ),
1298
1367
  esbuild.build( {
1299
1368
  entryPoints: routeEntryPoints,
@@ -1303,7 +1372,14 @@ async function buildRoute( routeName ) {
1303
1372
  target: browserslistToEsbuild(),
1304
1373
  minify: false,
1305
1374
  define: getDefine( true ),
1306
- plugins: routePlugins,
1375
+ plugins: [
1376
+ wordpressExternalsPlugin(
1377
+ 'route.min',
1378
+ 'esm',
1379
+ [],
1380
+ false // Skip asset file for non-minified build
1381
+ ),
1382
+ ],
1307
1383
  } ),
1308
1384
  ] );
1309
1385
  }
@@ -1318,11 +1394,6 @@ async function buildRoute( routeName ) {
1318
1394
  // Write temporary entry file
1319
1395
  await writeFile( tempEntryPath, syntheticEntry );
1320
1396
 
1321
- const contentPlugins = [
1322
- wordpressExternalsPlugin( 'content.min', 'esm' ),
1323
- ...styleBundlingPlugins,
1324
- ];
1325
-
1326
1397
  // Build both minified and non-minified versions in parallel
1327
1398
  await Promise.all( [
1328
1399
  esbuild.build( {
@@ -1333,7 +1404,15 @@ async function buildRoute( routeName ) {
1333
1404
  target: browserslistToEsbuild(),
1334
1405
  minify: true,
1335
1406
  define: getDefine( false ),
1336
- plugins: contentPlugins,
1407
+ plugins: [
1408
+ wordpressExternalsPlugin(
1409
+ 'content.min',
1410
+ 'esm',
1411
+ [],
1412
+ true // Generate asset file for minified build
1413
+ ),
1414
+ ...styleBundlingPlugins,
1415
+ ],
1337
1416
  } ),
1338
1417
  esbuild.build( {
1339
1418
  entryPoints: [ tempEntryPath ],
@@ -1343,7 +1422,15 @@ async function buildRoute( routeName ) {
1343
1422
  target: browserslistToEsbuild(),
1344
1423
  minify: false,
1345
1424
  define: getDefine( true ),
1346
- plugins: contentPlugins,
1425
+ plugins: [
1426
+ wordpressExternalsPlugin(
1427
+ 'content.min',
1428
+ 'esm',
1429
+ [],
1430
+ false // Skip asset file for non-minified build
1431
+ ),
1432
+ ...styleBundlingPlugins,
1433
+ ],
1347
1434
  } ),
1348
1435
  ] );
1349
1436
 
@@ -1380,24 +1467,28 @@ async function buildAllRoutes() {
1380
1467
 
1381
1468
  /**
1382
1469
  * Main build function.
1470
+ *
1471
+ * @param {string?} baseUrlExpression
1383
1472
  */
1384
- async function buildAll() {
1473
+ async function buildAll( baseUrlExpression ) {
1385
1474
  console.log( 'šŸ”Ø Building packages...\n' );
1386
1475
 
1387
1476
  const startTime = Date.now();
1388
1477
 
1389
- // Build maps: short name ↔ full name from package.json
1478
+ // Build maps: short name ↔ full name ↔ package.json from package.json files
1390
1479
  const shortToFull = new Map();
1391
1480
  const fullToShort = new Map();
1481
+ const fullToPackageJson = new Map();
1392
1482
  for ( const pkg of PACKAGES ) {
1393
1483
  const packageJson = getPackageInfoFromFile(
1394
1484
  path.join( PACKAGES_DIR, pkg, 'package.json' )
1395
1485
  );
1396
1486
  shortToFull.set( pkg, packageJson.name );
1397
1487
  fullToShort.set( packageJson.name, pkg );
1488
+ fullToPackageJson.set( packageJson.name, packageJson );
1398
1489
  }
1399
1490
 
1400
- const levels = groupByDepth( Array.from( shortToFull.values() ) );
1491
+ const levels = groupByDepth( fullToPackageJson );
1401
1492
 
1402
1493
  console.log( 'šŸ“ Phase 1: Transpiling packages...\n' );
1403
1494
 
@@ -1444,26 +1535,40 @@ async function buildAll() {
1444
1535
  await buildAllRoutes();
1445
1536
 
1446
1537
  // Collect route and page data for PHP generation
1447
- const routes = getAllRoutes( ROOT_DIR ).map( ( routeName ) => {
1538
+ // Use flatMap to expand routes with multiple pages into separate entries
1539
+ const routes = getAllRoutes( ROOT_DIR ).flatMap( ( routeName ) => {
1448
1540
  const metadata = getRouteMetadata( ROOT_DIR, routeName );
1541
+
1542
+ // Skip routes without pages
1543
+ if ( ! metadata || ! metadata.pages || metadata.pages.length === 0 ) {
1544
+ return [];
1545
+ }
1449
1546
  const routeFiles = getRouteFiles(
1450
1547
  path.join( ROOT_DIR, 'routes', routeName )
1451
1548
  );
1452
- return {
1453
- name: routeName,
1454
- path: metadata?.path,
1455
- page: metadata?.page,
1456
- hasRoute: routeFiles.hasRoute,
1457
- hasContent: routeFiles.hasStage || routeFiles.hasInspector,
1458
- };
1549
+
1550
+ // Create a route entry for each page
1551
+ return metadata.pages.map( ( page ) => {
1552
+ return {
1553
+ name: routeName,
1554
+ path: metadata.path,
1555
+ page,
1556
+ hasRoute: routeFiles.hasRoute,
1557
+ hasContent: routeFiles.hasStage || routeFiles.hasInspector,
1558
+ };
1559
+ } );
1459
1560
  } );
1460
1561
 
1461
1562
  // Normalize PAGES config to support both string and object formats
1462
1563
  const normalizedPages = PAGES.map( ( page ) => {
1463
1564
  if ( typeof page === 'string' ) {
1464
- return { id: page, init: [] };
1565
+ return { id: page, init: [], title: undefined };
1465
1566
  }
1466
- return { id: page.id, init: page.init || [] };
1567
+ return {
1568
+ id: page.id,
1569
+ init: page.init || [],
1570
+ title: page.title || undefined,
1571
+ };
1467
1572
  } );
1468
1573
 
1469
1574
  const pageData = normalizedPages.map( ( page ) => {
@@ -1472,6 +1577,7 @@ async function buildAll() {
1472
1577
  slug: page.id,
1473
1578
  routes: pageRoutes,
1474
1579
  initModules: page.init,
1580
+ title: page.title,
1475
1581
  };
1476
1582
  } );
1477
1583
 
@@ -1521,7 +1627,10 @@ async function buildAll() {
1521
1627
  }
1522
1628
 
1523
1629
  console.log( '\nšŸ“„ Generating PHP registration files...\n' );
1524
- const phpReplacements = await getPhpReplacements( ROOT_DIR );
1630
+ const phpReplacements = await getPhpReplacements(
1631
+ ROOT_DIR,
1632
+ baseUrlExpression
1633
+ );
1525
1634
  await Promise.all( [
1526
1635
  generateMainIndexPhp( phpReplacements ),
1527
1636
  generateModuleRegistrationPhp( modules, phpReplacements ),
@@ -1566,17 +1675,18 @@ async function watchMode() {
1566
1675
  let isRebuilding = false;
1567
1676
  const needsRebuild = new Set();
1568
1677
 
1569
- // Build maps: short name ↔ full name from package.json (once)
1678
+ // Build maps: short name ↔ full name ↔ package.json from package.json files (once)
1570
1679
  const shortToFull = new Map();
1571
1680
  const fullToShort = new Map();
1681
+ const fullToPackageJson = new Map();
1572
1682
  for ( const pkg of PACKAGES ) {
1573
1683
  const packageJson = getPackageInfoFromFile(
1574
1684
  path.join( PACKAGES_DIR, pkg, 'package.json' )
1575
1685
  );
1576
1686
  shortToFull.set( pkg, packageJson.name );
1577
1687
  fullToShort.set( packageJson.name, pkg );
1688
+ fullToPackageJson.set( packageJson.name, packageJson );
1578
1689
  }
1579
- const allFullNames = Array.from( shortToFull.values() );
1580
1690
 
1581
1691
  // Get all routes for dependency tracking
1582
1692
  const allRoutes = getAllRoutes( ROOT_DIR );
@@ -1599,7 +1709,7 @@ async function watchMode() {
1599
1709
  const fullName = shortToFull.get( packageName );
1600
1710
  const affectedScripts = findScriptsToRebundle(
1601
1711
  fullName,
1602
- allFullNames
1712
+ fullToPackageJson
1603
1713
  );
1604
1714
 
1605
1715
  for ( const fullScript of affectedScripts ) {
@@ -1622,7 +1732,7 @@ async function watchMode() {
1622
1732
  // Find and rebuild affected routes
1623
1733
  const affectedRoutes = findRoutesToRebuild(
1624
1734
  fullName,
1625
- allFullNames,
1735
+ fullToPackageJson,
1626
1736
  ROOT_DIR,
1627
1737
  allRoutes
1628
1738
  );
@@ -1815,10 +1925,16 @@ async function main() {
1815
1925
  short: 'w',
1816
1926
  default: false,
1817
1927
  },
1928
+ 'base-url': {
1929
+ type: 'string',
1930
+ default: "plugins_url( 'build', dirname( __FILE__ ) )",
1931
+ },
1818
1932
  },
1819
1933
  } );
1820
1934
 
1821
- await buildAll();
1935
+ const baseUrlExpression = values[ 'base-url' ];
1936
+
1937
+ await buildAll( baseUrlExpression );
1822
1938
 
1823
1939
  if ( values.watch ) {
1824
1940
  console.log( '\nšŸ‘€ Watching for changes...\n' );