@10up/wp-vite-plugins 1.0.0-alpha.2 → 1.0.0-alpha.3

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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { resolve, join, relative, dirname, extname } from 'path';
2
- import { readdirSync, existsSync, readFileSync, realpathSync } from 'fs';
1
+ import { resolve, join, relative, dirname, extname, isAbsolute } from 'path';
2
+ import { readdirSync, existsSync, readFileSync, rmSync, mkdirSync, writeFileSync, realpathSync } from 'fs';
3
3
  import crypto, { createHash } from 'crypto';
4
4
  import fg from 'fast-glob';
5
5
  import { init, parse } from 'es-module-lexer';
@@ -874,6 +874,599 @@ function ${prefix}_register_page_routes( $page_routes, $register_function_name )
874
874
  ${pageFunctions}
875
875
  `;
876
876
  }
877
+ function renderDevServerPhp(ctx) {
878
+ const prefixUnderscore = toUnderscore(ctx.prefix);
879
+ const fn = `${prefixUnderscore}_wp_vite_dev`;
880
+ const client = `${ctx.prefix}-vite-client`;
881
+ const checkConstant = `${prefixUnderscore.toUpperCase()}_WP_VITE_CHECK_DEV_SERVER`;
882
+ const checkFilter = `${prefixUnderscore}_wp_vite_check_dev_server`;
883
+ return `<?php
884
+ /**
885
+ * Vite dev-server bridge.
886
+ * Auto-generated by build process. Do not edit this file manually.
887
+ *
888
+ * Require it once, unconditionally, next to the rest of your dist requires:
889
+ *
890
+ * $wp_vite_dev = __DIR__ . '/dist/dev-server.php';
891
+ * if ( file_exists( $wp_vite_dev ) ) {
892
+ * require_once $wp_vite_dev;
893
+ * }
894
+ *
895
+ * With no dev server running it costs a single \`file_exists()\` and returns.
896
+ * When ${ctx.hotFile} is sitting next to it, it repoints every script,
897
+ * stylesheet and Script Module WordPress would have loaded out of this
898
+ * directory at the running Vite server instead.
899
+ *
900
+ * @package ${ctx.prefix}
901
+ */
902
+
903
+ /*
904
+ * Two loaders, a theme that also boots its own mu-plugin: requiring this file
905
+ * twice must not redeclare anything or stack a second set of filters.
906
+ *
907
+ * The guard has to *wrap* the declarations rather than \`return\` above them.
908
+ * PHP binds an unconditionally-declared top-level function when it compiles the
909
+ * file \u2014 before a single statement in it runs \u2014 so an early return would fatal
910
+ * on the second require anyway. Conditional declarations are bound at run time,
911
+ * which is what makes the check mean something.
912
+ */
913
+ if ( ! function_exists( '${fn}_hot' ) ) :
914
+
915
+ /**
916
+ * Whether ${fn}_hot() should verify the dev server is actually reachable
917
+ * before trusting the hot file's mere presence.
918
+ *
919
+ * Off by default. Every other check in this bridge is a single
920
+ * \`file_exists()\` because a running dev server keeps that contract
921
+ * deliberately \u2014 write on 'listening', delete on exit, exit handlers bound
922
+ * once. The one hole is a \`kill -9\`'d process, which leaves the file behind
923
+ * pointing at a socket nothing is listening on any more. Closing that hole
924
+ * costs a real HTTP request \u2014 timed short, but still a round trip \u2014 on every
925
+ * single request this bridge participates in, so it stays opt-in rather than
926
+ * something every site pays for.
927
+ *
928
+ * Enable it with the \`${checkConstant}\` constant, or the
929
+ * \`${checkFilter}\` filter for anything more dynamic (e.g. only inside a
930
+ * container where \`kill -9\`ing the dev server is routine).
931
+ *
932
+ * @return bool
933
+ */
934
+ function ${fn}_should_verify_dev_server() {
935
+ $default = defined( '${checkConstant}' ) && constant( '${checkConstant}' );
936
+
937
+ /**
938
+ * Filters whether the dev-server bridge verifies the running server is
939
+ * reachable before trusting the hot file.
940
+ *
941
+ * @param bool $verify Whether to probe. Default from the
942
+ * ${checkConstant} constant, else false.
943
+ */
944
+ return (bool) apply_filters( '${checkFilter}', $default );
945
+ }
946
+
947
+ /**
948
+ * The decoded hot file, or null when no dev server is running.
949
+ *
950
+ * The presence of the file is the entire protocol \u2014 a running server writes it
951
+ * on 'listening' and deletes it on exit. Cached in a static because this is on
952
+ * the request path for every enqueued asset.
953
+ *
954
+ * @return array|null Array with 'origin', 'base' and 'plugins' keys, or null.
955
+ */
956
+ function ${fn}_hot() {
957
+ static $hot = false;
958
+
959
+ if ( false !== $hot ) {
960
+ return $hot;
961
+ }
962
+
963
+ $hot = null;
964
+ $hot_file = __DIR__ . '/${ctx.hotFile}';
965
+
966
+ if ( ! file_exists( $hot_file ) ) {
967
+ return $hot;
968
+ }
969
+
970
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
971
+ $contents = file_get_contents( $hot_file );
972
+ $decoded = is_string( $contents ) ? json_decode( $contents, true ) : null;
973
+
974
+ if ( ! is_array( $decoded ) || empty( $decoded['origin'] ) || ! is_string( $decoded['origin'] ) ) {
975
+ return $hot;
976
+ }
977
+
978
+ $base = isset( $decoded['base'] ) && is_string( $decoded['base'] ) ? $decoded['base'] : '/';
979
+ $base = '/' . ltrim( $base, '/' );
980
+ if ( '/' !== substr( $base, -1 ) ) {
981
+ $base .= '/';
982
+ }
983
+
984
+ $hot = array(
985
+ 'origin' => rtrim( $decoded['origin'], '/' ),
986
+ 'base' => $base,
987
+ 'plugins' => isset( $decoded['plugins'] ) && is_array( $decoded['plugins'] ) ? $decoded['plugins'] : array(),
988
+ );
989
+
990
+ // Stale hot file: the file is there, but nothing answers for it (a
991
+ // \`kill -9\`'d dev server). See ${fn}_should_verify_dev_server() above \u2014
992
+ // off by default, so this whole block is skipped on the common path.
993
+ if ( ${fn}_should_verify_dev_server() && function_exists( 'wp_remote_head' ) ) {
994
+ $response = wp_remote_head(
995
+ $hot['origin'] . $hot['base'] . '@vite/client',
996
+ array( 'timeout' => 0.2, 'blocking' => true )
997
+ );
998
+
999
+ $status_code = is_wp_error( $response ) ? 0 : (int) wp_remote_retrieve_response_code( $response );
1000
+
1001
+ if ( $status_code < 200 || $status_code >= 400 ) {
1002
+ $hot = null;
1003
+ }
1004
+ }
1005
+
1006
+ return $hot;
1007
+ }
1008
+
1009
+ /**
1010
+ * Turn a project-relative source path into the URL the dev server serves it at.
1011
+ *
1012
+ * @param string $source Project-relative path from an .asset.php 'source' key.
1013
+ * @return string Absolute URL, or '' when no dev server is running.
1014
+ */
1015
+ function ${fn}_url( $source ) {
1016
+ $hot = ${fn}_hot();
1017
+
1018
+ if ( null === $hot ) {
1019
+ return '';
1020
+ }
1021
+
1022
+ return $hot['origin'] . $hot['base'] . ltrim( (string) $source, '/' );
1023
+ }
1024
+
1025
+ /**
1026
+ * URL of the directory this file lives in.
1027
+ *
1028
+ * Derived from __DIR__ rather than configured, so the same generated file works
1029
+ * whether it was built into a theme, a plugin or an mu-plugin. The candidates
1030
+ * are tried most-specific first: content_url() is the general answer, but a
1031
+ * theme under a custom theme root \u2014 or anything reached through a symlink \u2014 is
1032
+ * only spelled correctly by its own helper.
1033
+ *
1034
+ * @return string Trailing-slashed URL, or '' when this directory is outside
1035
+ * every location WordPress can build a URL for.
1036
+ */
1037
+ function ${fn}_dist_url() {
1038
+ static $dist_url = false;
1039
+
1040
+ if ( false !== $dist_url ) {
1041
+ return $dist_url;
1042
+ }
1043
+
1044
+ $dist_url = '';
1045
+ $dir = rtrim( str_replace( '\\\\', '/', __DIR__ ), '/' );
1046
+ $roots = array();
1047
+
1048
+ if ( function_exists( 'get_stylesheet_directory' ) && function_exists( 'get_stylesheet_directory_uri' ) ) {
1049
+ $roots[] = array( get_stylesheet_directory(), get_stylesheet_directory_uri() );
1050
+ }
1051
+ if ( function_exists( 'get_template_directory' ) && function_exists( 'get_template_directory_uri' ) ) {
1052
+ $roots[] = array( get_template_directory(), get_template_directory_uri() );
1053
+ }
1054
+ if ( defined( 'WPMU_PLUGIN_DIR' ) && defined( 'WPMU_PLUGIN_URL' ) ) {
1055
+ $roots[] = array( WPMU_PLUGIN_DIR, WPMU_PLUGIN_URL );
1056
+ }
1057
+ if ( defined( 'WP_PLUGIN_DIR' ) && defined( 'WP_PLUGIN_URL' ) ) {
1058
+ $roots[] = array( WP_PLUGIN_DIR, WP_PLUGIN_URL );
1059
+ }
1060
+ if ( defined( 'WP_CONTENT_DIR' ) && function_exists( 'content_url' ) ) {
1061
+ $roots[] = array( WP_CONTENT_DIR, content_url() );
1062
+ }
1063
+
1064
+ foreach ( $roots as $root ) {
1065
+ list( $root_dir, $root_url ) = $root;
1066
+
1067
+ if ( ! is_string( $root_dir ) || ! is_string( $root_url ) || '' === $root_dir || '' === $root_url ) {
1068
+ continue;
1069
+ }
1070
+
1071
+ $root_dir = rtrim( str_replace( '\\\\', '/', $root_dir ), '/' );
1072
+
1073
+ if ( '' === $root_dir || 0 !== strpos( $dir . '/', $root_dir . '/' ) ) {
1074
+ continue;
1075
+ }
1076
+
1077
+ $dist_url = rtrim( $root_url, '/' ) . substr( $dir, strlen( $root_dir ) ) . '/';
1078
+ break;
1079
+ }
1080
+
1081
+ return $dist_url;
1082
+ }
1083
+
1084
+ /**
1085
+ * Map an asset URL pointing into this directory back to the file the dev
1086
+ * server serves it from.
1087
+ *
1088
+ * The lookup is the .asset.php sidecar next to where the built file would have
1089
+ * been: dist-relative URL, extension stripped, '.asset.php' appended. Its
1090
+ * 'source' key \u2014 written by both a build and a running dev server \u2014 is the
1091
+ * project-relative path of the file Vite compiles on demand.
1092
+ *
1093
+ * @param string $url Asset URL as WordPress is about to print it, '?ver=' and all.
1094
+ * @return string|null Project-relative source path, or null when this URL is
1095
+ * not ours or has no sidecar.
1096
+ */
1097
+ function ${fn}_source( $url ) {
1098
+ static $cache = array();
1099
+
1100
+ if ( ! is_string( $url ) || '' === $url ) {
1101
+ return null;
1102
+ }
1103
+
1104
+ $dist_url = ${fn}_dist_url();
1105
+
1106
+ if ( '' === $dist_url ) {
1107
+ return null;
1108
+ }
1109
+
1110
+ // The filters hand us the versioned URL; the sidecar lives at the
1111
+ // unversioned path.
1112
+ $query = strpos( $url, '?' );
1113
+ if ( false !== $query ) {
1114
+ $url = substr( $url, 0, $query );
1115
+ }
1116
+
1117
+ if ( array_key_exists( $url, $cache ) ) {
1118
+ return $cache[ $url ];
1119
+ }
1120
+
1121
+ $cache[ $url ] = null;
1122
+ $relative = null;
1123
+
1124
+ // A protocol-relative src, and an http/https mismatch between the enqueued
1125
+ // URL and content_url(), are both routine; neither should stop a match. The
1126
+ // path-only form additionally catches root-relative and CDN-fronted srcs.
1127
+ $path_only = preg_replace( '#^([a-z][a-z0-9+.-]*:)?//[^/]*#i', '', $dist_url );
1128
+ foreach ( array( $dist_url, is_string( $path_only ) ? $path_only : '' ) as $prefix ) {
1129
+ if ( '' === $prefix ) {
1130
+ continue;
1131
+ }
1132
+
1133
+ $bare_prefix = preg_replace( '#^[a-z][a-z0-9+.-]*:#i', '', $prefix );
1134
+ $bare_url = preg_replace( '#^[a-z][a-z0-9+.-]*:#i', '', $url );
1135
+
1136
+ if ( 0 !== strpos( $bare_url, $bare_prefix ) ) {
1137
+ continue;
1138
+ }
1139
+
1140
+ $relative = substr( $bare_url, strlen( $bare_prefix ) );
1141
+ break;
1142
+ }
1143
+
1144
+ // The relative path reaches a require(), and it came out of a URL. Nothing
1145
+ // but the shape a build actually emits gets through.
1146
+ if ( null === $relative || ! preg_match( '#^[A-Za-z0-9_][A-Za-z0-9_./-]*$#', $relative ) || false !== strpos( $relative, '..' ) ) {
1147
+ return null;
1148
+ }
1149
+
1150
+ $asset_file = __DIR__ . '/' . preg_replace( '/\\.(js|mjs|css)$/', '', $relative ) . '.asset.php';
1151
+
1152
+ if ( ! file_exists( $asset_file ) ) {
1153
+ return null;
1154
+ }
1155
+
1156
+ $asset = require $asset_file;
1157
+
1158
+ if ( ! is_array( $asset ) || empty( $asset['source'] ) || ! is_string( $asset['source'] ) ) {
1159
+ return null;
1160
+ }
1161
+
1162
+ $cache[ $url ] = $asset['source'];
1163
+
1164
+ return $cache[ $url ];
1165
+ }
1166
+
1167
+ /**
1168
+ * Registry of handles whose <script> tag has to be stamped type="module".
1169
+ *
1170
+ * Everything the dev server serves is ESM \u2014 that is the point of dev mode \u2014 so
1171
+ * every handle this file repoints joins the set.
1172
+ *
1173
+ * @param string $handle Script handle.
1174
+ * @param bool $mark Add the handle to the registry.
1175
+ * @return bool Whether the handle prints as a module.
1176
+ */
1177
+ function ${fn}_is_module( $handle, $mark = false ) {
1178
+ static $handles = array();
1179
+
1180
+ if ( $mark ) {
1181
+ $handles[ $handle ] = true;
1182
+ }
1183
+
1184
+ return isset( $handles[ $handle ] );
1185
+ }
1186
+
1187
+ // Nothing below this line matters when no dev server is running.
1188
+ if ( null === ${fn}_hot() ) {
1189
+ return;
1190
+ }
1191
+
1192
+ /**
1193
+ * Register and enqueue Vite's HMR client, early.
1194
+ *
1195
+ * It opens the websocket, applies hot updates, and renders the error overlay.
1196
+ * Every handle this file repoints gets it as a dependency, so it is always the
1197
+ * first module in the document.
1198
+ */
1199
+ function ${fn}_enqueue_client() {
1200
+ $src = ${fn}_url( '@vite/client' );
1201
+
1202
+ if ( '' === $src ) {
1203
+ return;
1204
+ }
1205
+
1206
+ wp_register_script( '${client}', $src, array(), null, false );
1207
+ wp_enqueue_script( '${client}' );
1208
+ ${fn}_is_module( '${client}', true );
1209
+
1210
+ ${fn}_enqueue_react_refresh_preamble();
1211
+ }
1212
+ add_action( 'wp_enqueue_scripts', '${fn}_enqueue_client', -1000 );
1213
+ add_action( 'admin_enqueue_scripts', '${fn}_enqueue_client', -1000 );
1214
+
1215
+ /**
1216
+ * Attach the React Fast Refresh preamble to the vite-client handle, when the
1217
+ * running dev server reports \`vite:react-refresh\` in the hot file's plugin
1218
+ * list.
1219
+ *
1220
+ * The preamble has to run before any React component module evaluates \u2014
1221
+ * @vitejs/plugin-react's transform assumes \`window.$RefreshReg$\` already
1222
+ * exists, and every transformed component module throws otherwise. Attaching
1223
+ * it to the vite-client handle rather than to each rewritten script gets that
1224
+ * ordering for free, two different ways depending on how the component reaches
1225
+ * the page:
1226
+ *
1227
+ * - **Classic scripts.** ${fn}_prepare() (below) makes the client a
1228
+ * dependency of every classic handle this bridge repoints, so WordPress's
1229
+ * own dependency graph orders the client \u2014 and the inline block
1230
+ * \`wp_add_inline_script( \u2026, 'after' )\` attaches to it \u2014 before them.
1231
+ * - **Script Modules.** There is no cross-system dependency a classic handle
1232
+ * can declare on a Script Module, so this relies on where WordPress prints
1233
+ * each group instead: the client is a classic-registered handle (its
1234
+ * \`type="module"\` is only a printed attribute, stamped by
1235
+ * ${fn}_script_tag() below), and every generated page in this package
1236
+ * prints classic scripts \u2014 the client included \u2014 before it prints Script
1237
+ * Modules. Same-document module scripts without \`async\` execute in that
1238
+ * print order, so the preamble still runs first.
1239
+ *
1240
+ * Same mechanics vite-for-wp uses for the classic-script half.
1241
+ *
1242
+ * Guarded by a static so a request that somehow fires both enqueue hooks
1243
+ * cannot attach the preamble, and its type="module" filter, twice.
1244
+ */
1245
+ function ${fn}_enqueue_react_refresh_preamble() {
1246
+ static $attached = false;
1247
+
1248
+ if ( $attached ) {
1249
+ return;
1250
+ }
1251
+
1252
+ $hot = ${fn}_hot();
1253
+
1254
+ if ( null === $hot || ! in_array( 'vite:react-refresh', $hot['plugins'], true ) ) {
1255
+ return;
1256
+ }
1257
+
1258
+ $attached = true;
1259
+
1260
+ $preamble = sprintf(
1261
+ 'import RefreshRuntime from "%1$s"
1262
+ RefreshRuntime.injectIntoGlobalHook(window)
1263
+ window.$RefreshReg$ = () => {}
1264
+ window.$RefreshSig$ = () => (type) => type
1265
+ window.__vite_plugin_react_preamble_installed__ = true',
1266
+ esc_js( ${fn}_url( '@react-refresh' ) )
1267
+ );
1268
+
1269
+ wp_add_inline_script( '${client}', $preamble, 'after' );
1270
+ add_filter( 'wp_inline_script_attributes', '${fn}_react_refresh_inline_attributes' );
1271
+ }
1272
+
1273
+ /**
1274
+ * Stamp type="module" on exactly the inline block ${fn}_enqueue_react_refresh_preamble()
1275
+ * attached \u2014 the preamble is a top-level \`import\`, so it must not print as a
1276
+ * classic inline script the way an 'after' block otherwise would.
1277
+ *
1278
+ * WordPress assigns an inline block printed at the 'after' position the id
1279
+ * \`<handle>-js-after\`; matching on it (rather than the handle alone) is what
1280
+ * keeps this from touching some other inline script attached to the same
1281
+ * vite-client handle by unrelated code.
1282
+ *
1283
+ * @param array $attributes Attributes about to be printed on the inline <script> tag.
1284
+ * @return array
1285
+ */
1286
+ function ${fn}_react_refresh_inline_attributes( $attributes ) {
1287
+ if ( is_array( $attributes ) && isset( $attributes['id'] ) && '${client}-js-after' === $attributes['id'] ) {
1288
+ $attributes['type'] = 'module';
1289
+ }
1290
+
1291
+ return $attributes;
1292
+ }
1293
+
1294
+ /**
1295
+ * The one pass that has to happen while the queues are still mutable: wire the
1296
+ * HMR client into everything about to be repointed, and swap this directory's
1297
+ * stylesheets for the JS modules Vite serves them as.
1298
+ *
1299
+ * Runs last on the enqueue hooks \u2014 the URL rewriting itself happens later, in
1300
+ * script_loader_src, which also catches handles registered after this point.
1301
+ */
1302
+ function ${fn}_prepare() {
1303
+ $claimed = array();
1304
+
1305
+ if ( function_exists( 'wp_scripts' ) && function_exists( 'wp_script_is' ) ) {
1306
+ $has_client = wp_script_is( '${client}', 'registered' );
1307
+
1308
+ foreach ( wp_scripts()->registered as $handle => $script ) {
1309
+ if ( '${client}' === $handle || empty( $script->src ) ) {
1310
+ continue;
1311
+ }
1312
+
1313
+ $source = ${fn}_source( $script->src );
1314
+
1315
+ if ( null === $source ) {
1316
+ continue;
1317
+ }
1318
+
1319
+ $claimed[ $source ] = true;
1320
+
1321
+ // Ordering only. A missing client handle would make WordPress drop
1322
+ // the script entirely, so never depend on one that isn't there.
1323
+ if ( $has_client && is_array( $script->deps ) && ! in_array( '${client}', $script->deps, true ) ) {
1324
+ $script->deps[] = '${client}';
1325
+ }
1326
+ }
1327
+ }
1328
+
1329
+ if ( ! function_exists( 'wp_styles' ) ) {
1330
+ return;
1331
+ }
1332
+
1333
+ $styles = wp_styles();
1334
+
1335
+ foreach ( $styles->queue as $handle ) {
1336
+ if ( ! isset( $styles->registered[ $handle ] ) || empty( $styles->registered[ $handle ]->src ) ) {
1337
+ continue;
1338
+ }
1339
+
1340
+ $source = ${fn}_source( $styles->registered[ $handle ]->src );
1341
+
1342
+ if ( null === $source ) {
1343
+ continue;
1344
+ }
1345
+
1346
+ wp_dequeue_style( $handle );
1347
+
1348
+ // A stylesheet whose source is already served by a repointed script is
1349
+ // covered: in dev the entry imports its own CSS.
1350
+ if ( isset( $claimed[ $source ] ) ) {
1351
+ continue;
1352
+ }
1353
+
1354
+ $claimed[ $source ] = true;
1355
+ $shim = $handle . '-vite-dev';
1356
+
1357
+ wp_register_script(
1358
+ $shim,
1359
+ ${fn}_url( $source ),
1360
+ wp_script_is( '${client}', 'registered' ) ? array( '${client}' ) : array(),
1361
+ null,
1362
+ false
1363
+ );
1364
+ wp_enqueue_script( $shim );
1365
+ ${fn}_is_module( $shim, true );
1366
+ }
1367
+ }
1368
+ add_action( 'wp_enqueue_scripts', '${fn}_prepare', PHP_INT_MAX );
1369
+ add_action( 'admin_enqueue_scripts', '${fn}_prepare', PHP_INT_MAX );
1370
+
1371
+ /**
1372
+ * Repoint a classic script at the dev server.
1373
+ *
1374
+ * @param string $src Script URL.
1375
+ * @param string $handle Script handle.
1376
+ * @return string Rewritten URL, or the original.
1377
+ */
1378
+ function ${fn}_script_src( $src, $handle ) {
1379
+ $source = ${fn}_source( $src );
1380
+
1381
+ if ( null === $source ) {
1382
+ return $src;
1383
+ }
1384
+
1385
+ // script_loader_src always runs before script_loader_tag for the same
1386
+ // handle (both live in WP_Scripts::do_item()), so marking here is enough.
1387
+ ${fn}_is_module( $handle, true );
1388
+
1389
+ return ${fn}_url( $source );
1390
+ }
1391
+ add_filter( 'script_loader_src', '${fn}_script_src', 10, 2 );
1392
+
1393
+ /**
1394
+ * Stamp type="module" on the handles this file repointed.
1395
+ *
1396
+ * @param string $tag Script tag HTML.
1397
+ * @param string $handle Script handle.
1398
+ * @return string Rewritten tag.
1399
+ */
1400
+ function ${fn}_script_tag( $tag, $handle ) {
1401
+ if ( ! is_string( $tag ) || ! ${fn}_is_module( $handle ) ) {
1402
+ return $tag;
1403
+ }
1404
+
1405
+ // WP 6.2+. Without it, the crude rewrite is still better than letting an
1406
+ // ESM file load as a classic script and throw on its first import.
1407
+ if ( ! class_exists( 'WP_HTML_Tag_Processor' ) ) {
1408
+ return false === strpos( $tag, 'type="module"' )
1409
+ ? str_replace( '<script ', '<script type="module" ', $tag )
1410
+ : $tag;
1411
+ }
1412
+
1413
+ $processor = new WP_HTML_Tag_Processor( $tag );
1414
+
1415
+ while ( $processor->next_tag( 'script' ) ) {
1416
+ // Inline blocks attached to the same handle stay classic; only the
1417
+ // src'd tag is the module.
1418
+ if ( null === $processor->get_attribute( 'src' ) ) {
1419
+ continue;
1420
+ }
1421
+
1422
+ $processor->set_attribute( 'type', 'module' );
1423
+ }
1424
+
1425
+ return $processor->get_updated_html();
1426
+ }
1427
+ add_filter( 'script_loader_tag', '${fn}_script_tag', 10, 2 );
1428
+
1429
+ /**
1430
+ * Repoint a Script Module at the dev server.
1431
+ *
1432
+ * script_module_loader_src (WP 6.5+) is the single choke point inside
1433
+ * WP_Script_Modules::get_src() for all three Script Module outputs \u2014 the
1434
+ * <script type="module"> tag, its <link rel="modulepreload">, and the import
1435
+ * map \u2014 so one filter keeps all three consistent and no consumer registration
1436
+ * has to change.
1437
+ *
1438
+ * @param string $src Script Module URL.
1439
+ * @param string $id Script Module identifier.
1440
+ * @return string Rewritten URL, or the original.
1441
+ */
1442
+ function ${fn}_script_module_src( $src, $id ) {
1443
+ $source = ${fn}_source( $src );
1444
+
1445
+ return null === $source ? $src : ${fn}_url( $source );
1446
+ }
1447
+ add_filter( 'script_module_loader_src', '${fn}_script_module_src', 10, 2 );
1448
+
1449
+ /**
1450
+ * Last resort for a stylesheet ${fn}_prepare() never saw \u2014 one registered
1451
+ * after the enqueue hooks, or pulled in as another stylesheet's dependency.
1452
+ *
1453
+ * Vite's '?direct' serves the compiled CSS with a text/css content type instead
1454
+ * of the JS module a bare .css request returns. Correct styles, no hot reload.
1455
+ *
1456
+ * @param string $src Stylesheet URL.
1457
+ * @param string $handle Stylesheet handle.
1458
+ * @return string Rewritten URL, or the original.
1459
+ */
1460
+ function ${fn}_style_src( $src, $handle ) {
1461
+ $source = ${fn}_source( $src );
1462
+
1463
+ return null === $source ? $src : ${fn}_url( $source ) . '?direct';
1464
+ }
1465
+ add_filter( 'style_loader_src', '${fn}_style_src', 10, 2 );
1466
+
1467
+ endif;
1468
+ `;
1469
+ }
877
1470
  function generatePhpFiles(ctx, pages) {
878
1471
  const files = [
879
1472
  { fileName: "build.php", source: renderBuildPhp(ctx) },
@@ -1815,37 +2408,135 @@ function isBarePackageName(name) {
1815
2408
  }
1816
2409
  return name.length > 0 && !name.includes("/") && !name.startsWith(".");
1817
2410
  }
2411
+ function entrySourcePath(facadeModuleId, root) {
2412
+ if (!facadeModuleId) return null;
2413
+ const relativePath = relative(root, facadeModuleId).replace(/\\/g, "/");
2414
+ if (relativePath === "" || relativePath === ".." || relativePath.startsWith("../")) {
2415
+ return null;
2416
+ }
2417
+ if (isAbsolute(relativePath)) return null;
2418
+ return relativePath;
2419
+ }
2420
+ function phpSingleQuoteEscape(value) {
2421
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
2422
+ }
1818
2423
  var DEFAULT_VENDOR = {
1819
2424
  react: { global: "React", handle: "react" },
1820
2425
  "react-dom": { global: "ReactDOM", handle: "react-dom" },
1821
2426
  "react-dom/client": { global: "ReactDOM", handle: "react-dom" },
1822
- "react/jsx-runtime": { global: "ReactJSXRuntime", handle: "react-jsx-runtime" },
1823
- "react/jsx-dev-runtime": { global: "ReactJSXRuntime", handle: "react-jsx-runtime" },
2427
+ "react/jsx-runtime": {
2428
+ global: "ReactJSXRuntime",
2429
+ handle: "react-jsx-runtime"
2430
+ },
2431
+ "react/jsx-dev-runtime": {
2432
+ global: "ReactJSXRuntime",
2433
+ handle: "react-jsx-runtime"
2434
+ },
1824
2435
  moment: { global: "moment", handle: "moment" },
1825
2436
  jquery: { global: "jQuery", handle: "jquery" },
1826
2437
  lodash: { global: "lodash", handle: "lodash" },
1827
2438
  "lodash-es": { global: "lodash", handle: "lodash" }
1828
2439
  };
1829
2440
  var CJS_GLOBAL_SHIM_PREFIX = "\0wp-external-global:";
2441
+ function matchesScriptModuleNamespace(packageName, scriptModuleNamespaces) {
2442
+ if (!packageName.startsWith("@")) return false;
2443
+ const slashIndex = packageName.indexOf("/");
2444
+ if (slashIndex < 0) return false;
2445
+ const namespace = packageName.slice(1, slashIndex);
2446
+ for (const pattern of scriptModuleNamespaces) {
2447
+ if (pattern.endsWith("*")) {
2448
+ if (namespace.startsWith(pattern.slice(0, -1))) return true;
2449
+ } else if (namespace === pattern) {
2450
+ return true;
2451
+ }
2452
+ }
2453
+ return false;
2454
+ }
2455
+ function resolveExternal(specifier, options) {
2456
+ const projectRoot = options.projectRoot;
2457
+ const vendorExternals = options.vendorExternals ?? DEFAULT_VENDOR;
2458
+ const externalNamespaces = options.externalNamespaces ?? {};
2459
+ const scriptModuleNamespaces = options.scriptModuleNamespaces ?? [];
2460
+ const bundledPackages = options.bundledPackages instanceof Set ? options.bundledPackages : new Set(options.bundledPackages ?? []);
2461
+ if (bundledPackages.has(splitImport(specifier).name)) return null;
2462
+ if (vendorExternals[specifier]) {
2463
+ const vendor = vendorExternals[specifier];
2464
+ return {
2465
+ global: vendor.global,
2466
+ handle: vendor.handle,
2467
+ specifier,
2468
+ moduleEligible: false,
2469
+ scriptEligible: true
2470
+ };
2471
+ }
2472
+ const { name, subpath } = splitImport(specifier);
2473
+ if (name.startsWith("@wordpress/")) {
2474
+ const meta = readPackageMetadata(name, projectRoot);
2475
+ if (!meta) {
2476
+ return null;
2477
+ }
2478
+ const shortName = name.slice("@wordpress/".length);
2479
+ const moduleEligible = isScriptModule(meta, subpath);
2480
+ const scriptEligible = !!meta.wpScript;
2481
+ if (!moduleEligible && !scriptEligible) {
2482
+ return null;
2483
+ }
2484
+ return {
2485
+ global: scriptEligible ? `wp.${camelize(shortName)}` : null,
2486
+ handle: scriptEligible ? `wp-${shortName}` : null,
2487
+ specifier,
2488
+ moduleEligible,
2489
+ scriptEligible
2490
+ };
2491
+ }
2492
+ for (const [namespace, config] of Object.entries(externalNamespaces)) {
2493
+ if (name.startsWith(`@${namespace}/`)) {
2494
+ const shortName = name.slice(`@${namespace}/`.length);
2495
+ return {
2496
+ global: `${config.global}.${camelize(shortName)}`,
2497
+ handle: `${config.handlePrefix}-${shortName}`,
2498
+ specifier,
2499
+ moduleEligible: false,
2500
+ scriptEligible: true
2501
+ };
2502
+ }
2503
+ }
2504
+ if (matchesScriptModuleNamespace(name, scriptModuleNamespaces)) {
2505
+ return {
2506
+ global: null,
2507
+ handle: null,
2508
+ specifier,
2509
+ moduleEligible: true,
2510
+ scriptEligible: false
2511
+ };
2512
+ }
2513
+ return null;
2514
+ }
1830
2515
  var JS_IDENTIFIER = "[A-Za-z_$][\\w$]*";
2516
+ var SIDE_EFFECT_IMPORT = /^\s*import\s*['"][^'"]+['"]\s*;?\s*$/;
1831
2517
  function rewriteImport(importStatement, globalAccess) {
1832
- const match = importStatement.match(/^\s*import\s+(.*?)\s+from\s+['"][^'"]+['"]\s*;?\s*$/s);
1833
- if (!match || !match[1]) return "";
2518
+ if (SIDE_EFFECT_IMPORT.test(importStatement)) return "";
2519
+ const match = importStatement.match(/^\s*import\s*(.*?)\s*from\s*['"][^'"]+['"]\s*;?\s*$/s);
2520
+ if (!match || !match[1]) return null;
1834
2521
  const bindings = match[1].trim();
1835
- const namespaceImport = bindings.match(new RegExp(`^\\*\\s+as\\s+(${JS_IDENTIFIER})$`));
2522
+ const namespaceImport = bindings.match(new RegExp(`^\\*\\s*as\\s+(${JS_IDENTIFIER})$`));
1836
2523
  if (namespaceImport) return `const ${namespaceImport[1]} = ${globalAccess};`;
1837
2524
  const defaultWithNamed = bindings.match(
1838
2525
  new RegExp(`^(${JS_IDENTIFIER})\\s*,\\s*\\{(.+)\\}$`, "s")
1839
2526
  );
1840
2527
  if (defaultWithNamed) {
2528
+ const destructured = namedImportsToDestructuring(defaultWithNamed[2].trim());
2529
+ if (destructured === null) return null;
1841
2530
  return [
1842
2531
  `const ${defaultWithNamed[1]} = (${globalAccess} && ${globalAccess}.default) || ${globalAccess};`,
1843
- `const { ${namedImportsToDestructuring(defaultWithNamed[2].trim())} } = ${globalAccess};`
2532
+ `const { ${destructured} } = ${globalAccess};`
1844
2533
  ].join(" ");
1845
2534
  }
1846
2535
  if (bindings.startsWith("{")) {
1847
2536
  const namedImports = bindings.replace(/^\{|\}$/g, "").trim();
1848
- return `const { ${namedImportsToDestructuring(namedImports)} } = ${globalAccess};`;
2537
+ const destructured = namedImportsToDestructuring(namedImports);
2538
+ if (destructured === null) return null;
2539
+ return `const { ${destructured} } = ${globalAccess};`;
1849
2540
  }
1850
2541
  if (new RegExp(`^${JS_IDENTIFIER}$`).test(bindings)) {
1851
2542
  return `const ${bindings} = (${globalAccess} && ${globalAccess}.default) || ${globalAccess};`;
@@ -1854,10 +2545,23 @@ function rewriteImport(importStatement, globalAccess) {
1854
2545
  }
1855
2546
  function namedImportsToDestructuring(namedImports) {
1856
2547
  const aliasPattern = new RegExp(`^(${JS_IDENTIFIER})\\s+as\\s+(${JS_IDENTIFIER})$`);
1857
- return namedImports.split(",").map((binding) => binding.trim()).filter(Boolean).map((binding) => {
2548
+ const plainPattern = new RegExp(`^${JS_IDENTIFIER}$`);
2549
+ const bindings = [];
2550
+ for (const raw of namedImports.split(",")) {
2551
+ const binding = raw.trim();
2552
+ if (!binding) continue;
1858
2553
  const aliased = binding.match(aliasPattern);
1859
- return aliased ? `${aliased[1]}: ${aliased[2]}` : binding;
1860
- }).join(", ");
2554
+ if (aliased) {
2555
+ bindings.push(`${aliased[1]}: ${aliased[2]}`);
2556
+ continue;
2557
+ }
2558
+ if (plainPattern.test(binding)) {
2559
+ bindings.push(binding);
2560
+ continue;
2561
+ }
2562
+ return null;
2563
+ }
2564
+ return bindings.join(", ");
1861
2565
  }
1862
2566
  function wpExternals(options = {}) {
1863
2567
  const projectRoot = options.projectRoot ?? process.cwd();
@@ -1867,6 +2571,7 @@ function wpExternals(options = {}) {
1867
2571
  const moduleEntryMatchers = options.moduleEntryMatchers ?? [];
1868
2572
  const wpBuildAssetMatchers = options.wpBuildAssetMatchers ?? [];
1869
2573
  const scriptModuleNamespaces = options.scriptModuleNamespaces ?? [];
2574
+ const globalsSink = options.globalsSink;
1870
2575
  for (const name of options.bundle ?? []) {
1871
2576
  if (isBarePackageName(name)) continue;
1872
2577
  const { name: rootName, subpath } = splitImport(name);
@@ -1875,24 +2580,22 @@ function wpExternals(options = {}) {
1875
2580
  );
1876
2581
  }
1877
2582
  const bundledPackages = /* @__PURE__ */ new Set([...DEFAULT_BUNDLED_PACKAGES, ...options.bundle ?? []]);
1878
- function matchesScriptModuleNamespace(packageName) {
1879
- if (!packageName.startsWith("@")) return false;
1880
- const slashIndex = packageName.indexOf("/");
1881
- if (slashIndex < 0) return false;
1882
- const namespace = packageName.slice(1, slashIndex);
1883
- for (const pattern of scriptModuleNamespaces) {
1884
- if (pattern.endsWith("*")) {
1885
- if (namespace.startsWith(pattern.slice(0, -1))) return true;
1886
- } else if (namespace === pattern) {
1887
- return true;
1888
- }
1889
- }
1890
- return false;
2583
+ const resolveExternalOptions = {
2584
+ projectRoot,
2585
+ vendorExternals,
2586
+ externalNamespaces,
2587
+ scriptModuleNamespaces,
2588
+ bundledPackages
2589
+ };
2590
+ function resolveExternalForThisPlugin(specifier) {
2591
+ const external = resolveExternal(specifier, resolveExternalOptions);
2592
+ if (external?.global) globalsSink?.set(external.specifier, external.global);
2593
+ return external;
1891
2594
  }
1892
2595
  function buildModeForFile(moduleId) {
1893
- const filePath = moduleId.split("?")[0] ?? moduleId;
2596
+ const filePath = (moduleId.split("?")[0] ?? moduleId).replace(/\\/g, "/");
1894
2597
  for (const pattern of moduleEntryMatchers) {
1895
- if (filePath.includes(pattern)) return "module";
2598
+ if (filePath.includes(pattern.replace(/\\/g, "/"))) return "module";
1896
2599
  }
1897
2600
  return buildType;
1898
2601
  }
@@ -1904,61 +2607,6 @@ function wpExternals(options = {}) {
1904
2607
  }
1905
2608
  return false;
1906
2609
  }
1907
- function resolveExternal(specifier) {
1908
- if (bundledPackages.has(splitImport(specifier).name)) return null;
1909
- if (vendorExternals[specifier]) {
1910
- const vendor = vendorExternals[specifier];
1911
- return {
1912
- global: vendor.global,
1913
- handle: vendor.handle,
1914
- specifier,
1915
- moduleEligible: false,
1916
- scriptEligible: true
1917
- };
1918
- }
1919
- const { name, subpath } = splitImport(specifier);
1920
- if (name.startsWith("@wordpress/")) {
1921
- const meta = readPackageMetadata(name, projectRoot);
1922
- if (!meta) {
1923
- return null;
1924
- }
1925
- const shortName = name.slice("@wordpress/".length);
1926
- const moduleEligible = isScriptModule(meta, subpath);
1927
- const scriptEligible = !!meta.wpScript;
1928
- if (!moduleEligible && !scriptEligible) {
1929
- return null;
1930
- }
1931
- return {
1932
- global: scriptEligible ? `wp.${camelize(shortName)}` : null,
1933
- handle: scriptEligible ? `wp-${shortName}` : null,
1934
- specifier,
1935
- moduleEligible,
1936
- scriptEligible
1937
- };
1938
- }
1939
- for (const [namespace, config] of Object.entries(externalNamespaces)) {
1940
- if (name.startsWith(`@${namespace}/`)) {
1941
- const shortName = name.slice(`@${namespace}/`.length);
1942
- return {
1943
- global: `${config.global}.${camelize(shortName)}`,
1944
- handle: `${config.handlePrefix}-${shortName}`,
1945
- specifier,
1946
- moduleEligible: false,
1947
- scriptEligible: true
1948
- };
1949
- }
1950
- }
1951
- if (matchesScriptModuleNamespace(name)) {
1952
- return {
1953
- global: null,
1954
- handle: null,
1955
- specifier,
1956
- moduleEligible: true,
1957
- scriptEligible: false
1958
- };
1959
- }
1960
- return null;
1961
- }
1962
2610
  function moduleImportKind(specifier, isDynamic) {
1963
2611
  if (specifier === "@wordpress/interactivity") return "static";
1964
2612
  return isDynamic ? "dynamic" : "static";
@@ -1979,7 +2627,7 @@ function wpExternals(options = {}) {
1979
2627
  enforce: "pre",
1980
2628
  resolveId(source, _importer, options2) {
1981
2629
  const isRequireCall = options2?.kind === "require-call";
1982
- const external = resolveExternal(source);
2630
+ const external = resolveExternalForThisPlugin(source);
1983
2631
  if (external) {
1984
2632
  if (isRequireCall) {
1985
2633
  if (external.global) {
@@ -1994,7 +2642,7 @@ function wpExternals(options = {}) {
1994
2642
  return { id: source, external: true };
1995
2643
  }
1996
2644
  const commonjsMatch = source.match(/^\0([^?]+)\?commonjs-external$/);
1997
- if (commonjsMatch && commonjsMatch[1] && resolveExternal(commonjsMatch[1])) {
2645
+ if (commonjsMatch && commonjsMatch[1] && resolveExternalForThisPlugin(commonjsMatch[1])) {
1998
2646
  return source;
1999
2647
  }
2000
2648
  return null;
@@ -2002,7 +2650,7 @@ function wpExternals(options = {}) {
2002
2650
  load(id) {
2003
2651
  if (id.startsWith(CJS_GLOBAL_SHIM_PREFIX)) {
2004
2652
  const specifier = id.slice(CJS_GLOBAL_SHIM_PREFIX.length);
2005
- const external2 = resolveExternal(specifier);
2653
+ const external2 = resolveExternalForThisPlugin(specifier);
2006
2654
  if (!external2 || !external2.global) return null;
2007
2655
  if (external2.handle) recordDeps(handleDepsByModule, id, [external2.handle]);
2008
2656
  return `module.exports = (typeof window !== "undefined" ? window.${external2.global} : {});
