@nasti-toolchain/nasti 2.3.1 → 2.4.1

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/cli.cjs CHANGED
@@ -5,10 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __glob = (map) => (path17) => {
9
- var fn = map[path17];
8
+ var __glob = (map) => (path18) => {
9
+ var fn = map[path18];
10
10
  if (fn) return fn();
11
- throw new Error("Module not found in bundle: " + path17);
11
+ throw new Error("Module not found in bundle: " + path18);
12
12
  };
13
13
  var __esm = (fn, res) => function __init() {
14
14
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -184,7 +184,10 @@ var init_defaults = __esm({
184
184
  target: "es2022",
185
185
  rolldownOptions: {},
186
186
  emptyOutDir: true,
187
- css: {},
187
+ css: {
188
+ inject: true,
189
+ emit: true
190
+ },
188
191
  reportCompressedSize: true,
189
192
  chunkSizeWarningLimit: 500,
190
193
  cssCodeSplit: true,
@@ -420,7 +423,11 @@ async function resolveConfig(inlineConfig = {}, command) {
420
423
  allowClearScreen: clearScreen2,
421
424
  customLogger: merged.customLogger
422
425
  });
423
- const mergedBuild = { ...defaults.build, ...merged.build };
426
+ const mergedBuild = {
427
+ ...defaults.build,
428
+ ...merged.build,
429
+ css: { ...defaults.build.css, ...merged.build?.css }
430
+ };
424
431
  if (merged.build?.cssMinify === void 0) {
425
432
  mergedBuild.cssMinify = !!mergedBuild.minify;
426
433
  }
@@ -451,11 +458,17 @@ async function resolveConfig(inlineConfig = {}, command) {
451
458
  bundledDev: merged.experimental?.bundledDev ?? defaults.experimental.bundledDev
452
459
  }
453
460
  };
454
- const userEnvironments = {
461
+ const rawUserEnvironments = {
455
462
  client: {},
456
463
  ssr: {},
457
464
  ...merged.environments ?? {}
458
465
  };
466
+ const userEnvironments = Object.fromEntries(
467
+ Object.entries(rawUserEnvironments).map(([name, options]) => [
468
+ name,
469
+ deepMerge({}, options)
470
+ ])
471
+ );
459
472
  for (const [name, envOptions] of Object.entries(userEnvironments)) {
460
473
  for (const plugin of rawPlugins) {
461
474
  if (plugin.configEnvironment) {
@@ -466,6 +479,7 @@ async function resolveConfig(inlineConfig = {}, command) {
466
479
  }
467
480
  for (const [name, envOptions] of Object.entries(userEnvironments)) {
468
481
  const consumer = envOptions.consumer ?? (name === "client" ? "client" : "server");
482
+ const vueOptions = deepMerge({}, envOptions.vue ?? {});
469
483
  if (name === "client") {
470
484
  if (envOptions.resolve) {
471
485
  Object.assign(resolved.resolve, {
@@ -473,9 +487,14 @@ async function resolveConfig(inlineConfig = {}, command) {
473
487
  alias: { ...resolved.resolve.alias, ...envOptions.resolve.alias }
474
488
  });
475
489
  }
476
- if (envOptions.build) Object.assign(resolved.build, envOptions.build);
490
+ if (envOptions.build) {
491
+ const { css, ...environmentBuild } = envOptions.build;
492
+ Object.assign(resolved.build, environmentBuild);
493
+ if (css) resolved.build.css = { ...resolved.build.css, ...css };
494
+ }
477
495
  resolved.environments.client = {
478
496
  consumer,
497
+ buildEnabled: envOptions.buildEnabled ?? true,
479
498
  entry: normalizeEnvironmentEntries(envOptions.entry, root),
480
499
  html: import_node_path.default.resolve(
481
500
  root,
@@ -484,15 +503,18 @@ async function resolveConfig(inlineConfig = {}, command) {
484
503
  driver: envOptions.driver,
485
504
  // 同引用 —— 精确镜像(assertClientEnvironmentMirror 校验)
486
505
  resolve: resolved.resolve,
487
- build: resolved.build
506
+ build: resolved.build,
507
+ vue: vueOptions
488
508
  };
489
509
  continue;
490
510
  }
491
511
  resolved.environments[name] = {
492
512
  consumer,
513
+ buildEnabled: envOptions.buildEnabled ?? true,
493
514
  entry: normalizeEnvironmentEntries(envOptions.entry, root),
494
515
  html: envOptions.consumer === "client" && envOptions.html ? import_node_path.default.resolve(root, envOptions.html) : void 0,
495
516
  driver: envOptions.driver,
517
+ vue: vueOptions,
496
518
  resolve: {
497
519
  alias: { ...resolved.resolve.alias, ...envOptions.resolve?.alias },
498
520
  extensions: envOptions.resolve?.extensions ?? [...resolved.resolve.extensions],
@@ -503,6 +525,7 @@ async function resolveConfig(inlineConfig = {}, command) {
503
525
  build: {
504
526
  ...resolved.build,
505
527
  ...envOptions.build,
528
+ css: { ...resolved.build.css, ...envOptions.build?.css },
506
529
  // 非 client 环境默认产出到 <outDir>/<envName>(如 dist/ssr),可显式覆盖
507
530
  outDir: envOptions.build?.outDir ?? import_node_path.default.join(resolved.build.outDir, name),
508
531
  // server 产物默认不压缩(可调试性优先,与 Vite SSR 默认一致),可显式覆盖
@@ -749,17 +772,23 @@ var init_plugin_container = __esm({
749
772
  }
750
773
  async transform(code, id) {
751
774
  let currentCode = code;
775
+ let lastResult;
752
776
  for (const plugin of this.plugins) {
753
777
  if (!plugin.transform) continue;
754
778
  const result = await plugin.transform.call(this.ctx, currentCode, id);
755
779
  if (result == null) continue;
756
780
  if (typeof result === "string") {
757
781
  currentCode = result;
782
+ lastResult = void 0;
758
783
  } else {
759
784
  currentCode = result.code;
785
+ lastResult = result;
760
786
  }
761
787
  }
762
- return currentCode === code ? null : { code: currentCode };
788
+ return currentCode === code ? null : {
789
+ ...lastResult,
790
+ code: currentCode
791
+ };
763
792
  }
764
793
  /** 完整的模块处理管道: resolveId → load → transform */
765
794
  async processModule(source, importer) {
@@ -782,17 +811,39 @@ var init_plugin_container = __esm({
782
811
  }
783
812
  });
784
813
 
814
+ // src/core/url.ts
815
+ function removeTimestampQuery(url) {
816
+ const hashIndex = url.indexOf("#");
817
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
818
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
819
+ const queryIndex = withoutHash.indexOf("?");
820
+ if (queryIndex < 0) return url;
821
+ const pathname = withoutHash.slice(0, queryIndex);
822
+ const query = withoutHash.slice(queryIndex + 1).split("&").filter((part) => !/^t=\d+$/.test(part)).join("&");
823
+ return pathname + (query ? `?${query}` : "") + hash;
824
+ }
825
+ var init_url = __esm({
826
+ "src/core/url.ts"() {
827
+ "use strict";
828
+ }
829
+ });
830
+
785
831
  // src/core/module-graph.ts
786
832
  var ModuleGraph;
787
833
  var init_module_graph = __esm({
788
834
  "src/core/module-graph.ts"() {
789
835
  "use strict";
836
+ init_url();
790
837
  ModuleGraph = class {
838
+ environmentName;
791
839
  urlToModuleMap = /* @__PURE__ */ new Map();
792
840
  idToModuleMap = /* @__PURE__ */ new Map();
793
841
  fileToModulesMap = /* @__PURE__ */ new Map();
842
+ constructor(environmentName = "client") {
843
+ this.environmentName = environmentName;
844
+ }
794
845
  getModuleByUrl(url) {
795
- return this.urlToModuleMap.get(url);
846
+ return this.urlToModuleMap.get(removeTimestampQuery(url));
796
847
  }
797
848
  getModuleById(id) {
798
849
  return this.idToModuleMap.get(id);
@@ -801,10 +852,11 @@ var init_module_graph = __esm({
801
852
  return this.fileToModulesMap.get(file);
802
853
  }
803
854
  async ensureEntryFromUrl(url) {
804
- let mod = this.urlToModuleMap.get(url);
855
+ const normalizedUrl = removeTimestampQuery(url);
856
+ let mod = this.urlToModuleMap.get(normalizedUrl);
805
857
  if (mod) return mod;
806
- mod = this.createModule(url);
807
- this.urlToModuleMap.set(url, mod);
858
+ mod = this.createModule(normalizedUrl);
859
+ this.urlToModuleMap.set(normalizedUrl, mod);
808
860
  return mod;
809
861
  }
810
862
  createModule(url, id) {
@@ -818,7 +870,9 @@ var init_module_graph = __esm({
818
870
  acceptedHmrDeps: /* @__PURE__ */ new Set(),
819
871
  transformResult: null,
820
872
  lastHMRTimestamp: 0,
821
- isSelfAccepting: false
873
+ invalidationVersion: 0,
874
+ isSelfAccepting: false,
875
+ environment: this.environmentName
822
876
  };
823
877
  this.idToModuleMap.set(mod.id, mod);
824
878
  return mod;
@@ -860,10 +914,64 @@ var init_module_graph = __esm({
860
914
  }
861
915
  }
862
916
  }
917
+ /**
918
+ * 用一次转换得到的信息原子更新 import 与 HMR accept 关系。
919
+ * 依赖节点会在真正被浏览器请求前预先创建,这样入口模块先转换时也能建立完整图。
920
+ */
921
+ async updateModuleInfo(mod, importedUrls, acceptedUrls, isSelfAccepting, expectedInvalidationVersion) {
922
+ const importedModules = await Promise.all(
923
+ [...importedUrls].map((url) => this.ensureEntryFromUrl(url))
924
+ );
925
+ const acceptedModules = await Promise.all(
926
+ [...acceptedUrls].map((url) => this.ensureEntryFromUrl(url))
927
+ );
928
+ if (expectedInvalidationVersion !== void 0 && mod.invalidationVersion !== expectedInvalidationVersion) {
929
+ return null;
930
+ }
931
+ const previousImports = new Set(mod.importedModules);
932
+ for (const imported of previousImports) {
933
+ imported.importers.delete(mod);
934
+ }
935
+ mod.importedModules.clear();
936
+ mod.acceptedHmrDeps.clear();
937
+ for (const imported of importedModules) {
938
+ mod.importedModules.add(imported);
939
+ imported.importers.add(mod);
940
+ }
941
+ for (const accepted of acceptedModules) {
942
+ mod.acceptedHmrDeps.add(accepted);
943
+ }
944
+ mod.isSelfAccepting = isSelfAccepting;
945
+ const pruned = /* @__PURE__ */ new Set();
946
+ for (const imported of previousImports) {
947
+ if (!mod.importedModules.has(imported) && imported.importers.size === 0) {
948
+ pruned.add(imported);
949
+ }
950
+ }
951
+ return pruned;
952
+ }
863
953
  /** 使模块的转换缓存失效 */
864
- invalidateModule(mod) {
954
+ invalidateModule(mod, timestamp = Date.now()) {
865
955
  mod.transformResult = null;
866
- mod.lastHMRTimestamp = Date.now();
956
+ mod.lastHMRTimestamp = timestamp;
957
+ mod.invalidationVersion++;
958
+ }
959
+ /**
960
+ * 仅失效到 HMR 边界:显式接受依赖的模块本身不会重执行;自接受模块需要失效,
961
+ * 但不再继续影响其 importer。这样既能传播依赖时间戳,也不会隐式重复副作用。
962
+ */
963
+ invalidateModuleAndImporters(mod, timestamp = Date.now(), seen = /* @__PURE__ */ new Set()) {
964
+ if (seen.has(mod)) return;
965
+ seen.add(mod);
966
+ this.invalidateModule(mod, timestamp);
967
+ for (const importer of mod.importers) {
968
+ if (importer.acceptedHmrDeps.has(mod)) continue;
969
+ if (importer.isSelfAccepting) {
970
+ this.invalidateModule(importer, timestamp);
971
+ continue;
972
+ }
973
+ this.invalidateModuleAndImporters(importer, timestamp, seen);
974
+ }
867
975
  }
868
976
  /** 使所有模块缓存失效 */
869
977
  invalidateAll() {
@@ -874,34 +982,32 @@ var init_module_graph = __esm({
874
982
  /** 获取 HMR 传播边界 - 从变更模块向上遍历找到接受更新的边界 */
875
983
  getHmrBoundaries(mod) {
876
984
  const boundaries = [];
877
- const visited = /* @__PURE__ */ new Set();
878
- const propagate = (node, via) => {
879
- if (visited.has(node)) return true;
880
- visited.add(node);
881
- if (node.isSelfAccepting) {
882
- boundaries.push({ boundary: node, acceptedVia: via });
883
- return true;
985
+ const traversed = /* @__PURE__ */ new Set();
986
+ const addBoundary = (boundary, acceptedVia) => {
987
+ if (!boundaries.some(
988
+ (item) => item.boundary === boundary && item.acceptedVia === acceptedVia
989
+ )) {
990
+ boundaries.push({ boundary, acceptedVia });
884
991
  }
885
- if (node.acceptedHmrDeps.has(via)) {
886
- boundaries.push({ boundary: node, acceptedVia: via });
992
+ };
993
+ const propagate = (node) => {
994
+ if (traversed.has(node)) return true;
995
+ traversed.add(node);
996
+ if (node.isSelfAccepting) {
997
+ addBoundary(node, node);
887
998
  return true;
888
999
  }
889
1000
  if (node.importers.size === 0) return false;
890
1001
  for (const importer of node.importers) {
891
- if (!propagate(importer, node)) return false;
1002
+ if (importer.acceptedHmrDeps.has(node)) {
1003
+ addBoundary(importer, node);
1004
+ continue;
1005
+ }
1006
+ if (!propagate(importer)) return false;
892
1007
  }
893
1008
  return true;
894
1009
  };
895
- if (mod.isSelfAccepting) {
896
- boundaries.push({ boundary: mod, acceptedVia: mod });
897
- return boundaries;
898
- }
899
- for (const importer of mod.importers) {
900
- if (!propagate(importer, mod)) {
901
- return [];
902
- }
903
- }
904
- return boundaries;
1010
+ return propagate(mod) ? boundaries : [];
905
1011
  }
906
1012
  };
907
1013
  }
@@ -924,12 +1030,12 @@ function createNoopHotChannel() {
924
1030
  }
925
1031
  };
926
1032
  }
927
- function createWsHotChannel(ws) {
1033
+ function createWsHotChannel(ws, environmentName = "client") {
928
1034
  const listeners = /* @__PURE__ */ new Map();
929
1035
  let invokeHandlers;
930
1036
  return {
931
1037
  send(payload) {
932
- ws.send(payload);
1038
+ ws.send({ ...payload, environment: payload.environment ?? environmentName });
933
1039
  },
934
1040
  on(event, listener) {
935
1041
  let set = listeners.get(event);
@@ -941,8 +1047,8 @@ function createWsHotChannel(ws) {
941
1047
  },
942
1048
  listen() {
943
1049
  },
1050
+ // 多个 environment 共享底层 WebSocket server;它由 DevServer.close() 统一关闭。
944
1051
  close() {
945
- ws.close();
946
1052
  },
947
1053
  setInvokeHandler(handlers) {
948
1054
  invokeHandlers = handlers;
@@ -1040,6 +1146,10 @@ var init_environment = __esm({
1040
1146
  moduleGraph;
1041
1147
  candidatePlugins;
1042
1148
  pluginApi;
1149
+ buildMetadata = {};
1150
+ cssModules = /* @__PURE__ */ new Map();
1151
+ assetModules = /* @__PURE__ */ new Map();
1152
+ transformRequestHandler;
1043
1153
  initialized = false;
1044
1154
  constructor(name, config, init = {}) {
1045
1155
  const options = config.environments[name];
@@ -1054,7 +1164,7 @@ var init_environment = __esm({
1054
1164
  this.config = config;
1055
1165
  this.options = options;
1056
1166
  this.hot = init.hot ?? createNoopHotChannel();
1057
- this.moduleGraph = new ModuleGraph();
1167
+ this.moduleGraph = new ModuleGraph(name);
1058
1168
  this.candidatePlugins = init.plugins ?? config.plugins;
1059
1169
  this.pluginApi = init.pluginApi ?? getPluginApi(config);
1060
1170
  }
@@ -1096,6 +1206,53 @@ var init_environment = __esm({
1096
1206
  logger: this.config.logger
1097
1207
  };
1098
1208
  }
1209
+ configureDevPipeline(transformRequest2) {
1210
+ this.transformRequestHandler = transformRequest2;
1211
+ }
1212
+ async transformRequest(url) {
1213
+ if (!this.transformRequestHandler) {
1214
+ throw new Error(
1215
+ `[nasti] environment "${this.name}" does not have an initialized dev transform pipeline`
1216
+ );
1217
+ }
1218
+ return this.transformRequestHandler(url);
1219
+ }
1220
+ setCssModule(module2) {
1221
+ this.cssModules.set(module2.id, { ...module2 });
1222
+ }
1223
+ getCssModule(id) {
1224
+ const module2 = this.cssModules.get(id);
1225
+ return module2 ? { ...module2 } : void 0;
1226
+ }
1227
+ getCssModules() {
1228
+ return Object.freeze(
1229
+ Object.fromEntries(
1230
+ [...this.cssModules].map(([id, module2]) => [id, { ...module2 }])
1231
+ )
1232
+ );
1233
+ }
1234
+ setAssetModule(id, fileName) {
1235
+ this.assetModules.set(id, fileName);
1236
+ }
1237
+ getAssetModules() {
1238
+ return Object.freeze(Object.fromEntries(this.assetModules));
1239
+ }
1240
+ setBuildMetadata(metadata) {
1241
+ const { entries: currentEntries, ...currentMetadata } = this.buildMetadata;
1242
+ const { entries, ...nextMetadata } = metadata;
1243
+ this.buildMetadata = {
1244
+ ...currentMetadata,
1245
+ ...nextMetadata,
1246
+ ...currentEntries || entries ? { entries: { ...currentEntries, ...entries } } : {}
1247
+ };
1248
+ }
1249
+ getBuildMetadata() {
1250
+ const { entries, ...metadata } = this.buildMetadata;
1251
+ return {
1252
+ ...metadata,
1253
+ ...entries ? { entries: { ...entries } } : {}
1254
+ };
1255
+ }
1099
1256
  async close() {
1100
1257
  try {
1101
1258
  await this.driver?.close?.(this.getDriverContext());
@@ -1338,23 +1495,106 @@ var init_env = __esm({
1338
1495
  }
1339
1496
  });
1340
1497
 
1341
- // src/server/middleware.ts
1342
- var middleware_exports = {};
1343
- __export(middleware_exports, {
1344
- REACT_REFRESH_GLOBAL_PREAMBLE: () => REACT_REFRESH_GLOBAL_PREAMBLE,
1345
- getReactRefreshRuntimeEsm: () => getReactRefreshRuntimeEsm,
1346
- transformMiddleware: () => transformMiddleware,
1347
- transformRequest: () => transformRequest
1498
+ // src/plugins/assets.ts
1499
+ function assetsPlugin(config) {
1500
+ const emittedAssets = /* @__PURE__ */ new Set();
1501
+ return {
1502
+ name: "nasti:assets",
1503
+ resolveId(source) {
1504
+ if (source.endsWith("?url") || source.endsWith("?raw")) {
1505
+ return source;
1506
+ }
1507
+ return null;
1508
+ },
1509
+ load(id) {
1510
+ const ext = import_node_path4.default.extname(id.replace(/\?.*$/, ""));
1511
+ if (id.endsWith("?raw")) {
1512
+ const file = id.slice(0, -4);
1513
+ if (import_node_fs4.default.existsSync(file)) {
1514
+ const content = import_node_fs4.default.readFileSync(file, "utf-8");
1515
+ return `export default ${JSON.stringify(content)}`;
1516
+ }
1517
+ }
1518
+ if (id.endsWith("?url") || ASSET_EXTENSIONS.has(ext)) {
1519
+ const file = id.replace(/\?.*$/, "");
1520
+ if (!import_node_fs4.default.existsSync(file)) return null;
1521
+ if (config.command === "serve") {
1522
+ const url = "/" + import_node_path4.default.relative(config.root, file);
1523
+ return `export default ${JSON.stringify(url)}`;
1524
+ }
1525
+ const content = import_node_fs4.default.readFileSync(file);
1526
+ const hash = import_node_crypto.default.createHash("sha256").update(content).digest("hex").slice(0, 8);
1527
+ const basename = import_node_path4.default.basename(file, ext);
1528
+ const hashedName = `${config.build.assetsDir}/${basename}.${hash}${ext}`;
1529
+ const environment = this.environment;
1530
+ if (!environment) {
1531
+ throw new Error("[nasti:assets] build environment is not initialized");
1532
+ }
1533
+ if (!emittedAssets.has(hashedName)) {
1534
+ this.emitFile({
1535
+ type: "asset",
1536
+ fileName: hashedName,
1537
+ source: content
1538
+ });
1539
+ emittedAssets.add(hashedName);
1540
+ }
1541
+ environment.setAssetModule(file, hashedName);
1542
+ return `export default ${JSON.stringify(config.base + hashedName)}`;
1543
+ }
1544
+ return null;
1545
+ }
1546
+ };
1547
+ }
1548
+ function isAssetFile(id) {
1549
+ const ext = import_node_path4.default.extname(id.replace(/\?.*$/, ""));
1550
+ return ASSET_EXTENSIONS.has(ext);
1551
+ }
1552
+ var import_node_path4, import_node_fs4, import_node_crypto, ASSET_EXTENSIONS;
1553
+ var init_assets = __esm({
1554
+ "src/plugins/assets.ts"() {
1555
+ "use strict";
1556
+ import_node_path4 = __toESM(require("path"), 1);
1557
+ import_node_fs4 = __toESM(require("fs"), 1);
1558
+ import_node_crypto = __toESM(require("crypto"), 1);
1559
+ ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
1560
+ ".png",
1561
+ ".jpg",
1562
+ ".jpeg",
1563
+ ".gif",
1564
+ ".svg",
1565
+ ".ico",
1566
+ ".webp",
1567
+ ".avif",
1568
+ ".mp4",
1569
+ ".webm",
1570
+ ".ogg",
1571
+ ".mp3",
1572
+ ".wav",
1573
+ ".flac",
1574
+ ".aac",
1575
+ ".woff",
1576
+ ".woff2",
1577
+ ".eot",
1578
+ ".ttf",
1579
+ ".otf",
1580
+ ".pdf",
1581
+ ".txt"
1582
+ ]);
1583
+ }
1348
1584
  });
1349
- function getReactRefreshRuntimeEsm() {
1350
- if (__refreshRuntimeCache) return __refreshRuntimeCache;
1585
+
1586
+ // src/server/middleware.ts
1587
+ function getReactRefreshRuntimeEsm(includeBoundaryHelpers = false) {
1588
+ if (__refreshRuntimeCache) {
1589
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
1590
+ }
1351
1591
  let cjsPath;
1352
1592
  try {
1353
1593
  const pkgPath = __require.resolve("react-refresh/package.json");
1354
- cjsPath = import_node_path4.default.join(import_node_path4.default.dirname(pkgPath), "cjs", "react-refresh-runtime.development.js");
1594
+ cjsPath = import_node_path5.default.join(import_node_path5.default.dirname(pkgPath), "cjs", "react-refresh-runtime.development.js");
1355
1595
  } catch (err) {
1356
- cjsPath = import_node_path4.default.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
1357
- if (!import_node_fs4.default.existsSync(cjsPath)) {
1596
+ cjsPath = import_node_path5.default.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
1597
+ if (!import_node_fs5.default.existsSync(cjsPath)) {
1358
1598
  const origMsg = err instanceof Error ? err.message : String(err);
1359
1599
  throw new Error(
1360
1600
  `[nasti] Missing dependency "react-refresh". Install it with: npm install react-refresh
@@ -1362,7 +1602,7 @@ Original resolve error: ${origMsg}`
1362
1602
  );
1363
1603
  }
1364
1604
  }
1365
- const cjsSource = import_node_fs4.default.readFileSync(cjsPath, "utf-8");
1605
+ const cjsSource = import_node_fs5.default.readFileSync(cjsPath, "utf-8");
1366
1606
  __refreshRuntimeCache = `// Wrapped react-refresh runtime -> ESM
1367
1607
  const exports = {};
1368
1608
  const module = { exports };
@@ -1382,7 +1622,7 @@ export const findAffectedHostInstances = __rt.findAffectedHostInstances;
1382
1622
  export const collectCustomHooksForSignature = __rt.collectCustomHooksForSignature;
1383
1623
  export default __rt;
1384
1624
  `;
1385
- return __refreshRuntimeCache;
1625
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
1386
1626
  }
1387
1627
  function buildReactRefreshWrapper(moduleUrl, transformedCode) {
1388
1628
  const urlLit = JSON.stringify(moduleUrl);
@@ -1408,27 +1648,46 @@ window.$RefreshReg$ = prevRefreshReg;
1408
1648
  window.$RefreshSig$ = prevRefreshSig;
1409
1649
 
1410
1650
  if (__nasti_hot__) {
1411
- __nasti_hot__.accept(() => {
1412
- clearTimeout(window.__nasti_refresh_timer__);
1413
- window.__nasti_refresh_timer__ = setTimeout(() => {
1414
- RefreshRuntime.performReactRefresh();
1415
- }, 30);
1651
+ let __nasti_current_exports__;
1652
+ __nasti_hot__.accept((nextExports) => {
1653
+ if (!nextExports) return;
1654
+ if (!__nasti_current_exports__) {
1655
+ __nasti_hot__.invalidate('Could not Fast Refresh (previous exports unavailable)');
1656
+ return;
1657
+ }
1658
+ const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate(
1659
+ ${urlLit},
1660
+ __nasti_current_exports__,
1661
+ nextExports,
1662
+ );
1663
+ if (invalidateMessage) __nasti_hot__.invalidate(invalidateMessage);
1664
+ });
1665
+ RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
1666
+ __nasti_current_exports__ = currentExports;
1667
+ RefreshRuntime.registerExportsForReactRefresh(${urlLit}, currentExports);
1416
1668
  });
1417
1669
  }
1418
1670
  `;
1419
1671
  }
1420
1672
  function injectImportMetaHot(code, moduleUrl) {
1421
- if (!/\bimport\.meta\.hot\b/.test(code)) return code;
1673
+ const hotRE = /\bimport\.meta\.hot\b/g;
1674
+ const matches = [...maskStringsAndComments(code).matchAll(hotRE)];
1675
+ if (matches.length === 0) return code;
1676
+ for (const match of matches.reverse()) {
1677
+ const start = match.index;
1678
+ code = code.slice(0, start) + "__nasti_hot__" + code.slice(start + match[0].length);
1679
+ }
1422
1680
  const urlLit = JSON.stringify(moduleUrl);
1423
1681
  const header = `import { createHotContext as __nasti_createHotContext__ } from "/@nasti/client";
1424
1682
  const __nasti_hot__ = __nasti_createHotContext__(${urlLit});
1425
1683
  `;
1426
- return header + code.replace(/\bimport\.meta\.hot\b/g, "__nasti_hot__");
1684
+ return header + code;
1427
1685
  }
1428
1686
  function transformMiddleware(ctx) {
1429
1687
  ctx.envDefine = buildEnvDefine(
1430
1688
  loadEnv(ctx.config.mode, ctx.config.root, ctx.config.envPrefix),
1431
- ctx.config.mode
1689
+ ctx.config.mode,
1690
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
1432
1691
  );
1433
1692
  return async (req, res, next) => {
1434
1693
  const url = req.url ?? "/";
@@ -1473,7 +1732,7 @@ function transformMiddleware(ctx) {
1473
1732
  return;
1474
1733
  }
1475
1734
  }
1476
- if (isModuleRequest(url)) {
1735
+ if (isModuleRequest(url, req.headers["sec-fetch-dest"])) {
1477
1736
  try {
1478
1737
  const result = await transformRequest(url, ctx);
1479
1738
  if (result) {
@@ -1499,13 +1758,14 @@ function transformMiddleware(ctx) {
1499
1758
  }
1500
1759
  async function transformRequest(url, ctx) {
1501
1760
  const { config, pluginContainer, moduleGraph } = ctx;
1761
+ url = removeTimestampQuery(url);
1502
1762
  const cleanReqUrl = url.split("?")[0];
1503
1763
  const cached2 = moduleGraph.getModuleByUrl(url);
1504
1764
  if (cached2?.transformResult) {
1505
1765
  return cached2.transformResult;
1506
1766
  }
1507
1767
  if (cleanReqUrl === "/@react-refresh") {
1508
- return { code: getReactRefreshRuntimeEsm() };
1768
+ return { code: getReactRefreshRuntimeEsm(true) };
1509
1769
  }
1510
1770
  if (cleanReqUrl.startsWith("/@modules/") && url.includes("?")) {
1511
1771
  const idParam = new URLSearchParams(url.slice(url.indexOf("?") + 1)).get("id");
@@ -1513,8 +1773,8 @@ async function transformRequest(url, ctx) {
1513
1773
  let realIdValid = false;
1514
1774
  try {
1515
1775
  if (idParam) {
1516
- realId = import_node_fs4.default.realpathSync(idParam);
1517
- realIdValid = import_node_fs4.default.statSync(realId).isFile() && (realId.includes(`${import_node_path4.default.sep}node_modules${import_node_path4.default.sep}`) || isUnderRoot(realId, config.root));
1776
+ realId = import_node_fs5.default.realpathSync(idParam);
1777
+ realIdValid = import_node_fs5.default.statSync(realId).isFile() && (realId.includes(`${import_node_path5.default.sep}node_modules${import_node_path5.default.sep}`) || isUnderRoot(realId, config.root));
1518
1778
  }
1519
1779
  } catch {
1520
1780
  realId = null;
@@ -1541,40 +1801,63 @@ async function transformRequest(url, ctx) {
1541
1801
  }
1542
1802
  const rawQuery = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
1543
1803
  if (rawQuery && !/^t=\d+$/.test(rawQuery)) {
1544
- const loaded = await pluginContainer.load(url);
1545
- if (loaded != null) {
1546
- let code2 = typeof loaded === "string" ? loaded : loaded.code;
1804
+ const mod2 = await moduleGraph.ensureEntryFromUrl(url);
1805
+ const transformVersion2 = mod2.invalidationVersion;
1806
+ const loaded2 = await pluginContainer.load(url);
1807
+ if (loaded2 != null) {
1808
+ let code2 = typeof loaded2 === "string" ? loaded2 : loaded2.code;
1809
+ let map2 = typeof loaded2 === "string" ? void 0 : loaded2.map;
1547
1810
  const transformed = await pluginContainer.transform(code2, url);
1548
1811
  if (transformed != null) {
1549
1812
  code2 = typeof transformed === "string" ? transformed : transformed.code;
1813
+ if (typeof transformed !== "string" && transformed.map != null) {
1814
+ map2 = transformed.map;
1815
+ }
1550
1816
  }
1551
- const mod2 = await moduleGraph.ensureEntryFromUrl(url);
1552
- moduleGraph.registerModule(mod2, cleanReqUrl);
1553
- code2 = injectImportMetaHot(code2, url);
1817
+ const parentFile = resolveUrlToFile(cleanReqUrl, config.root) ?? cleanReqUrl;
1818
+ moduleGraph.registerModule(mod2, parentFile);
1819
+ const hotInfo2 = rewriteHotAcceptDeps(code2, config, parentFile);
1820
+ code2 = injectImportMetaHot(hotInfo2.code, url);
1554
1821
  code2 = replaceEnvInCode(code2, ctx.envDefine ?? buildEnvDefine(
1555
1822
  loadEnv(config.mode, config.root, config.envPrefix),
1556
- config.mode
1823
+ config.mode,
1824
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
1557
1825
  ));
1558
- code2 = rewriteImports(code2, config, cleanReqUrl);
1559
- const transformResult2 = { code: code2 };
1560
- mod2.transformResult = transformResult2;
1826
+ const importedUrls2 = /* @__PURE__ */ new Set();
1827
+ code2 = rewriteImports(code2, config, parentFile, importedUrls2, moduleGraph);
1828
+ const pruned2 = await moduleGraph.updateModuleInfo(
1829
+ mod2,
1830
+ importedUrls2,
1831
+ hotInfo2.acceptedUrls,
1832
+ hotInfo2.isSelfAccepting,
1833
+ transformVersion2
1834
+ );
1835
+ const transformResult2 = { code: code2, map: map2 };
1836
+ if (pruned2) {
1837
+ if (pruned2.size > 0) ctx.onPrune?.([...pruned2].map((item) => item.url));
1838
+ mod2.transformResult = transformResult2;
1839
+ }
1561
1840
  return transformResult2;
1562
1841
  }
1563
1842
  }
1564
1843
  const filePath = resolveUrlToFile(url, config.root);
1565
- if (!filePath || !import_node_fs4.default.existsSync(filePath)) return null;
1844
+ if (!filePath || !import_node_fs5.default.existsSync(filePath)) return null;
1566
1845
  const mod = await moduleGraph.ensureEntryFromUrl(url);
1567
1846
  moduleGraph.registerModule(mod, filePath);
1847
+ const transformVersion = mod.invalidationVersion;
1568
1848
  if (cleanReqUrl.startsWith("/@modules/")) {
1569
1849
  const code2 = await bundlePackageAsEsm(filePath, config.root);
1570
1850
  const transformResult2 = { code: code2 };
1571
1851
  mod.transformResult = transformResult2;
1572
1852
  return transformResult2;
1573
1853
  }
1574
- let code = import_node_fs4.default.readFileSync(filePath, "utf-8");
1854
+ const loaded = await pluginContainer.load(filePath);
1855
+ let code = loaded == null ? import_node_fs5.default.readFileSync(filePath, "utf-8") : typeof loaded === "string" ? loaded : loaded.code;
1856
+ let map = loaded && typeof loaded !== "string" ? loaded.map : void 0;
1575
1857
  const pluginResult = await pluginContainer.transform(code, filePath);
1576
1858
  if (pluginResult) {
1577
1859
  code = typeof pluginResult === "string" ? pluginResult : pluginResult.code;
1860
+ if (typeof pluginResult !== "string") map = pluginResult.map;
1578
1861
  }
1579
1862
  const stableUrl = cleanReqUrl;
1580
1863
  let wrappedWithRefresh = false;
@@ -1585,26 +1868,41 @@ async function transformRequest(url, ctx) {
1585
1868
  sourcemap: true,
1586
1869
  jsxRuntime: "automatic",
1587
1870
  jsxImportSource: config.framework === "vue" ? "vue" : "react",
1588
- reactRefresh: useRefresh
1871
+ reactRefresh: useRefresh,
1872
+ target: ctx.environment?.options.build.target ?? config.build.target
1589
1873
  });
1590
1874
  code = result.code;
1875
+ if (result.map) map = JSON.parse(result.map);
1591
1876
  if (useRefresh) {
1592
1877
  code = buildReactRefreshWrapper(stableUrl, code);
1593
1878
  wrappedWithRefresh = true;
1594
- mod.isSelfAccepting = true;
1595
1879
  }
1596
1880
  }
1881
+ const hotInfo = rewriteHotAcceptDeps(code, config, filePath);
1882
+ code = hotInfo.code;
1597
1883
  if (!wrappedWithRefresh) {
1598
1884
  code = injectImportMetaHot(code, stableUrl);
1599
1885
  }
1600
1886
  const envDefine = ctx.envDefine ?? buildEnvDefine(
1601
1887
  loadEnv(config.mode, config.root, config.envPrefix),
1602
- config.mode
1888
+ config.mode,
1889
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
1603
1890
  );
1604
1891
  code = replaceEnvInCode(code, envDefine);
1605
- code = rewriteImports(code, config, filePath);
1606
- const transformResult = { code };
1607
- mod.transformResult = transformResult;
1892
+ const importedUrls = /* @__PURE__ */ new Set();
1893
+ code = rewriteImports(code, config, filePath, importedUrls, moduleGraph);
1894
+ const pruned = await moduleGraph.updateModuleInfo(
1895
+ mod,
1896
+ importedUrls,
1897
+ hotInfo.acceptedUrls,
1898
+ wrappedWithRefresh || hotInfo.isSelfAccepting,
1899
+ transformVersion
1900
+ );
1901
+ const transformResult = { code, map };
1902
+ if (pruned) {
1903
+ if (pruned.size > 0) ctx.onPrune?.([...pruned].map((item) => item.url));
1904
+ mod.transformResult = transformResult;
1905
+ }
1608
1906
  return transformResult;
1609
1907
  }
1610
1908
  async function loadVirtualModule(spec, ctx) {
@@ -1612,7 +1910,7 @@ async function loadVirtualModule(spec, ctx) {
1612
1910
  const resolved = await pluginContainer.resolveId(spec);
1613
1911
  if (resolved == null) return null;
1614
1912
  const resolvedId = typeof resolved === "string" ? resolved : resolved.id;
1615
- const looksVirtual = resolvedId.startsWith("\0") || !import_node_fs4.default.existsSync(resolvedId);
1913
+ const looksVirtual = resolvedId.startsWith("\0") || !import_node_fs5.default.existsSync(resolvedId);
1616
1914
  if (!looksVirtual) return null;
1617
1915
  const loadResult = await pluginContainer.load(resolvedId);
1618
1916
  if (loadResult == null) return null;
@@ -1623,9 +1921,10 @@ async function loadVirtualModule(spec, ctx) {
1623
1921
  }
1624
1922
  code = replaceEnvInCode(code, ctx.envDefine ?? buildEnvDefine(
1625
1923
  loadEnv(config.mode, config.root, config.envPrefix),
1626
- config.mode
1924
+ config.mode,
1925
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
1627
1926
  ));
1628
- const anchor = import_node_path4.default.join(config.root, "__nasti_virtual__.ts");
1927
+ const anchor = import_node_path5.default.join(config.root, "__nasti_virtual__.ts");
1629
1928
  code = rewriteImports(code, config, anchor);
1630
1929
  return { id: resolvedId, result: { code } };
1631
1930
  }
@@ -1651,7 +1950,7 @@ async function doBundlePackage(entryFile, root) {
1651
1950
  await bundle2.close();
1652
1951
  let code = result.output[0].code;
1653
1952
  code = code.replace(/process\.env\.NODE_ENV/g, '"development"');
1654
- const externalBaseDir = import_node_path4.default.dirname(entryFile);
1953
+ const externalBaseDir = import_node_path5.default.dirname(entryFile);
1655
1954
  code = code.replace(
1656
1955
  /^(import\b[^;'"]*?\bfrom\s+)(['"])([^'"./][^'"]*)(\2)/gm,
1657
1956
  (_, prefix, q, spec) => `${prefix}${q}${externalSpecToModuleUrl(spec, externalBaseDir, root)}${q}`
@@ -1669,16 +1968,16 @@ async function doBundlePackage(entryFile, root) {
1669
1968
  return code;
1670
1969
  }
1671
1970
  async function tryGenerateSubpathShim(entryFile, root) {
1672
- const NM = `${import_node_path4.default.sep}node_modules${import_node_path4.default.sep}`;
1971
+ const NM = `${import_node_path5.default.sep}node_modules${import_node_path5.default.sep}`;
1673
1972
  if (!entryFile.includes(NM)) return null;
1674
1973
  let pkgDir = null;
1675
1974
  let pkgName = null;
1676
- let dir = import_node_path4.default.dirname(entryFile);
1975
+ let dir = import_node_path5.default.dirname(entryFile);
1677
1976
  while (true) {
1678
- const pkgJsonPath = import_node_path4.default.join(dir, "package.json");
1679
- if (import_node_fs4.default.existsSync(pkgJsonPath)) {
1977
+ const pkgJsonPath = import_node_path5.default.join(dir, "package.json");
1978
+ if (import_node_fs5.default.existsSync(pkgJsonPath)) {
1680
1979
  try {
1681
- const pkg = JSON.parse(import_node_fs4.default.readFileSync(pkgJsonPath, "utf-8"));
1980
+ const pkg = JSON.parse(import_node_fs5.default.readFileSync(pkgJsonPath, "utf-8"));
1682
1981
  if (typeof pkg?.name === "string" && pkg.name) {
1683
1982
  pkgDir = dir;
1684
1983
  pkgName = pkg.name;
@@ -1687,16 +1986,16 @@ async function tryGenerateSubpathShim(entryFile, root) {
1687
1986
  } catch {
1688
1987
  }
1689
1988
  }
1690
- const parent = import_node_path4.default.dirname(dir);
1989
+ const parent = import_node_path5.default.dirname(dir);
1691
1990
  if (parent === dir) return null;
1692
1991
  dir = parent;
1693
1992
  if (!dir.includes(NM)) return null;
1694
1993
  }
1695
1994
  if (!pkgDir || !pkgName) return null;
1696
- const entryExt = import_node_path4.default.extname(entryFile);
1995
+ const entryExt = import_node_path5.default.extname(entryFile);
1697
1996
  const mainEntry = pickMainEntryByExtension(pkgDir, entryExt);
1698
1997
  if (!mainEntry) return null;
1699
- if (import_node_path4.default.resolve(mainEntry) === import_node_path4.default.resolve(entryFile)) return null;
1998
+ if (import_node_path5.default.resolve(mainEntry) === import_node_path5.default.resolve(entryFile)) return null;
1700
1999
  let mainNs;
1701
2000
  let subNs;
1702
2001
  try {
@@ -1720,7 +2019,7 @@ async function tryGenerateSubpathShim(entryFile, root) {
1720
2019
  if (mainNs["default"] !== subNs["default"]) return null;
1721
2020
  }
1722
2021
  const rootMain = resolveNodeModule(root, pkgName);
1723
- const mainEntryUrl = rootMain && rootMain.startsWith(pkgDir + import_node_path4.default.sep) ? `/@modules/${pkgName}` : `/@modules/${pkgName}?id=${encodeURIComponent(mainEntry)}`;
2022
+ const mainEntryUrl = rootMain && rootMain.startsWith(pkgDir + import_node_path5.default.sep) ? `/@modules/${pkgName}` : `/@modules/${pkgName}?id=${encodeURIComponent(mainEntry)}`;
1724
2023
  const lines = [
1725
2024
  `// Nasti subpath shim \u2192 ${pkgName} (avoid duplicate bundling)`,
1726
2025
  `import * as __pkg from "${mainEntryUrl}";`
@@ -1734,10 +2033,10 @@ async function tryGenerateSubpathShim(entryFile, root) {
1734
2033
  return lines.join("\n") + "\n";
1735
2034
  }
1736
2035
  function pickMainEntryByExtension(pkgDir, preferredExt) {
1737
- const pkgJsonPath = import_node_path4.default.join(pkgDir, "package.json");
2036
+ const pkgJsonPath = import_node_path5.default.join(pkgDir, "package.json");
1738
2037
  let pkg;
1739
2038
  try {
1740
- pkg = JSON.parse(import_node_fs4.default.readFileSync(pkgJsonPath, "utf-8"));
2039
+ pkg = JSON.parse(import_node_fs5.default.readFileSync(pkgJsonPath, "utf-8"));
1741
2040
  } catch {
1742
2041
  return null;
1743
2042
  }
@@ -1756,14 +2055,14 @@ function pickMainEntryByExtension(pkgDir, preferredExt) {
1756
2055
  if (typeof pkg.module === "string") candidates.push(pkg.module);
1757
2056
  if (typeof pkg.main === "string") candidates.push(pkg.main);
1758
2057
  for (const cand of candidates) {
1759
- if (import_node_path4.default.extname(cand) === preferredExt) {
1760
- const full = import_node_path4.default.resolve(pkgDir, cand);
1761
- if (import_node_fs4.default.existsSync(full)) return full;
2058
+ if (import_node_path5.default.extname(cand) === preferredExt) {
2059
+ const full = import_node_path5.default.resolve(pkgDir, cand);
2060
+ if (import_node_fs5.default.existsSync(full)) return full;
1762
2061
  }
1763
2062
  }
1764
2063
  for (const cand of candidates) {
1765
- const full = import_node_path4.default.resolve(pkgDir, cand);
1766
- if (import_node_fs4.default.existsSync(full)) return full;
2064
+ const full = import_node_path5.default.resolve(pkgDir, cand);
2065
+ if (import_node_fs5.default.existsSync(full)) return full;
1767
2066
  }
1768
2067
  return null;
1769
2068
  }
@@ -1809,72 +2108,231 @@ async function injectCjsNamedExports(code, entryFile) {
1809
2108
  return code;
1810
2109
  }
1811
2110
  }
1812
- function rewriteImports(code, config, filePath) {
2111
+ function rewriteImports(code, config, filePath, importedUrls, moduleGraph) {
2112
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
2113
+ const transformSpec = (spec) => {
2114
+ const resolved = removeTimestampQuery(resolveSpec(spec));
2115
+ importedUrls?.add(resolved);
2116
+ const timestamp = moduleGraph?.getModuleByUrl(resolved)?.lastHMRTimestamp ?? 0;
2117
+ return timestamp > 0 ? appendTimestampQuery(resolved, timestamp) : resolved;
2118
+ };
2119
+ return code.replace(
2120
+ /\bfrom\s+(['"])([^'"]+)\1/g,
2121
+ (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
2122
+ ).replace(
2123
+ /\bimport\s+(['"])([^'"]+)\1/g,
2124
+ (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
2125
+ ).replace(
2126
+ /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
2127
+ (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
2128
+ );
2129
+ }
2130
+ function createModuleSpecifierResolver(config, filePath) {
1813
2131
  const root = config.root;
1814
- const fileDir = import_node_path4.default.dirname(filePath);
2132
+ const fileDir = import_node_path5.default.dirname(filePath);
1815
2133
  const aliasEntries = Object.entries(config.resolve.alias).sort(
1816
2134
  ([a], [b]) => b.length - a.length
1817
2135
  );
1818
- const toRootUrl = (abs) => "/" + import_node_path4.default.relative(root, abs).replace(/\\/g, "/");
1819
- const transformSpec = (spec) => {
1820
- const suffixMatch = spec.match(/[?#].*$/);
2136
+ const toRootUrl = (abs) => "/" + import_node_path5.default.relative(root, abs).replace(/\\/g, "/");
2137
+ return (specifier) => {
2138
+ const suffixMatch = specifier.match(/[?#].*$/);
1821
2139
  const suffix = suffixMatch ? suffixMatch[0] : "";
1822
- const baseSpec = suffix ? spec.slice(0, -suffix.length) : spec;
2140
+ const baseSpec = suffix ? specifier.slice(0, -suffix.length) : specifier;
1823
2141
  for (const [key, value] of aliasEntries) {
1824
2142
  if (baseSpec === key || baseSpec.startsWith(key + "/")) {
1825
2143
  const aliasBase = resolveAliasTarget(value, root);
1826
2144
  const sub = baseSpec.slice(key.length).replace(/^\//, "");
1827
- const target = sub ? import_node_path4.default.join(aliasBase, sub) : aliasBase;
2145
+ const target = sub ? import_node_path5.default.join(aliasBase, sub) : aliasBase;
1828
2146
  const resolved = tryResolveDiskPath(target);
1829
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
2147
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1830
2148
  }
1831
2149
  }
1832
2150
  if (baseSpec.startsWith("./") || baseSpec.startsWith("../")) {
1833
- const target = import_node_path4.default.resolve(fileDir, baseSpec);
1834
- const resolved = tryResolveDiskPath(target);
1835
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
2151
+ const resolved = tryResolveDiskPath(import_node_path5.default.resolve(fileDir, baseSpec));
2152
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1836
2153
  }
1837
2154
  if (baseSpec.startsWith("/") && !baseSpec.startsWith("/@")) {
1838
- const target = import_node_path4.default.join(root, baseSpec.replace(/^\//, ""));
1839
- const resolved = tryResolveDiskPath(target);
1840
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
2155
+ const resolved = tryResolveDiskPath(import_node_path5.default.join(root, baseSpec.replace(/^\//, "")));
2156
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1841
2157
  }
1842
- if (baseSpec.startsWith("/")) return spec;
1843
- return `/@modules/${spec}`;
2158
+ if (baseSpec.startsWith("/")) return specifier;
2159
+ return `/@modules/${specifier}`;
1844
2160
  };
1845
- return code.replace(
1846
- /\bfrom\s+(['"])([^'"]+)\1/g,
1847
- (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
1848
- ).replace(
1849
- /\bimport\s+(['"])([^'"]+)\1/g,
1850
- (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
1851
- ).replace(
1852
- /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
1853
- (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
1854
- );
2161
+ }
2162
+ function rewriteHotAcceptDeps(code, config, filePath) {
2163
+ const acceptedUrls = /* @__PURE__ */ new Set();
2164
+ const edits = [];
2165
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
2166
+ const acceptRE = /(?:\bimport\.meta\.hot|\b__nasti_hot__)(?:(?:\?\.)|\.)accept\s*\(/g;
2167
+ const searchableCode = maskStringsAndComments(code);
2168
+ let isSelfAccepting = false;
2169
+ let match;
2170
+ while (match = acceptRE.exec(searchableCode)) {
2171
+ let cursor = match.index + match[0].length;
2172
+ const skipTrivia = () => {
2173
+ while (cursor < code.length) {
2174
+ if (/\s/.test(code[cursor])) {
2175
+ cursor++;
2176
+ continue;
2177
+ }
2178
+ if (code[cursor] === "/" && code[cursor + 1] === "/") {
2179
+ cursor += 2;
2180
+ while (cursor < code.length && code[cursor] !== "\n") cursor++;
2181
+ continue;
2182
+ }
2183
+ if (code[cursor] === "/" && code[cursor + 1] === "*") {
2184
+ cursor += 2;
2185
+ while (cursor < code.length && !(code[cursor] === "*" && code[cursor + 1] === "/")) cursor++;
2186
+ cursor += 2;
2187
+ continue;
2188
+ }
2189
+ break;
2190
+ }
2191
+ };
2192
+ skipTrivia();
2193
+ const first = code[cursor];
2194
+ if (!first || first === ")" || first !== "[" && first !== "'" && first !== '"' && first !== "`") {
2195
+ isSelfAccepting = true;
2196
+ continue;
2197
+ }
2198
+ const readLiteral = () => {
2199
+ const quote = code[cursor];
2200
+ if (quote !== "'" && quote !== '"' && quote !== "`") return;
2201
+ const start = cursor;
2202
+ cursor++;
2203
+ let raw = "";
2204
+ while (cursor < code.length) {
2205
+ const char = code[cursor];
2206
+ if (char === "\\") {
2207
+ raw += code[cursor + 1] ?? "";
2208
+ cursor += 2;
2209
+ continue;
2210
+ }
2211
+ if (char === quote) {
2212
+ cursor++;
2213
+ const resolved = removeTimestampQuery(resolveSpec(raw));
2214
+ acceptedUrls.add(resolved);
2215
+ edits.push({ start, end: cursor, value: JSON.stringify(resolved) });
2216
+ return;
2217
+ }
2218
+ if (quote === "`" && char === "$" && code[cursor + 1] === "{") return;
2219
+ raw += char;
2220
+ cursor++;
2221
+ }
2222
+ };
2223
+ if (first === "[") {
2224
+ cursor++;
2225
+ while (cursor < code.length) {
2226
+ skipTrivia();
2227
+ if (code[cursor] === ",") {
2228
+ cursor++;
2229
+ skipTrivia();
2230
+ }
2231
+ if (code[cursor] === "]") break;
2232
+ const before = cursor;
2233
+ readLiteral();
2234
+ if (cursor === before) break;
2235
+ }
2236
+ } else {
2237
+ readLiteral();
2238
+ }
2239
+ }
2240
+ for (const edit of edits.sort((a, b) => b.start - a.start)) {
2241
+ code = code.slice(0, edit.start) + edit.value + code.slice(edit.end);
2242
+ }
2243
+ return { code, acceptedUrls, isSelfAccepting };
2244
+ }
2245
+ function maskStringsAndComments(code) {
2246
+ const masked = code.split("");
2247
+ let state = "code";
2248
+ const isRegexStart = (index2) => {
2249
+ let previous = index2 - 1;
2250
+ while (previous >= 0 && /\s/.test(code[previous])) previous--;
2251
+ return previous < 0 || "=(:,!&|?{};[]+-*%^~<>".includes(code[previous]);
2252
+ };
2253
+ for (let i = 0; i < code.length; i++) {
2254
+ const char = code[i];
2255
+ const next = code[i + 1];
2256
+ if (state === "code") {
2257
+ if (char === "'") state = "single";
2258
+ else if (char === '"') state = "double";
2259
+ else if (char === "`") state = "template";
2260
+ else if (char === "/" && next === "/") state = "line-comment";
2261
+ else if (char === "/" && next === "*") state = "block-comment";
2262
+ else if (char === "/" && isRegexStart(i)) state = "regex";
2263
+ else continue;
2264
+ masked[i] = " ";
2265
+ continue;
2266
+ }
2267
+ if (state === "line-comment") {
2268
+ if (char === "\n") {
2269
+ state = "code";
2270
+ } else {
2271
+ masked[i] = " ";
2272
+ }
2273
+ continue;
2274
+ }
2275
+ if (state === "block-comment") {
2276
+ masked[i] = char === "\n" ? "\n" : " ";
2277
+ if (char === "*" && next === "/") {
2278
+ masked[i + 1] = " ";
2279
+ i++;
2280
+ state = "code";
2281
+ }
2282
+ continue;
2283
+ }
2284
+ if (state === "regex" || state === "regex-class") {
2285
+ masked[i] = char === "\n" ? "\n" : " ";
2286
+ if (char === "\\") {
2287
+ if (i + 1 < code.length) masked[++i] = " ";
2288
+ } else if (state === "regex" && char === "[") {
2289
+ state = "regex-class";
2290
+ } else if (state === "regex-class" && char === "]") {
2291
+ state = "regex";
2292
+ } else if (state === "regex" && char === "/") {
2293
+ state = "code";
2294
+ }
2295
+ continue;
2296
+ }
2297
+ masked[i] = char === "\n" ? "\n" : " ";
2298
+ if (char === "\\") {
2299
+ if (i + 1 < code.length) masked[++i] = " ";
2300
+ continue;
2301
+ }
2302
+ if (state === "single" && char === "'" || state === "double" && char === '"' || state === "template" && char === "`") {
2303
+ state = "code";
2304
+ }
2305
+ }
2306
+ return masked.join("");
1855
2307
  }
1856
2308
  function resolveAliasTarget(value, root) {
1857
- if (import_node_path4.default.isAbsolute(value) && import_node_fs4.default.existsSync(value)) return value;
1858
- if (value.startsWith("/")) return import_node_path4.default.join(root, value.slice(1));
1859
- return import_node_path4.default.resolve(root, value);
2309
+ if (import_node_path5.default.isAbsolute(value) && import_node_fs5.default.existsSync(value)) return value;
2310
+ if (value.startsWith("/")) return import_node_path5.default.join(root, value.slice(1));
2311
+ return import_node_path5.default.resolve(root, value);
1860
2312
  }
1861
2313
  function tryResolveDiskPath(target) {
1862
- if (import_node_fs4.default.existsSync(target) && import_node_fs4.default.statSync(target).isFile()) return target;
2314
+ if (import_node_fs5.default.existsSync(target) && import_node_fs5.default.statSync(target).isFile()) return target;
1863
2315
  for (const ext of RESOLVE_EXTENSIONS) {
1864
2316
  const withExt = target + ext;
1865
- if (import_node_fs4.default.existsSync(withExt) && import_node_fs4.default.statSync(withExt).isFile()) return withExt;
2317
+ if (import_node_fs5.default.existsSync(withExt) && import_node_fs5.default.statSync(withExt).isFile()) return withExt;
1866
2318
  }
1867
- if (import_node_fs4.default.existsSync(target) && import_node_fs4.default.statSync(target).isDirectory()) {
2319
+ if (import_node_fs5.default.existsSync(target) && import_node_fs5.default.statSync(target).isDirectory()) {
1868
2320
  for (const ext of RESOLVE_EXTENSIONS) {
1869
- const idx = import_node_path4.default.join(target, "index" + ext);
1870
- if (import_node_fs4.default.existsSync(idx) && import_node_fs4.default.statSync(idx).isFile()) return idx;
2321
+ const idx = import_node_path5.default.join(target, "index" + ext);
2322
+ if (import_node_fs5.default.existsSync(idx) && import_node_fs5.default.statSync(idx).isFile()) return idx;
1871
2323
  }
1872
2324
  }
1873
2325
  return null;
1874
2326
  }
1875
2327
  function isUnderRoot(abs, root) {
1876
- const rel = import_node_path4.default.relative(root, abs);
1877
- return !!rel && !rel.startsWith("..") && !import_node_path4.default.isAbsolute(rel);
2328
+ const rel = import_node_path5.default.relative(root, abs);
2329
+ return !!rel && !rel.startsWith("..") && !import_node_path5.default.isAbsolute(rel);
2330
+ }
2331
+ function appendTimestampQuery(url, timestamp) {
2332
+ const hashIndex = url.indexOf("#");
2333
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
2334
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
2335
+ return `${withoutHash}${withoutHash.includes("?") ? "&" : "?"}t=${timestamp}${hash}`;
1878
2336
  }
1879
2337
  function externalSpecToModuleUrl(spec, baseDir, root) {
1880
2338
  const resolved = resolveNodeModule(baseDir, spec);
@@ -1887,7 +2345,7 @@ function resolveNodeModule(baseDir, moduleName) {
1887
2345
  const resolved = resolveNodeModuleEntry(baseDir, moduleName);
1888
2346
  if (!resolved) return null;
1889
2347
  try {
1890
- return import_node_fs4.default.realpathSync(resolved);
2348
+ return import_node_fs5.default.realpathSync(resolved);
1891
2349
  } catch {
1892
2350
  return resolved;
1893
2351
  }
@@ -1907,21 +2365,21 @@ function resolveNodeModuleEntry(root, moduleName) {
1907
2365
  let pkgDir = null;
1908
2366
  let dir = root;
1909
2367
  for (; ; ) {
1910
- const candidate = import_node_path4.default.join(dir, "node_modules", pkgName);
1911
- if (import_node_fs4.default.existsSync(candidate)) {
2368
+ const candidate = import_node_path5.default.join(dir, "node_modules", pkgName);
2369
+ if (import_node_fs5.default.existsSync(candidate)) {
1912
2370
  pkgDir = candidate;
1913
2371
  break;
1914
2372
  }
1915
- const parent = import_node_path4.default.dirname(dir);
2373
+ const parent = import_node_path5.default.dirname(dir);
1916
2374
  if (parent === dir) break;
1917
2375
  dir = parent;
1918
2376
  }
1919
2377
  if (!pkgDir) return null;
1920
- const pkgJsonPath = import_node_path4.default.join(pkgDir, "package.json");
1921
- if (!import_node_fs4.default.existsSync(pkgJsonPath)) return null;
2378
+ const pkgJsonPath = import_node_path5.default.join(pkgDir, "package.json");
2379
+ if (!import_node_fs5.default.existsSync(pkgJsonPath)) return null;
1922
2380
  let pkg;
1923
2381
  try {
1924
- pkg = JSON.parse(import_node_fs4.default.readFileSync(pkgJsonPath, "utf-8"));
2382
+ pkg = JSON.parse(import_node_fs5.default.readFileSync(pkgJsonPath, "utf-8"));
1925
2383
  } catch {
1926
2384
  return null;
1927
2385
  }
@@ -1934,32 +2392,32 @@ function resolveNodeModuleEntry(root, moduleName) {
1934
2392
  const subDirs = [""];
1935
2393
  for (const field of ["module", "main"]) {
1936
2394
  if (typeof pkg[field] === "string") {
1937
- const dir2 = import_node_path4.default.dirname(pkg[field]);
2395
+ const dir2 = import_node_path5.default.dirname(pkg[field]);
1938
2396
  if (dir2 && dir2 !== "." && !subDirs.includes(dir2)) subDirs.push(dir2);
1939
2397
  }
1940
2398
  }
1941
2399
  for (const dir2 of subDirs) {
1942
- const direct = import_node_path4.default.join(pkgDir, dir2, subpath);
1943
- if (import_node_fs4.default.existsSync(direct) && import_node_fs4.default.statSync(direct).isFile()) return direct;
2400
+ const direct = import_node_path5.default.join(pkgDir, dir2, subpath);
2401
+ if (import_node_fs5.default.existsSync(direct) && import_node_fs5.default.statSync(direct).isFile()) return direct;
1944
2402
  for (const ext of RESOLVE_EXTENSIONS) {
1945
- if (import_node_fs4.default.existsSync(direct + ext)) return direct + ext;
2403
+ if (import_node_fs5.default.existsSync(direct + ext)) return direct + ext;
1946
2404
  }
1947
2405
  }
1948
2406
  return null;
1949
2407
  }
1950
2408
  for (const field of ["module", "jsnext:main", "jsnext", "main"]) {
1951
2409
  if (typeof pkg[field] === "string") {
1952
- const entry = import_node_path4.default.join(pkgDir, pkg[field]);
1953
- if (import_node_fs4.default.existsSync(entry)) return entry;
2410
+ const entry = import_node_path5.default.join(pkgDir, pkg[field]);
2411
+ if (import_node_fs5.default.existsSync(entry)) return entry;
1954
2412
  }
1955
2413
  }
1956
- const indexFallback = import_node_path4.default.join(pkgDir, "index.js");
1957
- if (import_node_fs4.default.existsSync(indexFallback)) return indexFallback;
2414
+ const indexFallback = import_node_path5.default.join(pkgDir, "index.js");
2415
+ if (import_node_fs5.default.existsSync(indexFallback)) return indexFallback;
1958
2416
  return null;
1959
2417
  }
1960
2418
  function resolvePackageExports(exports2, key, pkgDir) {
1961
2419
  if (typeof exports2 === "string") {
1962
- return key === "." ? import_node_path4.default.join(pkgDir, exports2) : null;
2420
+ return key === "." ? import_node_path5.default.join(pkgDir, exports2) : null;
1963
2421
  }
1964
2422
  const entry = exports2[key];
1965
2423
  if (entry === void 0) {
@@ -1971,7 +2429,7 @@ function resolvePackageExports(exports2, key, pkgDir) {
1971
2429
  return resolveExportValue(entry, pkgDir);
1972
2430
  }
1973
2431
  function resolveExportValue(value, pkgDir) {
1974
- if (typeof value === "string") return import_node_path4.default.join(pkgDir, value);
2432
+ if (typeof value === "string") return import_node_path5.default.join(pkgDir, value);
1975
2433
  if (Array.isArray(value)) {
1976
2434
  for (const item of value) {
1977
2435
  const r = resolveExportValue(item, pkgDir);
@@ -1995,54 +2453,62 @@ function resolveUrlToFile(url, root) {
1995
2453
  const moduleName = cleanUrl.slice("/@modules/".length);
1996
2454
  return resolveNodeModule(root, moduleName);
1997
2455
  }
1998
- const filePath = import_node_path4.default.resolve(root, cleanUrl.replace(/^\//, ""));
1999
- if (import_node_fs4.default.existsSync(filePath) && import_node_fs4.default.statSync(filePath).isFile()) {
2456
+ const filePath = import_node_path5.default.resolve(root, cleanUrl.replace(/^\//, ""));
2457
+ if (import_node_fs5.default.existsSync(filePath) && import_node_fs5.default.statSync(filePath).isFile()) {
2000
2458
  return filePath;
2001
2459
  }
2002
2460
  for (const ext of RESOLVE_EXTENSIONS) {
2003
2461
  const withExt = filePath + ext;
2004
- if (import_node_fs4.default.existsSync(withExt)) return withExt;
2462
+ if (import_node_fs5.default.existsSync(withExt)) return withExt;
2005
2463
  }
2006
2464
  for (const ext of RESOLVE_EXTENSIONS) {
2007
- const indexFile = import_node_path4.default.join(filePath, "index" + ext);
2008
- if (import_node_fs4.default.existsSync(indexFile)) return indexFile;
2465
+ const indexFile = import_node_path5.default.join(filePath, "index" + ext);
2466
+ if (import_node_fs5.default.existsSync(indexFile)) return indexFile;
2009
2467
  }
2010
2468
  return null;
2011
2469
  }
2012
- function isModuleRequest(url) {
2470
+ function isModuleRequest(url, destination) {
2013
2471
  const cleanUrl = url.split("?")[0];
2014
2472
  if (/\.(ts|tsx|jsx|js|mjs|vue|css|json)$/.test(cleanUrl)) return true;
2015
2473
  if (cleanUrl.startsWith("/@modules/")) return true;
2016
- if (!import_node_path4.default.extname(cleanUrl)) return true;
2474
+ if (isAssetFile(cleanUrl)) {
2475
+ const query = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
2476
+ const isExplicitAssetModule = /(?:^|&)(?:url|raw)(?:&|$)/.test(query);
2477
+ return isExplicitAssetModule || destination === "script";
2478
+ }
2479
+ if (!import_node_path5.default.extname(cleanUrl)) return true;
2017
2480
  return false;
2018
2481
  }
2019
2482
  function getHmrClientCode() {
2020
2483
  return `
2021
2484
  // Nasti HMR Client
2022
- const socket = new WebSocket(\`ws://\${location.host}\`, 'nasti-hmr');
2485
+ const socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
2486
+ const socket = new WebSocket(socketProtocol + '://' + location.host, 'nasti-hmr');
2023
2487
  const hotModulesMap = new Map();
2024
2488
  const disposeMap = new Map();
2025
2489
  const pruneMap = new Map();
2490
+ const dataMap = new Map();
2491
+ const customListenersMap = new Map();
2492
+ let updateQueue = [];
2493
+ let pendingUpdateQueue = false;
2026
2494
 
2027
2495
  socket.addEventListener('message', async ({ data }) => {
2028
2496
  const payload = JSON.parse(data);
2497
+ // \u9ED8\u8BA4\u6D4F\u89C8\u5668 client \u53EA\u6D88\u8D39\u81EA\u5DF1\u7684 HMR \u6D88\u606F\uFF1Bnative/worker \u73AF\u5883\u901A\u8FC7\u5404\u81EA\u7684
2498
+ // HotChannel \u6216 app-level HMR \u534F\u8C03\u5668\u5904\u7406\u540C\u4E00 transport \u4E0A\u7684\u547D\u540D\u6D88\u606F\u3002
2499
+ if (payload.environment && payload.environment !== 'client') return;
2029
2500
  switch (payload.type) {
2030
2501
  case 'connected':
2031
- console.log('[nasti] connected.');
2502
+ console.debug('[nasti] connected.');
2032
2503
  clearErrorOverlay();
2033
2504
  break;
2034
2505
  case 'update':
2035
2506
  try {
2036
- await Promise.all(payload.updates.map((update) => {
2037
- if (update.type === 'js-update') {
2038
- return fetchUpdate(update);
2039
- } else if (update.type === 'css-update') {
2040
- return updateCss(update.path);
2041
- }
2042
- }));
2507
+ // CSS \u5728 unbundled \u6A21\u5F0F\u4E0B\u4E5F\u662F\u4F1A\u6CE8\u5165 <style> \u7684 JS \u6A21\u5757\uFF0C\u548C\u666E\u901A JS
2508
+ // \u4E00\u6837\u91CD\u65B0 import \u624D\u80FD\u6267\u884C dispose/accept \u5E76\u4FDD\u6301\u9875\u9762\u72B6\u6001\u3002
2509
+ await Promise.all(payload.updates.map(queueUpdate));
2043
2510
  clearErrorOverlay();
2044
- console.log('[nasti] HMR update complete, reloading page');
2045
- location.reload();
2511
+ console.debug('[nasti] HMR update complete.');
2046
2512
  } catch (err) {
2047
2513
  console.error('[nasti] HMR update failed:', err);
2048
2514
  showErrorOverlay(err);
@@ -2053,11 +2519,34 @@ socket.addEventListener('message', async ({ data }) => {
2053
2519
  location.reload();
2054
2520
  break;
2055
2521
  case 'prune':
2056
- payload.paths.forEach((p) => {
2057
- const cb = pruneMap.get(p);
2058
- if (cb) cb();
2059
- });
2522
+ await Promise.all(payload.paths.map(async (path) => {
2523
+ const data = dataMap.get(path);
2524
+ const dispose = disposeMap.get(path);
2525
+ const prune = pruneMap.get(path);
2526
+ if (dispose) await dispose(data);
2527
+ if (prune) await prune(data);
2528
+ hotModulesMap.delete(path);
2529
+ disposeMap.delete(path);
2530
+ pruneMap.delete(path);
2531
+ dataMap.delete(path);
2532
+ clearCustomListeners(path);
2533
+ }));
2534
+ break;
2535
+ case 'custom': {
2536
+ const listenersByOwner = customListenersMap.get(payload.event);
2537
+ if (!listenersByOwner) break;
2538
+ const results = await Promise.allSettled(
2539
+ [...listenersByOwner.values()]
2540
+ .flatMap((listeners) => [...listeners])
2541
+ .map((listener) => Promise.resolve().then(() => listener(payload.data)))
2542
+ );
2543
+ for (const result of results) {
2544
+ if (result.status === 'rejected') {
2545
+ console.error('[nasti] custom HMR event listener failed:', result.reason);
2546
+ }
2547
+ }
2060
2548
  break;
2549
+ }
2061
2550
  case 'error':
2062
2551
  console.error('[nasti] error:', payload.err.message);
2063
2552
  showErrorOverlay(payload.err);
@@ -2065,33 +2554,64 @@ socket.addEventListener('message', async ({ data }) => {
2065
2554
  }
2066
2555
  });
2067
2556
 
2068
- // \u81EA\u52A8\u91CD\u8FDE\uFF08\u65AD\u7EBF\u65F6\u6307\u6570\u9000\u907F\uFF09
2557
+ // \u670D\u52A1\u91CD\u542F\u540E\u65E7\u6A21\u5757\u56FE\u5DF2\u5931\u6548\uFF0C\u91CD\u8FDE\u65F6\u6574\u9875\u5237\u65B0\u662F\u5FC5\u8981\u515C\u5E95\uFF1B\u6B63\u5E38 update \u4E0D\u518D\u5237\u65B0\u3002
2069
2558
  let reconnectTimer = 0;
2070
2559
  socket.addEventListener('close', () => {
2071
2560
  clearTimeout(reconnectTimer);
2072
2561
  reconnectTimer = setTimeout(() => location.reload(), 1000);
2073
2562
  });
2074
2563
 
2564
+ /**
2565
+ * \u540C\u4E00\u6279\u66F4\u65B0\u5148\u5168\u90E8\u62C9\u53D6\uFF0C\u518D\u6309\u670D\u52A1\u7AEF\u6D88\u606F\u987A\u5E8F\u6267\u884C accept \u56DE\u8C03\uFF0C\u907F\u514D HTTP \u5F80\u8FD4\u901F\u5EA6
2566
+ * \u6539\u53D8\u6A21\u5757\u5E94\u7528\u987A\u5E8F\u3002\u8FD9\u4E0E Vite HMRClient \u7684 fetch/apply \u4E24\u9636\u6BB5\u4E00\u81F4\u3002
2567
+ */
2568
+ async function queueUpdate(update) {
2569
+ updateQueue.push(fetchUpdate(update));
2570
+ if (pendingUpdateQueue) return;
2571
+
2572
+ pendingUpdateQueue = true;
2573
+ await Promise.resolve();
2574
+ pendingUpdateQueue = false;
2575
+ const loading = updateQueue;
2576
+ updateQueue = [];
2577
+ const applyUpdates = await Promise.all(loading);
2578
+ for (const apply of applyUpdates) {
2579
+ if (apply) apply();
2580
+ }
2581
+ }
2582
+
2075
2583
  async function fetchUpdate(update) {
2076
2584
  const mod = hotModulesMap.get(update.path);
2077
- // \u5148\u8DD1 dispose\uFF08\u7ED9\u6A21\u5757\u673A\u4F1A\u6E05\u7406\u526F\u4F5C\u7528\uFF09
2078
- const dispose = disposeMap.get(update.path);
2079
- if (dispose) dispose();
2585
+ // \u5C1A\u672A\u5728\u5F53\u524D\u9875\u9762\u52A0\u8F7D\u7684\u52A8\u6001\u6A21\u5757\u4E0D\u9700\u8981\u66F4\u65B0\u3002
2586
+ if (!mod) return;
2080
2587
 
2081
- const newMod = await import(update.acceptedPath + '?t=' + update.timestamp);
2082
- if (mod) {
2083
- // \u590D\u5236\u56DE\u8C03\u6570\u7EC4\u907F\u514D\u56DE\u8C03\u5185\u90E8\u53C8\u4FEE\u6539 hotModulesMap \u9020\u6210\u8FED\u4EE3\u5F02\u5E38
2084
- [...mod.callbacks].forEach((cb) => cb(newMod));
2085
- }
2588
+ // \u5FC5\u987B\u5728\u91CD\u65B0 import \u524D\u786E\u5B9A\u65E7\u56DE\u8C03\uFF1B\u65B0\u6A21\u5757\u6267\u884C createHotContext \u65F6\u4F1A\u6E05\u7A7A\u5E76\u6CE8\u518C\u65B0\u56DE\u8C03\u3002
2589
+ const qualifiedCallbacks = mod.callbacks.filter(({ deps }) =>
2590
+ deps.includes(update.acceptedPath)
2591
+ );
2592
+ const isSelfUpdate = update.path === update.acceptedPath;
2593
+ if (!isSelfUpdate && qualifiedCallbacks.length === 0) return;
2594
+
2595
+ const dispose = disposeMap.get(update.acceptedPath);
2596
+ if (dispose) await dispose(dataMap.get(update.acceptedPath));
2597
+ const newMod = await import(appendTimestampQuery(update.acceptedPath, update.timestamp));
2598
+
2599
+ return () => {
2600
+ for (const { deps, fn } of qualifiedCallbacks) {
2601
+ fn(deps.map((dep) => dep === update.acceptedPath ? newMod : undefined));
2602
+ }
2603
+ const detail = isSelfUpdate
2604
+ ? update.path
2605
+ : update.acceptedPath + ' via ' + update.path;
2606
+ console.debug('[nasti] hot updated:', detail);
2607
+ };
2086
2608
  }
2087
2609
 
2088
- function updateCss(path) {
2089
- const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
2090
- if (el) {
2091
- return fetch(path + '?t=' + Date.now())
2092
- .then(r => r.text())
2093
- .then(css => { el.textContent = css; });
2094
- }
2610
+ function appendTimestampQuery(url, timestamp) {
2611
+ const hashIndex = url.indexOf('#');
2612
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : '';
2613
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
2614
+ return withoutHash + (withoutHash.includes('?') ? '&' : '?') + 't=' + timestamp + hash;
2095
2615
  }
2096
2616
 
2097
2617
  function clearErrorOverlay() {
@@ -2119,23 +2639,31 @@ function showErrorOverlay(err) {
2119
2639
  document.body.appendChild(overlay);
2120
2640
  }
2121
2641
 
2122
- /**
2123
- * \u751F\u6210 import.meta.hot \u7684 hot context\u3002
2124
- * \u5173\u952E\u7EA6\u675F\uFF1A\u540C\u4E00 ownerPath \u7684 accept \u56DE\u8C03\u5FC5\u987B\u66FF\u6362\uFF08\u4E0D\u662F append\uFF09\u3002
2125
- * \u6BCF\u6B21\u6A21\u5757\u91CD\u65B0 import \u90FD\u4F1A\u8C03\u7528 createHotContext\uFF0C\u65E7\u56DE\u8C03\u4F1A\u88AB fetchUpdate \u8C03\u7528\u540E\u7ACB\u5373\u88AB\u65B0 import
2126
- * \u91CC\u7684 accept \u66FF\u6362\u3002\u4E0D\u66FF\u6362\u7684\u8BDD\u6BCF\u7F16\u8F91\u4E00\u6B21\u5C31\u591A\u4E00\u4E2A\u56DE\u8C03\uFF0C\u8D8A\u8DD1\u8D8A\u6162\u3002
2127
- */
2128
2642
  export function createHotContext(ownerPath) {
2643
+ if (!dataMap.has(ownerPath)) dataMap.set(ownerPath, {});
2644
+
2645
+ // \u6A21\u5757\u91CD\u65B0\u6267\u884C\u65F6\u4E22\u5F03\u65E7 accept \u56DE\u8C03\uFF0C\u4F46\u4FDD\u7559\u540C\u4E00\u4E2A hot.data \u5BF9\u8C61\u3002
2646
+ const existing = hotModulesMap.get(ownerPath);
2647
+ if (existing) existing.callbacks = [];
2648
+ clearCustomListeners(ownerPath);
2649
+
2650
+ const acceptDeps = (deps, callback = () => {}) => {
2651
+ const mod = hotModulesMap.get(ownerPath) || { id: ownerPath, callbacks: [] };
2652
+ mod.callbacks.push({ deps, fn: callback });
2653
+ hotModulesMap.set(ownerPath, mod);
2654
+ };
2655
+
2129
2656
  return {
2130
2657
  accept(deps, callback) {
2131
- // \u81EA\u63A5\u53D7: hot.accept() \u6216 hot.accept(callback)
2132
2658
  if (typeof deps === 'function' || deps === undefined) {
2133
- hotModulesMap.set(ownerPath, { callbacks: [deps || (() => {})] });
2134
- return;
2659
+ acceptDeps([ownerPath], ([mod]) => deps?.(mod));
2660
+ } else if (typeof deps === 'string') {
2661
+ acceptDeps([deps], ([mod]) => callback?.(mod));
2662
+ } else if (Array.isArray(deps)) {
2663
+ acceptDeps(deps, callback);
2664
+ } else {
2665
+ throw new Error('invalid hot.accept() usage');
2135
2666
  }
2136
- // \u4F9D\u8D56\u63A5\u53D7: hot.accept(deps, callback)\uFF0C\u591A\u6B21\u8C03\u7528\u8FFD\u52A0
2137
- const existing = hotModulesMap.get(ownerPath)?.callbacks ?? [];
2138
- hotModulesMap.set(ownerPath, { callbacks: [...existing, callback] });
2139
2667
  },
2140
2668
  prune(callback) {
2141
2669
  pruneMap.set(ownerPath, callback);
@@ -2143,30 +2671,122 @@ export function createHotContext(ownerPath) {
2143
2671
  dispose(callback) {
2144
2672
  disposeMap.set(ownerPath, callback);
2145
2673
  },
2674
+ on(event, callback) {
2675
+ let listenersByOwner = customListenersMap.get(event);
2676
+ if (!listenersByOwner) {
2677
+ listenersByOwner = new Map();
2678
+ customListenersMap.set(event, listenersByOwner);
2679
+ }
2680
+ let listeners = listenersByOwner.get(ownerPath);
2681
+ if (!listeners) {
2682
+ listeners = new Set();
2683
+ listenersByOwner.set(ownerPath, listeners);
2684
+ }
2685
+ listeners.add(callback);
2686
+ },
2687
+ off(event, callback) {
2688
+ const listenersByOwner = customListenersMap.get(event);
2689
+ const listeners = listenersByOwner?.get(ownerPath);
2690
+ listeners?.delete(callback);
2691
+ if (listeners?.size === 0) listenersByOwner.delete(ownerPath);
2692
+ if (listenersByOwner?.size === 0) customListenersMap.delete(event);
2693
+ },
2146
2694
  invalidate() {
2147
2695
  location.reload();
2148
2696
  },
2149
- data: {},
2697
+ data: dataMap.get(ownerPath),
2150
2698
  };
2151
2699
  }
2700
+
2701
+ function clearCustomListeners(ownerPath) {
2702
+ for (const [event, listenersByOwner] of customListenersMap) {
2703
+ listenersByOwner.delete(ownerPath);
2704
+ if (listenersByOwner.size === 0) customListenersMap.delete(event);
2705
+ }
2706
+ }
2152
2707
  `;
2153
2708
  }
2154
- var import_node_path4, import_node_fs4, import_node_module, import_node_url2, import_picocolors3, import_meta, __dirname_esm, __require, __refreshRuntimeCache, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
2709
+ var import_node_path5, import_node_fs5, import_node_module, import_node_url2, import_picocolors3, import_meta, __dirname_esm, __require, __refreshRuntimeCache, REACT_REFRESH_BOUNDARY_HELPERS, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
2155
2710
  var init_middleware = __esm({
2156
2711
  "src/server/middleware.ts"() {
2157
2712
  "use strict";
2158
- import_node_path4 = __toESM(require("path"), 1);
2159
- import_node_fs4 = __toESM(require("fs"), 1);
2713
+ import_node_path5 = __toESM(require("path"), 1);
2714
+ import_node_fs5 = __toESM(require("fs"), 1);
2160
2715
  import_node_module = require("module");
2161
2716
  import_node_url2 = require("url");
2162
2717
  import_picocolors3 = __toESM(require("picocolors"), 1);
2163
2718
  init_transformer();
2164
2719
  init_html();
2165
2720
  init_env();
2721
+ init_url();
2722
+ init_assets();
2166
2723
  import_meta = {};
2167
- __dirname_esm = import_node_path4.default.dirname((0, import_node_url2.fileURLToPath)(import_meta.url));
2724
+ __dirname_esm = import_node_path5.default.dirname((0, import_node_url2.fileURLToPath)(import_meta.url));
2168
2725
  __require = (0, import_node_module.createRequire)(import_meta.url);
2169
2726
  __refreshRuntimeCache = null;
2727
+ REACT_REFRESH_BOUNDARY_HELPERS = `
2728
+ function __nastiIsPlainObject(obj) {
2729
+ return Object.prototype.toString.call(obj) === '[object Object]' &&
2730
+ (obj.constructor === Object || obj.constructor === undefined);
2731
+ }
2732
+ function __nastiIsCompoundComponent(type) {
2733
+ if (!__nastiIsPlainObject(type)) return false;
2734
+ for (const key in type) {
2735
+ if (!isLikelyComponentType(type[key])) return false;
2736
+ }
2737
+ return true;
2738
+ }
2739
+ export function registerExportsForReactRefresh(filename, moduleExports) {
2740
+ for (const key in moduleExports) {
2741
+ if (key === '__esModule') continue;
2742
+ const value = moduleExports[key];
2743
+ if (isLikelyComponentType(value)) {
2744
+ register(value, filename + ' export ' + key);
2745
+ } else if (__nastiIsCompoundComponent(value)) {
2746
+ for (const subKey in value) {
2747
+ register(value[subKey], filename + ' export ' + key + '-' + subKey);
2748
+ }
2749
+ }
2750
+ }
2751
+ }
2752
+ let __nastiRefreshTimer;
2753
+ function __nastiEnqueueRefresh() {
2754
+ clearTimeout(__nastiRefreshTimer);
2755
+ __nastiRefreshTimer = setTimeout(() => performReactRefresh(), 16);
2756
+ }
2757
+ function __nastiCheckExports(ignored, exports, predicate) {
2758
+ for (const key in exports) {
2759
+ if (ignored.includes(key)) continue;
2760
+ if (!predicate(key, exports[key])) return key;
2761
+ }
2762
+ return true;
2763
+ }
2764
+ export function validateRefreshBoundaryAndEnqueueUpdate(id, prevExports, nextExports) {
2765
+ const ignored = window.__getReactRefreshIgnoredExports?.({ id }) ?? [];
2766
+ if (__nastiCheckExports(ignored, prevExports, (key) => key in nextExports) !== true) {
2767
+ return 'Could not Fast Refresh (export removed)';
2768
+ }
2769
+ if (__nastiCheckExports(ignored, nextExports, (key) => key in prevExports) !== true) {
2770
+ return 'Could not Fast Refresh (new export)';
2771
+ }
2772
+ let hasExports = false;
2773
+ const compatible = __nastiCheckExports(ignored, nextExports, (key, value) => {
2774
+ hasExports = true;
2775
+ return isLikelyComponentType(value) ||
2776
+ __nastiIsCompoundComponent(value) ||
2777
+ prevExports[key] === value;
2778
+ });
2779
+ if (!hasExports) {
2780
+ return 'Could not Fast Refresh (no exports)';
2781
+ }
2782
+ if (compatible === true) {
2783
+ __nastiEnqueueRefresh();
2784
+ return;
2785
+ }
2786
+ return 'Could not Fast Refresh ("' + compatible + '" export is incompatible)';
2787
+ }
2788
+ export const __hmr_import = (module) => import(module);
2789
+ `;
2170
2790
  REACT_REFRESH_GLOBAL_PREAMBLE = `
2171
2791
  import RefreshRuntime from "/@react-refresh";
2172
2792
  RefreshRuntime.injectIntoGlobalHook(window);
@@ -2182,28 +2802,37 @@ window.__vite_plugin_react_preamble_installed__ = true;
2182
2802
  });
2183
2803
 
2184
2804
  // src/server/hmr.ts
2185
- async function handleFileChange(file, server) {
2186
- const { moduleGraph, ws, config } = server;
2805
+ async function handleFileChange(file, server, environmentName = "client", timestamp = Date.now()) {
2806
+ const { config } = server;
2807
+ const environment = server.environments[environmentName];
2808
+ if (!environment) {
2809
+ throw new Error(`[nasti] unknown dev environment "${environmentName}"`);
2810
+ }
2811
+ const moduleGraph = environment.moduleGraph;
2187
2812
  const logger = config.logger;
2188
- const relativePath = "/" + import_node_path5.default.relative(config.root, file);
2189
- const shortFile = import_node_path5.default.relative(config.root, file);
2813
+ const relativePath = "/" + import_node_path6.default.relative(config.root, file);
2814
+ const shortFile = import_node_path6.default.relative(config.root, file);
2190
2815
  const mods = moduleGraph.getModulesByFile(file);
2191
2816
  if (!mods || mods.size === 0) {
2192
- return;
2817
+ return null;
2193
2818
  }
2194
2819
  const updates = [];
2195
- const timestamp = Date.now();
2820
+ const graph = moduleGraph;
2821
+ const invalidatedModules = /* @__PURE__ */ new Set();
2822
+ const affectedSet = /* @__PURE__ */ new Set();
2823
+ let fullReload = false;
2196
2824
  for (const mod of mods) {
2197
- moduleGraph.invalidateModule(mod);
2825
+ graph.invalidateModuleAndImporters(mod, timestamp, invalidatedModules);
2198
2826
  const ctx = {
2199
2827
  file,
2200
2828
  timestamp,
2201
2829
  modules: [mod],
2202
- read: () => import_node_fs5.default.readFileSync(file, "utf-8"),
2203
- server
2830
+ read: () => import_node_fs6.default.readFileSync(file, "utf-8"),
2831
+ server,
2832
+ environment
2204
2833
  };
2205
2834
  let affectedModules = [mod];
2206
- for (const plugin of config.plugins) {
2835
+ for (const plugin of environment.plugins) {
2207
2836
  if (plugin.handleHotUpdate) {
2208
2837
  const result = await plugin.handleHotUpdate(ctx);
2209
2838
  if (result) {
@@ -2212,36 +2841,59 @@ async function handleFileChange(file, server) {
2212
2841
  }
2213
2842
  }
2214
2843
  for (const affected of affectedModules) {
2215
- const boundaries = moduleGraph.getHmrBoundaries(affected);
2844
+ affectedSet.add(affected);
2845
+ graph.invalidateModuleAndImporters(affected, timestamp, invalidatedModules);
2846
+ const boundaries = graph.getHmrBoundaries(affected);
2216
2847
  if (boundaries.length === 0) {
2217
- logger.info(import_picocolors4.default.green("page reload ") + import_picocolors4.default.dim(shortFile), { timestamp: true });
2218
- ws.send({ type: "full-reload", path: relativePath });
2219
- return;
2848
+ fullReload = true;
2849
+ continue;
2220
2850
  }
2221
- for (const { boundary } of boundaries) {
2222
- updates.push({
2851
+ for (const { boundary, acceptedVia } of boundaries) {
2852
+ const update = {
2223
2853
  type: boundary.type === "css" ? "css-update" : "js-update",
2224
2854
  path: boundary.url,
2225
- acceptedPath: affected.url,
2855
+ acceptedPath: acceptedVia.url,
2226
2856
  timestamp
2227
- });
2857
+ };
2858
+ if (!updates.some(
2859
+ (existing) => existing.type === update.type && existing.path === update.path && existing.acceptedPath === update.acceptedPath
2860
+ )) {
2861
+ updates.push(update);
2862
+ }
2228
2863
  }
2229
2864
  }
2230
2865
  }
2231
- if (updates.length > 0) {
2866
+ const transformed = await Promise.all(
2867
+ [...affectedSet].map(async (module2) => ({
2868
+ module: module2,
2869
+ result: await environment.transformRequest(module2.url)
2870
+ }))
2871
+ );
2872
+ const logPrefix = environmentName === "client" ? "" : `[${environmentName}] `;
2873
+ if (fullReload) {
2874
+ logger.info(import_picocolors4.default.green(`${logPrefix}reload `) + import_picocolors4.default.dim(shortFile), { timestamp: true });
2875
+ environment.hot.send({ type: "full-reload", path: relativePath });
2876
+ } else if (updates.length > 0) {
2232
2877
  logger.info(
2233
- updates.map((u) => import_picocolors4.default.green("hmr update ") + import_picocolors4.default.dim(u.path)).join("\n"),
2878
+ updates.map((u) => import_picocolors4.default.green(`${logPrefix}hmr update `) + import_picocolors4.default.dim(u.path)).join("\n"),
2234
2879
  { timestamp: true }
2235
2880
  );
2236
- ws.send({ type: "update", updates });
2881
+ environment.hot.send({ type: "update", updates });
2237
2882
  }
2883
+ return {
2884
+ environment,
2885
+ modules: [...affectedSet],
2886
+ updates,
2887
+ transformed,
2888
+ fullReload
2889
+ };
2238
2890
  }
2239
- var import_node_path5, import_node_fs5, import_picocolors4;
2891
+ var import_node_path6, import_node_fs6, import_picocolors4;
2240
2892
  var init_hmr = __esm({
2241
2893
  "src/server/hmr.ts"() {
2242
2894
  "use strict";
2243
- import_node_path5 = __toESM(require("path"), 1);
2244
- import_node_fs5 = __toESM(require("fs"), 1);
2895
+ import_node_path6 = __toESM(require("path"), 1);
2896
+ import_node_fs6 = __toESM(require("fs"), 1);
2245
2897
  import_picocolors4 = __toESM(require("picocolors"), 1);
2246
2898
  }
2247
2899
  });
@@ -2249,7 +2901,7 @@ var init_hmr = __esm({
2249
2901
  // src/plugins/resolve.ts
2250
2902
  function resolvePlugin(config) {
2251
2903
  const { alias, extensions } = config.resolve;
2252
- const require2 = (0, import_node_module2.createRequire)(import_node_path6.default.resolve(config.root, "package.json"));
2904
+ const require2 = (0, import_node_module2.createRequire)(import_node_path7.default.resolve(config.root, "package.json"));
2253
2905
  const aliasEntries = Object.entries(alias).sort(
2254
2906
  ([a], [b]) => b.length - a.length
2255
2907
  );
@@ -2257,10 +2909,10 @@ function resolvePlugin(config) {
2257
2909
  if (config.framework === "vue") {
2258
2910
  try {
2259
2911
  const vuePkgJson = require2.resolve("vue/package.json", { paths: [config.root] });
2260
- const vueDir = import_node_path6.default.dirname(vuePkgJson);
2261
- const mod = JSON.parse(import_node_fs6.default.readFileSync(vuePkgJson, "utf-8")).module;
2262
- const entry = import_node_path6.default.join(vueDir, mod ?? "dist/vue.runtime.esm-bundler.js");
2263
- if (import_node_fs6.default.existsSync(entry)) vueRuntimeEntry = entry;
2912
+ const vueDir = import_node_path7.default.dirname(vuePkgJson);
2913
+ const mod = JSON.parse(import_node_fs7.default.readFileSync(vuePkgJson, "utf-8")).module;
2914
+ const entry = import_node_path7.default.join(vueDir, mod ?? "dist/vue.runtime.esm-bundler.js");
2915
+ if (import_node_fs7.default.existsSync(entry)) vueRuntimeEntry = entry;
2264
2916
  } catch {
2265
2917
  }
2266
2918
  }
@@ -2272,32 +2924,33 @@ function resolvePlugin(config) {
2272
2924
  if (source === key || source.startsWith(key + "/")) {
2273
2925
  const aliasBase = resolveAliasTarget2(value, config.root);
2274
2926
  const sub = source.slice(key.length).replace(/^\//, "");
2275
- const target = sub ? import_node_path6.default.join(aliasBase, sub) : aliasBase;
2927
+ const target = sub ? import_node_path7.default.join(aliasBase, sub) : aliasBase;
2276
2928
  const resolved = tryResolveFile(target, extensions);
2277
2929
  if (resolved) return resolved;
2278
2930
  break;
2279
2931
  }
2280
2932
  }
2281
2933
  if (source.startsWith("/") && !source.startsWith("//")) {
2282
- const rootRelative = import_node_path6.default.join(config.root, source.slice(1));
2934
+ const rootRelative = import_node_path7.default.join(config.root, source.slice(1));
2283
2935
  const resolved = tryResolveFile(rootRelative, extensions);
2284
2936
  if (resolved) return resolved;
2285
2937
  }
2286
- if (import_node_path6.default.isAbsolute(source) && import_node_fs6.default.existsSync(source)) {
2938
+ if (import_node_path7.default.isAbsolute(source) && import_node_fs7.default.existsSync(source)) {
2287
2939
  const resolved = tryResolveFile(source, extensions);
2288
2940
  if (resolved) return resolved;
2289
2941
  }
2290
2942
  if (source.startsWith(".")) {
2291
- const dir = importer ? import_node_path6.default.dirname(importer) : config.root;
2292
- const absolute = import_node_path6.default.resolve(dir, source);
2943
+ const dir = importer ? import_node_path7.default.dirname(importer) : config.root;
2944
+ const absolute = import_node_path7.default.resolve(dir, source);
2293
2945
  const resolved = tryResolveFile(absolute, extensions);
2294
2946
  if (resolved) return resolved;
2295
2947
  }
2296
2948
  if (!source.startsWith("/") && !source.startsWith(".")) {
2297
2949
  if (vueRuntimeEntry && source === "vue") return vueRuntimeEntry;
2950
+ if (config.command === "build") return null;
2298
2951
  try {
2299
2952
  const resolved = require2.resolve(source, {
2300
- paths: [importer ? import_node_path6.default.dirname(importer) : config.root]
2953
+ paths: [importer ? import_node_path7.default.dirname(importer) : config.root]
2301
2954
  });
2302
2955
  return resolved;
2303
2956
  } catch {
@@ -2308,46 +2961,46 @@ function resolvePlugin(config) {
2308
2961
  },
2309
2962
  load(id) {
2310
2963
  if (id.startsWith("\0")) return null;
2311
- if (!import_node_fs6.default.existsSync(id)) return null;
2964
+ if (!import_node_fs7.default.existsSync(id)) return null;
2312
2965
  if (id.endsWith(".json")) {
2313
- const content = import_node_fs6.default.readFileSync(id, "utf-8");
2966
+ const content = import_node_fs7.default.readFileSync(id, "utf-8");
2314
2967
  return `export default ${content}`;
2315
2968
  }
2316
- return import_node_fs6.default.readFileSync(id, "utf-8");
2969
+ return null;
2317
2970
  }
2318
2971
  };
2319
2972
  }
2320
2973
  function resolveAliasTarget2(value, root) {
2321
- if (import_node_path6.default.isAbsolute(value) && import_node_fs6.default.existsSync(value)) return value;
2322
- if (value.startsWith("/")) return import_node_path6.default.join(root, value.slice(1));
2323
- return import_node_path6.default.resolve(root, value);
2974
+ if (import_node_path7.default.isAbsolute(value) && import_node_fs7.default.existsSync(value)) return value;
2975
+ if (value.startsWith("/")) return import_node_path7.default.join(root, value.slice(1));
2976
+ return import_node_path7.default.resolve(root, value);
2324
2977
  }
2325
2978
  function tryResolveFile(file, extensions) {
2326
- if (import_node_fs6.default.existsSync(file) && import_node_fs6.default.statSync(file).isFile()) {
2979
+ if (import_node_fs7.default.existsSync(file) && import_node_fs7.default.statSync(file).isFile()) {
2327
2980
  return file;
2328
2981
  }
2329
2982
  for (const ext of extensions) {
2330
2983
  const withExt = file + ext;
2331
- if (import_node_fs6.default.existsSync(withExt) && import_node_fs6.default.statSync(withExt).isFile()) {
2984
+ if (import_node_fs7.default.existsSync(withExt) && import_node_fs7.default.statSync(withExt).isFile()) {
2332
2985
  return withExt;
2333
2986
  }
2334
2987
  }
2335
- if (import_node_fs6.default.existsSync(file) && import_node_fs6.default.statSync(file).isDirectory()) {
2988
+ if (import_node_fs7.default.existsSync(file) && import_node_fs7.default.statSync(file).isDirectory()) {
2336
2989
  for (const ext of extensions) {
2337
- const indexFile = import_node_path6.default.join(file, "index" + ext);
2338
- if (import_node_fs6.default.existsSync(indexFile)) {
2990
+ const indexFile = import_node_path7.default.join(file, "index" + ext);
2991
+ if (import_node_fs7.default.existsSync(indexFile)) {
2339
2992
  return indexFile;
2340
2993
  }
2341
2994
  }
2342
2995
  }
2343
2996
  return null;
2344
2997
  }
2345
- var import_node_path6, import_node_fs6, import_node_module2;
2998
+ var import_node_path7, import_node_fs7, import_node_module2;
2346
2999
  var init_resolve = __esm({
2347
3000
  "src/plugins/resolve.ts"() {
2348
3001
  "use strict";
2349
- import_node_path6 = __toESM(require("path"), 1);
2350
- import_node_fs6 = __toESM(require("fs"), 1);
3002
+ import_node_path7 = __toESM(require("path"), 1);
3003
+ import_node_fs7 = __toESM(require("fs"), 1);
2351
3004
  import_node_module2 = require("module");
2352
3005
  }
2353
3006
  });
@@ -2387,27 +3040,27 @@ var require_process = __commonJS({
2387
3040
  var require_filesystem = __commonJS({
2388
3041
  "node_modules/detect-libc/lib/filesystem.js"(exports2, module2) {
2389
3042
  "use strict";
2390
- var fs12 = require("fs");
3043
+ var fs13 = require("fs");
2391
3044
  var LDD_PATH = "/usr/bin/ldd";
2392
3045
  var SELF_PATH = "/proc/self/exe";
2393
3046
  var MAX_LENGTH = 2048;
2394
- var readFileSync = (path17) => {
2395
- const fd = fs12.openSync(path17, "r");
3047
+ var readFileSync = (path18) => {
3048
+ const fd = fs13.openSync(path18, "r");
2396
3049
  const buffer = Buffer.alloc(MAX_LENGTH);
2397
- const bytesRead = fs12.readSync(fd, buffer, 0, MAX_LENGTH, 0);
2398
- fs12.close(fd, () => {
3050
+ const bytesRead = fs13.readSync(fd, buffer, 0, MAX_LENGTH, 0);
3051
+ fs13.close(fd, () => {
2399
3052
  });
2400
3053
  return buffer.subarray(0, bytesRead);
2401
3054
  };
2402
- var readFile = (path17) => new Promise((resolve, reject) => {
2403
- fs12.open(path17, "r", (err, fd) => {
3055
+ var readFile = (path18) => new Promise((resolve, reject) => {
3056
+ fs13.open(path18, "r", (err, fd) => {
2404
3057
  if (err) {
2405
3058
  reject(err);
2406
3059
  } else {
2407
3060
  const buffer = Buffer.alloc(MAX_LENGTH);
2408
- fs12.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
3061
+ fs13.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
2409
3062
  resolve(buffer.subarray(0, bytesRead));
2410
- fs12.close(fd, () => {
3063
+ fs13.close(fd, () => {
2411
3064
  });
2412
3065
  });
2413
3066
  }
@@ -2519,11 +3172,11 @@ var require_detect_libc = __commonJS({
2519
3172
  }
2520
3173
  return null;
2521
3174
  };
2522
- var familyFromInterpreterPath = (path17) => {
2523
- if (path17) {
2524
- if (path17.includes("/ld-musl-")) {
3175
+ var familyFromInterpreterPath = (path18) => {
3176
+ if (path18) {
3177
+ if (path18.includes("/ld-musl-")) {
2525
3178
  return MUSL;
2526
- } else if (path17.includes("/ld-linux-")) {
3179
+ } else if (path18.includes("/ld-linux-")) {
2527
3180
  return GLIBC;
2528
3181
  }
2529
3182
  }
@@ -2570,8 +3223,8 @@ var require_detect_libc = __commonJS({
2570
3223
  cachedFamilyInterpreter = null;
2571
3224
  try {
2572
3225
  const selfContent = await readFile(SELF_PATH);
2573
- const path17 = interpreterPath(selfContent);
2574
- cachedFamilyInterpreter = familyFromInterpreterPath(path17);
3226
+ const path18 = interpreterPath(selfContent);
3227
+ cachedFamilyInterpreter = familyFromInterpreterPath(path18);
2575
3228
  } catch (e) {
2576
3229
  }
2577
3230
  return cachedFamilyInterpreter;
@@ -2583,8 +3236,8 @@ var require_detect_libc = __commonJS({
2583
3236
  cachedFamilyInterpreter = null;
2584
3237
  try {
2585
3238
  const selfContent = readFileSync(SELF_PATH);
2586
- const path17 = interpreterPath(selfContent);
2587
- cachedFamilyInterpreter = familyFromInterpreterPath(path17);
3239
+ const path18 = interpreterPath(selfContent);
3240
+ cachedFamilyInterpreter = familyFromInterpreterPath(path18);
2588
3241
  } catch (e) {
2589
3242
  }
2590
3243
  return cachedFamilyInterpreter;
@@ -3231,12 +3884,31 @@ var init_node = __esm({
3231
3884
  function createCssEngine() {
3232
3885
  return {
3233
3886
  styles: /* @__PURE__ */ new Map(),
3887
+ modules: /* @__PURE__ */ new Map(),
3888
+ chunks: /* @__PURE__ */ new Map(),
3234
3889
  entryCss: /* @__PURE__ */ new Map(),
3235
3890
  allCss: [],
3236
3891
  pendingSingle: [],
3237
3892
  singleFileName: null
3238
3893
  };
3239
3894
  }
3895
+ function getCssMetadata(engine) {
3896
+ return {
3897
+ modules: Object.fromEntries(
3898
+ [...engine.modules].map(([id, module2]) => [id, { ...module2 }])
3899
+ ),
3900
+ chunks: Object.fromEntries(
3901
+ [...engine.chunks].map(([fileName, chunk]) => [
3902
+ fileName,
3903
+ {
3904
+ fileName,
3905
+ moduleIds: [...chunk.moduleIds],
3906
+ cssFileNames: [...chunk.cssFileNames]
3907
+ }
3908
+ ])
3909
+ )
3910
+ };
3911
+ }
3240
3912
  function normalizeCssModuleId(id) {
3241
3913
  return id.startsWith("\0") ? id.slice(1) : id;
3242
3914
  }
@@ -3291,7 +3963,7 @@ function hasTailwindDirectives(css) {
3291
3963
  }
3292
3964
  async function loadTailwind(projectRoot) {
3293
3965
  if (cached && cachedRoot === projectRoot) return cached;
3294
- const req = (0, import_node_module3.createRequire)(import_node_path7.default.join(projectRoot, "package.json"));
3966
+ const req = (0, import_node_module3.createRequire)(import_node_path8.default.join(projectRoot, "package.json"));
3295
3967
  let nodePath;
3296
3968
  let oxidePath;
3297
3969
  try {
@@ -3312,7 +3984,7 @@ async function compileTailwind(css, fromFile, projectRoot) {
3312
3984
  const { node, oxide } = await loadTailwind(projectRoot);
3313
3985
  const dependencies = [];
3314
3986
  const compiler2 = await node.compile(css, {
3315
- base: import_node_path7.default.dirname(fromFile),
3987
+ base: import_node_path8.default.dirname(fromFile),
3316
3988
  from: fromFile,
3317
3989
  onDependency: (p) => dependencies.push(p)
3318
3990
  });
@@ -3323,11 +3995,11 @@ async function compileTailwind(css, fromFile, projectRoot) {
3323
3995
  dependencies: [...dependencies, ...scanner.files]
3324
3996
  };
3325
3997
  }
3326
- var import_node_path7, import_node_module3, import_node_url3, TAILWIND_DIRECTIVE_RE, cached, cachedRoot;
3998
+ var import_node_path8, import_node_module3, import_node_url3, TAILWIND_DIRECTIVE_RE, cached, cachedRoot;
3327
3999
  var init_tailwind = __esm({
3328
4000
  "src/plugins/tailwind.ts"() {
3329
4001
  "use strict";
3330
- import_node_path7 = __toESM(require("path"), 1);
4002
+ import_node_path8 = __toESM(require("path"), 1);
3331
4003
  import_node_module3 = require("module");
3332
4004
  import_node_url3 = require("url");
3333
4005
  TAILWIND_DIRECTIVE_RE = /@(?:import\s+["']tailwindcss(?:\b|\/)|tailwind\b|theme\b|apply\b|plugin\b|source\b|utility\b|variant\b|custom-variant\b|reference\b)/;
@@ -3356,15 +4028,29 @@ function cssPlugin(config, engine, consumer = "client") {
3356
4028
  }
3357
4029
  const rewritten = rewriteCssUrls(cssSource, file, config.root);
3358
4030
  const escaped = JSON.stringify(rewritten);
4031
+ const normalizedId = normalizeCssModuleId(id);
4032
+ const cssModule = { id: normalizedId, source: code, code: rewritten };
4033
+ const map = config.build.sourcemap ? createIdentitySourceMap(code, id) : void 0;
4034
+ engine?.modules.set(normalizedId, cssModule);
4035
+ this.environment?.setCssModule?.(cssModule);
3359
4036
  if (query === "inline") {
3360
4037
  return { code: `export default ${escaped};
3361
- `, moduleType: "js" };
4038
+ `, map, moduleType: "js" };
3362
4039
  }
3363
4040
  if (consumer === "server") {
3364
4041
  return { code: `export default ${escaped};
3365
- `, moduleType: "js" };
4042
+ `, map, moduleType: "js" };
3366
4043
  }
3367
4044
  if (config.command === "serve") {
4045
+ if (config.build.css.inject === false) {
4046
+ return {
4047
+ code: `export default ${escaped};
4048
+ `,
4049
+ map,
4050
+ moduleType: "js",
4051
+ moduleSideEffects: "no-treeshake"
4052
+ };
4053
+ }
3368
4054
  return {
3369
4055
  code: `
3370
4056
  const css = ${escaped};
@@ -3388,16 +4074,18 @@ if (import.meta.hot) {
3388
4074
 
3389
4075
  export default css;
3390
4076
  `,
4077
+ map,
3391
4078
  // bundled dev(DevEngine)下该模块会进 Rolldown:不标 js 会按 .css
3392
4079
  // 扩展名走 CSS 管线触发 #4271 报错;unbundled 中间件忽略此字段
3393
4080
  moduleType: "js"
3394
4081
  };
3395
4082
  }
3396
4083
  if (engine) {
3397
- engine.styles.set(normalizeCssModuleId(id), rewritten);
4084
+ engine.styles.set(normalizedId, rewritten);
3398
4085
  return {
3399
4086
  code: `export default '';
3400
4087
  `,
4088
+ map,
3401
4089
  moduleType: "js",
3402
4090
  // 防止空 stub 被 tree-shake 出 chunk.moduleIds(css-post 靠它定位)
3403
4091
  moduleSideEffects: "no-treeshake"
@@ -3417,26 +4105,43 @@ document.head.appendChild(style);
3417
4105
 
3418
4106
  export default css;
3419
4107
  `,
4108
+ map,
3420
4109
  moduleType: "js"
3421
4110
  };
3422
4111
  }
3423
4112
  };
3424
4113
  }
4114
+ function createIdentitySourceMap(code, id) {
4115
+ const map = new import_source_map_js.SourceMapGenerator({ file: id });
4116
+ const lines = code.split("\n");
4117
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
4118
+ for (let column = 0; column <= lines[lineIndex].length; column++) {
4119
+ map.addMapping({
4120
+ generated: { line: lineIndex + 1, column },
4121
+ original: { line: lineIndex + 1, column },
4122
+ source: id
4123
+ });
4124
+ }
4125
+ }
4126
+ map.setSourceContent(id, code);
4127
+ return map.toJSON();
4128
+ }
3425
4129
  function rewriteCssUrls(css, from, root) {
3426
4130
  return css.replace(/url\(\s*['"]?([^'")\s]+)['"]?\s*\)/g, (match, url) => {
3427
4131
  if (url.startsWith("/") || url.startsWith("data:") || url.startsWith("http")) {
3428
4132
  return match;
3429
4133
  }
3430
- const resolved = import_node_path8.default.resolve(import_node_path8.default.dirname(from), url);
3431
- const relative = "/" + import_node_path8.default.relative(root, resolved).replace(/\\/g, "/");
4134
+ const resolved = import_node_path9.default.resolve(import_node_path9.default.dirname(from), url);
4135
+ const relative = "/" + import_node_path9.default.relative(root, resolved).replace(/\\/g, "/");
3432
4136
  return `url(${relative})`;
3433
4137
  });
3434
4138
  }
3435
- var import_node_path8;
4139
+ var import_node_path9, import_source_map_js;
3436
4140
  var init_css = __esm({
3437
4141
  "src/plugins/css.ts"() {
3438
4142
  "use strict";
3439
- import_node_path8 = __toESM(require("path"), 1);
4143
+ import_node_path9 = __toESM(require("path"), 1);
4144
+ import_source_map_js = require("source-map-js");
3440
4145
  init_css_engine();
3441
4146
  init_tailwind();
3442
4147
  }
@@ -3446,19 +4151,27 @@ var init_css = __esm({
3446
4151
  function collectChunkCss(chunk, engine) {
3447
4152
  const ids = chunk.moduleIds ?? Object.keys(chunk.modules);
3448
4153
  let css = "";
4154
+ const moduleIds = [];
3449
4155
  for (const id of ids) {
3450
- const styles = engine.styles.get(normalizeCssModuleId(id));
3451
- if (styles) css += styles + "\n";
4156
+ const normalizedId = normalizeCssModuleId(id);
4157
+ const styles = engine.styles.get(normalizedId);
4158
+ if (styles) {
4159
+ css += styles + "\n";
4160
+ moduleIds.push(normalizedId);
4161
+ }
3452
4162
  }
3453
- return css;
4163
+ return { css, moduleIds };
3454
4164
  }
3455
4165
  function cssPostPlugin(config, engine) {
3456
4166
  return {
3457
4167
  name: "nasti:css-post",
3458
4168
  enforce: "post",
3459
4169
  async renderChunk(code, chunk) {
3460
- const css = collectChunkCss(chunk, engine);
4170
+ const { css, moduleIds } = collectChunkCss(chunk, engine);
3461
4171
  if (!css) return null;
4172
+ const ownership = { moduleIds, cssFileNames: [] };
4173
+ engine.chunks.set(chunk.fileName, ownership);
4174
+ if (config.build.css.emit === false) return null;
3462
4175
  if (!config.build.cssCodeSplit) {
3463
4176
  engine.pendingSingle.push(css);
3464
4177
  return null;
@@ -3471,6 +4184,7 @@ function cssPostPlugin(config, engine) {
3471
4184
  });
3472
4185
  const fileName = this.getFileName(ref);
3473
4186
  engine.allCss.push(fileName);
4187
+ ownership.cssFileNames.push(fileName);
3474
4188
  if (chunk.isEntry) {
3475
4189
  const key = chunk.facadeModuleId ?? chunk.name;
3476
4190
  const existing = engine.entryCss.get(key) ?? [];
@@ -3478,100 +4192,34 @@ function cssPostPlugin(config, engine) {
3478
4192
  engine.entryCss.set(key, existing);
3479
4193
  return null;
3480
4194
  }
4195
+ if (config.build.css.inject === false) return null;
3481
4196
  const href = JSON.stringify(config.base + fileName);
3482
4197
  const snippet = `
3483
4198
  ;(function(){try{var d=document,h=${href};if(!d.querySelector('link[data-nasti-css="'+h+'"]')){var l=d.createElement('link');l.rel='stylesheet';l.href=h;l.setAttribute('data-nasti-css',h);d.head.appendChild(l);}}catch(e){}})();`;
3484
4199
  return { code: code + snippet, map: null };
3485
4200
  },
3486
4201
  augmentChunkHash(chunk) {
3487
- const css = collectChunkCss(chunk, engine);
4202
+ const { css } = collectChunkCss(chunk, engine);
3488
4203
  return css || void 0;
3489
4204
  },
3490
4205
  async generateBundle() {
3491
4206
  if (config.build.cssCodeSplit || engine.pendingSingle.length === 0) return;
3492
- const merged = engine.pendingSingle.join("\n");
3493
- const finalCss = config.build.cssMinify ? await minifyCss(merged, config) : merged;
3494
- const ref = this.emitFile({ type: "asset", name: "style.css", source: finalCss });
3495
- const fileName = this.getFileName(ref);
3496
- engine.singleFileName = fileName;
3497
- engine.allCss.push(fileName);
3498
- }
3499
- };
3500
- }
3501
- var init_css_post = __esm({
3502
- "src/plugins/css-post.ts"() {
3503
- "use strict";
3504
- init_css_engine();
3505
- }
3506
- });
3507
-
3508
- // src/plugins/assets.ts
3509
- function assetsPlugin(config) {
3510
- return {
3511
- name: "nasti:assets",
3512
- resolveId(source) {
3513
- if (source.endsWith("?url") || source.endsWith("?raw")) {
3514
- return source;
3515
- }
3516
- return null;
3517
- },
3518
- load(id) {
3519
- const ext = import_node_path9.default.extname(id.replace(/\?.*$/, ""));
3520
- if (id.endsWith("?raw")) {
3521
- const file = id.slice(0, -4);
3522
- if (import_node_fs7.default.existsSync(file)) {
3523
- const content = import_node_fs7.default.readFileSync(file, "utf-8");
3524
- return `export default ${JSON.stringify(content)}`;
3525
- }
3526
- }
3527
- if (id.endsWith("?url") || ASSET_EXTENSIONS.has(ext)) {
3528
- const file = id.replace(/\?.*$/, "");
3529
- if (!import_node_fs7.default.existsSync(file)) return null;
3530
- if (config.command === "serve") {
3531
- const url = "/" + import_node_path9.default.relative(config.root, file);
3532
- return `export default ${JSON.stringify(url)}`;
3533
- }
3534
- const content = import_node_fs7.default.readFileSync(file);
3535
- const hash = import_node_crypto.default.createHash("sha256").update(content).digest("hex").slice(0, 8);
3536
- const basename = import_node_path9.default.basename(file, ext);
3537
- const hashedName = `${config.build.assetsDir}/${basename}.${hash}${ext}`;
3538
- return `export default ${JSON.stringify(config.base + hashedName)}`;
3539
- }
3540
- return null;
3541
- }
3542
- };
3543
- }
3544
- var import_node_path9, import_node_fs7, import_node_crypto, ASSET_EXTENSIONS;
3545
- var init_assets = __esm({
3546
- "src/plugins/assets.ts"() {
3547
- "use strict";
3548
- import_node_path9 = __toESM(require("path"), 1);
3549
- import_node_fs7 = __toESM(require("fs"), 1);
3550
- import_node_crypto = __toESM(require("crypto"), 1);
3551
- ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
3552
- ".png",
3553
- ".jpg",
3554
- ".jpeg",
3555
- ".gif",
3556
- ".svg",
3557
- ".ico",
3558
- ".webp",
3559
- ".avif",
3560
- ".mp4",
3561
- ".webm",
3562
- ".ogg",
3563
- ".mp3",
3564
- ".wav",
3565
- ".flac",
3566
- ".aac",
3567
- ".woff",
3568
- ".woff2",
3569
- ".eot",
3570
- ".ttf",
3571
- ".otf",
3572
- ".pdf",
3573
- ".txt"
3574
- ]);
4207
+ const merged = engine.pendingSingle.join("\n");
4208
+ const finalCss = config.build.cssMinify ? await minifyCss(merged, config) : merged;
4209
+ const ref = this.emitFile({ type: "asset", name: "style.css", source: finalCss });
4210
+ const fileName = this.getFileName(ref);
4211
+ engine.singleFileName = fileName;
4212
+ engine.allCss.push(fileName);
4213
+ for (const ownership of engine.chunks.values()) {
4214
+ if (ownership.moduleIds.length > 0) ownership.cssFileNames.push(fileName);
4215
+ }
4216
+ }
4217
+ };
4218
+ }
4219
+ var init_css_post = __esm({
4220
+ "src/plugins/css-post.ts"() {
4221
+ "use strict";
4222
+ init_css_engine();
3575
4223
  }
3576
4224
  });
3577
4225
 
@@ -3585,9 +4233,10 @@ async function loadVueCompiler() {
3585
4233
  return null;
3586
4234
  }
3587
4235
  }
3588
- function vuePlugin(config) {
4236
+ function vuePlugin(config, environmentName = "client") {
3589
4237
  const isDev = config.command === "serve";
3590
4238
  const descriptorCache = /* @__PURE__ */ new Map();
4239
+ const vueOptions = config.environments[environmentName]?.vue ?? {};
3591
4240
  return {
3592
4241
  name: "nasti:vue",
3593
4242
  enforce: "pre",
@@ -3607,32 +4256,63 @@ function vuePlugin(config) {
3607
4256
  const sfc = await loadVueCompiler();
3608
4257
  if (!sfc) return null;
3609
4258
  const [, filePath, indexStr] = match;
3610
- let descriptor = descriptorCache.get(filePath);
3611
- if (!descriptor) {
4259
+ let cached2 = descriptorCache.get(filePath);
4260
+ if (!cached2) {
3612
4261
  try {
3613
- const fs12 = await import("fs");
3614
- const source = fs12.readFileSync(filePath, "utf-8");
3615
- const parsed = sfc.parse(source, { filename: filePath });
4262
+ const fs13 = await import("fs");
4263
+ const rawSource = fs13.readFileSync(filePath, "utf-8");
4264
+ const transformedSfc = await applySourceTransform(
4265
+ vueOptions.transformSfc,
4266
+ rawSource,
4267
+ { filename: filePath, environmentName, type: "sfc" }
4268
+ );
4269
+ const parsed = sfc.parse(transformedSfc.code, {
4270
+ ...vueOptions.parse,
4271
+ filename: filePath,
4272
+ sourceMap: true
4273
+ });
3616
4274
  if (parsed.errors.length) return null;
3617
- descriptor = parsed.descriptor;
3618
- descriptorCache.set(filePath, descriptor);
4275
+ cached2 = {
4276
+ descriptor: parsed.descriptor,
4277
+ sourceMap: transformedSfc.map
4278
+ };
4279
+ descriptorCache.set(filePath, cached2);
3619
4280
  } catch {
3620
4281
  return null;
3621
4282
  }
3622
4283
  }
4284
+ const { descriptor, sourceMap: sfcSourceMap } = cached2;
3623
4285
  const index2 = parseInt(indexStr ?? "0", 10);
3624
4286
  const style = descriptor.styles[index2];
3625
4287
  if (!style) return null;
3626
4288
  const scopeId = hashId(filePath);
4289
+ const transformedStyle = await applySourceTransform(
4290
+ vueOptions.transformStyle,
4291
+ style.content,
4292
+ { filename: filePath, environmentName, type: "style", index: index2 }
4293
+ );
4294
+ const wantsStyleSourceMap = !!config.build.sourcemap || transformedStyle.map != null || sfcSourceMap != null;
4295
+ const styleInputMap = wantsStyleSourceMap ? composeSourceMapChain(
4296
+ [transformedStyle.map, style.map, sfcSourceMap],
4297
+ { filename: filePath, environmentName, type: "style", index: index2 }
4298
+ ) : void 0;
3627
4299
  const result = await sfc.compileStyleAsync({
3628
- source: style.content,
4300
+ ...vueOptions.style,
4301
+ source: transformedStyle.code,
3629
4302
  filename: filePath,
3630
4303
  id: `data-v-${scopeId}`,
3631
4304
  scoped: style.scoped ?? false,
4305
+ inMap: styleInputMap,
3632
4306
  // <style lang="scss|less|stylus"> 需经对应预处理器(缺省 undefined = 纯 CSS)
3633
4307
  preprocessLang: style.lang
3634
4308
  });
3635
- return result.code;
4309
+ if (transformedStyle.map != null && result.map == null) {
4310
+ warnUnchainableMap(
4311
+ { filename: filePath, environmentName, type: "style", index: index2 },
4312
+ "compiler-sfc did not return a style map"
4313
+ );
4314
+ }
4315
+ return wantsStyleSourceMap ? { code: result.code, map: result.map } : result.code;
3636
4316
  },
3637
4317
  async transform(code, id) {
3638
4318
  if (!VUE_FILE_RE.test(id) && !VUE_QUERY_RE.test(id)) return null;
@@ -3644,57 +4324,144 @@ function vuePlugin(config) {
3644
4324
  if (VUE_QUERY_RE.test(id)) {
3645
4325
  return null;
3646
4326
  }
3647
- const { descriptor, errors } = sfc.parse(code, { filename: id });
4327
+ const transformedSfc = await applySourceTransform(
4328
+ vueOptions.transformSfc,
4329
+ code,
4330
+ { filename: id, environmentName, type: "sfc" }
4331
+ );
4332
+ code = transformedSfc.code;
4333
+ const { descriptor, errors } = sfc.parse(code, {
4334
+ ...vueOptions.parse,
4335
+ filename: id,
4336
+ sourceMap: true
4337
+ });
3648
4338
  if (errors.length) {
3649
- console.error(`[nasti:vue] Parse error in ${id}:`, errors[0].message);
4339
+ const firstError = errors[0];
4340
+ console.error(
4341
+ `[nasti:vue] Parse error in ${id}:`,
4342
+ typeof firstError === "string" ? firstError : firstError.message
4343
+ );
3650
4344
  return null;
3651
4345
  }
3652
- descriptorCache.set(id, descriptor);
4346
+ descriptorCache.set(id, {
4347
+ descriptor,
4348
+ sourceMap: transformedSfc.map
4349
+ });
3653
4350
  const scopeId = hashId(id);
4351
+ const wantsSourceMap = !!config.build.sourcemap || transformedSfc.map != null;
3654
4352
  let scriptCode = "";
4353
+ let scriptMap;
3655
4354
  if (descriptor.script || descriptor.scriptSetup) {
4355
+ const inlineTemplate = vueOptions.script?.inlineTemplate !== false;
3656
4356
  const compiled = sfc.compileScript(descriptor, {
4357
+ ...vueOptions.script,
3657
4358
  id: scopeId,
3658
4359
  isProd: !isDev,
3659
- inlineTemplate: true,
4360
+ inlineTemplate,
4361
+ sourceMap: wantsSourceMap,
3660
4362
  // 让 compileScript 产出 `const __sfc__ = ...`(而非默认的 `export default {...}`)。
3661
4363
  // 否则下方追加的 `__sfc__.render` / `__sfc__.__scopeId` / HMR 记录会引用一个
3662
4364
  // 不存在的 `__sfc__`,并与 compileScript 自带的 `export default` 形成双重默认导出。
3663
4365
  genDefaultAs: "__sfc__"
3664
4366
  });
3665
4367
  scriptCode = compiled.content;
4368
+ scriptMap = composeSourceMapChain(
4369
+ [compiled.map, transformedSfc.map],
4370
+ { filename: id, environmentName, type: "sfc" }
4371
+ );
4372
+ if (transformedSfc.map != null && scriptMap == null) {
4373
+ warnUnchainableMap(
4374
+ { filename: id, environmentName, type: "sfc" },
4375
+ "compiler-sfc did not return a script map"
4376
+ );
4377
+ }
3666
4378
  }
3667
4379
  let templateCode = "";
3668
- if (descriptor.template && !descriptor.scriptSetup) {
4380
+ let templateMap;
4381
+ const scriptSetupIsInline = !!descriptor.scriptSetup && vueOptions.script?.inlineTemplate !== false;
4382
+ if (descriptor.template && !scriptSetupIsInline) {
4383
+ const transformedTemplate = await applySourceTransform(
4384
+ vueOptions.transformTemplate,
4385
+ descriptor.template.content,
4386
+ { filename: id, environmentName, type: "template" }
4387
+ );
4388
+ const templateInputMap = composeSourceMapChain(
4389
+ [
4390
+ transformedTemplate.map,
4391
+ descriptor.template.map,
4392
+ transformedSfc.map
4393
+ ],
4394
+ { filename: id, environmentName, type: "template" }
4395
+ );
4396
+ const customCompilerOptions = vueOptions.template?.compilerOptions ?? {};
3669
4397
  const compiled = sfc.compileTemplate({
3670
- source: descriptor.template.content,
4398
+ ...vueOptions.template,
4399
+ source: transformedTemplate.code,
3671
4400
  filename: id,
3672
4401
  id: scopeId,
3673
- compilerOptions: { scopeId: `data-v-${scopeId}` }
4402
+ inMap: templateInputMap,
4403
+ compilerOptions: {
4404
+ ...customCompilerOptions,
4405
+ scopeId: `data-v-${scopeId}`
4406
+ }
3674
4407
  });
3675
4408
  templateCode = compiled.code;
4409
+ if (wantsSourceMap || transformedTemplate.map != null) {
4410
+ templateMap = compiled.map;
4411
+ }
4412
+ if (transformedTemplate.map != null && templateMap == null) {
4413
+ warnUnchainableMap(
4414
+ { filename: id, environmentName, type: "template" },
4415
+ "compiler-sfc did not return a template map"
4416
+ );
4417
+ }
3676
4418
  }
3677
- let output = scriptCode || "const __sfc__ = {}";
4419
+ const outputNode = new import_source_map_js2.SourceNode();
4420
+ let hasMappedOutput = false;
4421
+ const append = (fragment, map) => {
4422
+ const normalizedMap = normalizeSourceMap(
4423
+ map,
4424
+ { filename: id, environmentName, type: "sfc" }
4425
+ );
4426
+ if (!normalizedMap) {
4427
+ outputNode.add(fragment);
4428
+ return;
4429
+ }
4430
+ try {
4431
+ outputNode.add(
4432
+ import_source_map_js2.SourceNode.fromStringWithSourceMap(
4433
+ fragment,
4434
+ new import_source_map_js2.SourceMapConsumer(normalizedMap)
4435
+ )
4436
+ );
4437
+ hasMappedOutput = true;
4438
+ } catch (error) {
4439
+ warnUnchainableMap(
4440
+ { filename: id, environmentName, type: "sfc" },
4441
+ `source-map assembly failed: ${error instanceof Error ? error.message : String(error)}`
4442
+ );
4443
+ outputNode.add(fragment);
4444
+ }
4445
+ };
4446
+ append(scriptCode || "const __sfc__ = {}", scriptMap);
3678
4447
  if (templateCode) {
3679
- output += `
3680
- ${templateCode}
3681
- `;
3682
- output += `
3683
- __sfc__.render = render
3684
- `;
4448
+ append("\n");
4449
+ append(templateCode, templateMap);
4450
+ append("\n");
4451
+ append("\n__sfc__.render = render\n");
3685
4452
  }
3686
4453
  if (descriptor.styles.length > 0) {
3687
4454
  for (let i = 0; i < descriptor.styles.length; i++) {
3688
- output += `
4455
+ append(`
3689
4456
  import "${id}?vue&type=style&index=${i}&lang.css"
3690
- `;
4457
+ `);
3691
4458
  }
3692
4459
  }
3693
- output += `
4460
+ append(`
3694
4461
  __sfc__.__scopeId = "data-v-${scopeId}"
3695
- `;
4462
+ `);
3696
4463
  if (isDev) {
3697
- output += `
4464
+ append(`
3698
4465
  __sfc__.__hmrId = ${JSON.stringify(scopeId)}
3699
4466
  if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') {
3700
4467
  __VUE_HMR_RUNTIME__.createRecord(__sfc__.__hmrId, __sfc__)
@@ -3708,17 +4475,34 @@ if (import.meta.hot) {
3708
4475
  }
3709
4476
  })
3710
4477
  }
3711
- `;
4478
+ `);
4479
+ }
4480
+ append("\nexport default __sfc__\n");
4481
+ const renderedOutput = outputNode.toStringWithSourceMap({ file: id });
4482
+ const output = renderedOutput.code;
4483
+ const outputMap = hasMappedOutput ? renderedOutput.map.toJSON() : void 0;
4484
+ if (transformedSfc.map != null && outputMap == null) {
4485
+ warnUnchainableMap(
4486
+ { filename: id, environmentName, type: "sfc" },
4487
+ "the compiled SFC output contained no chainable mappings"
4488
+ );
3712
4489
  }
3713
- output += `
3714
- export default __sfc__
3715
- `;
3716
4490
  const lang = descriptor.scriptSetup?.lang ?? descriptor.script?.lang;
3717
4491
  if (lang === "ts") {
3718
- const transpiled = transformCode(`${id}.ts`, output, { sourcemap: false });
3719
- return { code: transpiled.code };
4492
+ const transpiled = transformCode(`${id}.ts`, output, {
4493
+ sourcemap: wantsSourceMap,
4494
+ target: config.build.target
4495
+ });
4496
+ const transpiledMap = transpiled.map ? JSON.parse(transpiled.map) : void 0;
4497
+ return {
4498
+ code: transpiled.code,
4499
+ map: composeSourceMapChain(
4500
+ [transpiledMap, outputMap],
4501
+ { filename: id, environmentName, type: "sfc" }
4502
+ )
4503
+ };
3720
4504
  }
3721
- return { code: output };
4505
+ return { code: output, map: outputMap };
3722
4506
  },
3723
4507
  handleHotUpdate(ctx) {
3724
4508
  const { file, modules } = ctx;
@@ -3732,17 +4516,77 @@ export default __sfc__
3732
4516
  }
3733
4517
  };
3734
4518
  }
4519
+ async function applySourceTransform(transform2, source, context) {
4520
+ if (!transform2) return { code: source };
4521
+ const result = await transform2(source, context);
4522
+ return typeof result === "string" ? { code: result } : result;
4523
+ }
4524
+ function normalizeSourceMap(map, context) {
4525
+ if (map == null) return void 0;
4526
+ try {
4527
+ const value = typeof map === "string" ? JSON.parse(map) : map;
4528
+ if (value && typeof value === "object" && Array.isArray(value.sources) && Array.isArray(value.names) && typeof value.mappings === "string") {
4529
+ return value;
4530
+ }
4531
+ } catch {
4532
+ }
4533
+ warnUnchainableMap(context, "the provided map is not a valid source map");
4534
+ return void 0;
4535
+ }
4536
+ function composeSourceMapChain(maps, context) {
4537
+ const pending = maps.filter((map) => map != null);
4538
+ if (pending.length === 0) return void 0;
4539
+ let composed = normalizeSourceMap(pending.shift(), context);
4540
+ for (const map of pending) {
4541
+ const input = normalizeSourceMap(map, context);
4542
+ if (!input) continue;
4543
+ if (!composed) {
4544
+ composed = input;
4545
+ continue;
4546
+ }
4547
+ try {
4548
+ const consumer = new import_source_map_js2.SourceMapConsumer(composed);
4549
+ if (consumer.sources.length !== 1) {
4550
+ warnUnchainableMap(
4551
+ context,
4552
+ "a generated map has multiple sources and cannot be chained safely"
4553
+ );
4554
+ continue;
4555
+ }
4556
+ const generator = import_source_map_js2.SourceMapGenerator.fromSourceMap(consumer);
4557
+ generator.applySourceMap(
4558
+ new import_source_map_js2.SourceMapConsumer(input),
4559
+ consumer.sources[0]
4560
+ );
4561
+ composed = generator.toJSON();
4562
+ } catch (error) {
4563
+ warnUnchainableMap(
4564
+ context,
4565
+ `source-map composition failed: ${error instanceof Error ? error.message : String(error)}`
4566
+ );
4567
+ }
4568
+ }
4569
+ return composed;
4570
+ }
4571
+ function warnUnchainableMap(context, reason) {
4572
+ debug3?.(
4573
+ `source map warning for ${context.filename} (${context.type}, ${context.environmentName}): ${reason}`
4574
+ );
4575
+ }
3735
4576
  function hashId(filename) {
3736
4577
  return import_node_crypto2.default.createHash("sha256").update(filename).digest("hex").slice(0, 8);
3737
4578
  }
3738
- var import_node_crypto2, VUE_FILE_RE, VUE_QUERY_RE, compiler;
4579
+ var import_node_crypto2, import_source_map_js2, VUE_FILE_RE, VUE_QUERY_RE, debug3, compiler;
3739
4580
  var init_vue = __esm({
3740
4581
  "src/plugins/vue.ts"() {
3741
4582
  "use strict";
3742
4583
  import_node_crypto2 = __toESM(require("crypto"), 1);
4584
+ import_source_map_js2 = require("source-map-js");
3743
4585
  init_transformer();
4586
+ init_debug();
3744
4587
  VUE_FILE_RE = /\.vue$/;
3745
4588
  VUE_QUERY_RE = /\.vue\?vue&type=(script|template|style)(&index=\d+)?(&lang[.=]\w+)?/;
4589
+ debug3 = createDebugger("nasti:vue");
3746
4590
  compiler = null;
3747
4591
  }
3748
4592
  });
@@ -3750,16 +4594,27 @@ var init_vue = __esm({
3750
4594
  // src/plugins/builtins.ts
3751
4595
  function resolvePluginList(config, userPlugins, opts = {}) {
3752
4596
  const isServe = config.command === "serve";
4597
+ let environmentOptions;
4598
+ if (opts.environmentName) {
4599
+ environmentOptions = config.environments[opts.environmentName];
4600
+ if (!environmentOptions) {
4601
+ throw new Error(
4602
+ `[nasti] unknown environment "${opts.environmentName}" \u2014 declare it in config.environments`
4603
+ );
4604
+ }
4605
+ }
4606
+ const pluginConfig = environmentOptions ? { ...config, resolve: environmentOptions.resolve, build: environmentOptions.build } : config;
4607
+ const consumer = opts.consumer ?? environmentOptions?.consumer;
3753
4608
  return [
3754
4609
  // vuePlugin 排最前(enforce: 'pre' 语义):.vue 先编译成 JS 再走后续管道
3755
- ...config.framework === "vue" ? [vuePlugin(config)] : [],
3756
- resolvePlugin(config),
3757
- cssPlugin(config, opts.cssEngine, opts.consumer),
3758
- assetsPlugin(config),
3759
- ...isServe ? [htmlPlugin(config)] : [],
4610
+ ...config.framework === "vue" ? [vuePlugin(pluginConfig, opts.environmentName ?? "client")] : [],
4611
+ resolvePlugin(pluginConfig),
4612
+ cssPlugin(pluginConfig, opts.cssEngine, consumer),
4613
+ assetsPlugin(pluginConfig),
4614
+ ...isServe ? [htmlPlugin(pluginConfig)] : [],
3760
4615
  ...userPlugins,
3761
4616
  // cssPostPlugin 最后(enforce: 'post' 语义):renderChunk 聚合抽取
3762
- ...!isServe && opts.cssEngine ? [cssPostPlugin(config, opts.cssEngine)] : []
4617
+ ...!isServe && opts.cssEngine ? [cssPostPlugin(pluginConfig, opts.cssEngine)] : []
3763
4618
  ];
3764
4619
  }
3765
4620
  var init_builtins = __esm({
@@ -3788,7 +4643,7 @@ function createModuleRunner(environment) {
3788
4643
  }
3789
4644
  return new NastiModuleRunner(environment);
3790
4645
  }
3791
- var import_node_path10, import_node_fs8, import_node_module4, import_node_url4, debug3, NODE_BUILTINS, NastiModuleRunner, AsyncFunction;
4646
+ var import_node_path10, import_node_fs8, import_node_module4, import_node_url4, debug4, NODE_BUILTINS, NastiModuleRunner, AsyncFunction;
3792
4647
  var init_runnable_environment = __esm({
3793
4648
  "src/server/runnable-environment.ts"() {
3794
4649
  "use strict";
@@ -3799,7 +4654,7 @@ var init_runnable_environment = __esm({
3799
4654
  init_transformer();
3800
4655
  init_env();
3801
4656
  init_debug();
3802
- debug3 = createDebugger("nasti:ssr");
4657
+ debug4 = createDebugger("nasti:ssr");
3803
4658
  NODE_BUILTINS = /* @__PURE__ */ new Set([...import_node_module4.builtinModules, ...import_node_module4.builtinModules.map((m) => `node:${m}`)]);
3804
4659
  NastiModuleRunner = class {
3805
4660
  environment;
@@ -3877,6 +4732,7 @@ var init_runnable_environment = __esm({
3877
4732
  if (shouldTransform(cleanId)) {
3878
4733
  const result = transformCode(cleanId, code, {
3879
4734
  sourcemap: false,
4735
+ target: this.environment.options.build.target,
3880
4736
  jsxRuntime: "automatic",
3881
4737
  jsxImportSource: this.config.framework === "vue" ? "vue" : "react"
3882
4738
  });
@@ -3893,7 +4749,7 @@ var init_runnable_environment = __esm({
3893
4749
  );
3894
4750
  }
3895
4751
  const runnerResult = await moduleRunnerTransform(resolvedId, code);
3896
- debug3?.(`fetchModule ${resolvedId} (${runnerResult.deps?.length ?? 0} deps)`);
4752
+ debug4?.(`fetchModule ${resolvedId} (${runnerResult.deps?.length ?? 0} deps)`);
3897
4753
  return { id: resolvedId, code: runnerResult.code };
3898
4754
  }
3899
4755
  completeExtension(id) {
@@ -4012,7 +4868,7 @@ async function tryNativeReporterPlugin(config, logger) {
4012
4868
  logInfo: (msg) => logger.info(msg)
4013
4869
  });
4014
4870
  } catch (err) {
4015
- debug4?.(`native viteReporterPlugin unavailable, falling back to JS table: ${err}`);
4871
+ debug5?.(`native viteReporterPlugin unavailable, falling back to JS table: ${err}`);
4016
4872
  return null;
4017
4873
  }
4018
4874
  }
@@ -4067,7 +4923,7 @@ function warnLargeChunks(output, config, logger) {
4067
4923
  )
4068
4924
  );
4069
4925
  }
4070
- var import_node_path11, import_node_zlib, import_picocolors5, debug4, numberFormatter;
4926
+ var import_node_path11, import_node_zlib, import_picocolors5, debug5, numberFormatter;
4071
4927
  var init_reporter = __esm({
4072
4928
  "src/build/reporter.ts"() {
4073
4929
  "use strict";
@@ -4075,7 +4931,7 @@ var init_reporter = __esm({
4075
4931
  import_node_zlib = require("zlib");
4076
4932
  import_picocolors5 = __toESM(require("picocolors"), 1);
4077
4933
  init_debug();
4078
- debug4 = createDebugger("nasti:reporter");
4934
+ debug5 = createDebugger("nasti:reporter");
4079
4935
  numberFormatter = new Intl.NumberFormat("en", {
4080
4936
  maximumFractionDigits: 2,
4081
4937
  minimumFractionDigits: 2
@@ -4083,6 +4939,156 @@ var init_reporter = __esm({
4083
4939
  }
4084
4940
  });
4085
4941
 
4942
+ // src/core/build-app-context.ts
4943
+ function createBuildAppContext(config, results) {
4944
+ const output = [];
4945
+ const emitted = /* @__PURE__ */ new Set();
4946
+ const outDir = import_node_path12.default.resolve(config.root, config.build.outDir);
4947
+ let environmentArtifacts;
4948
+ return {
4949
+ config,
4950
+ results,
4951
+ get output() {
4952
+ return Object.freeze([...output]);
4953
+ },
4954
+ getResult(environmentName) {
4955
+ return results[environmentName];
4956
+ },
4957
+ getArtifact(environmentName, fileName) {
4958
+ const normalized = normalizeEnvironmentFileName(fileName);
4959
+ return results[environmentName]?.output.find(
4960
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalized
4961
+ );
4962
+ },
4963
+ getEntry(environmentName, entryName) {
4964
+ const result = results[environmentName];
4965
+ const fileName = result?.entries?.[entryName];
4966
+ if (!fileName) return void 0;
4967
+ return result.output.find(
4968
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalizeEnvironmentFileName(fileName)
4969
+ );
4970
+ },
4971
+ getManifest(environmentName) {
4972
+ return results[environmentName]?.manifest;
4973
+ },
4974
+ getChunk(environmentName, fileName) {
4975
+ const normalized = normalizeEnvironmentFileName(fileName);
4976
+ return results[environmentName]?.chunks?.[normalized];
4977
+ },
4978
+ getCss(environmentName) {
4979
+ return results[environmentName]?.css;
4980
+ },
4981
+ getSourceMap(environmentName, fileName) {
4982
+ const normalized = normalizeEnvironmentFileName(fileName);
4983
+ return results[environmentName]?.sourceMaps?.[normalized];
4984
+ },
4985
+ resolvePublicPath(environmentName, fileName) {
4986
+ const result = results[environmentName];
4987
+ if (!result) return void 0;
4988
+ const normalized = normalizeEnvironmentFileName(fileName);
4989
+ const base = result.publicPath ?? config.base;
4990
+ return joinPublicPath(base, normalized);
4991
+ },
4992
+ emitFile(file) {
4993
+ const fileName = normalizeAppFileName(file.fileName);
4994
+ const collisionKey = artifactCollisionKey(fileName);
4995
+ if (emitted.has(collisionKey)) {
4996
+ throw new Error(`[nasti] app artifact already emitted: ${fileName}`);
4997
+ }
4998
+ environmentArtifacts ??= collectEnvironmentArtifacts(config, results, outDir);
4999
+ if (environmentArtifacts.has(collisionKey)) {
5000
+ throw new Error(`[nasti] app artifact conflicts with environment output: ${fileName}`);
5001
+ }
5002
+ const target = import_node_path12.default.resolve(outDir, ...fileName.split("/"));
5003
+ const relative = import_node_path12.default.relative(outDir, target);
5004
+ if (relative.startsWith("..") || import_node_path12.default.isAbsolute(relative)) {
5005
+ throw new Error(`[nasti] app artifact must stay inside build.outDir: ${file.fileName}`);
5006
+ }
5007
+ assertNoSymlinkComponents(outDir, fileName);
5008
+ import_node_fs9.default.mkdirSync(import_node_path12.default.dirname(target), { recursive: true });
5009
+ import_node_fs9.default.writeFileSync(target, file.source);
5010
+ const artifact = {
5011
+ ...file,
5012
+ fileName,
5013
+ type: "asset"
5014
+ };
5015
+ emitted.add(collisionKey);
5016
+ output.push(artifact);
5017
+ return fileName;
5018
+ }
5019
+ };
5020
+ }
5021
+ function joinPublicPath(base, fileName) {
5022
+ return `${base.endsWith("/") ? base : `${base}/`}${fileName.replace(/^\//, "")}`;
5023
+ }
5024
+ function normalizeEnvironmentFileName(fileName) {
5025
+ return import_node_path12.default.posix.normalize(fileName.replace(/\\/g, "/").replace(/^\.\//, ""));
5026
+ }
5027
+ function isInvalidEnvironmentFileName(fileName) {
5028
+ return !fileName || fileName === "." || fileName === ".." || fileName.startsWith("../") || import_node_path12.default.posix.isAbsolute(fileName) || /^[A-Za-z]:\//.test(fileName);
5029
+ }
5030
+ function normalizeAppFileName(fileName) {
5031
+ const normalized = normalizeEnvironmentFileName(fileName);
5032
+ if (isInvalidEnvironmentFileName(normalized)) {
5033
+ throw new Error(`[nasti] invalid app artifact fileName: ${fileName}`);
5034
+ }
5035
+ return normalized;
5036
+ }
5037
+ function artifactCollisionKey(fileName) {
5038
+ return normalizeEnvironmentFileName(fileName).toLowerCase();
5039
+ }
5040
+ function collectEnvironmentArtifacts(config, results, appOutDir) {
5041
+ const occupied = /* @__PURE__ */ new Set();
5042
+ for (const [environmentName, result] of Object.entries(results)) {
5043
+ const environment = config.environments[environmentName];
5044
+ if (!environment) continue;
5045
+ const environmentOutDir = import_node_path12.default.resolve(config.root, environment.build.outDir);
5046
+ for (const artifact of result.output) {
5047
+ const artifactPath = import_node_path12.default.resolve(
5048
+ environmentOutDir,
5049
+ ...normalizeEnvironmentFileName(artifact.fileName).split("/")
5050
+ );
5051
+ const relative = import_node_path12.default.relative(appOutDir, artifactPath);
5052
+ if (!relative.startsWith("..") && !import_node_path12.default.isAbsolute(relative)) {
5053
+ occupied.add(artifactCollisionKey(relative));
5054
+ }
5055
+ }
5056
+ }
5057
+ return occupied;
5058
+ }
5059
+ function assertNoSymlinkComponents(outDir, fileName) {
5060
+ let current = outDir;
5061
+ for (const segment of fileName.split("/")) {
5062
+ current = import_node_path12.default.join(current, segment);
5063
+ let stats;
5064
+ try {
5065
+ stats = import_node_fs9.default.lstatSync(current);
5066
+ } catch (error) {
5067
+ if (error.code === "ENOENT") continue;
5068
+ throw error;
5069
+ }
5070
+ if (stats.isSymbolicLink()) {
5071
+ throw new Error(`[nasti] app artifact path cannot traverse a symlink: ${fileName}`);
5072
+ }
5073
+ }
5074
+ }
5075
+ function inferEnvironmentEntries(output) {
5076
+ const entries = {};
5077
+ for (const artifact of output) {
5078
+ if (artifact.type !== "chunk" || !artifact.isEntry || !artifact.name) continue;
5079
+ entries[artifact.name] = normalizeEnvironmentFileName(artifact.fileName);
5080
+ }
5081
+ return Object.keys(entries).length > 0 ? entries : void 0;
5082
+ }
5083
+ var import_node_fs9, import_node_path12;
5084
+ var init_build_app_context = __esm({
5085
+ "src/core/build-app-context.ts"() {
5086
+ "use strict";
5087
+ import_node_fs9 = __toESM(require("fs"), 1);
5088
+ import_node_path12 = __toESM(require("path"), 1);
5089
+ }
5090
+ });
5091
+
4086
5092
  // src/build/index.ts
4087
5093
  var build_exports = {};
4088
5094
  __export(build_exports, {
@@ -4096,9 +5102,14 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
4096
5102
  const config = environment.config;
4097
5103
  const envOptions = environment.options;
4098
5104
  const isServer = environment.consumer === "server";
4099
- const outDir = import_node_path12.default.resolve(config.root, envOptions.build.outDir);
5105
+ const outDir = import_node_path13.default.resolve(config.root, envOptions.build.outDir);
4100
5106
  const assetsDir = envOptions.build.assetsDir;
4101
- const { output: userOutput, transform: userTransform, ...restInputOptions } = envOptions.build.rolldownOptions;
5107
+ const {
5108
+ output: userOutput,
5109
+ transform: userTransform,
5110
+ resolve: userResolve,
5111
+ ...restInputOptions
5112
+ } = envOptions.build.rolldownOptions;
4102
5113
  const vueDefine = config.framework === "vue" ? {
4103
5114
  __VUE_OPTIONS_API__: "true",
4104
5115
  __VUE_PROD_DEVTOOLS__: "false",
@@ -4110,27 +5121,34 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
4110
5121
  const inputOptions = {
4111
5122
  ...restInputOptions,
4112
5123
  input: entryPoints,
4113
- transform: { ...userTransform, define: mergedDefine },
5124
+ transform: {
5125
+ ...userTransform,
5126
+ target: userTransform?.target ?? envOptions.build.target,
5127
+ define: mergedDefine
5128
+ },
4114
5129
  plugins: rolldownPlugins,
5130
+ // client/server consumer 都必须使用当前环境的 conditions/mainFields;Lynx
5131
+ // BG/MT 通常同为 client consumer,但仍可能声明不同的运行时条件。
5132
+ resolve: {
5133
+ ...userResolve ?? {},
5134
+ // Environment API 是条件解析的唯一高层入口,优先于继承来的底层选项。
5135
+ conditionNames: envOptions.resolve.conditions,
5136
+ mainFields: envOptions.resolve.mainFields
5137
+ },
4115
5138
  ...isServer ? {
4116
5139
  platform: restInputOptions.platform ?? "node",
4117
- resolve: {
4118
- conditionNames: envOptions.resolve.conditions,
4119
- mainFields: envOptions.resolve.mainFields,
4120
- ...restInputOptions.resolve
4121
- },
4122
5140
  // server 产物:node 内建恒外部化;bare specifier 默认外部化
4123
5141
  //(同 Vite ssr.external 默认 —— 依赖由 node_modules 运行时解析),
4124
5142
  // 相对/绝对/虚拟模块照常打包。需要内联依赖时经 rolldownOptions.external 覆盖。
4125
5143
  external: restInputOptions.external ?? ((id) => {
4126
5144
  if (NODE_BUILTINS2.has(id)) return true;
4127
- return !id.startsWith(".") && !import_node_path12.default.isAbsolute(id) && !id.startsWith("\0");
5145
+ return !id.startsWith(".") && !import_node_path13.default.isAbsolute(id) && !id.startsWith("\0");
4128
5146
  })
4129
5147
  } : {}
4130
5148
  };
4131
5149
  const outputOptions = isServer ? {
4132
5150
  format: "esm",
4133
- sourcemap: !!envOptions.build.sourcemap,
5151
+ sourcemap: envOptions.build.sourcemap,
4134
5152
  minify: !!envOptions.build.minify,
4135
5153
  entryFileNames: "[name].js",
4136
5154
  chunkFileNames: "chunks/[name]-[hash].js",
@@ -4139,7 +5157,7 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
4139
5157
  dir: outDir
4140
5158
  } : {
4141
5159
  format: "esm",
4142
- sourcemap: !!envOptions.build.sourcemap,
5160
+ sourcemap: envOptions.build.sourcemap,
4143
5161
  minify: !!envOptions.build.minify,
4144
5162
  entryFileNames: `${assetsDir}/[name].[hash].js`,
4145
5163
  chunkFileNames: `${assetsDir}/[name].[hash].js`,
@@ -4151,27 +5169,177 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
4151
5169
  };
4152
5170
  return { inputOptions, outputOptions, outDir };
4153
5171
  }
4154
- function toRolldownPlugins(plugins) {
5172
+ function toRolldownPlugins(plugins, environment) {
5173
+ const wrap = (hook) => {
5174
+ if (!hook) return hook;
5175
+ return function(...args) {
5176
+ return hook.apply(attachEnvironment(this, environment), args);
5177
+ };
5178
+ };
4155
5179
  return plugins.map((p) => ({
4156
5180
  name: p.name,
4157
- resolveId: p.resolveId,
4158
- load: p.load,
4159
- transform: p.transform,
4160
- buildStart: p.buildStart,
4161
- buildEnd: p.buildEnd,
5181
+ resolveId: wrap(p.resolveId),
5182
+ load: wrap(p.load),
5183
+ transform: wrap(p.transform),
5184
+ buildStart: wrap(p.buildStart),
5185
+ buildEnd: wrap(p.buildEnd),
4162
5186
  // closeBundle 在 bundle.close() 时触发 —— PWA manifest/SW 等终态产物依赖
4163
- closeBundle: p.closeBundle,
4164
- renderChunk: p.renderChunk,
4165
- augmentChunkHash: p.augmentChunkHash,
4166
- generateBundle: p.generateBundle
5187
+ closeBundle: wrap(p.closeBundle),
5188
+ renderChunk: wrap(p.renderChunk),
5189
+ augmentChunkHash: wrap(p.augmentChunkHash),
5190
+ generateBundle: wrap(p.generateBundle)
4167
5191
  }));
4168
5192
  }
5193
+ function attachEnvironment(context, environment) {
5194
+ if (context?.environment === environment) return context;
5195
+ try {
5196
+ Object.defineProperty(context, "environment", {
5197
+ configurable: true,
5198
+ enumerable: false,
5199
+ writable: false,
5200
+ value: environment
5201
+ });
5202
+ return context;
5203
+ } catch {
5204
+ return new Proxy(context, {
5205
+ get(target, property) {
5206
+ if (property === "environment") return environment;
5207
+ const value = Reflect.get(target, property, target);
5208
+ return typeof value === "function" ? value.bind(target) : value;
5209
+ },
5210
+ set(target, property, value) {
5211
+ return Reflect.set(target, property, value, target);
5212
+ }
5213
+ });
5214
+ }
5215
+ }
5216
+ function finalizeEnvironmentResult(environment, result) {
5217
+ const metadata = environment.getBuildMetadata();
5218
+ const inferredEntries = inferEnvironmentEntries(result.output);
5219
+ const entries = {
5220
+ ...inferredEntries,
5221
+ ...metadata.entries,
5222
+ ...result.entries
5223
+ };
5224
+ const normalizedEntries = Object.fromEntries(
5225
+ Object.entries(entries).map(([name, fileName]) => {
5226
+ const normalized = normalizeEnvironmentFileName(fileName);
5227
+ if (isInvalidEnvironmentFileName(normalized)) {
5228
+ throw new Error(
5229
+ `[nasti] environment "${environment.name}" returned invalid entry "${name}": ${fileName}`
5230
+ );
5231
+ }
5232
+ return [name, normalized];
5233
+ })
5234
+ );
5235
+ const inferredMetadata = inferOutputMetadata(environment, result.output);
5236
+ return {
5237
+ publicPath: environment.config.base,
5238
+ ...inferredMetadata,
5239
+ ...metadata,
5240
+ ...result,
5241
+ output: result.output,
5242
+ chunks: {
5243
+ ...inferredMetadata.chunks,
5244
+ ...metadata.chunks,
5245
+ ...result.chunks
5246
+ },
5247
+ assets: {
5248
+ ...inferredMetadata.assets,
5249
+ ...metadata.assets,
5250
+ ...result.assets
5251
+ },
5252
+ sourceMaps: {
5253
+ ...inferredMetadata.sourceMaps,
5254
+ ...metadata.sourceMaps,
5255
+ ...result.sourceMaps
5256
+ },
5257
+ ...Object.keys(normalizedEntries).length > 0 ? { entries: normalizedEntries } : {}
5258
+ };
5259
+ }
5260
+ function inferOutputMetadata(environment, output) {
5261
+ const chunks = {};
5262
+ const assets = {};
5263
+ const sourceMaps = {};
5264
+ const cssChunks = environment.getBuildMetadata().css?.chunks ?? {};
5265
+ const assetModules = environment.getAssetModules();
5266
+ const publicPath = environment.config.base;
5267
+ for (const artifact of output) {
5268
+ const fileName = normalizeEnvironmentFileName(artifact.fileName);
5269
+ if (artifact.map != null) sourceMaps[fileName] = artifact.map;
5270
+ if (artifact.type === "chunk") {
5271
+ const moduleIds = [...artifact.moduleIds ?? []];
5272
+ chunks[fileName] = {
5273
+ fileName,
5274
+ name: artifact.name ?? fileName,
5275
+ isEntry: !!artifact.isEntry,
5276
+ isDynamicEntry: !!artifact.isDynamicEntry,
5277
+ imports: [...artifact.imports ?? []],
5278
+ dynamicImports: [...artifact.dynamicImports ?? []],
5279
+ moduleIds,
5280
+ css: [...cssChunks[fileName]?.cssFileNames ?? []],
5281
+ assets: [
5282
+ ...new Set(
5283
+ moduleIds.map((id) => assetModules[id]).filter((asset) => !!asset)
5284
+ )
5285
+ ]
5286
+ };
5287
+ } else if (artifact.type === "asset") {
5288
+ assets[fileName] = {
5289
+ fileName,
5290
+ names: [...artifact.names ?? (artifact.name ? [artifact.name] : [])],
5291
+ publicPath: joinPublicPath(publicPath, fileName)
5292
+ };
5293
+ }
5294
+ }
5295
+ return { chunks, assets, sourceMaps };
5296
+ }
5297
+ function prepareBuildOutputDirectories(config, buildableNames) {
5298
+ const directories = /* @__PURE__ */ new Set();
5299
+ const protectedPaths = /* @__PURE__ */ new Set();
5300
+ const clientIsBuilt = buildableNames.includes("client");
5301
+ if (!clientIsBuilt && config.build.emptyOutDir) {
5302
+ directories.add(import_node_path13.default.resolve(config.root, config.build.outDir));
5303
+ }
5304
+ for (const name of buildableNames) {
5305
+ const environment = config.environments[name];
5306
+ const outDir = import_node_path13.default.resolve(config.root, environment.build.outDir);
5307
+ if (!environment.build.emptyOutDir) {
5308
+ protectedPaths.add(outDir);
5309
+ continue;
5310
+ }
5311
+ if (!environment.driver) directories.add(outDir);
5312
+ }
5313
+ const containsPath = (parent, child) => {
5314
+ const relative = import_node_path13.default.relative(parent, child);
5315
+ return relative === "" || !relative.startsWith("..") && !import_node_path13.default.isAbsolute(relative);
5316
+ };
5317
+ const roots = [...directories].filter(
5318
+ (directory) => ![...protectedPaths].some((protectedPath) => containsPath(directory, protectedPath))
5319
+ ).sort((a, b) => a.length - b.length).filter(
5320
+ (directory, index2, all) => !all.slice(0, index2).some((parent) => containsPath(parent, directory))
5321
+ );
5322
+ for (const directory of roots) {
5323
+ if (import_node_fs10.default.existsSync(directory)) import_node_fs10.default.rmSync(directory, { recursive: true, force: true });
5324
+ }
5325
+ }
5326
+ function assertDriverBuildResult(environment, result) {
5327
+ const output = result != null && typeof result === "object" ? result.output : void 0;
5328
+ const hasValidOutput = Array.isArray(output) && output.every(
5329
+ (artifact) => artifact != null && typeof artifact === "object" && typeof artifact.fileName === "string" && typeof artifact.type === "string"
5330
+ );
5331
+ if (!hasValidOutput) {
5332
+ throw new Error(
5333
+ `[nasti] environment "${environment.name}" driver "${environment.driver?.name}" returned an invalid build result; expected { output: EnvironmentBuildOutput[] }`
5334
+ );
5335
+ }
5336
+ }
4169
5337
  function resolveClientEntries(config, html) {
4170
5338
  const configuredEntries = config.environments.client?.entry ?? [];
4171
5339
  if (configuredEntries.length > 0) return configuredEntries;
4172
5340
  const entryPoints = [];
4173
5341
  const htmlFile = config.environments.client?.html;
4174
- const htmlDir = htmlFile ? import_node_path12.default.dirname(htmlFile) : config.root;
5342
+ const htmlDir = htmlFile ? import_node_path13.default.dirname(htmlFile) : config.root;
4175
5343
  if (html) {
4176
5344
  const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
4177
5345
  for (const match of scriptMatches) {
@@ -4179,7 +5347,7 @@ function resolveClientEntries(config, html) {
4179
5347
  if (src && !src.startsWith("http")) {
4180
5348
  const cleanSrc = src.split(/[?#]/, 1)[0];
4181
5349
  entryPoints.push(
4182
- cleanSrc.startsWith("/") ? import_node_path12.default.resolve(config.root, cleanSrc.replace(/^\//, "")) : import_node_path12.default.resolve(htmlDir, cleanSrc)
5350
+ cleanSrc.startsWith("/") ? import_node_path13.default.resolve(config.root, cleanSrc.replace(/^\//, "")) : import_node_path13.default.resolve(htmlDir, cleanSrc)
4183
5351
  );
4184
5352
  }
4185
5353
  }
@@ -4187,8 +5355,8 @@ function resolveClientEntries(config, html) {
4187
5355
  if (entryPoints.length === 0) {
4188
5356
  const fallbackEntries = ["src/main.ts", "src/main.tsx", "src/main.js", "src/index.ts", "src/index.tsx", "src/index.js"];
4189
5357
  for (const entry of fallbackEntries) {
4190
- const fullPath = import_node_path12.default.resolve(config.root, entry);
4191
- if (import_node_fs9.default.existsSync(fullPath)) {
5358
+ const fullPath = import_node_path13.default.resolve(config.root, entry);
5359
+ if (import_node_fs10.default.existsSync(fullPath)) {
4192
5360
  entryPoints.push(fullPath);
4193
5361
  break;
4194
5362
  }
@@ -4203,6 +5371,7 @@ function createOxcTransformPlugin(config, environment) {
4203
5371
  if (!shouldTransform(id)) return null;
4204
5372
  const result = transformCode(id, code, {
4205
5373
  sourcemap: !!environment.options.build.sourcemap,
5374
+ target: environment.options.build.target,
4206
5375
  jsxRuntime: "automatic",
4207
5376
  jsxImportSource: config.framework === "vue" ? "vue" : "react"
4208
5377
  });
@@ -4216,16 +5385,20 @@ async function build(inlineConfig = {}) {
4216
5385
  const startTime = performance.now();
4217
5386
  logger.info(
4218
5387
  import_picocolors6.default.cyan(`
4219
- nasti v${"2.3.1"} `) + import_picocolors6.default.green(`building for ${config.mode}...`)
4220
- );
4221
- debug5?.(`root: ${config.root}`);
4222
- const buildableNames = Object.keys(config.environments).filter(
4223
- (name) => name === "client" || config.environments[name].entry.length > 0 || !!config.environments[name].driver
5388
+ nasti v${"2.4.1"} `) + import_picocolors6.default.green(`building for ${config.mode}...`)
4224
5389
  );
5390
+ debug6?.(`root: ${config.root}`);
5391
+ const buildableNames = Object.keys(config.environments).filter((name) => {
5392
+ const environment = config.environments[name];
5393
+ if (!environment.buildEnabled) return false;
5394
+ return name === "client" || environment.entry.length > 0 || !!environment.driver;
5395
+ });
4225
5396
  buildableNames.sort((a, b) => a === "client" ? -1 : b === "client" ? 1 : 0);
5397
+ prepareBuildOutputDirectories(config, buildableNames);
4226
5398
  const environments = {};
4227
5399
  const environmentResults = {};
4228
5400
  const initializedEnvironments = [];
5401
+ const buildAppContext = createBuildAppContext(config, environmentResults);
4229
5402
  let clientOutput = [];
4230
5403
  let buildFailed = false;
4231
5404
  try {
@@ -4236,12 +5409,12 @@ nasti v${"2.3.1"} `) + import_picocolors6.default.green(`building for ${config.m
4236
5409
  environmentResults[name] = built.result;
4237
5410
  if (name === "client") clientOutput = built.result.output;
4238
5411
  if (buildableNames.length > 1) {
4239
- debug5?.(`environment "${name}" built (${built.result.output.length} files)`);
5412
+ debug6?.(`environment "${name}" built (${built.result.output.length} files)`);
4240
5413
  }
4241
5414
  }
4242
5415
  const pluginApi = getPluginApi(config);
4243
5416
  for (const plugin of config.plugins) {
4244
- await plugin.afterBuildApp?.(environmentResults, pluginApi);
5417
+ await plugin.afterBuildApp?.(environmentResults, pluginApi, buildAppContext);
4245
5418
  }
4246
5419
  } catch (error) {
4247
5420
  buildFailed = true;
@@ -4268,22 +5441,31 @@ nasti v${"2.3.1"} `) + import_picocolors6.default.green(`building for ${config.m
4268
5441
  }
4269
5442
  }
4270
5443
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
4271
- const totalSize = Object.values(environments).flat().reduce((sum, chunk) => {
5444
+ const allOutput = [...Object.values(environments).flat(), ...buildAppContext.output];
5445
+ const totalSize = allOutput.reduce((sum, chunk) => {
4272
5446
  const content = chunk.type === "chunk" ? chunk.code : chunk.source;
4273
5447
  if (content == null) return sum;
4274
5448
  return sum + (typeof content === "string" ? Buffer.byteLength(content) : content.byteLength);
4275
5449
  }, 0);
4276
- const fileCount = Object.values(environments).flat().length;
5450
+ const fileCount = allOutput.length;
4277
5451
  const envSuffix = buildableNames.length > 1 ? ` (${buildableNames.join(" + ")})` : "";
4278
5452
  logger.info(import_picocolors6.default.green(`\u2713 built in ${elapsed}s`) + import_picocolors6.default.dim(envSuffix));
4279
5453
  logger.info(import_picocolors6.default.dim(` ${fileCount} files, ${displaySize(totalSize)} total \u2192 ${config.build.outDir}/`));
4280
- return { output: clientOutput, environments, environmentResults };
5454
+ return {
5455
+ output: clientOutput,
5456
+ environments,
5457
+ environmentResults,
5458
+ appOutput: [...buildAppContext.output]
5459
+ };
4281
5460
  }
4282
5461
  async function buildClientEnvironment(config) {
4283
5462
  const logger = config.logger;
4284
- const outDir = import_node_path12.default.resolve(config.root, config.build.outDir);
5463
+ const outDir = import_node_path13.default.resolve(config.root, config.build.outDir);
4285
5464
  const cssEngine = createCssEngine();
4286
- const pluginList = resolvePluginList(config, config.plugins, { cssEngine });
5465
+ const pluginList = resolvePluginList(config, config.plugins, {
5466
+ cssEngine,
5467
+ environmentName: "client"
5468
+ });
4287
5469
  const clientEnv = new NastiEnvironment("client", config, {
4288
5470
  mode: "build",
4289
5471
  plugins: pluginList,
@@ -4298,13 +5480,11 @@ async function buildClientEnvironment(config) {
4298
5480
  );
4299
5481
  }
4300
5482
  const result = await clientEnv.driver.build(clientEnv.getDriverContext());
4301
- return { environment: clientEnv, result };
4302
- }
4303
- if (config.build.emptyOutDir && import_node_fs9.default.existsSync(outDir)) {
4304
- import_node_fs9.default.rmSync(outDir, { recursive: true, force: true });
5483
+ assertDriverBuildResult(clientEnv, result);
5484
+ return { environment: clientEnv, result: finalizeEnvironmentResult(clientEnv, result) };
4305
5485
  }
4306
- import_node_fs9.default.mkdirSync(outDir, { recursive: true });
4307
- const htmlFile = config.environments.client.html ?? import_node_path12.default.resolve(config.root, "index.html");
5486
+ import_node_fs10.default.mkdirSync(outDir, { recursive: true });
5487
+ const htmlFile = config.environments.client.html ?? import_node_path13.default.resolve(config.root, "index.html");
4308
5488
  const html = await readHtmlFile(config.root, htmlFile);
4309
5489
  const entryPoints = resolveClientEntries(config, html);
4310
5490
  if (entryPoints.length === 0) {
@@ -4314,7 +5494,7 @@ async function buildClientEnvironment(config) {
4314
5494
  const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
4315
5495
  const rolldownPlugins = [
4316
5496
  createOxcTransformPlugin(config, clientEnv),
4317
- ...toRolldownPlugins(allPlugins),
5497
+ ...toRolldownPlugins(allPlugins, clientEnv),
4318
5498
  ...nativeReporter ? [nativeReporter] : []
4319
5499
  ];
4320
5500
  const { inputOptions, outputOptions } = getRolldownOptions(
@@ -4325,6 +5505,7 @@ async function buildClientEnvironment(config) {
4325
5505
  const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
4326
5506
  const { output } = await bundle2.write(outputOptions);
4327
5507
  await bundle2.close();
5508
+ clientEnv.setBuildMetadata({ css: getCssMetadata(cssEngine) });
4328
5509
  if (html) {
4329
5510
  let processedHtml = html;
4330
5511
  const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
@@ -4338,7 +5519,9 @@ async function buildClientEnvironment(config) {
4338
5519
  processedHtml = processHtml(processedHtml, result);
4339
5520
  }
4340
5521
  }
4341
- processedHtml = injectCssLinks(processedHtml, cssEngine, config);
5522
+ if (clientEnv.options.build.css.inject !== false) {
5523
+ processedHtml = injectCssLinks(processedHtml, cssEngine, config);
5524
+ }
4342
5525
  for (const chunk of output) {
4343
5526
  if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
4344
5527
  processedHtml = replaceEntryScript(
@@ -4351,13 +5534,16 @@ async function buildClientEnvironment(config) {
4351
5534
  );
4352
5535
  }
4353
5536
  }
4354
- import_node_fs9.default.writeFileSync(import_node_path12.default.resolve(outDir, "index.html"), processedHtml);
5537
+ import_node_fs10.default.writeFileSync(import_node_path13.default.resolve(outDir, "index.html"), processedHtml);
4355
5538
  }
4356
5539
  if (!nativeReporter && config.logLevel !== "silent") {
4357
5540
  reportBuildOutput(output, config, logger);
4358
5541
  }
4359
5542
  warnLargeChunks(output, config, logger);
4360
- return { environment: clientEnv, result: { output } };
5543
+ return {
5544
+ environment: clientEnv,
5545
+ result: finalizeEnvironmentResult(clientEnv, { output })
5546
+ };
4361
5547
  } catch (error) {
4362
5548
  try {
4363
5549
  await clientEnv.close();
@@ -4373,7 +5559,12 @@ async function buildClientEnvironment(config) {
4373
5559
  async function buildServerEnvironment(config, name) {
4374
5560
  const envOptions = config.environments[name];
4375
5561
  const logger = config.logger;
4376
- const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
5562
+ const cssEngine = envOptions.consumer === "client" ? createCssEngine() : void 0;
5563
+ const pluginList = resolvePluginList(config, config.plugins, {
5564
+ consumer: envOptions.consumer,
5565
+ environmentName: name,
5566
+ cssEngine
5567
+ });
4377
5568
  const environment = new NastiEnvironment(name, config, {
4378
5569
  mode: "build",
4379
5570
  plugins: pluginList,
@@ -4389,38 +5580,40 @@ async function buildServerEnvironment(config, name) {
4389
5580
  }
4390
5581
  try {
4391
5582
  const result = await environment.driver.build(environment.getDriverContext());
4392
- return { environment, result };
5583
+ assertDriverBuildResult(environment, result);
5584
+ return { environment, result: finalizeEnvironmentResult(environment, result) };
4393
5585
  } catch (error) {
4394
5586
  await environment.close();
4395
5587
  throw error;
4396
5588
  }
4397
5589
  }
4398
5590
  for (const entry of envOptions.entry) {
4399
- if (!import_node_fs9.default.existsSync(entry)) {
5591
+ if (!import_node_fs10.default.existsSync(entry)) {
4400
5592
  await environment.close();
4401
5593
  throw new Error(`[nasti] environment "${name}" entry not found: ${entry}`);
4402
5594
  }
4403
5595
  }
4404
5596
  const rolldownPlugins = [
4405
5597
  createOxcTransformPlugin(config, environment),
4406
- ...toRolldownPlugins(environment.plugins)
5598
+ ...toRolldownPlugins(environment.plugins, environment)
4407
5599
  ];
4408
5600
  const { inputOptions, outputOptions, outDir } = getRolldownOptions(
4409
5601
  environment,
4410
5602
  envOptions.entry,
4411
5603
  rolldownPlugins
4412
5604
  );
4413
- if (envOptions.build.emptyOutDir && import_node_fs9.default.existsSync(outDir)) {
4414
- import_node_fs9.default.rmSync(outDir, { recursive: true, force: true });
4415
- }
4416
- import_node_fs9.default.mkdirSync(outDir, { recursive: true });
5605
+ import_node_fs10.default.mkdirSync(outDir, { recursive: true });
4417
5606
  const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
4418
5607
  const { output } = await bundle2.write(outputOptions);
4419
5608
  await bundle2.close();
5609
+ if (cssEngine) environment.setBuildMetadata({ css: getCssMetadata(cssEngine) });
4420
5610
  logger.info(
4421
- import_picocolors6.default.dim(` [${name}] `) + output.map((o) => import_node_path12.default.join(envOptions.build.outDir, o.fileName)).join(import_picocolors6.default.dim(", "))
5611
+ import_picocolors6.default.dim(` [${name}] `) + output.map((o) => import_node_path13.default.join(envOptions.build.outDir, o.fileName)).join(import_picocolors6.default.dim(", "))
4422
5612
  );
4423
- return { environment, result: { output } };
5613
+ return {
5614
+ environment,
5615
+ result: finalizeEnvironmentResult(environment, { output })
5616
+ };
4424
5617
  }
4425
5618
  function injectCssLinks(html, cssEngine, config) {
4426
5619
  const cssLinkTags = [];
@@ -4447,9 +5640,9 @@ function escapeRegExp(string) {
4447
5640
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4448
5641
  }
4449
5642
  function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, urlPrefix) {
4450
- const rootRelative = import_node_path12.default.relative(config.root, facadeModuleId).split(import_node_path12.default.sep).join("/");
4451
- const resolvedHtmlFile = import_node_path12.default.resolve(config.root, htmlFile);
4452
- const htmlRelative = import_node_path12.default.relative(import_node_path12.default.dirname(resolvedHtmlFile), facadeModuleId).split(import_node_path12.default.sep).join("/");
5643
+ const rootRelative = import_node_path13.default.relative(config.root, facadeModuleId).split(import_node_path13.default.sep).join("/");
5644
+ const resolvedHtmlFile = import_node_path13.default.resolve(config.root, htmlFile);
5645
+ const htmlRelative = import_node_path13.default.relative(import_node_path13.default.dirname(resolvedHtmlFile), facadeModuleId).split(import_node_path13.default.sep).join("/");
4453
5646
  const candidates = /* @__PURE__ */ new Set([
4454
5647
  rootRelative,
4455
5648
  `/${rootRelative}`,
@@ -4465,12 +5658,12 @@ function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, ur
4465
5658
  }
4466
5659
  return processed;
4467
5660
  }
4468
- var import_node_path12, import_node_fs9, import_node_module5, import_rolldown, import_picocolors6, debug5, NODE_BUILTINS2;
5661
+ var import_node_path13, import_node_fs10, import_node_module5, import_rolldown, import_picocolors6, debug6, NODE_BUILTINS2;
4469
5662
  var init_build = __esm({
4470
5663
  "src/build/index.ts"() {
4471
5664
  "use strict";
4472
- import_node_path12 = __toESM(require("path"), 1);
4473
- import_node_fs9 = __toESM(require("fs"), 1);
5665
+ import_node_path13 = __toESM(require("path"), 1);
5666
+ import_node_fs10 = __toESM(require("fs"), 1);
4474
5667
  import_node_module5 = require("module");
4475
5668
  import_rolldown = require("rolldown");
4476
5669
  init_config();
@@ -4483,8 +5676,9 @@ var init_build = __esm({
4483
5676
  init_reporter();
4484
5677
  init_debug();
4485
5678
  init_plugin_api();
5679
+ init_build_app_context();
4486
5680
  import_picocolors6 = __toESM(require("picocolors"), 1);
4487
- debug5 = createDebugger("nasti:build");
5681
+ debug6 = createDebugger("nasti:build");
4488
5682
  NODE_BUILTINS2 = /* @__PURE__ */ new Set([...import_node_module5.builtinModules, ...import_node_module5.builtinModules.map((m) => `node:${m}`)]);
4489
5683
  }
4490
5684
  });
@@ -4508,7 +5702,7 @@ async function createBundledDevServer(opts) {
4508
5702
  }
4509
5703
  } catch (err) {
4510
5704
  throw new Error(
4511
- `[nasti] experimental.bundledDev requires rolldown's experimental dev() API (locked to the installed rc; got: ${err.message}). Remove --bundle / experimental.bundledDev to use the default unbundled dev server.`
5705
+ `[nasti] experimental.bundledDev requires rolldown's experimental dev() API (locked version is incompatible; got: ${err.message}). Remove --bundle / experimental.bundledDev to use the default unbundled dev server.`
4512
5706
  );
4513
5707
  }
4514
5708
  const html = await readHtmlFile(config.root, config.environments.client?.html);
@@ -4526,7 +5720,7 @@ async function createBundledDevServer(opts) {
4526
5720
  createReactRefreshRuntimePlugin(entryPoints),
4527
5721
  createBundledOxcRefreshPlugin()
4528
5722
  ] : [],
4529
- ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins)),
5723
+ ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins, clientEnv)),
4530
5724
  ...useReactRefresh ? [
4531
5725
  refreshWrapperFn({
4532
5726
  cwd: config.root,
@@ -4561,7 +5755,7 @@ async function createBundledDevServer(opts) {
4561
5755
  for (const { clientId, update } of updates) {
4562
5756
  if (update.type === "Noop") continue;
4563
5757
  if (update.type === "FullReload") {
4564
- debug6?.(`full reload for ${clientId}: ${update.reason ?? ""}`);
5758
+ debug7?.(`full reload for ${clientId}: ${update.reason ?? ""}`);
4565
5759
  needsLatestOutput = true;
4566
5760
  continue;
4567
5761
  }
@@ -4575,7 +5769,7 @@ async function createBundledDevServer(opts) {
4575
5769
  }
4576
5770
  const url = `/${patchPath}`;
4577
5771
  logger.info(
4578
- import_picocolors7.default.green("hmr update ") + import_picocolors7.default.dim(changedFiles.map((f) => import_node_path13.default.relative(config.root, f)).join(", ")),
5772
+ import_picocolors7.default.green("hmr update ") + import_picocolors7.default.dim(changedFiles.map((f) => import_node_path14.default.relative(config.root, f)).join(", ")),
4579
5773
  { timestamp: true }
4580
5774
  );
4581
5775
  sendTo(clientId, { type: "hmr:update", path: url, url });
@@ -4611,7 +5805,7 @@ async function createBundledDevServer(opts) {
4611
5805
  },
4612
5806
  {
4613
5807
  watch: { skipWrite: true },
4614
- rebuildStrategy: "auto",
5808
+ rebuildStrategy: "never",
4615
5809
  onOutput(result) {
4616
5810
  if (result instanceof Error) {
4617
5811
  logger.error(import_picocolors7.default.red(`[bundled] build error: ${result.message}`), { error: result });
@@ -4627,7 +5821,13 @@ async function createBundledDevServer(opts) {
4627
5821
  memoryFiles.set(`${file.fileName}.map`, JSON.stringify(file.map));
4628
5822
  }
4629
5823
  }
4630
- debug6?.(`bundle output refreshed (${result.output.length} files)`);
5824
+ debug7?.(`bundle output refreshed (${result.output.length} files)`);
5825
+ },
5826
+ onAdditionalAssets(result) {
5827
+ for (const file of result.output) {
5828
+ const content = file.type === "chunk" ? file.code : file.source;
5829
+ if (content != null) memoryFiles.set(file.fileName, content);
5830
+ }
4631
5831
  },
4632
5832
  async onHmrUpdates(result) {
4633
5833
  if (result instanceof Error) {
@@ -4636,7 +5836,7 @@ async function createBundledDevServer(opts) {
4636
5836
  return;
4637
5837
  }
4638
5838
  const { updates, changedFiles } = result;
4639
- debug6?.(
5839
+ debug7?.(
4640
5840
  `onHmrUpdates(engine watcher): ${changedFiles.length} changed, ${updates.length} updates`
4641
5841
  );
4642
5842
  if (changedFiles.length === 0) return;
@@ -4655,24 +5855,29 @@ async function createBundledDevServer(opts) {
4655
5855
  if (!clientId) return;
4656
5856
  wss.handleUpgrade(req, socket, head, (ws) => {
4657
5857
  bundledClients.set(clientId, ws);
4658
- debug6?.(`bundled client connected: ${clientId}`);
4659
- ws.send(JSON.stringify({ type: "connected" }));
5858
+ debug7?.(`bundled client connected: ${clientId}`);
5859
+ void engine.registerClient(clientId).then(async () => {
5860
+ for (const fileName of entryFileNames.values()) {
5861
+ await engine.notifyPayloadDelivered(fileName);
5862
+ }
5863
+ ws.send(JSON.stringify({ type: "connected" }));
5864
+ }).catch((err) => {
5865
+ debug7?.(`registerClient failed for ${clientId}: ${err?.message ?? err}`);
5866
+ ws.close();
5867
+ });
4660
5868
  ws.on("message", async (raw) => {
4661
5869
  try {
4662
5870
  const msg = JSON.parse(String(raw));
4663
- if (msg.type === "hmr:module-registered" && Array.isArray(msg.modules)) {
4664
- await engine.registerModules(clientId, msg.modules);
4665
- debug6?.(`registered ${msg.modules.length} modules for ${clientId}`);
4666
- } else if (msg.type === "hmr:invalidate") {
5871
+ if (msg.type === "hmr:invalidate") {
4667
5872
  scheduleFullReload();
4668
5873
  }
4669
5874
  } catch (err) {
4670
- debug6?.(`bundled ws message error: ${err.message}`);
5875
+ debug7?.(`bundled ws message error: ${err.message}`);
4671
5876
  }
4672
5877
  });
4673
5878
  ws.on("close", () => {
4674
5879
  bundledClients.delete(clientId);
4675
- engine.removeClient(clientId).catch((err) => debug6?.(`removeClient failed for ${clientId}: ${err?.message ?? err}`));
5880
+ engine.removeClient(clientId).catch((err) => debug7?.(`removeClient failed for ${clientId}: ${err?.message ?? err}`));
4676
5881
  });
4677
5882
  });
4678
5883
  });
@@ -4689,10 +5894,18 @@ async function createBundledDevServer(opts) {
4689
5894
  res.end("// [nasti] lazy endpoint requires id & clientId");
4690
5895
  return;
4691
5896
  }
4692
- const code = await engine.compileEntry(id, clientId);
5897
+ const output = await engine.compileEntry(id, clientId);
5898
+ if (output.sourcemap && output.sourcemapFilename) {
5899
+ memoryFiles.set(output.sourcemapFilename, output.sourcemap);
5900
+ }
5901
+ res.once("finish", () => {
5902
+ void engine.notifyPayloadDelivered(output.filename).catch(
5903
+ (err) => debug7?.(`notifyPayloadDelivered failed: ${err?.message ?? err}`)
5904
+ );
5905
+ });
4693
5906
  res.setHeader("Content-Type", "application/javascript");
4694
5907
  res.setHeader("Cache-Control", "no-store");
4695
- res.end(code + "\n;export {}");
5908
+ res.end(output.code + "\n;export {}");
4696
5909
  return;
4697
5910
  }
4698
5911
  const patchHit = patches.get(pathname.replace(/^\//, ""));
@@ -4711,8 +5924,13 @@ async function createBundledDevServer(opts) {
4711
5924
  return;
4712
5925
  }
4713
5926
  res.setHeader("ETag", hit.etag);
4714
- res.setHeader("Content-Type", MIME_TYPES[import_node_path13.default.extname(fileName)] ?? "application/octet-stream");
5927
+ res.setHeader("Content-Type", MIME_TYPES[import_node_path14.default.extname(fileName)] ?? "application/octet-stream");
4715
5928
  res.setHeader("Cache-Control", "no-cache");
5929
+ res.once("finish", () => {
5930
+ void engine.notifyPayloadDelivered(fileName).catch(
5931
+ (err) => debug7?.(`notifyPayloadDelivered failed: ${err?.message ?? err}`)
5932
+ );
5933
+ });
4716
5934
  res.end(hit.content);
4717
5935
  return;
4718
5936
  }
@@ -4747,7 +5965,7 @@ function stripCatchAllLoad(plugins) {
4747
5965
  );
4748
5966
  }
4749
5967
  function createReactRefreshRuntimePlugin(entryPoints) {
4750
- const entryIds = new Set(entryPoints.map((p) => import_node_path13.default.resolve(p)));
5968
+ const entryIds = new Set(entryPoints.map((p) => import_node_path14.default.resolve(p)));
4751
5969
  return {
4752
5970
  name: "nasti:bundled-react-refresh",
4753
5971
  resolveId(source) {
@@ -4765,7 +5983,7 @@ function createReactRefreshRuntimePlugin(entryPoints) {
4765
5983
  return null;
4766
5984
  },
4767
5985
  transform(code, id) {
4768
- if (!entryIds.has(import_node_path13.default.resolve(id.split("?")[0]))) return null;
5986
+ if (!entryIds.has(import_node_path14.default.resolve(id.split("?")[0]))) return null;
4769
5987
  return { code: `import ${JSON.stringify(PREAMBLE_SPEC)};
4770
5988
  ${code}`, map: null };
4771
5989
  }
@@ -4812,11 +6030,11 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
4812
6030
  }
4813
6031
  return processed;
4814
6032
  }
4815
- var import_node_path13, import_node_crypto3, import_ws2, import_picocolors7, debug6, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
6033
+ var import_node_path14, import_node_crypto3, import_ws2, import_picocolors7, debug7, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
4816
6034
  var init_dev_engine = __esm({
4817
6035
  "src/server/bundled/dev-engine.ts"() {
4818
6036
  "use strict";
4819
- import_node_path13 = __toESM(require("path"), 1);
6037
+ import_node_path14 = __toESM(require("path"), 1);
4820
6038
  import_node_crypto3 = __toESM(require("crypto"), 1);
4821
6039
  import_ws2 = require("ws");
4822
6040
  import_picocolors7 = __toESM(require("picocolors"), 1);
@@ -4825,7 +6043,7 @@ var init_dev_engine = __esm({
4825
6043
  init_transformer();
4826
6044
  init_middleware();
4827
6045
  init_debug();
4828
- debug6 = createDebugger("nasti:bundled");
6046
+ debug7 = createDebugger("nasti:bundled");
4829
6047
  MIME_TYPES = {
4830
6048
  ".js": "application/javascript",
4831
6049
  ".mjs": "application/javascript",
@@ -4943,14 +6161,16 @@ async function createServer(inlineConfig = {}) {
4943
6161
  const startTime = performance.now();
4944
6162
  const config = await resolveConfig(inlineConfig, "serve");
4945
6163
  const logger = config.logger;
4946
- const allPlugins = resolvePluginList(config, config.plugins);
6164
+ const allPlugins = resolvePluginList(config, config.plugins, {
6165
+ environmentName: "client"
6166
+ });
4947
6167
  const configWithPlugins = { ...config, plugins: allPlugins };
4948
6168
  const app = (0, import_connect.default)();
4949
6169
  const httpServer = import_node_http.default.createServer(app);
4950
6170
  const ws = createWebSocketServer(httpServer);
4951
6171
  const pluginApi = getPluginApi(config);
4952
6172
  const clientEnv = new NastiEnvironment("client", config, {
4953
- hot: createWsHotChannel(ws),
6173
+ hot: createWsHotChannel(ws, "client"),
4954
6174
  mode: "dev",
4955
6175
  plugins: allPlugins,
4956
6176
  pluginApi
@@ -4960,15 +6180,45 @@ async function createServer(inlineConfig = {}) {
4960
6180
  for (const name of Object.keys(config.environments)) {
4961
6181
  if (name === "client") continue;
4962
6182
  const consumer = config.environments[name].consumer;
4963
- const envPlugins = resolvePluginList(config, config.plugins, { consumer });
6183
+ const envPlugins = resolvePluginList(config, config.plugins, {
6184
+ consumer,
6185
+ environmentName: name
6186
+ });
4964
6187
  environments[name] = new NastiEnvironment(name, config, {
6188
+ hot: consumer === "client" ? createWsHotChannel(ws, name) : void 0,
4965
6189
  mode: "dev",
4966
6190
  plugins: envPlugins,
4967
6191
  pluginApi
4968
6192
  });
4969
6193
  }
4970
6194
  for (const [name, environment] of Object.entries(environments)) {
4971
- if (name !== "client" && environment.options.driver) await environment.init();
6195
+ if (name === "client" || environment.consumer === "client" || environment.options.driver) {
6196
+ await environment.init();
6197
+ }
6198
+ }
6199
+ const transformContexts = /* @__PURE__ */ new Map();
6200
+ for (const environment of Object.values(environments)) {
6201
+ if (environment.consumer !== "client" || environment.driver) continue;
6202
+ const environmentConfig = {
6203
+ ...configWithPlugins,
6204
+ resolve: environment.options.resolve,
6205
+ build: environment.options.build,
6206
+ plugins: environment.plugins
6207
+ };
6208
+ const context = {
6209
+ config: environmentConfig,
6210
+ pluginContainer: environment.pluginContainer,
6211
+ moduleGraph: environment.moduleGraph,
6212
+ environment,
6213
+ envDefine: buildEnvDefine(
6214
+ loadEnv(environmentConfig.mode, environmentConfig.root, environmentConfig.envPrefix),
6215
+ environmentConfig.mode,
6216
+ ssrDefineOverrides(environment.consumer)
6217
+ ),
6218
+ onPrune: (paths) => environment.hot.send({ type: "prune", paths })
6219
+ };
6220
+ transformContexts.set(environment.name, context);
6221
+ environment.configureDevPipeline((url) => transformRequest(url, context));
4972
6222
  }
4973
6223
  let ssrRunner = null;
4974
6224
  async function getSsrRunner() {
@@ -4983,7 +6233,6 @@ async function createServer(inlineConfig = {}) {
4983
6233
  return ssrRunner;
4984
6234
  }
4985
6235
  const moduleGraph = clientEnv.moduleGraph;
4986
- const pluginContainer = clientEnv.pluginContainer;
4987
6236
  let bundledServer = null;
4988
6237
  if (config.experimental.bundledDev) {
4989
6238
  const { createBundledDevServer: createBundledDevServer2 } = await Promise.resolve().then(() => (init_dev_engine(), dev_engine_exports));
@@ -4995,14 +6244,14 @@ async function createServer(inlineConfig = {}) {
4995
6244
  app.use(bundledServer.middleware);
4996
6245
  }
4997
6246
  const ignoredSegments = /* @__PURE__ */ new Set(["node_modules", ".git", ".nasti"]);
4998
- const outDirAbs = import_node_path14.default.resolve(config.root, config.build.outDir);
6247
+ const outDirAbs = import_node_path15.default.resolve(config.root, config.build.outDir);
4999
6248
  const watcher = (0, import_chokidar.watch)(config.root, {
5000
6249
  ignored: (filePath) => {
5001
6250
  if (filePath === config.root) return false;
5002
- if (filePath === outDirAbs || filePath.startsWith(outDirAbs + import_node_path14.default.sep)) return true;
5003
- const rel = import_node_path14.default.relative(config.root, filePath);
5004
- if (!rel || rel.startsWith("..") || import_node_path14.default.isAbsolute(rel)) return false;
5005
- for (const seg of rel.split(import_node_path14.default.sep)) {
6251
+ if (filePath === outDirAbs || filePath.startsWith(outDirAbs + import_node_path15.default.sep)) return true;
6252
+ const rel = import_node_path15.default.relative(config.root, filePath);
6253
+ if (!rel || rel.startsWith("..") || import_node_path15.default.isAbsolute(rel)) return false;
6254
+ for (const seg of rel.split(import_node_path15.default.sep)) {
5006
6255
  if (ignoredSegments.has(seg)) return true;
5007
6256
  }
5008
6257
  return false;
@@ -5012,6 +6261,15 @@ async function createServer(inlineConfig = {}) {
5012
6261
  let server;
5013
6262
  const environmentServices = {};
5014
6263
  let environmentDriversStarted = false;
6264
+ let devPipelinesStarted = false;
6265
+ const startDevPipelines = async () => {
6266
+ if (devPipelinesStarted) return;
6267
+ devPipelinesStarted = true;
6268
+ for (const environment of Object.values(environments)) {
6269
+ if (!transformContexts.has(environment.name)) continue;
6270
+ await environment.pluginContainer.buildStart();
6271
+ }
6272
+ };
5015
6273
  const logCloseError = (target, error) => {
5016
6274
  const normalized = error instanceof Error ? error : new Error(String(error));
5017
6275
  logger.error(`[nasti] failed to close ${target}`, { error: normalized });
@@ -5063,14 +6321,61 @@ async function createServer(inlineConfig = {}) {
5063
6321
  });
5064
6322
  }
5065
6323
  };
6324
+ const updateClientEnvironments = async (file) => {
6325
+ const timestamp = Date.now();
6326
+ const results = {};
6327
+ for (const environment of Object.values(environments)) {
6328
+ if (environment.consumer !== "client" || environment.driver) continue;
6329
+ try {
6330
+ const result = await handleFileChange(file, server, environment.name, timestamp);
6331
+ if (result) results[environment.name] = result;
6332
+ } catch (error) {
6333
+ const normalized = error instanceof Error ? error : new Error(String(error));
6334
+ logger.error(
6335
+ `[nasti] HMR failed for environment "${environment.name}": ${normalized.message}`,
6336
+ { error: normalized }
6337
+ );
6338
+ try {
6339
+ environment.hot.send({
6340
+ type: "error",
6341
+ err: { message: normalized.message, stack: normalized.stack }
6342
+ });
6343
+ } catch (channelError) {
6344
+ const channelFailure = channelError instanceof Error ? channelError : new Error(String(channelError));
6345
+ logger.error(
6346
+ `[nasti] failed to deliver HMR error to environment "${environment.name}"`,
6347
+ { error: channelFailure }
6348
+ );
6349
+ }
6350
+ }
6351
+ }
6352
+ if (Object.keys(results).length === 0) return;
6353
+ const context = {
6354
+ file,
6355
+ timestamp,
6356
+ environments: Object.freeze({ ...results }),
6357
+ server
6358
+ };
6359
+ for (const plugin of config.plugins) {
6360
+ await plugin.handleHotUpdateApp?.(context);
6361
+ }
6362
+ };
6363
+ const queueClientEnvironmentUpdate = (file) => {
6364
+ void updateClientEnvironments(file).catch((error) => {
6365
+ const normalized = error instanceof Error ? error : new Error(String(error));
6366
+ logger.error(`[nasti] multi-environment HMR failed: ${normalized.message}`, {
6367
+ error: normalized
6368
+ });
6369
+ });
6370
+ };
5066
6371
  watcher.on("change", (file) => {
5067
6372
  ssrRunner?.invalidateFile(file);
5068
- handleFileChange(file, server);
6373
+ queueClientEnvironmentUpdate(file);
5069
6374
  notifyEnvironmentDrivers(file, "change");
5070
6375
  });
5071
6376
  watcher.on("add", (file) => {
5072
6377
  ssrRunner?.invalidateFile(file);
5073
- handleFileChange(file, server);
6378
+ queueClientEnvironmentUpdate(file);
5074
6379
  notifyEnvironmentDrivers(file, "add");
5075
6380
  });
5076
6381
  watcher.on("unlink", (file) => {
@@ -5088,7 +6393,7 @@ async function createServer(inlineConfig = {}) {
5088
6393
  async listen(port) {
5089
6394
  const finalPort = port ?? config.server.port;
5090
6395
  const host = config.server.host === true ? "0.0.0.0" : config.server.host;
5091
- await pluginContainer.buildStart();
6396
+ await startDevPipelines();
5092
6397
  await startEnvironmentDrivers();
5093
6398
  return new Promise((resolve, reject) => {
5094
6399
  let currentPort = finalPort;
@@ -5103,7 +6408,7 @@ async function createServer(inlineConfig = {}) {
5103
6408
  const readyIn = Math.ceil(performance.now() - startTime);
5104
6409
  logger.info(
5105
6410
  `
5106
- ${import_picocolors8.default.cyan(import_picocolors8.default.bold("NASTI"))} ${import_picocolors8.default.cyan(`v${"2.3.1"}`)} ${import_picocolors8.default.dim("ready in")} ${import_picocolors8.default.bold(readyIn)} ${import_picocolors8.default.dim("ms")}
6411
+ ${import_picocolors8.default.cyan(import_picocolors8.default.bold("NASTI"))} ${import_picocolors8.default.cyan(`v${"2.4.1"}`)} ${import_picocolors8.default.dim("ready in")} ${import_picocolors8.default.bold(readyIn)} ${import_picocolors8.default.dim("ms")}
5107
6412
  `
5108
6413
  );
5109
6414
  printServerUrls(
@@ -5130,15 +6435,26 @@ async function createServer(inlineConfig = {}) {
5130
6435
  });
5131
6436
  },
5132
6437
  async transformRequest(url) {
5133
- const { transformRequest: transformRequest2 } = await Promise.resolve().then(() => (init_middleware(), middleware_exports));
5134
- return transformRequest2(url, { config: configWithPlugins, pluginContainer, moduleGraph });
6438
+ return clientEnv.transformRequest(url);
6439
+ },
6440
+ async transformEnvironmentRequest(environmentName, url) {
6441
+ const environment = environments[environmentName];
6442
+ if (!environment) {
6443
+ throw new Error(`[nasti] unknown dev environment "${environmentName}"`);
6444
+ }
6445
+ return environment.transformRequest(url);
5135
6446
  },
5136
6447
  async ssrLoadModule(url) {
5137
6448
  const runner = await getSsrRunner();
5138
6449
  return runner.import(url);
5139
6450
  },
5140
6451
  async close() {
5141
- await pluginContainer.buildEnd();
6452
+ if (devPipelinesStarted) {
6453
+ for (const environment of Object.values(environments).reverse()) {
6454
+ if (!transformContexts.has(environment.name)) continue;
6455
+ await environment.pluginContainer.buildEnd();
6456
+ }
6457
+ }
5142
6458
  await bundledServer?.close();
5143
6459
  let environmentCloseFailed = false;
5144
6460
  let firstEnvironmentCloseError;
@@ -5188,12 +6504,8 @@ async function createServer(inlineConfig = {}) {
5188
6504
  }
5189
6505
  throw error;
5190
6506
  }
5191
- app.use(transformMiddleware({
5192
- config: configWithPlugins,
5193
- pluginContainer,
5194
- moduleGraph
5195
- }));
5196
- const publicDir = import_node_path14.default.resolve(config.root, "public");
6507
+ app.use(transformMiddleware(transformContexts.get("client")));
6508
+ const publicDir = import_node_path15.default.resolve(config.root, "public");
5197
6509
  app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
5198
6510
  app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
5199
6511
  const postMiddlewares = [];
@@ -5219,12 +6531,12 @@ function getNetworkAddress() {
5219
6531
  }
5220
6532
  return "localhost";
5221
6533
  }
5222
- var import_node_http, import_node_path14, import_node_os, import_connect, import_sirv, import_chokidar, import_picocolors8;
6534
+ var import_node_http, import_node_path15, import_node_os, import_connect, import_sirv, import_chokidar, import_picocolors8;
5223
6535
  var init_server = __esm({
5224
6536
  "src/server/index.ts"() {
5225
6537
  "use strict";
5226
6538
  import_node_http = __toESM(require("http"), 1);
5227
- import_node_path14 = __toESM(require("path"), 1);
6539
+ import_node_path15 = __toESM(require("path"), 1);
5228
6540
  import_node_os = __toESM(require("os"), 1);
5229
6541
  import_connect = __toESM(require("connect"), 1);
5230
6542
  import_sirv = __toESM(require("sirv"), 1);
@@ -5239,6 +6551,7 @@ var init_server = __esm({
5239
6551
  init_hmr();
5240
6552
  init_builtins();
5241
6553
  init_plugin_api();
6554
+ init_env();
5242
6555
  }
5243
6556
  });
5244
6557
 
@@ -5293,16 +6606,16 @@ async function buildElectron(inlineConfig = {}) {
5293
6606
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
5294
6607
  const startTime = performance.now();
5295
6608
  assertElectronVersion(config);
5296
- console.log(import_picocolors9.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors9.default.dim(` v${"2.3.1"}`));
6609
+ console.log(import_picocolors9.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors9.default.dim(` v${"2.4.1"}`));
5297
6610
  console.log(import_picocolors9.default.dim(` root: ${config.root}`));
5298
6611
  console.log(import_picocolors9.default.dim(` mode: ${config.mode}`));
5299
6612
  console.log(import_picocolors9.default.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
5300
- const outDir = import_node_path15.default.resolve(config.root, config.build.outDir);
5301
- if (config.build.emptyOutDir && import_node_fs10.default.existsSync(outDir)) {
5302
- import_node_fs10.default.rmSync(outDir, { recursive: true, force: true });
6613
+ const outDir = import_node_path16.default.resolve(config.root, config.build.outDir);
6614
+ if (config.build.emptyOutDir && import_node_fs11.default.existsSync(outDir)) {
6615
+ import_node_fs11.default.rmSync(outDir, { recursive: true, force: true });
5303
6616
  }
5304
- import_node_fs10.default.mkdirSync(outDir, { recursive: true });
5305
- const rendererOutDir = import_node_path15.default.join(outDir, "renderer");
6617
+ import_node_fs11.default.mkdirSync(outDir, { recursive: true });
6618
+ const rendererOutDir = import_node_path16.default.join(outDir, "renderer");
5306
6619
  const { build: build2 } = await Promise.resolve().then(() => (init_build(), build_exports));
5307
6620
  await build2(createElectronRendererConfig(config, inlineConfig, {
5308
6621
  build: {
@@ -5311,8 +6624,8 @@ async function buildElectron(inlineConfig = {}) {
5311
6624
  emptyOutDir: false
5312
6625
  }
5313
6626
  }));
5314
- const mainEntry = import_node_path15.default.resolve(config.root, config.electron.main);
5315
- if (!import_node_fs10.default.existsSync(mainEntry)) {
6627
+ const mainEntry = import_node_path16.default.resolve(config.root, config.electron.main);
6628
+ if (!import_node_fs11.default.existsSync(mainEntry)) {
5316
6629
  throw new Error(
5317
6630
  `Electron main entry not found: ${config.electron.main}
5318
6631
  \u5728 nasti.config.ts \u7684 electron.main \u6307\u5B9A\u4E3B\u8FDB\u7A0B\u5165\u53E3\u6587\u4EF6\u3002`
@@ -5326,11 +6639,11 @@ async function buildElectron(inlineConfig = {}) {
5326
6639
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
5327
6640
  const preloadFiles = [];
5328
6641
  for (const entry of preloadEntries) {
5329
- if (!import_node_fs10.default.existsSync(entry)) {
6642
+ if (!import_node_fs11.default.existsSync(entry)) {
5330
6643
  console.warn(import_picocolors9.default.yellow(` \u26A0 preload entry not found, skipped: ${entry}`));
5331
6644
  continue;
5332
6645
  }
5333
- const base = import_node_path15.default.basename(entry).replace(/\.[^.]+$/, "");
6646
+ const base = import_node_path16.default.basename(entry).replace(/\.[^.]+$/, "");
5334
6647
  const out = outFileName(outDir, base, config.electron.preloadFormat);
5335
6648
  await bundleNode(config, entry, {
5336
6649
  outFile: out,
@@ -5342,10 +6655,10 @@ async function buildElectron(inlineConfig = {}) {
5342
6655
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
5343
6656
  console.log(import_picocolors9.default.green(`
5344
6657
  \u2713 Electron build complete in ${elapsed}s`));
5345
- console.log(import_picocolors9.default.dim(` renderer: ${import_node_path15.default.relative(config.root, rendererOutDir)}/`));
5346
- console.log(import_picocolors9.default.dim(` main: ${import_node_path15.default.relative(config.root, mainFile)}`));
6658
+ console.log(import_picocolors9.default.dim(` renderer: ${import_node_path16.default.relative(config.root, rendererOutDir)}/`));
6659
+ console.log(import_picocolors9.default.dim(` main: ${import_node_path16.default.relative(config.root, mainFile)}`));
5347
6660
  for (const pf of preloadFiles) {
5348
- console.log(import_picocolors9.default.dim(` preload: ${import_node_path15.default.relative(config.root, pf)}`));
6661
+ console.log(import_picocolors9.default.dim(` preload: ${import_node_path16.default.relative(config.root, pf)}`));
5349
6662
  }
5350
6663
  console.log();
5351
6664
  return { rendererOutDir, mainFile, preloadFiles };
@@ -5383,7 +6696,7 @@ async function bundleNode(config, entry, opts) {
5383
6696
  },
5384
6697
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5385
6698
  });
5386
- import_node_fs10.default.mkdirSync(import_node_path15.default.dirname(opts.outFile), { recursive: true });
6699
+ import_node_fs11.default.mkdirSync(import_node_path16.default.dirname(opts.outFile), { recursive: true });
5387
6700
  await bundle2.write({
5388
6701
  sourcemap: !!config.build.sourcemap,
5389
6702
  minify: !!config.build.minify,
@@ -5394,7 +6707,7 @@ async function bundleNode(config, entry, opts) {
5394
6707
  codeSplitting: false
5395
6708
  });
5396
6709
  await bundle2.close();
5397
- console.log(import_picocolors9.default.dim(` \u2713 ${opts.label} \u2192 ${import_node_path15.default.relative(config.root, opts.outFile)}`));
6710
+ console.log(import_picocolors9.default.dim(` \u2713 ${opts.label} \u2192 ${import_node_path16.default.relative(config.root, opts.outFile)}`));
5398
6711
  return opts.outFile;
5399
6712
  }
5400
6713
  function createElectronRendererConfig(config, inlineConfig = {}, overrides = {}) {
@@ -5418,11 +6731,11 @@ function createElectronRendererConfig(config, inlineConfig = {}, overrides = {})
5418
6731
  }
5419
6732
  function outFileName(outDir, base, format) {
5420
6733
  const ext = format === "cjs" ? ".cjs" : ".mjs";
5421
- return import_node_path15.default.join(outDir, base + ext);
6734
+ return import_node_path16.default.join(outDir, base + ext);
5422
6735
  }
5423
6736
  function normalizePreload(preload, root) {
5424
6737
  const list = Array.isArray(preload) ? preload : preload ? [preload] : [];
5425
- return list.map((p) => import_node_path15.default.resolve(root, p));
6738
+ return list.map((p) => import_node_path16.default.resolve(root, p));
5426
6739
  }
5427
6740
  function assertElectronVersion(config) {
5428
6741
  const min = config.electron.minVersion;
@@ -5437,21 +6750,21 @@ function assertElectronVersion(config) {
5437
6750
  }
5438
6751
  function detectInstalledElectron(root) {
5439
6752
  try {
5440
- const pkgPath = import_node_path15.default.resolve(root, "node_modules/electron/package.json");
5441
- if (!import_node_fs10.default.existsSync(pkgPath)) return null;
5442
- const pkg = JSON.parse(import_node_fs10.default.readFileSync(pkgPath, "utf-8"));
6753
+ const pkgPath = import_node_path16.default.resolve(root, "node_modules/electron/package.json");
6754
+ if (!import_node_fs11.default.existsSync(pkgPath)) return null;
6755
+ const pkg = JSON.parse(import_node_fs11.default.readFileSync(pkgPath, "utf-8"));
5443
6756
  const major = parseInt(String(pkg.version).split(".")[0], 10);
5444
6757
  return Number.isFinite(major) ? major : null;
5445
6758
  } catch {
5446
6759
  return null;
5447
6760
  }
5448
6761
  }
5449
- var import_node_path15, import_node_fs10, import_rolldown2, import_picocolors9;
6762
+ var import_node_path16, import_node_fs11, import_rolldown2, import_picocolors9;
5450
6763
  var init_electron2 = __esm({
5451
6764
  "src/build/electron.ts"() {
5452
6765
  "use strict";
5453
- import_node_path15 = __toESM(require("path"), 1);
5454
- import_node_fs10 = __toESM(require("fs"), 1);
6766
+ import_node_path16 = __toESM(require("path"), 1);
6767
+ import_node_fs11 = __toESM(require("fs"), 1);
5455
6768
  import_rolldown2 = require("rolldown");
5456
6769
  import_picocolors9 = __toESM(require("picocolors"), 1);
5457
6770
  init_config();
@@ -5472,7 +6785,7 @@ async function startElectronDev(inlineConfig = {}) {
5472
6785
  const { noSpawn, ...rest } = inlineConfig;
5473
6786
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
5474
6787
  warnElectronVersion(config);
5475
- console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.3.1"}`));
6788
+ console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.4.1"}`));
5476
6789
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5477
6790
  const server = await createServer2({
5478
6791
  ...rest,
@@ -5482,11 +6795,11 @@ async function startElectronDev(inlineConfig = {}) {
5482
6795
  await server.listen();
5483
6796
  const devUrl = `http://localhost:${server.config.server.port}` + electronRendererDevPath(config.electron.renderer);
5484
6797
  console.log(import_picocolors10.default.dim(` renderer: ${devUrl}`));
5485
- const stageDir = import_node_path16.default.resolve(config.root, ".nasti");
5486
- import_node_fs11.default.mkdirSync(stageDir, { recursive: true });
5487
- const mainEntry = import_node_path16.default.resolve(config.root, config.electron.main);
6798
+ const stageDir = import_node_path17.default.resolve(config.root, ".nasti");
6799
+ import_node_fs12.default.mkdirSync(stageDir, { recursive: true });
6800
+ const mainEntry = import_node_path17.default.resolve(config.root, config.electron.main);
5488
6801
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
5489
- const builtMainFile = import_node_path16.default.join(stageDir, "main" + extFor(config.electron.mainFormat));
6802
+ const builtMainFile = import_node_path17.default.join(stageDir, "main" + extFor(config.electron.mainFormat));
5490
6803
  const builtPreloadFiles = [];
5491
6804
  const compileAll = async () => {
5492
6805
  await compileNode(config, mainEntry, {
@@ -5496,9 +6809,9 @@ async function startElectronDev(inlineConfig = {}) {
5496
6809
  });
5497
6810
  builtPreloadFiles.length = 0;
5498
6811
  for (const entry of preloadEntries) {
5499
- if (!import_node_fs11.default.existsSync(entry)) continue;
5500
- const base = import_node_path16.default.basename(entry).replace(/\.[^.]+$/, "");
5501
- const out = import_node_path16.default.join(stageDir, base + extFor(config.electron.preloadFormat));
6812
+ if (!import_node_fs12.default.existsSync(entry)) continue;
6813
+ const base = import_node_path17.default.basename(entry).replace(/\.[^.]+$/, "");
6814
+ const out = import_node_path17.default.join(stageDir, base + extFor(config.electron.preloadFormat));
5502
6815
  await compileNode(config, entry, {
5503
6816
  outFile: out,
5504
6817
  format: config.electron.preloadFormat,
@@ -5537,7 +6850,7 @@ async function startElectronDev(inlineConfig = {}) {
5537
6850
  };
5538
6851
  spawnElectron();
5539
6852
  if (config.electron.autoRestart) {
5540
- const watchTargets = [mainEntry, ...preloadEntries].filter(import_node_fs11.default.existsSync);
6853
+ const watchTargets = [mainEntry, ...preloadEntries].filter(import_node_fs12.default.existsSync);
5541
6854
  const watcher = import_chokidar2.default.watch(watchTargets, { ignoreInitial: true });
5542
6855
  let restarting = null;
5543
6856
  let pending = false;
@@ -5617,7 +6930,7 @@ async function compileNode(config, entry, opts) {
5617
6930
  platform: "node",
5618
6931
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5619
6932
  });
5620
- import_node_fs11.default.mkdirSync(import_node_path16.default.dirname(opts.outFile), { recursive: true });
6933
+ import_node_fs12.default.mkdirSync(import_node_path17.default.dirname(opts.outFile), { recursive: true });
5621
6934
  await bundle2.write({
5622
6935
  file: opts.outFile,
5623
6936
  format: opts.format === "cjs" ? "cjs" : "esm",
@@ -5630,18 +6943,18 @@ async function compileNode(config, entry, opts) {
5630
6943
  await bundle2.close();
5631
6944
  }
5632
6945
  function electronRendererDevPath(renderer) {
5633
- const normalized = renderer.split(import_node_path16.default.sep).join("/").replace(/^\.?\//, "");
6946
+ const normalized = renderer.split(import_node_path17.default.sep).join("/").replace(/^\.?\//, "");
5634
6947
  return normalized === "index.html" ? "/" : `/${normalized}`;
5635
6948
  }
5636
6949
  function resolveElectronBinary(config) {
5637
- if (config.electron.electronPath && import_node_fs11.default.existsSync(config.electron.electronPath)) {
6950
+ if (config.electron.electronPath && import_node_fs12.default.existsSync(config.electron.electronPath)) {
5638
6951
  return config.electron.electronPath;
5639
6952
  }
5640
6953
  try {
5641
- const require2 = (0, import_node_module7.createRequire)(import_node_path16.default.resolve(config.root, "package.json"));
6954
+ const require2 = (0, import_node_module7.createRequire)(import_node_path17.default.resolve(config.root, "package.json"));
5642
6955
  const pathFile = require2.resolve("electron");
5643
6956
  const electronModule = require2(pathFile);
5644
- if (typeof electronModule === "string" && import_node_fs11.default.existsSync(electronModule)) {
6957
+ if (typeof electronModule === "string" && import_node_fs12.default.existsSync(electronModule)) {
5645
6958
  return electronModule;
5646
6959
  }
5647
6960
  } catch {
@@ -5666,12 +6979,12 @@ function warnElectronVersion(config) {
5666
6979
  );
5667
6980
  }
5668
6981
  }
5669
- var import_node_path16, import_node_fs11, import_node_module7, import_node_child_process, import_chokidar2, import_picocolors10, import_rolldown3;
6982
+ var import_node_path17, import_node_fs12, import_node_module7, import_node_child_process, import_chokidar2, import_picocolors10, import_rolldown3;
5670
6983
  var init_electron_dev = __esm({
5671
6984
  "src/server/electron-dev.ts"() {
5672
6985
  "use strict";
5673
- import_node_path16 = __toESM(require("path"), 1);
5674
- import_node_fs11 = __toESM(require("fs"), 1);
6986
+ import_node_path17 = __toESM(require("path"), 1);
6987
+ import_node_fs12 = __toESM(require("fs"), 1);
5675
6988
  import_node_module7 = require("module");
5676
6989
  import_node_child_process = require("child_process");
5677
6990
  import_chokidar2 = __toESM(require("chokidar"), 1);
@@ -5824,20 +7137,20 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
5824
7137
  const logger = createCliLogger(options);
5825
7138
  try {
5826
7139
  const http2 = await import("http");
5827
- const path17 = await import("path");
7140
+ const path18 = await import("path");
5828
7141
  const os2 = await import("os");
5829
7142
  const sirv2 = (await import("sirv")).default;
5830
7143
  const connect2 = (await import("connect")).default;
5831
7144
  const { printServerUrls: printServerUrls2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
5832
- const resolvedRoot = path17.resolve(root ?? ".");
5833
- const outDir = path17.resolve(resolvedRoot, options.outDir);
7145
+ const resolvedRoot = path18.resolve(root ?? ".");
7146
+ const outDir = path18.resolve(resolvedRoot, options.outDir);
5834
7147
  const app = connect2();
5835
7148
  app.use(sirv2(outDir, { single: true, etag: true, gzip: true, brotli: true }));
5836
7149
  const port = options.port;
5837
7150
  const host = options.host === true ? "0.0.0.0" : options.host ?? "localhost";
5838
7151
  http2.createServer(app).listen(port, host, () => {
5839
7152
  logger.info(`
5840
- ${import_picocolors11.default.cyan(import_picocolors11.default.bold("NASTI"))} ${import_picocolors11.default.cyan(`v${"2.3.1"}`)} ${import_picocolors11.default.dim("preview")}
7153
+ ${import_picocolors11.default.cyan(import_picocolors11.default.bold("NASTI"))} ${import_picocolors11.default.cyan(`v${"2.4.1"}`)} ${import_picocolors11.default.dim("preview")}
5841
7154
  `);
5842
7155
  printServerUrls2(
5843
7156
  {
@@ -5854,6 +7167,6 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
5854
7167
  }
5855
7168
  });
5856
7169
  cli.help();
5857
- cli.version("2.3.1");
7170
+ cli.version("2.4.1");
5858
7171
  cli.parse();
5859
7172
  //# sourceMappingURL=cli.cjs.map