@@ -2010,7 +2658,7 @@ function wpExternals(options = {}) {
2010
2658
  }
2011
2659
  const commonjsMatch = id.match(/^\0([^?]+)\?commonjs-external$/);
2012
2660
  if (!commonjsMatch || !commonjsMatch[1]) return null;
2013
- const external = resolveExternal(commonjsMatch[1]);
2661
+ const external = resolveExternalForThisPlugin(commonjsMatch[1]);
2014
2662
  if (!external) return null;
2015
2663
  if (buildModeForFile(id) === "module" && external.moduleEligible) {
2016
2664
  return `export default {};`;
@@ -2022,9 +2670,13 @@ function wpExternals(options = {}) {
2022
2670
  return `const __wpExternal = (typeof window !== "undefined" ? window.${external.global} : {}); export default __wpExternal;`;
2023
2671
  }
2024
2672
  };
2673
+ let resolvedRoot = projectRoot;
2025
2674
  const transformPlugin = {
2026
2675
  name: `wp-externals(${buildType})`,
2027
2676
  enforce: "post",
2677
+ configResolved(config) {
2678
+ resolvedRoot = config.root;
2679
+ },
2028
2680
  async transform(code, id) {
2029
2681
  if (!code.includes("@wordpress/") && !Object.keys(vendorExternals).some((packageName) => code.includes(packageName)) && !Object.keys(externalNamespaces).some(
2030
2682
  (namespace) => code.includes(`@${namespace}/`)
@@ -2064,7 +2716,7 @@ Most likely cause: JSX in a file that isn't .jsx/.tsx. JSX must live in .jsx/.ts
2064
2716
  for (const importRecord of imports) {
2065
2717
  const importedSpecifier = importRecord.n;
2066
2718
  if (importedSpecifier === void 0) continue;
2067
- const external = resolveExternal(importedSpecifier);
2719
+ const external = resolveExternalForThisPlugin(importedSpecifier);
2068
2720
  if (!external) continue;
2069
2721
  const isDynamic = importRecord.d > -1;
2070
2722
  if (fileMode === "script") {
@@ -2198,6 +2850,10 @@ Most likely cause: JSX in a file that isn't .jsx/.tsx. JSX must live in .jsx/.ts
2198
2850
  }
2199
2851
  fields.push(`'version' => '${version}'`);
2200
2852
  if (entryMode === "module") fields.push(`'type' => 'module'`);
2853
+ const sourcePath = entrySourcePath(output.facadeModuleId, resolvedRoot);
2854
+ if (sourcePath) {
2855
+ fields.push(`'source' => '${phpSingleQuoteEscape(sourcePath)}'`);
2856
+ }
2201
2857
  this.emitFile({
2202
2858
  type: "asset",
2203
2859
  fileName: fileName.replace(/\.m?js$/, ".asset.php"),
@@ -2558,7 +3214,14 @@ var CLASSIC_SCRIPT_DEFINE = {
2558
3214
  "import.meta.resolve": "undefined"
2559
3215
  };
2560
3216
  var LIVE_MODULE_STATEMENT = /^[ \t]*(?:import\s*(?:[{*"']|[A-Za-z_$][\w$]*\s*(?:,|from\b))|export\s*(?:[{*]|(?:default|const|let|var|function|class|async)\b))[^\n]*/m;
2561
- function wpScriptIife() {
3217
+ var ROLLDOWN_REQUIRE_SHIM = /typeof\s+require\s*(?:<|!==?|===?)/;
3218
+ var REQUIRE_CALL = /(?:^|[^.\w$])require\s*\(/;
3219
+ function buildRequireShim(globals) {
3220
+ const cases = [...globals.entries()].map(([specifier, global]) => `case ${JSON.stringify(specifier)}:return window.${global};`).join("");
3221
+ return `const require=(id)=>{switch(id){${cases}}throw new Error('require("'+id+'") is not mapped to a global in this classic script.');};`;
3222
+ }
3223
+ function wpScriptIife(options = {}) {
3224
+ const globals = options.globals ?? /* @__PURE__ */ new Map();
2562
3225
  return {
2563
3226
  name: "wp-script-iife",
2564
3227
  // Build-only. Vite filters plugins by `apply` before it runs `config`,
@@ -2574,34 +3237,68 @@ function wpScriptIife() {
2574
3237
  }
2575
3238
  return Object.keys(define).length > 0 ? { define } : null;
2576
3239
  },
2577
- async writeBundle(options, bundle) {
3240
+ async writeBundle(options2, bundle) {
2578
3241
  const { readFile, writeFile } = await import('fs/promises');
2579
- const { join: join5 } = await import('path');
2580
- const outDir = options.dir ?? ".";
3242
+ const { join: join6 } = await import('path');
3243
+ const outDir = options2.dir ?? ".";
3244
+ const pending = [];
3245
+ const failures = [];
2581
3246
  for (const [fileName, output] of Object.entries(bundle)) {
2582
3247
  if (output.type !== "chunk") continue;
2583
3248
  if (!fileName.endsWith(".js") && !fileName.endsWith(".mjs")) continue;
2584
3249
  if (!output.isEntry && !output.isDynamicEntry) continue;
2585
- const filePath = join5(outDir, fileName);
3250
+ const filePath = join6(outDir, fileName);
2586
3251
  let chunkSource = await readFile(filePath, "utf8");
2587
- const sideEffectImport = /^\s*import\s+["'][^"']+["'];?\s*\n?/;
3252
+ const sideEffectImport = /^\s*import\s*["'][^"']+["'];?\s*\n?/;
2588
3253
  while (sideEffectImport.test(chunkSource)) {
2589
3254
  chunkSource = chunkSource.replace(sideEffectImport, "");
2590
3255
  }
2591
- assertClassicScript(fileName, chunkSource, output.isEntry !== true);
2592
- await writeFile(filePath, `(()=>{
3256
+ const failure = classicScriptFailure(
3257
+ fileName,
3258
+ chunkSource,
3259
+ output.isEntry !== true
3260
+ );
3261
+ if (failure) {
3262
+ failures.push(failure);
3263
+ continue;
3264
+ }
3265
+ if (ROLLDOWN_REQUIRE_SHIM.test(chunkSource)) {
3266
+ pending.push({
3267
+ filePath,
3268
+ body: `${buildRequireShim(globals)}(()=>{
2593
3269
  ${chunkSource}
3270
+ })();`
3271
+ });
3272
+ continue;
3273
+ }
3274
+ if (globals.size > 0 && REQUIRE_CALL.test(chunkSource)) {
3275
+ this.warn(
3276
+ `${fileName} calls \`require()\` but carries none of Rolldown's runtime require shim, so no global mapping was injected. If this is a CJS dependency reaching for an external, it will throw at runtime \u2014 please report it, as the shim's shape may have changed.`
3277
+ );
3278
+ }
3279
+ pending.push({ filePath, body: chunkSource });
3280
+ }
3281
+ if (failures.length > 0) {
3282
+ throw new Error(
3283
+ `wp-script-iife: ${failures.length} chunk(s) are not valid as classic scripts, so nothing was wrapped:
3284
+
3285
+ ${failures.join("\n\n")}`
3286
+ );
3287
+ }
3288
+ for (const { filePath, body } of pending) {
3289
+ await writeFile(filePath, `(()=>{
3290
+ ${body}
2594
3291
  })();
2595
3292
  `);
2596
3293
  }
2597
3294
  }
2598
3295
  };
2599
3296
  }
2600
- function assertClassicScript(fileName, code, isDynamicOnly = false) {
3297
+ function classicScriptFailure(fileName, code, isDynamicOnly = false) {
2601
3298
  let parseError;
2602
3299
  try {
2603
3300
  new Function(code);
2604
- return;
3301
+ return null;
2605
3302
  } catch (error) {
2606
3303
  if (!(error instanceof SyntaxError)) throw error;
2607
3304
  parseError = error;
@@ -2609,24 +3306,19 @@ function assertClassicScript(fileName, code, isDynamicOnly = false) {
2609
3306
  const liveStatement = code.match(LIVE_MODULE_STATEMENT);
2610
3307
  if (liveStatement) {
2611
3308
  const specifiers = [...code.matchAll(/^[ \t]*(?:import|export)\b[^\n]*?["']([^"']+)["']/gm)].map((match) => match[1]).filter((specifier, index, all) => all.indexOf(specifier) === index);
2612
- throw new Error(
2613
- `wp-script-iife: ${fileName} still contains a top-level module statement, which is a SyntaxError once WordPress enqueues it as a classic script:
3309
+ return `wp-script-iife: ${fileName} still contains a top-level module statement, which is a SyntaxError once WordPress enqueues it as a classic script:
2614
3310
  ${liveStatement[0].trim()}
2615
3311
  ` + (specifiers.length > 0 ? ` Imported chunk(s): ${specifiers.join(", ")}
2616
- ` : "") + (isDynamicOnly ? ` This file is a dynamic-import chunk, so it needs its \`export\`s and cannot be an IIFE. A script-mode entry can't lazily load one either (WP has no module context to resolve it in) \u2014 drop the dynamic \`import()\`, or build this entry in module mode.` : ` This is almost always a shared chunk: a module two or more script-mode entries import gets hoisted into its own chunk instead of being duplicated into each entry. Add the wpScriptDedupe() plugin (auto-included by wp() in script mode \u2014 check you haven't set \`scriptDedupe: false\`), or build this entry in module mode.`)
2617
- );
3312
+ ` : "") + (isDynamicOnly ? ` This file is a dynamic-import chunk, so it needs its \`export\`s and cannot be an IIFE. A script-mode entry can't lazily load one either (WP has no module context to resolve it in) \u2014 drop the dynamic \`import()\`, or build this entry in module mode.` : ` This is almost always a shared chunk: a module two or more script-mode entries import gets hoisted into its own chunk instead of being duplicated into each entry. Add the wpScriptDedupe() plugin (auto-included by wp() in script mode \u2014 check you haven't set \`scriptDedupe: false\`), or build this entry in module mode.
3313
+ If the specifier above is a bare \`@wordpress/*\` package rather than a relative chunk path, this is instead a module-only package (no \`wpScript\` in its package.json) imported into a script-mode entry \u2014 wpExternals warns about it by name earlier in the build. Move that import to a viewScriptModule entry, or build this entry in module mode.`);
2618
3314
  }
2619
3315
  if (/\bimport\.meta\b/.test(code)) {
2620
- throw new Error(
2621
- `wp-script-iife: ${fileName} references \`import.meta\`, which is a SyntaxError once WordPress enqueues it as a classic script.
2622
- Vite injects its module-preload helper (which reads \`import.meta.url\`) whenever the bundled graph contains a dynamic \`import()\` \u2014 \`build.modulePreload: false\` does not remove it. This plugin's \`config()\` hook defines \`import.meta.url\`/\`import.meta.resolve\` away; a surviving reference means either that define was overridden, or the code touches another \`import.meta\` member. Extend \`define\` in your Vite config, or move this entry to module mode.`
2623
- );
3316
+ return `wp-script-iife: ${fileName} references \`import.meta\`, which is a SyntaxError once WordPress enqueues it as a classic script.
3317
+ Vite injects its module-preload helper (which reads \`import.meta.url\`) whenever the bundled graph contains a dynamic \`import()\` \u2014 \`build.modulePreload: false\` does not remove it. This plugin's \`config()\` hook defines \`import.meta.url\`/\`import.meta.resolve\` away; a surviving reference means either that define was overridden, or the code touches another \`import.meta\` member. Extend \`define\` in your Vite config, or move this entry to module mode.`;
2624
3318
  }
2625
- throw new Error(
2626
- `wp-script-iife: ${fileName} does not parse as a classic script, so WordPress could not load it:
3319
+ return `wp-script-iife: ${fileName} does not parse as a classic script, so WordPress could not load it:
2627
3320
  ${parseError.message}
2628
- Nothing in this plugin's usual suspects (a shared chunk's \`import\`, an \`import.meta\` reference) explains it \u2014 inspect the written file, and report it upstream if the source is valid.`
2629
- );
3321
+ Nothing in this plugin's usual suspects (a shared chunk's \`import\`, an \`import.meta\` reference) explains it \u2014 inspect the written file, and report it upstream if the source is valid.`;
2630
3322
  }
2631
3323
 
2632
3324
  // src/wp-clean-css-chunks.ts
@@ -2645,17 +3337,37 @@ function wpCleanCssChunks() {
2645
3337
  };
2646
3338
  }
2647
3339
  var STYLESHEET_EXTENSION = /\.(css|scss|sass)$/i;
3340
+ function tryRealpath(path) {
3341
+ try {
3342
+ return realpathSync(path);
3343
+ } catch {
3344
+ return path;
3345
+ }
3346
+ }
3347
+ function entrySourcePath2(sourcePath, root) {
3348
+ const relativePath = relative(tryRealpath(root), tryRealpath(sourcePath)).replace(/\\/g, "/");
3349
+ if (relativePath === "" || relativePath === ".." || relativePath.startsWith("../")) {
3350
+ return null;
3351
+ }
3352
+ if (isAbsolute(relativePath)) return null;
3353
+ return relativePath;
3354
+ }
3355
+ function phpSingleQuoteEscape2(value) {
3356
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
3357
+ }
2648
3358
  function wpCssAssetInfo() {
2649
3359
  const cssEntries = /* @__PURE__ */ new Map();
3360
+ let resolvedRoot = process.cwd();
2650
3361
  return {
2651
3362
  name: "wp-css-asset-info",
2652
3363
  configResolved(config) {
2653
3364
  cssEntries.clear();
3365
+ resolvedRoot = config.root;
2654
3366
  const input = config.build?.rollupOptions?.input;
2655
3367
  if (!input || typeof input !== "object" || Array.isArray(input)) return;
2656
3368
  for (const [entryName, source] of Object.entries(input)) {
2657
3369
  if (typeof source === "string" && STYLESHEET_EXTENSION.test(source)) {
2658
- cssEntries.set(entryName, source);
3370
+ cssEntries.set(entryName, resolve(config.root, source));
2659
3371
  }
2660
3372
  }
2661
3373
  },
@@ -2672,10 +3384,13 @@ function wpCssAssetInfo() {
2672
3384
  const assetPhpFileName = cssAsset.fileName.replace(/\.css$/i, ".asset.php");
2673
3385
  if (bundle[assetPhpFileName]) continue;
2674
3386
  const version = crypto.createHash("md5").update(cssAsset.source).digest("hex");
3387
+ const fields = [`'dependencies' => array()`, `'version' => '${version}'`];
3388
+ const source = entrySourcePath2(sourcePath, resolvedRoot);
3389
+ if (source) fields.push(`'source' => '${phpSingleQuoteEscape2(source)}'`);
2675
3390
  this.emitFile({
2676
3391
  type: "asset",
2677
3392
  fileName: assetPhpFileName,
2678
- source: `<?php return array( 'dependencies' => array(), 'version' => '${version}' );
3393
+ source: `<?php return array( ${fields.join(", ")} );
2679
3394
  `
2680
3395
  });
2681
3396
  }
@@ -2693,6 +3408,681 @@ function findCssAsset(bundle, expectedFileName, sourcePath) {
2693
3408
  }
2694
3409
  return void 0;
2695
3410
  }
3411
+ var HOT_FILE_NAME = ".wp-vite-dev.json";
3412
+ var DEV_SERVER_PHP_NAME = "dev-server.php";
3413
+ var DEFAULT_PHP_PREFIX = "wp-vite";
3414
+ var REPORTED_PLUGINS = ["vite:react-refresh"];
3415
+ var SCAN_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
3416
+ var STYLESHEET_EXTENSION2 = /\.(css|scss|sass)$/i;
3417
+ var JSX_SOURCE_EXTENSION = /\.[jt]sx$/i;
3418
+ var JSX_RUNTIME_SPECIFIER = "react/jsx-runtime";
3419
+ var DEFAULT_MAX_SCAN_DEPTH = 24;
3420
+ var REDISCOVER_DEBOUNCE_MS = 50;
3421
+ function wpDevServer(options = {}) {
3422
+ const projectRoot = options.projectRoot ?? process.cwd();
3423
+ const emitManifest = options.emitManifest ?? true;
3424
+ const buildType = options.buildType ?? "script";
3425
+ const skip = new Set(options.skip ?? []);
3426
+ const maxScanDepth = options.maxScanDepth ?? DEFAULT_MAX_SCAN_DEPTH;
3427
+ const moduleEntryMatchers = options.moduleEntryMatchers;
3428
+ const wpBuildAssetMatchers = (options.wpBuildAssetMatchers ?? []).map(toPosix);
3429
+ const resolveExternalOptions = {
3430
+ projectRoot,
3431
+ ...options.additionalExternals ? { vendorExternals: options.additionalExternals } : {},
3432
+ ...options.externalNamespaces ? { externalNamespaces: options.externalNamespaces } : {},
3433
+ ...options.scriptModuleNamespaces ? { scriptModuleNamespaces: options.scriptModuleNamespaces } : {},
3434
+ ...options.bundledPackages ? { bundledPackages: options.bundledPackages } : {}
3435
+ };
3436
+ const blocksDirAbs = options.blocksDir ? resolve(process.cwd(), options.blocksDir) : null;
3437
+ const blockStylesDirAbs = options.blocksStylesDir ? resolve(process.cwd(), options.blocksStylesDir) : null;
3438
+ const adminPagesDirAbs = options.adminPagesDir ? resolve(process.cwd(), options.adminPagesDir) : null;
3439
+ let config;
3440
+ let outDirAbs = "";
3441
+ let entries = [];
3442
+ let configuredEntries = {};
3443
+ let entriesByFile = /* @__PURE__ */ new Map();
3444
+ let writtenSidecars = /* @__PURE__ */ new Set();
3445
+ let writtenBlockFiles = /* @__PURE__ */ new Set();
3446
+ const collectEntries = () => discoverDevEntries({
3447
+ blocksDirAbs,
3448
+ blockStylesDirAbs,
3449
+ adminPagesDirAbs,
3450
+ skip,
3451
+ configuredEntries,
3452
+ buildType
3453
+ });
3454
+ const syncModuleEntryMatchers = () => {
3455
+ if (!moduleEntryMatchers) return;
3456
+ for (const entry of entries) {
3457
+ if (entry.mode !== "module" || entry.kind !== "js") continue;
3458
+ const matcher = toPosix(entry.file);
3459
+ if (!moduleEntryMatchers.includes(matcher)) moduleEntryMatchers.push(matcher);
3460
+ }
3461
+ };
3462
+ return {
3463
+ name: "wp-dev-server",
3464
+ // Build is untouched: every write below exists only because there is no
3465
+ // bundle to emit into.
3466
+ apply: "serve",
3467
+ /**
3468
+ * WordPress ships the *production* JSX runtime and nothing else. Core's
3469
+ * `react-jsx-runtime` handle is built from `react/jsx-runtime`, so
3470
+ * `window.ReactJSXRuntime` carries `jsx`, `jsxs` and `Fragment` — there
3471
+ * is no `jsxDEV` on it, in `SCRIPT_DEBUG` or out of it.
3472
+ *
3473
+ * Vite's serve default is the opposite: JSX compiles against the dev
3474
+ * runtime, `react/jsx-dev-runtime`. `wpExternals` then does exactly what
3475
+ * it is supposed to and rewrites that import to the global WordPress
3476
+ * registers for it, so every block editor script with JSX in it comes
3477
+ * out as `const { jsxDEV: _jsxDEV } = (window.ReactJSXRuntime)` —
3478
+ * `undefined`, and a `_jsxDEV is not a function` the moment anything
3479
+ * renders. Builds never see it (`development` is off there), which makes
3480
+ * it a dev-only cliff that a passing build tells you nothing about.
3481
+ *
3482
+ * Filled in only where the user has no opinion, the same rule
3483
+ * `wpScriptIife`'s `define` injection and `wpModuleEntrySignatures`
3484
+ * follow. The cost is the `__source`/`__self` debug props; the benefit is
3485
+ * that the editor script runs at all.
3486
+ */
3487
+ config(userConfig) {
3488
+ const oxc = userConfig.oxc;
3489
+ if (oxc === false) return null;
3490
+ const jsx = oxc?.jsx;
3491
+ if (jsx === "preserve") return null;
3492
+ if (jsx && typeof jsx === "object" && jsx.development !== void 0) return null;
3493
+ return { oxc: { jsx: { development: false } } };
3494
+ },
3495
+ configResolved(resolvedConfig) {
3496
+ config = resolvedConfig;
3497
+ outDirAbs = resolve(resolvedConfig.root, resolvedConfig.build.outDir);
3498
+ configuredEntries = readConfiguredEntries(resolvedConfig);
3499
+ entries = collectEntries();
3500
+ syncModuleEntryMatchers();
3501
+ },
3502
+ /**
3503
+ * Answer for the bare specifiers the escape above hands back to the
3504
+ * browser.
3505
+ *
3506
+ * `installBareSpecifierEscape` rewrites the served code *after*
3507
+ * import-analysis, which means Vite's module graph still holds the
3508
+ * `/@id/@wordpress/interactivity` form it produced — and Vite eagerly
3509
+ * pre-transforms every import it recorded. That request resolves to a
3510
+ * bare specifier nothing on disk backs, and the console gets a red
3511
+ * `Pre-transform error: Failed to load url @wordpress/interactivity` for
3512
+ * every Script Module entry, on a server that is working perfectly.
3513
+ *
3514
+ * An empty module makes the warm-up succeed and changes nothing else:
3515
+ * the URL is not in the code any browser receives, so it is never
3516
+ * fetched — WordPress's import map resolves the specifier instead.
3517
+ * Narrow by construction: only ids `resolveExternal()` recognizes (i.e.
3518
+ * an installed package whose own metadata says it is externalizable),
3519
+ * and never a path, so a genuinely missing module still fails loudly.
3520
+ */
3521
+ load(id) {
3522
+ if (id.startsWith(".") || id.startsWith("/") || id.startsWith("\0")) return null;
3523
+ if (isAbsolute(id) || !resolveExternal(id, resolveExternalOptions)) return null;
3524
+ return "export {};\n";
3525
+ },
3526
+ async configureServer(server) {
3527
+ installBareSpecifierEscape(server, config, resolveExternalOptions);
3528
+ const refreshAll = async () => {
3529
+ const { index, written } = await writeAllSidecars({
3530
+ entries,
3531
+ outDirAbs,
3532
+ root: config.root,
3533
+ resolveExternalOptions,
3534
+ wpBuildAssetMatchers,
3535
+ maxScanDepth
3536
+ });
3537
+ entriesByFile = index;
3538
+ pruneStale(writtenSidecars, written);
3539
+ writtenSidecars = written;
3540
+ };
3541
+ const refreshFor = async (file) => {
3542
+ const affected = entriesByFile.get(toPosix(file));
3543
+ if (!affected || affected.size === 0) return;
3544
+ const subset = entries.filter((entry) => affected.has(entry.name));
3545
+ const { index: rescanned } = await writeAllSidecars({
3546
+ entries: subset,
3547
+ outDirAbs,
3548
+ root: config.root,
3549
+ resolveExternalOptions,
3550
+ wpBuildAssetMatchers,
3551
+ maxScanDepth
3552
+ });
3553
+ for (const [scannedFile, names] of rescanned) {
3554
+ const existing = entriesByFile.get(scannedFile);
3555
+ if (existing) for (const name of names) existing.add(name);
3556
+ else entriesByFile.set(scannedFile, new Set(names));
3557
+ }
3558
+ };
3559
+ const refreshBlocksTree = () => {
3560
+ const written = writeBlocksTree({ blocksDirAbs, outDirAbs, emitManifest, skip });
3561
+ pruneStale(writtenBlockFiles, written);
3562
+ writtenBlockFiles = written;
3563
+ };
3564
+ const addWatchDirs = () => {
3565
+ for (const dir of [blocksDirAbs, blockStylesDirAbs, adminPagesDirAbs]) {
3566
+ if (dir && existsSync(dir)) server.watcher.add(dir);
3567
+ }
3568
+ };
3569
+ const rediscover = () => {
3570
+ addWatchDirs();
3571
+ refreshBlocksTree();
3572
+ configuredEntries = readConfiguredEntries(config);
3573
+ entries = collectEntries();
3574
+ syncModuleEntryMatchers();
3575
+ return refreshAll();
3576
+ };
3577
+ refreshBlocksTree();
3578
+ writeFileTree(
3579
+ join(outDirAbs, DEV_SERVER_PHP_NAME),
3580
+ renderDevServerPhp({
3581
+ prefix: resolvePhpPrefix(options.phpPrefix, config.root),
3582
+ hotFile: HOT_FILE_NAME
3583
+ })
3584
+ );
3585
+ await refreshAll();
3586
+ addWatchDirs();
3587
+ watchSources({
3588
+ server,
3589
+ sourceDirs: [blocksDirAbs, blockStylesDirAbs, adminPagesDirAbs],
3590
+ blocksDirAbs,
3591
+ onRediscover: rediscover,
3592
+ onFileChange: (file) => refreshFor(file)
3593
+ });
3594
+ const hotFilePath = join(outDirAbs, HOT_FILE_NAME);
3595
+ const httpServer = server.httpServer;
3596
+ if (httpServer) {
3597
+ httpServer.once("listening", () => {
3598
+ const hotFile = buildHotFile(config, httpServer.address());
3599
+ writeFileTree(hotFilePath, `${JSON.stringify(hotFile, null, " ")}
3600
+ `);
3601
+ registerHotFileCleanup(hotFilePath);
3602
+ });
3603
+ }
3604
+ },
3605
+ /**
3606
+ * Vite closes the plugin container on `server.close()`, which is what
3607
+ * makes this fire for a programmatically-created server (tests, a
3608
+ * wrapper process). The `process` handlers registered above cover the
3609
+ * interactive case, where nothing calls `close()` at all.
3610
+ */
3611
+ closeBundle() {
3612
+ removeHotFile(join(outDirAbs, HOT_FILE_NAME));
3613
+ }
3614
+ };
3615
+ }
3616
+ function wpDevServerPhp(options = {}) {
3617
+ let projectRoot = options.projectRoot ?? process.cwd();
3618
+ return {
3619
+ name: "wp-dev-server-php",
3620
+ apply: "build",
3621
+ configResolved(resolvedConfig) {
3622
+ projectRoot = options.projectRoot ?? resolvedConfig.root;
3623
+ },
3624
+ generateBundle() {
3625
+ this.emitFile({
3626
+ type: "asset",
3627
+ fileName: DEV_SERVER_PHP_NAME,
3628
+ source: renderDevServerPhp({
3629
+ prefix: resolvePhpPrefix(options.phpPrefix, projectRoot),
3630
+ hotFile: HOT_FILE_NAME
3631
+ })
3632
+ });
3633
+ }
3634
+ };
3635
+ }
3636
+ function resolvePhpPrefix(explicit, projectRoot) {
3637
+ const fromOption = sanitizePhpPrefix(explicit);
3638
+ if (fromOption) return fromOption;
3639
+ let packageJson = {};
3640
+ try {
3641
+ packageJson = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf8"));
3642
+ } catch {
3643
+ }
3644
+ return sanitizePhpPrefix(packageJson.wpPlugin?.name) ?? sanitizePhpPrefix(packageJson.name?.replace(/^@[^/]+\//, "")) ?? DEFAULT_PHP_PREFIX;
3645
+ }
3646
+ function sanitizePhpPrefix(value) {
3647
+ if (!value) return null;
3648
+ const cleaned = value.replace(/[^A-Za-z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
3649
+ return cleaned !== "" && isValidPhpIdentifier(cleaned) ? cleaned : null;
3650
+ }
3651
+ function buildHotFile(config, address) {
3652
+ return {
3653
+ origin: resolveDevOrigin(config, address),
3654
+ base: config.base,
3655
+ plugins: REPORTED_PLUGINS.filter(
3656
+ (name) => config.plugins.some((plugin) => plugin.name === name)
3657
+ )
3658
+ };
3659
+ }
3660
+ var UNPUBLISHABLE_ADDRESSES = /* @__PURE__ */ new Set([
3661
+ "0.0.0.0",
3662
+ "::",
3663
+ "::0",
3664
+ "0000:0000:0000:0000:0000:0000:0000:0000",
3665
+ "127.0.0.1",
3666
+ "::1",
3667
+ "0000:0000:0000:0000:0000:0000:0000:0001"
3668
+ ]);
3669
+ function resolveDevOrigin(config, address) {
3670
+ const explicitOrigin = config.server?.origin;
3671
+ if (typeof explicitOrigin === "string" && explicitOrigin.length > 0) {
3672
+ return explicitOrigin.replace(/\/+$/, "");
3673
+ }
3674
+ const hmr = typeof config.server?.hmr === "object" ? config.server.hmr : null;
3675
+ const hmrProtocol = hmr && "protocol" in hmr ? hmr.protocol : void 0;
3676
+ const protocol = hmrProtocol ? hmrProtocol === "wss" ? "https" : "http" : config.server?.https ? "https" : "http";
3677
+ const hmrHost = hmr && "host" in hmr && typeof hmr.host === "string" ? hmr.host : void 0;
3678
+ const configHost = typeof config.server?.host === "string" ? config.server.host : void 0;
3679
+ const boundHost = address && typeof address === "object" ? address.address : void 0;
3680
+ const host = hmrHost ?? configHost ?? (boundHost ? normalizeBoundHost(boundHost) : "localhost");
3681
+ const hmrClientPort = hmr && "clientPort" in hmr && typeof hmr.clientPort === "number" ? hmr.clientPort : void 0;
3682
+ const boundPort = address && typeof address === "object" ? address.port : void 0;
3683
+ const port = hmrClientPort ?? boundPort;
3684
+ return port === void 0 ? `${protocol}://${host}` : `${protocol}://${host}:${port}`;
3685
+ }
3686
+ function normalizeBoundHost(host) {
3687
+ if (UNPUBLISHABLE_ADDRESSES.has(host)) return "localhost";
3688
+ if (host.includes(":") && !host.startsWith("[")) return `[${host}]`;
3689
+ return host;
3690
+ }
3691
+ var pendingHotFiles = /* @__PURE__ */ new Set();
3692
+ var hotFileHandlersBound = false;
3693
+ function registerHotFileCleanup(hotFilePath) {
3694
+ pendingHotFiles.add(hotFilePath);
3695
+ if (hotFileHandlersBound) return;
3696
+ hotFileHandlersBound = true;
3697
+ const cleanAll = () => {
3698
+ for (const filePath of pendingHotFiles) removeHotFile(filePath);
3699
+ pendingHotFiles.clear();
3700
+ };
3701
+ process.on("exit", cleanAll);
3702
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
3703
+ process.on(signal, () => {
3704
+ cleanAll();
3705
+ process.exit();
3706
+ });
3707
+ }
3708
+ }
3709
+ function removeHotFile(hotFilePath) {
3710
+ pendingHotFiles.delete(hotFilePath);
3711
+ try {
3712
+ rmSync(hotFilePath, { force: true });
3713
+ } catch {
3714
+ }
3715
+ }
3716
+ var decoratedEnvironments = /* @__PURE__ */ new WeakSet();
3717
+ function installBareSpecifierEscape(server, config, resolveExternalOptions) {
3718
+ const base = config.base ?? "/";
3719
+ const wrappedPrefix = `${base.replace(/\/+$/, "")}/@id/`;
3720
+ const pattern = new RegExp(`(["'])${escapeRegExp2(wrappedPrefix)}([^"'\\s]+)\\1`, "g");
3721
+ const unwrap = (code) => code.replace(
3722
+ pattern,
3723
+ (match, quote, specifier) => resolveExternal(specifier, resolveExternalOptions) ? `${quote}${specifier}${quote}` : match
3724
+ );
3725
+ for (const environment of Object.values(server.environments)) {
3726
+ if (decoratedEnvironments.has(environment)) continue;
3727
+ decoratedEnvironments.add(environment);
3728
+ const original = environment.transformRequest.bind(environment);
3729
+ environment.transformRequest = async (url) => {
3730
+ const result = await original(url);
3731
+ if (!result || typeof result.code !== "string") return result;
3732
+ if (!result.code.includes(wrappedPrefix)) return result;
3733
+ const code = unwrap(result.code);
3734
+ return code === result.code ? result : { ...result, code };
3735
+ };
3736
+ }
3737
+ }
3738
+ function escapeRegExp2(value) {
3739
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3740
+ }
3741
+ function discoverDevEntries(options) {
3742
+ const { blocksDirAbs, blockStylesDirAbs, adminPagesDirAbs, skip } = options;
3743
+ const byName = /* @__PURE__ */ new Map();
3744
+ const add = (entry) => {
3745
+ if (!byName.has(entry.name)) byName.set(entry.name, entry);
3746
+ };
3747
+ if (blocksDirAbs && existsSync(blocksDirAbs)) {
3748
+ for (const [name, file] of Object.entries(discoverEntries(blocksDirAbs, "module", skip))) {
3749
+ add({ name, file, mode: "module", kind: entryKind(file) });
3750
+ }
3751
+ for (const [name, file] of Object.entries(discoverEntries(blocksDirAbs, "script", skip))) {
3752
+ add({ name, file, mode: "script", kind: entryKind(file) });
3753
+ }
3754
+ }
3755
+ if (blockStylesDirAbs && existsSync(blockStylesDirAbs)) {
3756
+ const stylesheets = fg.sync(`${toPosix(blockStylesDirAbs)}/**/*.{css,scss,sass}`, {
3757
+ absolute: true
3758
+ });
3759
+ for (const file of stylesheets) {
3760
+ const blockName = relative(blockStylesDirAbs, file).replace(extname(file), "").replace(/\\/g, "/");
3761
+ add({ name: `autoenqueue/${blockName}`, file, mode: "script", kind: "css" });
3762
+ }
3763
+ }
3764
+ if (adminPagesDirAbs && existsSync(adminPagesDirAbs)) {
3765
+ for (const page of discoverPages(adminPagesDirAbs)) {
3766
+ add({
3767
+ name: `modules/${page.dirName}/content`,
3768
+ file: page.content,
3769
+ mode: "module",
3770
+ kind: "js"
3771
+ });
3772
+ if (page.route) {
3773
+ add({
3774
+ name: `modules/${page.dirName}/route`,
3775
+ file: page.route,
3776
+ mode: "module",
3777
+ kind: "js"
3778
+ });
3779
+ }
3780
+ for (const subRoute of page.subRoutes) {
3781
+ const base = `modules/${page.dirName}/routes/${subRoute.name}`;
3782
+ if (subRoute.content) {
3783
+ add({
3784
+ name: `${base}/content`,
3785
+ file: subRoute.content,
3786
+ mode: "module",
3787
+ kind: "js"
3788
+ });
3789
+ }
3790
+ if (subRoute.route) {
3791
+ add({
3792
+ name: `${base}/route`,
3793
+ file: subRoute.route,
3794
+ mode: "module",
3795
+ kind: "js"
3796
+ });
3797
+ }
3798
+ }
3799
+ }
3800
+ }
3801
+ for (const [name, file] of Object.entries(options.configuredEntries ?? {})) {
3802
+ add({ name, file, mode: options.buildType ?? "script", kind: entryKind(file) });
3803
+ }
3804
+ return [...byName.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
3805
+ }
3806
+ function readConfiguredEntries(config) {
3807
+ const input = config.build?.rollupOptions?.input;
3808
+ if (!input || typeof input !== "object" || Array.isArray(input)) return {};
3809
+ const entries = {};
3810
+ for (const [name, source] of Object.entries(input)) {
3811
+ if (typeof source !== "string") continue;
3812
+ const file = resolve(config.root, source);
3813
+ if (existsSync(file)) entries[name] = file;
3814
+ }
3815
+ return entries;
3816
+ }
3817
+ function entryKind(file) {
3818
+ return STYLESHEET_EXTENSION2.test(file) ? "css" : "js";
3819
+ }
3820
+ async function scanEntryDependencies(entryFile, options) {
3821
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_SCAN_DEPTH;
3822
+ const moduleIds = /* @__PURE__ */ new Set();
3823
+ const dynamicModuleIds = /* @__PURE__ */ new Set();
3824
+ const handles = /* @__PURE__ */ new Set();
3825
+ const visited = /* @__PURE__ */ new Set();
3826
+ const files = [];
3827
+ let hasJsxSource = false;
3828
+ await init;
3829
+ const queue = [{ file: entryFile, depth: 0 }];
3830
+ while (queue.length > 0) {
3831
+ const { file, depth } = queue.shift();
3832
+ const key = toPosix(file);
3833
+ if (visited.has(key)) continue;
3834
+ visited.add(key);
3835
+ files.push(key);
3836
+ if (JSX_SOURCE_EXTENSION.test(key)) hasJsxSource = true;
3837
+ if (depth >= maxDepth) continue;
3838
+ if (STYLESHEET_EXTENSION2.test(file)) continue;
3839
+ let code;
3840
+ try {
3841
+ code = readFileSync(file, "utf8");
3842
+ } catch {
3843
+ continue;
3844
+ }
3845
+ for (const { specifier, isDynamic } of lexImports(code, file)) {
3846
+ if (specifier.startsWith(".") || isAbsolute(specifier)) {
3847
+ const resolved = resolveLocalImport(specifier, file);
3848
+ if (resolved) queue.push({ file: resolved, depth: depth + 1 });
3849
+ continue;
3850
+ }
3851
+ const external = resolveExternal(specifier, options.resolveExternalOptions);
3852
+ if (!external) continue;
3853
+ if (options.mode === "module" && external.moduleEligible) {
3854
+ if (isDynamic && specifier !== "@wordpress/interactivity") {
3855
+ dynamicModuleIds.add(external.specifier);
3856
+ } else {
3857
+ moduleIds.add(external.specifier);
3858
+ }
3859
+ continue;
3860
+ }
3861
+ if (external.handle) handles.add(external.handle);
3862
+ }
3863
+ }
3864
+ if (hasJsxSource) {
3865
+ const jsxRuntime = resolveExternal(JSX_RUNTIME_SPECIFIER, options.resolveExternalOptions);
3866
+ if (jsxRuntime?.handle) handles.add(jsxRuntime.handle);
3867
+ }
3868
+ for (const id of moduleIds) dynamicModuleIds.delete(id);
3869
+ return {
3870
+ moduleIds: [...moduleIds].sort(),
3871
+ dynamicModuleIds: [...dynamicModuleIds].sort(),
3872
+ handles: [...handles].sort(),
3873
+ files
3874
+ };
3875
+ }
3876
+ function lexImports(code, file) {
3877
+ try {
3878
+ const [imports] = parse(code, file);
3879
+ const found = [];
3880
+ for (const importRecord of imports) {
3881
+ if (importRecord.n === void 0) continue;
3882
+ found.push({ specifier: importRecord.n, isDynamic: importRecord.d > -1 });
3883
+ }
3884
+ return found;
3885
+ } catch {
3886
+ return regexImports(code);
3887
+ }
3888
+ }
3889
+ function stripComments(code) {
3890
+ return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:"'`\\])\/\/[^\n]*/g, "$1");
3891
+ }
3892
+ var DYNAMIC_IMPORT_RE = /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
3893
+ var STATIC_FROM_RE = /(?:^|[\s;}(])(?:import|export)\b[^;]*?\bfrom\s*(['"])([^'"]+)\1/g;
3894
+ var BARE_IMPORT_RE = /(?:^|[\s;}])import\s*(['"])([^'"]+)\1/g;
3895
+ function regexImports(code) {
3896
+ const source = stripComments(code);
3897
+ const found = /* @__PURE__ */ new Map();
3898
+ const record = (specifier, isDynamic) => {
3899
+ const existing = found.get(specifier);
3900
+ if (existing && !existing.isDynamic) return;
3901
+ found.set(specifier, { specifier, isDynamic });
3902
+ };
3903
+ for (const match of source.matchAll(DYNAMIC_IMPORT_RE)) record(match[2], true);
3904
+ for (const match of source.matchAll(STATIC_FROM_RE)) record(match[2], false);
3905
+ for (const match of source.matchAll(BARE_IMPORT_RE)) record(match[2], false);
3906
+ return [...found.values()];
3907
+ }
3908
+ function resolveLocalImport(specifier, importerFile) {
3909
+ const withoutQuery = specifier.split("?")[0] ?? specifier;
3910
+ const target = isAbsolute(withoutQuery) ? withoutQuery : join(dirname(importerFile), withoutQuery);
3911
+ if (existsSync(target) && !isDirectory(target)) return target;
3912
+ const candidates = [];
3913
+ const withoutJsExtension = target.replace(/\.[mc]?js$/, "");
3914
+ for (const extension of SCAN_EXTENSIONS) {
3915
+ candidates.push(`${target}${extension}`);
3916
+ if (withoutJsExtension !== target) candidates.push(`${withoutJsExtension}${extension}`);
3917
+ }
3918
+ for (const extension of SCAN_EXTENSIONS) candidates.push(join(target, `index${extension}`));
3919
+ return candidates.find((candidate) => existsSync(candidate)) ?? null;
3920
+ }
3921
+ function isDirectory(filePath) {
3922
+ try {
3923
+ return existsSync(filePath) && extname(filePath) === "";
3924
+ } catch {
3925
+ return false;
3926
+ }
3927
+ }
3928
+ function renderDevAssetPhp(input) {
3929
+ const { scan, mode, kind, source } = input;
3930
+ const phpList = (items) => items.length > 0 ? ` ${items.join(", ")} ` : "";
3931
+ const quoted = (items) => items.map((item) => `'${phpSingleQuoteEscape3(item)}'`);
3932
+ const fields = [];
3933
+ if (kind === "css") {
3934
+ fields.push(`'dependencies' => array()`);
3935
+ } else if (input.wpBuildShape) {
3936
+ fields.push(`'dependencies' => array(${phpList(quoted(scan.handles))})`);
3937
+ const moduleDependencies = [
3938
+ ...scan.moduleIds.map(
3939
+ (id) => `array( 'id' => '${phpSingleQuoteEscape3(id)}', 'import' => 'static' )`
3940
+ ),
3941
+ ...scan.dynamicModuleIds.map(
3942
+ (id) => `array( 'id' => '${phpSingleQuoteEscape3(id)}', 'import' => 'dynamic' )`
3943
+ )
3944
+ ];
3945
+ if (moduleDependencies.length > 0) {
3946
+ fields.push(`'module_dependencies' => array(${phpList(moduleDependencies)})`);
3947
+ }
3948
+ } else if (mode === "module") {
3949
+ const moduleDependencies = [
3950
+ ...quoted(scan.moduleIds),
3951
+ ...scan.dynamicModuleIds.map(
3952
+ (id) => `array( 'id' => '${phpSingleQuoteEscape3(id)}', 'import' => 'dynamic' )`
3953
+ )
3954
+ ];
3955
+ fields.push(`'dependencies' => array(${phpList(moduleDependencies)})`);
3956
+ if (scan.handles.length > 0) {
3957
+ fields.push(`'script_dependencies' => array(${phpList(quoted(scan.handles))})`);
3958
+ }
3959
+ } else {
3960
+ fields.push(`'dependencies' => array(${phpList(quoted(scan.handles))})`);
3961
+ }
3962
+ fields.push(`'version' => null`);
3963
+ if (kind === "js" && mode === "module") fields.push(`'type' => 'module'`);
3964
+ if (source) fields.push(`'source' => '${phpSingleQuoteEscape3(source)}'`);
3965
+ fields.push(`'dev' => true`);
3966
+ return `<?php return array( ${fields.join(", ")} );
3967
+ `;
3968
+ }
3969
+ async function writeAllSidecars(options) {
3970
+ const index = /* @__PURE__ */ new Map();
3971
+ const written = /* @__PURE__ */ new Set();
3972
+ for (const entry of options.entries) {
3973
+ const scan = entry.kind === "css" ? { moduleIds: [], dynamicModuleIds: [], handles: [], files: [toPosix(entry.file)] } : await scanEntryDependencies(entry.file, {
3974
+ mode: entry.mode,
3975
+ resolveExternalOptions: options.resolveExternalOptions,
3976
+ maxDepth: options.maxScanDepth
3977
+ });
3978
+ const php = renderDevAssetPhp({
3979
+ scan,
3980
+ mode: entry.mode,
3981
+ kind: entry.kind,
3982
+ source: projectRelativeSource(entry.file, options.root),
3983
+ wpBuildShape: options.wpBuildAssetMatchers.some(
3984
+ (matcher) => toPosix(entry.file).includes(matcher)
3985
+ )
3986
+ });
3987
+ const sidecarPath = join(options.outDirAbs, `${entry.name}.asset.php`);
3988
+ writeFileTree(sidecarPath, php);
3989
+ written.add(sidecarPath);
3990
+ for (const file of scan.files) {
3991
+ let names = index.get(file);
3992
+ if (!names) index.set(file, names = /* @__PURE__ */ new Set());
3993
+ names.add(entry.name);
3994
+ }
3995
+ }
3996
+ return { index, written };
3997
+ }
3998
+ function pruneStale(previous, current) {
3999
+ for (const filePath of previous) {
4000
+ if (current.has(filePath)) continue;
4001
+ try {
4002
+ rmSync(filePath, { force: true });
4003
+ } catch {
4004
+ }
4005
+ }
4006
+ }
4007
+ function projectRelativeSource(file, root) {
4008
+ const relativePath = relative(root, file).replace(/\\/g, "/");
4009
+ if (relativePath === "" || relativePath === ".." || relativePath.startsWith("../")) return null;
4010
+ if (isAbsolute(relativePath)) return null;
4011
+ return relativePath;
4012
+ }
4013
+ function phpSingleQuoteEscape3(value) {
4014
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
4015
+ }
4016
+ function writeBlocksTree(options) {
4017
+ const { blocksDirAbs, outDirAbs, emitManifest, skip } = options;
4018
+ const written = /* @__PURE__ */ new Set();
4019
+ if (!blocksDirAbs || !existsSync(blocksDirAbs)) return written;
4020
+ const files = fg.sync(
4021
+ [`${toPosix(blocksDirAbs)}/**/block.json`, `${toPosix(blocksDirAbs)}/**/*.php`],
4022
+ { absolute: true }
4023
+ );
4024
+ for (const file of files) {
4025
+ const relativePath = relative(blocksDirAbs, file).replace(/\\/g, "/");
4026
+ const blockDirName = relativePath.replace(/\/?[^/]+$/, "");
4027
+ if (blockDirName && skip.has(blockDirName)) continue;
4028
+ const source = relativePath.endsWith("block.json") ? transformBlockJson(readFileSync(file, "utf8"), file) : readFileSync(file);
4029
+ const target = join(outDirAbs, "blocks", relativePath);
4030
+ writeFileTree(target, source);
4031
+ written.add(target);
4032
+ }
4033
+ if (!emitManifest) return written;
4034
+ const manifest = buildBlocksManifest(blocksDirAbs, skip);
4035
+ if (manifest) {
4036
+ const target = join(outDirAbs, "blocks-manifest.php");
4037
+ writeFileTree(target, manifest);
4038
+ written.add(target);
4039
+ }
4040
+ return written;
4041
+ }
4042
+ function watchSources(options) {
4043
+ const { server, blocksDirAbs } = options;
4044
+ const sourceDirs = options.sourceDirs.filter((dir) => !!dir).map((dir) => `${toPosix(dir)}/`);
4045
+ const isUnderSourceDir = (posixFile) => sourceDirs.some((dir) => posixFile.startsWith(dir));
4046
+ const isBlocksMetadata = (posixFile) => {
4047
+ if (!blocksDirAbs) return false;
4048
+ if (!posixFile.startsWith(`${toPosix(blocksDirAbs)}/`)) return false;
4049
+ return posixFile.endsWith("/block.json") || posixFile.endsWith(".php");
4050
+ };
4051
+ const run = (work, file) => {
4052
+ void Promise.resolve(work).catch((error) => {
4053
+ server.config.logger.error(
4054
+ `[wp-dev-server] failed to refresh after ${file}: ${error instanceof Error ? error.message : String(error)}`
4055
+ );
4056
+ });
4057
+ };
4058
+ let pending = null;
4059
+ const scheduleRediscover = (file) => {
4060
+ if (pending) clearTimeout(pending);
4061
+ pending = setTimeout(() => {
4062
+ pending = null;
4063
+ run(options.onRediscover(), file);
4064
+ }, REDISCOVER_DEBOUNCE_MS);
4065
+ pending.unref?.();
4066
+ };
4067
+ const handle = (event, file) => {
4068
+ const posixFile = toPosix(file);
4069
+ if (isBlocksMetadata(posixFile) || event !== "change" && isUnderSourceDir(posixFile)) {
4070
+ scheduleRediscover(file);
4071
+ return;
4072
+ }
4073
+ run(options.onFileChange(file), file);
4074
+ };
4075
+ for (const event of ["add", "change", "unlink"]) {
4076
+ server.watcher.on(event, (file) => handle(event, file));
4077
+ }
4078
+ }
4079
+ function writeFileTree(filePath, contents) {
4080
+ mkdirSync(dirname(filePath), { recursive: true });
4081
+ writeFileSync(filePath, contents);
4082
+ }
4083
+ function toPosix(filePath) {
4084
+ return filePath.replace(/\\/g, "/");
4085
+ }
2696
4086
 
2697
4087
  // src/wp.ts
2698
4088
  function wpModuleEntrySignatures() {
@@ -2700,17 +4090,27 @@ function wpModuleEntrySignatures() {
2700
4090
  name: "wp-module-entry-signatures",
2701
4091
  config(userConfig) {
2702
4092
  if (userConfig.build?.rollupOptions?.preserveEntrySignatures !== void 0) return null;
2703
- return { build: { rollupOptions: { preserveEntrySignatures: "exports-only" } } };
4093
+ return {
4094
+ build: { rollupOptions: { preserveEntrySignatures: "exports-only" } }
4095
+ };
2704
4096
  }
2705
4097
  };
2706
4098
  }
2707
4099
  function wp(options = {}) {
2708
4100
  const buildType = options.buildType ?? "script";
2709
4101
  const plugins = [];
4102
+ const globalsSink = /* @__PURE__ */ new Map();
4103
+ let activeBlocksDir;
4104
+ let activeBlocksSkip;
4105
+ let activeEmitManifest;
4106
+ let activeBlockStylesDir;
2710
4107
  if (options.blocks !== false) {
2711
4108
  const blocksOptions = typeof options.blocks === "object" ? options.blocks : void 0;
2712
4109
  const blocksDir = blocksOptions?.blocksDir ?? options.blocksDir;
2713
4110
  if (blocksDir) {
4111
+ activeBlocksDir = blocksDir;
4112
+ activeBlocksSkip = blocksOptions?.skip;
4113
+ activeEmitManifest = blocksOptions?.emitManifest;
2714
4114
  plugins.push(
2715
4115
  wpBlocks({
2716
4116
  blocksDir,
@@ -2724,6 +4124,7 @@ function wp(options = {}) {
2724
4124
  const blockStylesOptions = typeof options.blockStyles === "object" ? options.blockStyles : void 0;
2725
4125
  const blocksStylesDir = blockStylesOptions?.blocksStylesDir ?? options.blocksStylesDir;
2726
4126
  if (blocksStylesDir) {
4127
+ activeBlockStylesDir = blocksStylesDir;
2727
4128
  plugins.push(
2728
4129
  wpBlockStyles({
2729
4130
  blocksStylesDir,
@@ -2760,26 +4161,66 @@ function wp(options = {}) {
2760
4161
  );
2761
4162
  }
2762
4163
  }
4164
+ const externalsOptions = typeof options.externals === "object" ? options.externals : void 0;
4165
+ const wpBuildAssetMatchers = [...externalsOptions?.wpBuildAssetMatchers ?? []];
4166
+ if (activeAdminPagesDir) {
4167
+ const matcher = resolve(process.cwd(), activeAdminPagesDir).replace(/\\/g, "/");
4168
+ if (!wpBuildAssetMatchers.includes(matcher)) wpBuildAssetMatchers.push(matcher);
4169
+ }
4170
+ const moduleEntryMatchers = [...externalsOptions?.moduleEntryMatchers ?? []];
2763
4171
  if (options.externals !== false) {
2764
- const externalsOptions = typeof options.externals === "object" ? options.externals : void 0;
2765
- const wpBuildAssetMatchers = [...externalsOptions?.wpBuildAssetMatchers ?? []];
2766
- if (activeAdminPagesDir) {
2767
- const matcher = resolve(process.cwd(), activeAdminPagesDir).replace(/\\/g, "/");
2768
- if (!wpBuildAssetMatchers.includes(matcher)) wpBuildAssetMatchers.push(matcher);
2769
- }
2770
4172
  plugins.push(
2771
4173
  ...wpExternals({
2772
4174
  buildType,
2773
4175
  ...externalsOptions,
4176
+ moduleEntryMatchers,
4177
+ // After the spread, deliberately: `globalsSink` is an internal
4178
+ // out-param, not user config. A consumer passing their own Map
4179
+ // through `externals: { globalsSink }` would otherwise sever the
4180
+ // wiring — wpExternals would fill their map, wpScriptIife would
4181
+ // read an empty one, and every shimmed `require()` would throw
4182
+ // at runtime with nothing said at build time.
4183
+ globalsSink,
2774
4184
  ...wpBuildAssetMatchers.length > 0 ? { wpBuildAssetMatchers } : {}
2775
4185
  })
2776
4186
  );
2777
4187
  }
4188
+ if (options.devServer !== false) {
4189
+ const devServerOptions = typeof options.devServer === "object" ? options.devServer : void 0;
4190
+ plugins.push(
4191
+ wpDevServer({
4192
+ // The mode the *declared* entries (`build.rollupOptions.input`)
4193
+ // are served in. Block and admin-page entries carry their own
4194
+ // verdict from discovery, so this only decides the project's own.
4195
+ buildType,
4196
+ ...activeBlocksDir ? { blocksDir: activeBlocksDir } : {},
4197
+ ...activeBlockStylesDir ? { blocksStylesDir: activeBlockStylesDir } : {},
4198
+ ...activeAdminPagesDir ? { adminPagesDir: activeAdminPagesDir } : {},
4199
+ ...activeBlocksSkip ? { skip: activeBlocksSkip } : {},
4200
+ ...activeEmitManifest !== void 0 ? { emitManifest: activeEmitManifest } : {},
4201
+ // The dev dependency scan has to reach the same externalization
4202
+ // verdicts `wpExternals` does, so it gets the same inputs.
4203
+ ...externalsOptions?.projectRoot ? { projectRoot: externalsOptions.projectRoot } : {},
4204
+ ...externalsOptions?.externalNamespaces ? { externalNamespaces: externalsOptions.externalNamespaces } : {},
4205
+ ...externalsOptions?.additionalExternals ? { additionalExternals: externalsOptions.additionalExternals } : {},
4206
+ ...externalsOptions?.scriptModuleNamespaces ? { scriptModuleNamespaces: externalsOptions.scriptModuleNamespaces } : {},
4207
+ ...externalsOptions?.bundle ? { bundledPackages: [...DEFAULT_BUNDLED_PACKAGES, ...externalsOptions.bundle] } : {},
4208
+ moduleEntryMatchers,
4209
+ ...wpBuildAssetMatchers.length > 0 ? { wpBuildAssetMatchers } : {},
4210
+ ...devServerOptions
4211
+ })
4212
+ );
4213
+ plugins.push(
4214
+ wpDevServerPhp(
4215
+ devServerOptions?.phpPrefix ? { phpPrefix: devServerOptions.phpPrefix } : {}
4216
+ )
4217
+ );
4218
+ }
2778
4219
  if (buildType === "script" && options.scriptDedupe !== false) {
2779
4220
  plugins.push(wpScriptDedupe());
2780
4221
  }
2781
4222
  if (buildType === "script" && options.iife !== false) {
2782
- plugins.push(wpScriptIife());
4223
+ plugins.push(wpScriptIife({ globals: globalsSink }));
2783
4224
  }
2784
4225
  if (options.cleanCssChunks !== false) {
2785
4226
  plugins.push(wpCleanCssChunks());
@@ -2793,6 +4234,6 @@ function wp(options = {}) {
2793
4234
  return plugins;
2794
4235
  }
2795
4236
 
2796
- export { DEFAULT_BUNDLED_PACKAGES, wp, wpAdminPages, wpBlockStyles, wpBlocks, wpCleanCssChunks, wpCopyAssets, wpCssAssetInfo, wpExternals, wpScriptDedupe, wpScriptIife };
4237
+ export { DEFAULT_BUNDLED_PACKAGES, DEV_SERVER_PHP_NAME, HOT_FILE_NAME, wp, wpAdminPages, wpBlockStyles, wpBlocks, wpCleanCssChunks, wpCopyAssets, wpCssAssetInfo, wpDevServer, wpDevServerPhp, wpExternals, wpScriptDedupe, wpScriptIife };
2797
4238
  //# sourceMappingURL=index.js.map
2798
4239
  //# sourceMappingURL=index.js.map