@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.js CHANGED
@@ -10,10 +10,10 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
10
10
  if (typeof require !== "undefined") return require.apply(this, arguments);
11
11
  throw Error('Dynamic require of "' + x + '" is not supported');
12
12
  });
13
- var __glob = (map) => (path17) => {
14
- var fn = map[path17];
13
+ var __glob = (map) => (path18) => {
14
+ var fn = map[path18];
15
15
  if (fn) return fn();
16
- throw new Error("Module not found in bundle: " + path17);
16
+ throw new Error("Module not found in bundle: " + path18);
17
17
  };
18
18
  var __esm = (fn, res) => function __init() {
19
19
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -189,7 +189,10 @@ var init_defaults = __esm({
189
189
  target: "es2022",
190
190
  rolldownOptions: {},
191
191
  emptyOutDir: true,
192
- css: {},
192
+ css: {
193
+ inject: true,
194
+ emit: true
195
+ },
193
196
  reportCompressedSize: true,
194
197
  chunkSizeWarningLimit: 500,
195
198
  cssCodeSplit: true,
@@ -428,7 +431,11 @@ async function resolveConfig(inlineConfig = {}, command) {
428
431
  allowClearScreen: clearScreen2,
429
432
  customLogger: merged.customLogger
430
433
  });
431
- const mergedBuild = { ...defaults.build, ...merged.build };
434
+ const mergedBuild = {
435
+ ...defaults.build,
436
+ ...merged.build,
437
+ css: { ...defaults.build.css, ...merged.build?.css }
438
+ };
432
439
  if (merged.build?.cssMinify === void 0) {
433
440
  mergedBuild.cssMinify = !!mergedBuild.minify;
434
441
  }
@@ -459,11 +466,17 @@ async function resolveConfig(inlineConfig = {}, command) {
459
466
  bundledDev: merged.experimental?.bundledDev ?? defaults.experimental.bundledDev
460
467
  }
461
468
  };
462
- const userEnvironments = {
469
+ const rawUserEnvironments = {
463
470
  client: {},
464
471
  ssr: {},
465
472
  ...merged.environments ?? {}
466
473
  };
474
+ const userEnvironments = Object.fromEntries(
475
+ Object.entries(rawUserEnvironments).map(([name, options]) => [
476
+ name,
477
+ deepMerge({}, options)
478
+ ])
479
+ );
467
480
  for (const [name, envOptions] of Object.entries(userEnvironments)) {
468
481
  for (const plugin of rawPlugins) {
469
482
  if (plugin.configEnvironment) {
@@ -474,6 +487,7 @@ async function resolveConfig(inlineConfig = {}, command) {
474
487
  }
475
488
  for (const [name, envOptions] of Object.entries(userEnvironments)) {
476
489
  const consumer = envOptions.consumer ?? (name === "client" ? "client" : "server");
490
+ const vueOptions = deepMerge({}, envOptions.vue ?? {});
477
491
  if (name === "client") {
478
492
  if (envOptions.resolve) {
479
493
  Object.assign(resolved.resolve, {
@@ -481,9 +495,14 @@ async function resolveConfig(inlineConfig = {}, command) {
481
495
  alias: { ...resolved.resolve.alias, ...envOptions.resolve.alias }
482
496
  });
483
497
  }
484
- if (envOptions.build) Object.assign(resolved.build, envOptions.build);
498
+ if (envOptions.build) {
499
+ const { css, ...environmentBuild } = envOptions.build;
500
+ Object.assign(resolved.build, environmentBuild);
501
+ if (css) resolved.build.css = { ...resolved.build.css, ...css };
502
+ }
485
503
  resolved.environments.client = {
486
504
  consumer,
505
+ buildEnabled: envOptions.buildEnabled ?? true,
487
506
  entry: normalizeEnvironmentEntries(envOptions.entry, root),
488
507
  html: path.resolve(
489
508
  root,
@@ -492,15 +511,18 @@ async function resolveConfig(inlineConfig = {}, command) {
492
511
  driver: envOptions.driver,
493
512
  // 同引用 —— 精确镜像(assertClientEnvironmentMirror 校验)
494
513
  resolve: resolved.resolve,
495
- build: resolved.build
514
+ build: resolved.build,
515
+ vue: vueOptions
496
516
  };
497
517
  continue;
498
518
  }
499
519
  resolved.environments[name] = {
500
520
  consumer,
521
+ buildEnabled: envOptions.buildEnabled ?? true,
501
522
  entry: normalizeEnvironmentEntries(envOptions.entry, root),
502
523
  html: envOptions.consumer === "client" && envOptions.html ? path.resolve(root, envOptions.html) : void 0,
503
524
  driver: envOptions.driver,
525
+ vue: vueOptions,
504
526
  resolve: {
505
527
  alias: { ...resolved.resolve.alias, ...envOptions.resolve?.alias },
506
528
  extensions: envOptions.resolve?.extensions ?? [...resolved.resolve.extensions],
@@ -511,6 +533,7 @@ async function resolveConfig(inlineConfig = {}, command) {
511
533
  build: {
512
534
  ...resolved.build,
513
535
  ...envOptions.build,
536
+ css: { ...resolved.build.css, ...envOptions.build?.css },
514
537
  // 非 client 环境默认产出到 <outDir>/<envName>(如 dist/ssr),可显式覆盖
515
538
  outDir: envOptions.build?.outDir ?? path.join(resolved.build.outDir, name),
516
539
  // server 产物默认不压缩(可调试性优先,与 Vite SSR 默认一致),可显式覆盖
@@ -754,17 +777,23 @@ var init_plugin_container = __esm({
754
777
  }
755
778
  async transform(code, id) {
756
779
  let currentCode = code;
780
+ let lastResult;
757
781
  for (const plugin of this.plugins) {
758
782
  if (!plugin.transform) continue;
759
783
  const result = await plugin.transform.call(this.ctx, currentCode, id);
760
784
  if (result == null) continue;
761
785
  if (typeof result === "string") {
762
786
  currentCode = result;
787
+ lastResult = void 0;
763
788
  } else {
764
789
  currentCode = result.code;
790
+ lastResult = result;
765
791
  }
766
792
  }
767
- return currentCode === code ? null : { code: currentCode };
793
+ return currentCode === code ? null : {
794
+ ...lastResult,
795
+ code: currentCode
796
+ };
768
797
  }
769
798
  /** 完整的模块处理管道: resolveId → load → transform */
770
799
  async processModule(source, importer) {
@@ -787,17 +816,39 @@ var init_plugin_container = __esm({
787
816
  }
788
817
  });
789
818
 
819
+ // src/core/url.ts
820
+ function removeTimestampQuery(url) {
821
+ const hashIndex = url.indexOf("#");
822
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
823
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
824
+ const queryIndex = withoutHash.indexOf("?");
825
+ if (queryIndex < 0) return url;
826
+ const pathname = withoutHash.slice(0, queryIndex);
827
+ const query = withoutHash.slice(queryIndex + 1).split("&").filter((part) => !/^t=\d+$/.test(part)).join("&");
828
+ return pathname + (query ? `?${query}` : "") + hash;
829
+ }
830
+ var init_url = __esm({
831
+ "src/core/url.ts"() {
832
+ "use strict";
833
+ }
834
+ });
835
+
790
836
  // src/core/module-graph.ts
791
837
  var ModuleGraph;
792
838
  var init_module_graph = __esm({
793
839
  "src/core/module-graph.ts"() {
794
840
  "use strict";
841
+ init_url();
795
842
  ModuleGraph = class {
843
+ environmentName;
796
844
  urlToModuleMap = /* @__PURE__ */ new Map();
797
845
  idToModuleMap = /* @__PURE__ */ new Map();
798
846
  fileToModulesMap = /* @__PURE__ */ new Map();
847
+ constructor(environmentName = "client") {
848
+ this.environmentName = environmentName;
849
+ }
799
850
  getModuleByUrl(url) {
800
- return this.urlToModuleMap.get(url);
851
+ return this.urlToModuleMap.get(removeTimestampQuery(url));
801
852
  }
802
853
  getModuleById(id) {
803
854
  return this.idToModuleMap.get(id);
@@ -806,10 +857,11 @@ var init_module_graph = __esm({
806
857
  return this.fileToModulesMap.get(file);
807
858
  }
808
859
  async ensureEntryFromUrl(url) {
809
- let mod = this.urlToModuleMap.get(url);
860
+ const normalizedUrl = removeTimestampQuery(url);
861
+ let mod = this.urlToModuleMap.get(normalizedUrl);
810
862
  if (mod) return mod;
811
- mod = this.createModule(url);
812
- this.urlToModuleMap.set(url, mod);
863
+ mod = this.createModule(normalizedUrl);
864
+ this.urlToModuleMap.set(normalizedUrl, mod);
813
865
  return mod;
814
866
  }
815
867
  createModule(url, id) {
@@ -823,7 +875,9 @@ var init_module_graph = __esm({
823
875
  acceptedHmrDeps: /* @__PURE__ */ new Set(),
824
876
  transformResult: null,
825
877
  lastHMRTimestamp: 0,
826
- isSelfAccepting: false
878
+ invalidationVersion: 0,
879
+ isSelfAccepting: false,
880
+ environment: this.environmentName
827
881
  };
828
882
  this.idToModuleMap.set(mod.id, mod);
829
883
  return mod;
@@ -865,10 +919,64 @@ var init_module_graph = __esm({
865
919
  }
866
920
  }
867
921
  }
922
+ /**
923
+ * 用一次转换得到的信息原子更新 import 与 HMR accept 关系。
924
+ * 依赖节点会在真正被浏览器请求前预先创建,这样入口模块先转换时也能建立完整图。
925
+ */
926
+ async updateModuleInfo(mod, importedUrls, acceptedUrls, isSelfAccepting, expectedInvalidationVersion) {
927
+ const importedModules = await Promise.all(
928
+ [...importedUrls].map((url) => this.ensureEntryFromUrl(url))
929
+ );
930
+ const acceptedModules = await Promise.all(
931
+ [...acceptedUrls].map((url) => this.ensureEntryFromUrl(url))
932
+ );
933
+ if (expectedInvalidationVersion !== void 0 && mod.invalidationVersion !== expectedInvalidationVersion) {
934
+ return null;
935
+ }
936
+ const previousImports = new Set(mod.importedModules);
937
+ for (const imported of previousImports) {
938
+ imported.importers.delete(mod);
939
+ }
940
+ mod.importedModules.clear();
941
+ mod.acceptedHmrDeps.clear();
942
+ for (const imported of importedModules) {
943
+ mod.importedModules.add(imported);
944
+ imported.importers.add(mod);
945
+ }
946
+ for (const accepted of acceptedModules) {
947
+ mod.acceptedHmrDeps.add(accepted);
948
+ }
949
+ mod.isSelfAccepting = isSelfAccepting;
950
+ const pruned = /* @__PURE__ */ new Set();
951
+ for (const imported of previousImports) {
952
+ if (!mod.importedModules.has(imported) && imported.importers.size === 0) {
953
+ pruned.add(imported);
954
+ }
955
+ }
956
+ return pruned;
957
+ }
868
958
  /** 使模块的转换缓存失效 */
869
- invalidateModule(mod) {
959
+ invalidateModule(mod, timestamp = Date.now()) {
870
960
  mod.transformResult = null;
871
- mod.lastHMRTimestamp = Date.now();
961
+ mod.lastHMRTimestamp = timestamp;
962
+ mod.invalidationVersion++;
963
+ }
964
+ /**
965
+ * 仅失效到 HMR 边界:显式接受依赖的模块本身不会重执行;自接受模块需要失效,
966
+ * 但不再继续影响其 importer。这样既能传播依赖时间戳,也不会隐式重复副作用。
967
+ */
968
+ invalidateModuleAndImporters(mod, timestamp = Date.now(), seen = /* @__PURE__ */ new Set()) {
969
+ if (seen.has(mod)) return;
970
+ seen.add(mod);
971
+ this.invalidateModule(mod, timestamp);
972
+ for (const importer of mod.importers) {
973
+ if (importer.acceptedHmrDeps.has(mod)) continue;
974
+ if (importer.isSelfAccepting) {
975
+ this.invalidateModule(importer, timestamp);
976
+ continue;
977
+ }
978
+ this.invalidateModuleAndImporters(importer, timestamp, seen);
979
+ }
872
980
  }
873
981
  /** 使所有模块缓存失效 */
874
982
  invalidateAll() {
@@ -879,34 +987,32 @@ var init_module_graph = __esm({
879
987
  /** 获取 HMR 传播边界 - 从变更模块向上遍历找到接受更新的边界 */
880
988
  getHmrBoundaries(mod) {
881
989
  const boundaries = [];
882
- const visited = /* @__PURE__ */ new Set();
883
- const propagate = (node, via) => {
884
- if (visited.has(node)) return true;
885
- visited.add(node);
886
- if (node.isSelfAccepting) {
887
- boundaries.push({ boundary: node, acceptedVia: via });
888
- return true;
990
+ const traversed = /* @__PURE__ */ new Set();
991
+ const addBoundary = (boundary, acceptedVia) => {
992
+ if (!boundaries.some(
993
+ (item) => item.boundary === boundary && item.acceptedVia === acceptedVia
994
+ )) {
995
+ boundaries.push({ boundary, acceptedVia });
889
996
  }
890
- if (node.acceptedHmrDeps.has(via)) {
891
- boundaries.push({ boundary: node, acceptedVia: via });
997
+ };
998
+ const propagate = (node) => {
999
+ if (traversed.has(node)) return true;
1000
+ traversed.add(node);
1001
+ if (node.isSelfAccepting) {
1002
+ addBoundary(node, node);
892
1003
  return true;
893
1004
  }
894
1005
  if (node.importers.size === 0) return false;
895
1006
  for (const importer of node.importers) {
896
- if (!propagate(importer, node)) return false;
1007
+ if (importer.acceptedHmrDeps.has(node)) {
1008
+ addBoundary(importer, node);
1009
+ continue;
1010
+ }
1011
+ if (!propagate(importer)) return false;
897
1012
  }
898
1013
  return true;
899
1014
  };
900
- if (mod.isSelfAccepting) {
901
- boundaries.push({ boundary: mod, acceptedVia: mod });
902
- return boundaries;
903
- }
904
- for (const importer of mod.importers) {
905
- if (!propagate(importer, mod)) {
906
- return [];
907
- }
908
- }
909
- return boundaries;
1015
+ return propagate(mod) ? boundaries : [];
910
1016
  }
911
1017
  };
912
1018
  }
@@ -929,12 +1035,12 @@ function createNoopHotChannel() {
929
1035
  }
930
1036
  };
931
1037
  }
932
- function createWsHotChannel(ws) {
1038
+ function createWsHotChannel(ws, environmentName = "client") {
933
1039
  const listeners = /* @__PURE__ */ new Map();
934
1040
  let invokeHandlers;
935
1041
  return {
936
1042
  send(payload) {
937
- ws.send(payload);
1043
+ ws.send({ ...payload, environment: payload.environment ?? environmentName });
938
1044
  },
939
1045
  on(event, listener) {
940
1046
  let set = listeners.get(event);
@@ -946,8 +1052,8 @@ function createWsHotChannel(ws) {
946
1052
  },
947
1053
  listen() {
948
1054
  },
1055
+ // 多个 environment 共享底层 WebSocket server;它由 DevServer.close() 统一关闭。
949
1056
  close() {
950
- ws.close();
951
1057
  },
952
1058
  setInvokeHandler(handlers) {
953
1059
  invokeHandlers = handlers;
@@ -1045,6 +1151,10 @@ var init_environment = __esm({
1045
1151
  moduleGraph;
1046
1152
  candidatePlugins;
1047
1153
  pluginApi;
1154
+ buildMetadata = {};
1155
+ cssModules = /* @__PURE__ */ new Map();
1156
+ assetModules = /* @__PURE__ */ new Map();
1157
+ transformRequestHandler;
1048
1158
  initialized = false;
1049
1159
  constructor(name, config, init = {}) {
1050
1160
  const options = config.environments[name];
@@ -1059,7 +1169,7 @@ var init_environment = __esm({
1059
1169
  this.config = config;
1060
1170
  this.options = options;
1061
1171
  this.hot = init.hot ?? createNoopHotChannel();
1062
- this.moduleGraph = new ModuleGraph();
1172
+ this.moduleGraph = new ModuleGraph(name);
1063
1173
  this.candidatePlugins = init.plugins ?? config.plugins;
1064
1174
  this.pluginApi = init.pluginApi ?? getPluginApi(config);
1065
1175
  }
@@ -1101,6 +1211,53 @@ var init_environment = __esm({
1101
1211
  logger: this.config.logger
1102
1212
  };
1103
1213
  }
1214
+ configureDevPipeline(transformRequest2) {
1215
+ this.transformRequestHandler = transformRequest2;
1216
+ }
1217
+ async transformRequest(url) {
1218
+ if (!this.transformRequestHandler) {
1219
+ throw new Error(
1220
+ `[nasti] environment "${this.name}" does not have an initialized dev transform pipeline`
1221
+ );
1222
+ }
1223
+ return this.transformRequestHandler(url);
1224
+ }
1225
+ setCssModule(module) {
1226
+ this.cssModules.set(module.id, { ...module });
1227
+ }
1228
+ getCssModule(id) {
1229
+ const module = this.cssModules.get(id);
1230
+ return module ? { ...module } : void 0;
1231
+ }
1232
+ getCssModules() {
1233
+ return Object.freeze(
1234
+ Object.fromEntries(
1235
+ [...this.cssModules].map(([id, module]) => [id, { ...module }])
1236
+ )
1237
+ );
1238
+ }
1239
+ setAssetModule(id, fileName) {
1240
+ this.assetModules.set(id, fileName);
1241
+ }
1242
+ getAssetModules() {
1243
+ return Object.freeze(Object.fromEntries(this.assetModules));
1244
+ }
1245
+ setBuildMetadata(metadata) {
1246
+ const { entries: currentEntries, ...currentMetadata } = this.buildMetadata;
1247
+ const { entries, ...nextMetadata } = metadata;
1248
+ this.buildMetadata = {
1249
+ ...currentMetadata,
1250
+ ...nextMetadata,
1251
+ ...currentEntries || entries ? { entries: { ...currentEntries, ...entries } } : {}
1252
+ };
1253
+ }
1254
+ getBuildMetadata() {
1255
+ const { entries, ...metadata } = this.buildMetadata;
1256
+ return {
1257
+ ...metadata,
1258
+ ...entries ? { entries: { ...entries } } : {}
1259
+ };
1260
+ }
1104
1261
  async close() {
1105
1262
  try {
1106
1263
  await this.driver?.close?.(this.getDriverContext());
@@ -1341,28 +1498,111 @@ var init_env = __esm({
1341
1498
  }
1342
1499
  });
1343
1500
 
1344
- // src/server/middleware.ts
1345
- var middleware_exports = {};
1346
- __export(middleware_exports, {
1347
- REACT_REFRESH_GLOBAL_PREAMBLE: () => REACT_REFRESH_GLOBAL_PREAMBLE,
1348
- getReactRefreshRuntimeEsm: () => getReactRefreshRuntimeEsm,
1349
- transformMiddleware: () => transformMiddleware,
1350
- transformRequest: () => transformRequest
1351
- });
1501
+ // src/plugins/assets.ts
1352
1502
  import path4 from "path";
1353
1503
  import fs4 from "fs";
1504
+ import crypto from "crypto";
1505
+ function assetsPlugin(config) {
1506
+ const emittedAssets = /* @__PURE__ */ new Set();
1507
+ return {
1508
+ name: "nasti:assets",
1509
+ resolveId(source) {
1510
+ if (source.endsWith("?url") || source.endsWith("?raw")) {
1511
+ return source;
1512
+ }
1513
+ return null;
1514
+ },
1515
+ load(id) {
1516
+ const ext = path4.extname(id.replace(/\?.*$/, ""));
1517
+ if (id.endsWith("?raw")) {
1518
+ const file = id.slice(0, -4);
1519
+ if (fs4.existsSync(file)) {
1520
+ const content = fs4.readFileSync(file, "utf-8");
1521
+ return `export default ${JSON.stringify(content)}`;
1522
+ }
1523
+ }
1524
+ if (id.endsWith("?url") || ASSET_EXTENSIONS.has(ext)) {
1525
+ const file = id.replace(/\?.*$/, "");
1526
+ if (!fs4.existsSync(file)) return null;
1527
+ if (config.command === "serve") {
1528
+ const url = "/" + path4.relative(config.root, file);
1529
+ return `export default ${JSON.stringify(url)}`;
1530
+ }
1531
+ const content = fs4.readFileSync(file);
1532
+ const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 8);
1533
+ const basename = path4.basename(file, ext);
1534
+ const hashedName = `${config.build.assetsDir}/${basename}.${hash}${ext}`;
1535
+ const environment = this.environment;
1536
+ if (!environment) {
1537
+ throw new Error("[nasti:assets] build environment is not initialized");
1538
+ }
1539
+ if (!emittedAssets.has(hashedName)) {
1540
+ this.emitFile({
1541
+ type: "asset",
1542
+ fileName: hashedName,
1543
+ source: content
1544
+ });
1545
+ emittedAssets.add(hashedName);
1546
+ }
1547
+ environment.setAssetModule(file, hashedName);
1548
+ return `export default ${JSON.stringify(config.base + hashedName)}`;
1549
+ }
1550
+ return null;
1551
+ }
1552
+ };
1553
+ }
1554
+ function isAssetFile(id) {
1555
+ const ext = path4.extname(id.replace(/\?.*$/, ""));
1556
+ return ASSET_EXTENSIONS.has(ext);
1557
+ }
1558
+ var ASSET_EXTENSIONS;
1559
+ var init_assets = __esm({
1560
+ "src/plugins/assets.ts"() {
1561
+ "use strict";
1562
+ ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
1563
+ ".png",
1564
+ ".jpg",
1565
+ ".jpeg",
1566
+ ".gif",
1567
+ ".svg",
1568
+ ".ico",
1569
+ ".webp",
1570
+ ".avif",
1571
+ ".mp4",
1572
+ ".webm",
1573
+ ".ogg",
1574
+ ".mp3",
1575
+ ".wav",
1576
+ ".flac",
1577
+ ".aac",
1578
+ ".woff",
1579
+ ".woff2",
1580
+ ".eot",
1581
+ ".ttf",
1582
+ ".otf",
1583
+ ".pdf",
1584
+ ".txt"
1585
+ ]);
1586
+ }
1587
+ });
1588
+
1589
+ // src/server/middleware.ts
1590
+ import path5 from "path";
1591
+ import fs5 from "fs";
1354
1592
  import { createRequire } from "module";
1355
1593
  import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "url";
1356
1594
  import pc3 from "picocolors";
1357
- function getReactRefreshRuntimeEsm() {
1358
- if (__refreshRuntimeCache) return __refreshRuntimeCache;
1595
+ function getReactRefreshRuntimeEsm(includeBoundaryHelpers = false) {
1596
+ if (__refreshRuntimeCache) {
1597
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
1598
+ }
1359
1599
  let cjsPath;
1360
1600
  try {
1361
1601
  const pkgPath = __require2.resolve("react-refresh/package.json");
1362
- cjsPath = path4.join(path4.dirname(pkgPath), "cjs", "react-refresh-runtime.development.js");
1602
+ cjsPath = path5.join(path5.dirname(pkgPath), "cjs", "react-refresh-runtime.development.js");
1363
1603
  } catch (err) {
1364
- cjsPath = path4.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
1365
- if (!fs4.existsSync(cjsPath)) {
1604
+ cjsPath = path5.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
1605
+ if (!fs5.existsSync(cjsPath)) {
1366
1606
  const origMsg = err instanceof Error ? err.message : String(err);
1367
1607
  throw new Error(
1368
1608
  `[nasti] Missing dependency "react-refresh". Install it with: npm install react-refresh
@@ -1370,7 +1610,7 @@ Original resolve error: ${origMsg}`
1370
1610
  );
1371
1611
  }
1372
1612
  }
1373
- const cjsSource = fs4.readFileSync(cjsPath, "utf-8");
1613
+ const cjsSource = fs5.readFileSync(cjsPath, "utf-8");
1374
1614
  __refreshRuntimeCache = `// Wrapped react-refresh runtime -> ESM
1375
1615
  const exports = {};
1376
1616
  const module = { exports };
@@ -1390,7 +1630,7 @@ export const findAffectedHostInstances = __rt.findAffectedHostInstances;
1390
1630
  export const collectCustomHooksForSignature = __rt.collectCustomHooksForSignature;
1391
1631
  export default __rt;
1392
1632
  `;
1393
- return __refreshRuntimeCache;
1633
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
1394
1634
  }
1395
1635
  function buildReactRefreshWrapper(moduleUrl, transformedCode) {
1396
1636
  const urlLit = JSON.stringify(moduleUrl);
@@ -1416,27 +1656,46 @@ window.$RefreshReg$ = prevRefreshReg;
1416
1656
  window.$RefreshSig$ = prevRefreshSig;
1417
1657
 
1418
1658
  if (__nasti_hot__) {
1419
- __nasti_hot__.accept(() => {
1420
- clearTimeout(window.__nasti_refresh_timer__);
1421
- window.__nasti_refresh_timer__ = setTimeout(() => {
1422
- RefreshRuntime.performReactRefresh();
1423
- }, 30);
1659
+ let __nasti_current_exports__;
1660
+ __nasti_hot__.accept((nextExports) => {
1661
+ if (!nextExports) return;
1662
+ if (!__nasti_current_exports__) {
1663
+ __nasti_hot__.invalidate('Could not Fast Refresh (previous exports unavailable)');
1664
+ return;
1665
+ }
1666
+ const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate(
1667
+ ${urlLit},
1668
+ __nasti_current_exports__,
1669
+ nextExports,
1670
+ );
1671
+ if (invalidateMessage) __nasti_hot__.invalidate(invalidateMessage);
1672
+ });
1673
+ RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
1674
+ __nasti_current_exports__ = currentExports;
1675
+ RefreshRuntime.registerExportsForReactRefresh(${urlLit}, currentExports);
1424
1676
  });
1425
1677
  }
1426
1678
  `;
1427
1679
  }
1428
1680
  function injectImportMetaHot(code, moduleUrl) {
1429
- if (!/\bimport\.meta\.hot\b/.test(code)) return code;
1681
+ const hotRE = /\bimport\.meta\.hot\b/g;
1682
+ const matches = [...maskStringsAndComments(code).matchAll(hotRE)];
1683
+ if (matches.length === 0) return code;
1684
+ for (const match of matches.reverse()) {
1685
+ const start = match.index;
1686
+ code = code.slice(0, start) + "__nasti_hot__" + code.slice(start + match[0].length);
1687
+ }
1430
1688
  const urlLit = JSON.stringify(moduleUrl);
1431
1689
  const header = `import { createHotContext as __nasti_createHotContext__ } from "/@nasti/client";
1432
1690
  const __nasti_hot__ = __nasti_createHotContext__(${urlLit});
1433
1691
  `;
1434
- return header + code.replace(/\bimport\.meta\.hot\b/g, "__nasti_hot__");
1692
+ return header + code;
1435
1693
  }
1436
1694
  function transformMiddleware(ctx) {
1437
1695
  ctx.envDefine = buildEnvDefine(
1438
1696
  loadEnv(ctx.config.mode, ctx.config.root, ctx.config.envPrefix),
1439
- ctx.config.mode
1697
+ ctx.config.mode,
1698
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
1440
1699
  );
1441
1700
  return async (req, res, next) => {
1442
1701
  const url = req.url ?? "/";
@@ -1481,7 +1740,7 @@ function transformMiddleware(ctx) {
1481
1740
  return;
1482
1741
  }
1483
1742
  }
1484
- if (isModuleRequest(url)) {
1743
+ if (isModuleRequest(url, req.headers["sec-fetch-dest"])) {
1485
1744
  try {
1486
1745
  const result = await transformRequest(url, ctx);
1487
1746
  if (result) {
@@ -1507,13 +1766,14 @@ function transformMiddleware(ctx) {
1507
1766
  }
1508
1767
  async function transformRequest(url, ctx) {
1509
1768
  const { config, pluginContainer, moduleGraph } = ctx;
1769
+ url = removeTimestampQuery(url);
1510
1770
  const cleanReqUrl = url.split("?")[0];
1511
1771
  const cached2 = moduleGraph.getModuleByUrl(url);
1512
1772
  if (cached2?.transformResult) {
1513
1773
  return cached2.transformResult;
1514
1774
  }
1515
1775
  if (cleanReqUrl === "/@react-refresh") {
1516
- return { code: getReactRefreshRuntimeEsm() };
1776
+ return { code: getReactRefreshRuntimeEsm(true) };
1517
1777
  }
1518
1778
  if (cleanReqUrl.startsWith("/@modules/") && url.includes("?")) {
1519
1779
  const idParam = new URLSearchParams(url.slice(url.indexOf("?") + 1)).get("id");
@@ -1521,8 +1781,8 @@ async function transformRequest(url, ctx) {
1521
1781
  let realIdValid = false;
1522
1782
  try {
1523
1783
  if (idParam) {
1524
- realId = fs4.realpathSync(idParam);
1525
- realIdValid = fs4.statSync(realId).isFile() && (realId.includes(`${path4.sep}node_modules${path4.sep}`) || isUnderRoot(realId, config.root));
1784
+ realId = fs5.realpathSync(idParam);
1785
+ realIdValid = fs5.statSync(realId).isFile() && (realId.includes(`${path5.sep}node_modules${path5.sep}`) || isUnderRoot(realId, config.root));
1526
1786
  }
1527
1787
  } catch {
1528
1788
  realId = null;
@@ -1549,40 +1809,63 @@ async function transformRequest(url, ctx) {
1549
1809
  }
1550
1810
  const rawQuery = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
1551
1811
  if (rawQuery && !/^t=\d+$/.test(rawQuery)) {
1552
- const loaded = await pluginContainer.load(url);
1553
- if (loaded != null) {
1554
- let code2 = typeof loaded === "string" ? loaded : loaded.code;
1812
+ const mod2 = await moduleGraph.ensureEntryFromUrl(url);
1813
+ const transformVersion2 = mod2.invalidationVersion;
1814
+ const loaded2 = await pluginContainer.load(url);
1815
+ if (loaded2 != null) {
1816
+ let code2 = typeof loaded2 === "string" ? loaded2 : loaded2.code;
1817
+ let map2 = typeof loaded2 === "string" ? void 0 : loaded2.map;
1555
1818
  const transformed = await pluginContainer.transform(code2, url);
1556
1819
  if (transformed != null) {
1557
1820
  code2 = typeof transformed === "string" ? transformed : transformed.code;
1821
+ if (typeof transformed !== "string" && transformed.map != null) {
1822
+ map2 = transformed.map;
1823
+ }
1558
1824
  }
1559
- const mod2 = await moduleGraph.ensureEntryFromUrl(url);
1560
- moduleGraph.registerModule(mod2, cleanReqUrl);
1561
- code2 = injectImportMetaHot(code2, url);
1825
+ const parentFile = resolveUrlToFile(cleanReqUrl, config.root) ?? cleanReqUrl;
1826
+ moduleGraph.registerModule(mod2, parentFile);
1827
+ const hotInfo2 = rewriteHotAcceptDeps(code2, config, parentFile);
1828
+ code2 = injectImportMetaHot(hotInfo2.code, url);
1562
1829
  code2 = replaceEnvInCode(code2, ctx.envDefine ?? buildEnvDefine(
1563
1830
  loadEnv(config.mode, config.root, config.envPrefix),
1564
- config.mode
1831
+ config.mode,
1832
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
1565
1833
  ));
1566
- code2 = rewriteImports(code2, config, cleanReqUrl);
1567
- const transformResult2 = { code: code2 };
1568
- mod2.transformResult = transformResult2;
1834
+ const importedUrls2 = /* @__PURE__ */ new Set();
1835
+ code2 = rewriteImports(code2, config, parentFile, importedUrls2, moduleGraph);
1836
+ const pruned2 = await moduleGraph.updateModuleInfo(
1837
+ mod2,
1838
+ importedUrls2,
1839
+ hotInfo2.acceptedUrls,
1840
+ hotInfo2.isSelfAccepting,
1841
+ transformVersion2
1842
+ );
1843
+ const transformResult2 = { code: code2, map: map2 };
1844
+ if (pruned2) {
1845
+ if (pruned2.size > 0) ctx.onPrune?.([...pruned2].map((item) => item.url));
1846
+ mod2.transformResult = transformResult2;
1847
+ }
1569
1848
  return transformResult2;
1570
1849
  }
1571
1850
  }
1572
1851
  const filePath = resolveUrlToFile(url, config.root);
1573
- if (!filePath || !fs4.existsSync(filePath)) return null;
1852
+ if (!filePath || !fs5.existsSync(filePath)) return null;
1574
1853
  const mod = await moduleGraph.ensureEntryFromUrl(url);
1575
1854
  moduleGraph.registerModule(mod, filePath);
1855
+ const transformVersion = mod.invalidationVersion;
1576
1856
  if (cleanReqUrl.startsWith("/@modules/")) {
1577
1857
  const code2 = await bundlePackageAsEsm(filePath, config.root);
1578
1858
  const transformResult2 = { code: code2 };
1579
1859
  mod.transformResult = transformResult2;
1580
1860
  return transformResult2;
1581
1861
  }
1582
- let code = fs4.readFileSync(filePath, "utf-8");
1862
+ const loaded = await pluginContainer.load(filePath);
1863
+ let code = loaded == null ? fs5.readFileSync(filePath, "utf-8") : typeof loaded === "string" ? loaded : loaded.code;
1864
+ let map = loaded && typeof loaded !== "string" ? loaded.map : void 0;
1583
1865
  const pluginResult = await pluginContainer.transform(code, filePath);
1584
1866
  if (pluginResult) {
1585
1867
  code = typeof pluginResult === "string" ? pluginResult : pluginResult.code;
1868
+ if (typeof pluginResult !== "string") map = pluginResult.map;
1586
1869
  }
1587
1870
  const stableUrl = cleanReqUrl;
1588
1871
  let wrappedWithRefresh = false;
@@ -1593,26 +1876,41 @@ async function transformRequest(url, ctx) {
1593
1876
  sourcemap: true,
1594
1877
  jsxRuntime: "automatic",
1595
1878
  jsxImportSource: config.framework === "vue" ? "vue" : "react",
1596
- reactRefresh: useRefresh
1879
+ reactRefresh: useRefresh,
1880
+ target: ctx.environment?.options.build.target ?? config.build.target
1597
1881
  });
1598
1882
  code = result.code;
1883
+ if (result.map) map = JSON.parse(result.map);
1599
1884
  if (useRefresh) {
1600
1885
  code = buildReactRefreshWrapper(stableUrl, code);
1601
1886
  wrappedWithRefresh = true;
1602
- mod.isSelfAccepting = true;
1603
1887
  }
1604
1888
  }
1889
+ const hotInfo = rewriteHotAcceptDeps(code, config, filePath);
1890
+ code = hotInfo.code;
1605
1891
  if (!wrappedWithRefresh) {
1606
1892
  code = injectImportMetaHot(code, stableUrl);
1607
1893
  }
1608
1894
  const envDefine = ctx.envDefine ?? buildEnvDefine(
1609
1895
  loadEnv(config.mode, config.root, config.envPrefix),
1610
- config.mode
1896
+ config.mode,
1897
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
1611
1898
  );
1612
1899
  code = replaceEnvInCode(code, envDefine);
1613
- code = rewriteImports(code, config, filePath);
1614
- const transformResult = { code };
1615
- mod.transformResult = transformResult;
1900
+ const importedUrls = /* @__PURE__ */ new Set();
1901
+ code = rewriteImports(code, config, filePath, importedUrls, moduleGraph);
1902
+ const pruned = await moduleGraph.updateModuleInfo(
1903
+ mod,
1904
+ importedUrls,
1905
+ hotInfo.acceptedUrls,
1906
+ wrappedWithRefresh || hotInfo.isSelfAccepting,
1907
+ transformVersion
1908
+ );
1909
+ const transformResult = { code, map };
1910
+ if (pruned) {
1911
+ if (pruned.size > 0) ctx.onPrune?.([...pruned].map((item) => item.url));
1912
+ mod.transformResult = transformResult;
1913
+ }
1616
1914
  return transformResult;
1617
1915
  }
1618
1916
  async function loadVirtualModule(spec, ctx) {
@@ -1620,7 +1918,7 @@ async function loadVirtualModule(spec, ctx) {
1620
1918
  const resolved = await pluginContainer.resolveId(spec);
1621
1919
  if (resolved == null) return null;
1622
1920
  const resolvedId = typeof resolved === "string" ? resolved : resolved.id;
1623
- const looksVirtual = resolvedId.startsWith("\0") || !fs4.existsSync(resolvedId);
1921
+ const looksVirtual = resolvedId.startsWith("\0") || !fs5.existsSync(resolvedId);
1624
1922
  if (!looksVirtual) return null;
1625
1923
  const loadResult = await pluginContainer.load(resolvedId);
1626
1924
  if (loadResult == null) return null;
@@ -1631,9 +1929,10 @@ async function loadVirtualModule(spec, ctx) {
1631
1929
  }
1632
1930
  code = replaceEnvInCode(code, ctx.envDefine ?? buildEnvDefine(
1633
1931
  loadEnv(config.mode, config.root, config.envPrefix),
1634
- config.mode
1932
+ config.mode,
1933
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
1635
1934
  ));
1636
- const anchor = path4.join(config.root, "__nasti_virtual__.ts");
1935
+ const anchor = path5.join(config.root, "__nasti_virtual__.ts");
1637
1936
  code = rewriteImports(code, config, anchor);
1638
1937
  return { id: resolvedId, result: { code } };
1639
1938
  }
@@ -1659,7 +1958,7 @@ async function doBundlePackage(entryFile, root) {
1659
1958
  await bundle2.close();
1660
1959
  let code = result.output[0].code;
1661
1960
  code = code.replace(/process\.env\.NODE_ENV/g, '"development"');
1662
- const externalBaseDir = path4.dirname(entryFile);
1961
+ const externalBaseDir = path5.dirname(entryFile);
1663
1962
  code = code.replace(
1664
1963
  /^(import\b[^;'"]*?\bfrom\s+)(['"])([^'"./][^'"]*)(\2)/gm,
1665
1964
  (_, prefix, q, spec) => `${prefix}${q}${externalSpecToModuleUrl(spec, externalBaseDir, root)}${q}`
@@ -1677,16 +1976,16 @@ async function doBundlePackage(entryFile, root) {
1677
1976
  return code;
1678
1977
  }
1679
1978
  async function tryGenerateSubpathShim(entryFile, root) {
1680
- const NM = `${path4.sep}node_modules${path4.sep}`;
1979
+ const NM = `${path5.sep}node_modules${path5.sep}`;
1681
1980
  if (!entryFile.includes(NM)) return null;
1682
1981
  let pkgDir = null;
1683
1982
  let pkgName = null;
1684
- let dir = path4.dirname(entryFile);
1983
+ let dir = path5.dirname(entryFile);
1685
1984
  while (true) {
1686
- const pkgJsonPath = path4.join(dir, "package.json");
1687
- if (fs4.existsSync(pkgJsonPath)) {
1985
+ const pkgJsonPath = path5.join(dir, "package.json");
1986
+ if (fs5.existsSync(pkgJsonPath)) {
1688
1987
  try {
1689
- const pkg = JSON.parse(fs4.readFileSync(pkgJsonPath, "utf-8"));
1988
+ const pkg = JSON.parse(fs5.readFileSync(pkgJsonPath, "utf-8"));
1690
1989
  if (typeof pkg?.name === "string" && pkg.name) {
1691
1990
  pkgDir = dir;
1692
1991
  pkgName = pkg.name;
@@ -1695,16 +1994,16 @@ async function tryGenerateSubpathShim(entryFile, root) {
1695
1994
  } catch {
1696
1995
  }
1697
1996
  }
1698
- const parent = path4.dirname(dir);
1997
+ const parent = path5.dirname(dir);
1699
1998
  if (parent === dir) return null;
1700
1999
  dir = parent;
1701
2000
  if (!dir.includes(NM)) return null;
1702
2001
  }
1703
2002
  if (!pkgDir || !pkgName) return null;
1704
- const entryExt = path4.extname(entryFile);
2003
+ const entryExt = path5.extname(entryFile);
1705
2004
  const mainEntry = pickMainEntryByExtension(pkgDir, entryExt);
1706
2005
  if (!mainEntry) return null;
1707
- if (path4.resolve(mainEntry) === path4.resolve(entryFile)) return null;
2006
+ if (path5.resolve(mainEntry) === path5.resolve(entryFile)) return null;
1708
2007
  let mainNs;
1709
2008
  let subNs;
1710
2009
  try {
@@ -1728,7 +2027,7 @@ async function tryGenerateSubpathShim(entryFile, root) {
1728
2027
  if (mainNs["default"] !== subNs["default"]) return null;
1729
2028
  }
1730
2029
  const rootMain = resolveNodeModule(root, pkgName);
1731
- const mainEntryUrl = rootMain && rootMain.startsWith(pkgDir + path4.sep) ? `/@modules/${pkgName}` : `/@modules/${pkgName}?id=${encodeURIComponent(mainEntry)}`;
2030
+ const mainEntryUrl = rootMain && rootMain.startsWith(pkgDir + path5.sep) ? `/@modules/${pkgName}` : `/@modules/${pkgName}?id=${encodeURIComponent(mainEntry)}`;
1732
2031
  const lines = [
1733
2032
  `// Nasti subpath shim \u2192 ${pkgName} (avoid duplicate bundling)`,
1734
2033
  `import * as __pkg from "${mainEntryUrl}";`
@@ -1742,10 +2041,10 @@ async function tryGenerateSubpathShim(entryFile, root) {
1742
2041
  return lines.join("\n") + "\n";
1743
2042
  }
1744
2043
  function pickMainEntryByExtension(pkgDir, preferredExt) {
1745
- const pkgJsonPath = path4.join(pkgDir, "package.json");
2044
+ const pkgJsonPath = path5.join(pkgDir, "package.json");
1746
2045
  let pkg;
1747
2046
  try {
1748
- pkg = JSON.parse(fs4.readFileSync(pkgJsonPath, "utf-8"));
2047
+ pkg = JSON.parse(fs5.readFileSync(pkgJsonPath, "utf-8"));
1749
2048
  } catch {
1750
2049
  return null;
1751
2050
  }
@@ -1764,14 +2063,14 @@ function pickMainEntryByExtension(pkgDir, preferredExt) {
1764
2063
  if (typeof pkg.module === "string") candidates.push(pkg.module);
1765
2064
  if (typeof pkg.main === "string") candidates.push(pkg.main);
1766
2065
  for (const cand of candidates) {
1767
- if (path4.extname(cand) === preferredExt) {
1768
- const full = path4.resolve(pkgDir, cand);
1769
- if (fs4.existsSync(full)) return full;
2066
+ if (path5.extname(cand) === preferredExt) {
2067
+ const full = path5.resolve(pkgDir, cand);
2068
+ if (fs5.existsSync(full)) return full;
1770
2069
  }
1771
2070
  }
1772
2071
  for (const cand of candidates) {
1773
- const full = path4.resolve(pkgDir, cand);
1774
- if (fs4.existsSync(full)) return full;
2072
+ const full = path5.resolve(pkgDir, cand);
2073
+ if (fs5.existsSync(full)) return full;
1775
2074
  }
1776
2075
  return null;
1777
2076
  }
@@ -1817,72 +2116,231 @@ async function injectCjsNamedExports(code, entryFile) {
1817
2116
  return code;
1818
2117
  }
1819
2118
  }
1820
- function rewriteImports(code, config, filePath) {
2119
+ function rewriteImports(code, config, filePath, importedUrls, moduleGraph) {
2120
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
2121
+ const transformSpec = (spec) => {
2122
+ const resolved = removeTimestampQuery(resolveSpec(spec));
2123
+ importedUrls?.add(resolved);
2124
+ const timestamp = moduleGraph?.getModuleByUrl(resolved)?.lastHMRTimestamp ?? 0;
2125
+ return timestamp > 0 ? appendTimestampQuery(resolved, timestamp) : resolved;
2126
+ };
2127
+ return code.replace(
2128
+ /\bfrom\s+(['"])([^'"]+)\1/g,
2129
+ (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
2130
+ ).replace(
2131
+ /\bimport\s+(['"])([^'"]+)\1/g,
2132
+ (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
2133
+ ).replace(
2134
+ /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
2135
+ (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
2136
+ );
2137
+ }
2138
+ function createModuleSpecifierResolver(config, filePath) {
1821
2139
  const root = config.root;
1822
- const fileDir = path4.dirname(filePath);
2140
+ const fileDir = path5.dirname(filePath);
1823
2141
  const aliasEntries = Object.entries(config.resolve.alias).sort(
1824
2142
  ([a], [b]) => b.length - a.length
1825
2143
  );
1826
- const toRootUrl = (abs) => "/" + path4.relative(root, abs).replace(/\\/g, "/");
1827
- const transformSpec = (spec) => {
1828
- const suffixMatch = spec.match(/[?#].*$/);
2144
+ const toRootUrl = (abs) => "/" + path5.relative(root, abs).replace(/\\/g, "/");
2145
+ return (specifier) => {
2146
+ const suffixMatch = specifier.match(/[?#].*$/);
1829
2147
  const suffix = suffixMatch ? suffixMatch[0] : "";
1830
- const baseSpec = suffix ? spec.slice(0, -suffix.length) : spec;
2148
+ const baseSpec = suffix ? specifier.slice(0, -suffix.length) : specifier;
1831
2149
  for (const [key, value] of aliasEntries) {
1832
2150
  if (baseSpec === key || baseSpec.startsWith(key + "/")) {
1833
2151
  const aliasBase = resolveAliasTarget(value, root);
1834
2152
  const sub = baseSpec.slice(key.length).replace(/^\//, "");
1835
- const target = sub ? path4.join(aliasBase, sub) : aliasBase;
2153
+ const target = sub ? path5.join(aliasBase, sub) : aliasBase;
1836
2154
  const resolved = tryResolveDiskPath(target);
1837
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
2155
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1838
2156
  }
1839
2157
  }
1840
2158
  if (baseSpec.startsWith("./") || baseSpec.startsWith("../")) {
1841
- const target = path4.resolve(fileDir, baseSpec);
1842
- const resolved = tryResolveDiskPath(target);
1843
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
2159
+ const resolved = tryResolveDiskPath(path5.resolve(fileDir, baseSpec));
2160
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1844
2161
  }
1845
2162
  if (baseSpec.startsWith("/") && !baseSpec.startsWith("/@")) {
1846
- const target = path4.join(root, baseSpec.replace(/^\//, ""));
1847
- const resolved = tryResolveDiskPath(target);
1848
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
2163
+ const resolved = tryResolveDiskPath(path5.join(root, baseSpec.replace(/^\//, "")));
2164
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1849
2165
  }
1850
- if (baseSpec.startsWith("/")) return spec;
1851
- return `/@modules/${spec}`;
2166
+ if (baseSpec.startsWith("/")) return specifier;
2167
+ return `/@modules/${specifier}`;
1852
2168
  };
1853
- return code.replace(
1854
- /\bfrom\s+(['"])([^'"]+)\1/g,
1855
- (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
1856
- ).replace(
1857
- /\bimport\s+(['"])([^'"]+)\1/g,
1858
- (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
1859
- ).replace(
1860
- /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
1861
- (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
1862
- );
2169
+ }
2170
+ function rewriteHotAcceptDeps(code, config, filePath) {
2171
+ const acceptedUrls = /* @__PURE__ */ new Set();
2172
+ const edits = [];
2173
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
2174
+ const acceptRE = /(?:\bimport\.meta\.hot|\b__nasti_hot__)(?:(?:\?\.)|\.)accept\s*\(/g;
2175
+ const searchableCode = maskStringsAndComments(code);
2176
+ let isSelfAccepting = false;
2177
+ let match;
2178
+ while (match = acceptRE.exec(searchableCode)) {
2179
+ let cursor = match.index + match[0].length;
2180
+ const skipTrivia = () => {
2181
+ while (cursor < code.length) {
2182
+ if (/\s/.test(code[cursor])) {
2183
+ cursor++;
2184
+ continue;
2185
+ }
2186
+ if (code[cursor] === "/" && code[cursor + 1] === "/") {
2187
+ cursor += 2;
2188
+ while (cursor < code.length && code[cursor] !== "\n") cursor++;
2189
+ continue;
2190
+ }
2191
+ if (code[cursor] === "/" && code[cursor + 1] === "*") {
2192
+ cursor += 2;
2193
+ while (cursor < code.length && !(code[cursor] === "*" && code[cursor + 1] === "/")) cursor++;
2194
+ cursor += 2;
2195
+ continue;
2196
+ }
2197
+ break;
2198
+ }
2199
+ };
2200
+ skipTrivia();
2201
+ const first = code[cursor];
2202
+ if (!first || first === ")" || first !== "[" && first !== "'" && first !== '"' && first !== "`") {
2203
+ isSelfAccepting = true;
2204
+ continue;
2205
+ }
2206
+ const readLiteral = () => {
2207
+ const quote = code[cursor];
2208
+ if (quote !== "'" && quote !== '"' && quote !== "`") return;
2209
+ const start = cursor;
2210
+ cursor++;
2211
+ let raw = "";
2212
+ while (cursor < code.length) {
2213
+ const char = code[cursor];
2214
+ if (char === "\\") {
2215
+ raw += code[cursor + 1] ?? "";
2216
+ cursor += 2;
2217
+ continue;
2218
+ }
2219
+ if (char === quote) {
2220
+ cursor++;
2221
+ const resolved = removeTimestampQuery(resolveSpec(raw));
2222
+ acceptedUrls.add(resolved);
2223
+ edits.push({ start, end: cursor, value: JSON.stringify(resolved) });
2224
+ return;
2225
+ }
2226
+ if (quote === "`" && char === "$" && code[cursor + 1] === "{") return;
2227
+ raw += char;
2228
+ cursor++;
2229
+ }
2230
+ };
2231
+ if (first === "[") {
2232
+ cursor++;
2233
+ while (cursor < code.length) {
2234
+ skipTrivia();
2235
+ if (code[cursor] === ",") {
2236
+ cursor++;
2237
+ skipTrivia();
2238
+ }
2239
+ if (code[cursor] === "]") break;
2240
+ const before = cursor;
2241
+ readLiteral();
2242
+ if (cursor === before) break;
2243
+ }
2244
+ } else {
2245
+ readLiteral();
2246
+ }
2247
+ }
2248
+ for (const edit of edits.sort((a, b) => b.start - a.start)) {
2249
+ code = code.slice(0, edit.start) + edit.value + code.slice(edit.end);
2250
+ }
2251
+ return { code, acceptedUrls, isSelfAccepting };
2252
+ }
2253
+ function maskStringsAndComments(code) {
2254
+ const masked = code.split("");
2255
+ let state = "code";
2256
+ const isRegexStart = (index2) => {
2257
+ let previous = index2 - 1;
2258
+ while (previous >= 0 && /\s/.test(code[previous])) previous--;
2259
+ return previous < 0 || "=(:,!&|?{};[]+-*%^~<>".includes(code[previous]);
2260
+ };
2261
+ for (let i = 0; i < code.length; i++) {
2262
+ const char = code[i];
2263
+ const next = code[i + 1];
2264
+ if (state === "code") {
2265
+ if (char === "'") state = "single";
2266
+ else if (char === '"') state = "double";
2267
+ else if (char === "`") state = "template";
2268
+ else if (char === "/" && next === "/") state = "line-comment";
2269
+ else if (char === "/" && next === "*") state = "block-comment";
2270
+ else if (char === "/" && isRegexStart(i)) state = "regex";
2271
+ else continue;
2272
+ masked[i] = " ";
2273
+ continue;
2274
+ }
2275
+ if (state === "line-comment") {
2276
+ if (char === "\n") {
2277
+ state = "code";
2278
+ } else {
2279
+ masked[i] = " ";
2280
+ }
2281
+ continue;
2282
+ }
2283
+ if (state === "block-comment") {
2284
+ masked[i] = char === "\n" ? "\n" : " ";
2285
+ if (char === "*" && next === "/") {
2286
+ masked[i + 1] = " ";
2287
+ i++;
2288
+ state = "code";
2289
+ }
2290
+ continue;
2291
+ }
2292
+ if (state === "regex" || state === "regex-class") {
2293
+ masked[i] = char === "\n" ? "\n" : " ";
2294
+ if (char === "\\") {
2295
+ if (i + 1 < code.length) masked[++i] = " ";
2296
+ } else if (state === "regex" && char === "[") {
2297
+ state = "regex-class";
2298
+ } else if (state === "regex-class" && char === "]") {
2299
+ state = "regex";
2300
+ } else if (state === "regex" && char === "/") {
2301
+ state = "code";
2302
+ }
2303
+ continue;
2304
+ }
2305
+ masked[i] = char === "\n" ? "\n" : " ";
2306
+ if (char === "\\") {
2307
+ if (i + 1 < code.length) masked[++i] = " ";
2308
+ continue;
2309
+ }
2310
+ if (state === "single" && char === "'" || state === "double" && char === '"' || state === "template" && char === "`") {
2311
+ state = "code";
2312
+ }
2313
+ }
2314
+ return masked.join("");
1863
2315
  }
1864
2316
  function resolveAliasTarget(value, root) {
1865
- if (path4.isAbsolute(value) && fs4.existsSync(value)) return value;
1866
- if (value.startsWith("/")) return path4.join(root, value.slice(1));
1867
- return path4.resolve(root, value);
2317
+ if (path5.isAbsolute(value) && fs5.existsSync(value)) return value;
2318
+ if (value.startsWith("/")) return path5.join(root, value.slice(1));
2319
+ return path5.resolve(root, value);
1868
2320
  }
1869
2321
  function tryResolveDiskPath(target) {
1870
- if (fs4.existsSync(target) && fs4.statSync(target).isFile()) return target;
2322
+ if (fs5.existsSync(target) && fs5.statSync(target).isFile()) return target;
1871
2323
  for (const ext of RESOLVE_EXTENSIONS) {
1872
2324
  const withExt = target + ext;
1873
- if (fs4.existsSync(withExt) && fs4.statSync(withExt).isFile()) return withExt;
2325
+ if (fs5.existsSync(withExt) && fs5.statSync(withExt).isFile()) return withExt;
1874
2326
  }
1875
- if (fs4.existsSync(target) && fs4.statSync(target).isDirectory()) {
2327
+ if (fs5.existsSync(target) && fs5.statSync(target).isDirectory()) {
1876
2328
  for (const ext of RESOLVE_EXTENSIONS) {
1877
- const idx = path4.join(target, "index" + ext);
1878
- if (fs4.existsSync(idx) && fs4.statSync(idx).isFile()) return idx;
2329
+ const idx = path5.join(target, "index" + ext);
2330
+ if (fs5.existsSync(idx) && fs5.statSync(idx).isFile()) return idx;
1879
2331
  }
1880
2332
  }
1881
2333
  return null;
1882
2334
  }
1883
2335
  function isUnderRoot(abs, root) {
1884
- const rel = path4.relative(root, abs);
1885
- return !!rel && !rel.startsWith("..") && !path4.isAbsolute(rel);
2336
+ const rel = path5.relative(root, abs);
2337
+ return !!rel && !rel.startsWith("..") && !path5.isAbsolute(rel);
2338
+ }
2339
+ function appendTimestampQuery(url, timestamp) {
2340
+ const hashIndex = url.indexOf("#");
2341
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
2342
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
2343
+ return `${withoutHash}${withoutHash.includes("?") ? "&" : "?"}t=${timestamp}${hash}`;
1886
2344
  }
1887
2345
  function externalSpecToModuleUrl(spec, baseDir, root) {
1888
2346
  const resolved = resolveNodeModule(baseDir, spec);
@@ -1895,7 +2353,7 @@ function resolveNodeModule(baseDir, moduleName) {
1895
2353
  const resolved = resolveNodeModuleEntry(baseDir, moduleName);
1896
2354
  if (!resolved) return null;
1897
2355
  try {
1898
- return fs4.realpathSync(resolved);
2356
+ return fs5.realpathSync(resolved);
1899
2357
  } catch {
1900
2358
  return resolved;
1901
2359
  }
@@ -1915,21 +2373,21 @@ function resolveNodeModuleEntry(root, moduleName) {
1915
2373
  let pkgDir = null;
1916
2374
  let dir = root;
1917
2375
  for (; ; ) {
1918
- const candidate = path4.join(dir, "node_modules", pkgName);
1919
- if (fs4.existsSync(candidate)) {
2376
+ const candidate = path5.join(dir, "node_modules", pkgName);
2377
+ if (fs5.existsSync(candidate)) {
1920
2378
  pkgDir = candidate;
1921
2379
  break;
1922
2380
  }
1923
- const parent = path4.dirname(dir);
2381
+ const parent = path5.dirname(dir);
1924
2382
  if (parent === dir) break;
1925
2383
  dir = parent;
1926
2384
  }
1927
2385
  if (!pkgDir) return null;
1928
- const pkgJsonPath = path4.join(pkgDir, "package.json");
1929
- if (!fs4.existsSync(pkgJsonPath)) return null;
2386
+ const pkgJsonPath = path5.join(pkgDir, "package.json");
2387
+ if (!fs5.existsSync(pkgJsonPath)) return null;
1930
2388
  let pkg;
1931
2389
  try {
1932
- pkg = JSON.parse(fs4.readFileSync(pkgJsonPath, "utf-8"));
2390
+ pkg = JSON.parse(fs5.readFileSync(pkgJsonPath, "utf-8"));
1933
2391
  } catch {
1934
2392
  return null;
1935
2393
  }
@@ -1942,32 +2400,32 @@ function resolveNodeModuleEntry(root, moduleName) {
1942
2400
  const subDirs = [""];
1943
2401
  for (const field of ["module", "main"]) {
1944
2402
  if (typeof pkg[field] === "string") {
1945
- const dir2 = path4.dirname(pkg[field]);
2403
+ const dir2 = path5.dirname(pkg[field]);
1946
2404
  if (dir2 && dir2 !== "." && !subDirs.includes(dir2)) subDirs.push(dir2);
1947
2405
  }
1948
2406
  }
1949
2407
  for (const dir2 of subDirs) {
1950
- const direct = path4.join(pkgDir, dir2, subpath);
1951
- if (fs4.existsSync(direct) && fs4.statSync(direct).isFile()) return direct;
2408
+ const direct = path5.join(pkgDir, dir2, subpath);
2409
+ if (fs5.existsSync(direct) && fs5.statSync(direct).isFile()) return direct;
1952
2410
  for (const ext of RESOLVE_EXTENSIONS) {
1953
- if (fs4.existsSync(direct + ext)) return direct + ext;
2411
+ if (fs5.existsSync(direct + ext)) return direct + ext;
1954
2412
  }
1955
2413
  }
1956
2414
  return null;
1957
2415
  }
1958
2416
  for (const field of ["module", "jsnext:main", "jsnext", "main"]) {
1959
2417
  if (typeof pkg[field] === "string") {
1960
- const entry = path4.join(pkgDir, pkg[field]);
1961
- if (fs4.existsSync(entry)) return entry;
2418
+ const entry = path5.join(pkgDir, pkg[field]);
2419
+ if (fs5.existsSync(entry)) return entry;
1962
2420
  }
1963
2421
  }
1964
- const indexFallback = path4.join(pkgDir, "index.js");
1965
- if (fs4.existsSync(indexFallback)) return indexFallback;
2422
+ const indexFallback = path5.join(pkgDir, "index.js");
2423
+ if (fs5.existsSync(indexFallback)) return indexFallback;
1966
2424
  return null;
1967
2425
  }
1968
2426
  function resolvePackageExports(exports, key, pkgDir) {
1969
2427
  if (typeof exports === "string") {
1970
- return key === "." ? path4.join(pkgDir, exports) : null;
2428
+ return key === "." ? path5.join(pkgDir, exports) : null;
1971
2429
  }
1972
2430
  const entry = exports[key];
1973
2431
  if (entry === void 0) {
@@ -1979,7 +2437,7 @@ function resolvePackageExports(exports, key, pkgDir) {
1979
2437
  return resolveExportValue(entry, pkgDir);
1980
2438
  }
1981
2439
  function resolveExportValue(value, pkgDir) {
1982
- if (typeof value === "string") return path4.join(pkgDir, value);
2440
+ if (typeof value === "string") return path5.join(pkgDir, value);
1983
2441
  if (Array.isArray(value)) {
1984
2442
  for (const item of value) {
1985
2443
  const r = resolveExportValue(item, pkgDir);
@@ -2003,54 +2461,62 @@ function resolveUrlToFile(url, root) {
2003
2461
  const moduleName = cleanUrl.slice("/@modules/".length);
2004
2462
  return resolveNodeModule(root, moduleName);
2005
2463
  }
2006
- const filePath = path4.resolve(root, cleanUrl.replace(/^\//, ""));
2007
- if (fs4.existsSync(filePath) && fs4.statSync(filePath).isFile()) {
2464
+ const filePath = path5.resolve(root, cleanUrl.replace(/^\//, ""));
2465
+ if (fs5.existsSync(filePath) && fs5.statSync(filePath).isFile()) {
2008
2466
  return filePath;
2009
2467
  }
2010
2468
  for (const ext of RESOLVE_EXTENSIONS) {
2011
2469
  const withExt = filePath + ext;
2012
- if (fs4.existsSync(withExt)) return withExt;
2470
+ if (fs5.existsSync(withExt)) return withExt;
2013
2471
  }
2014
2472
  for (const ext of RESOLVE_EXTENSIONS) {
2015
- const indexFile = path4.join(filePath, "index" + ext);
2016
- if (fs4.existsSync(indexFile)) return indexFile;
2473
+ const indexFile = path5.join(filePath, "index" + ext);
2474
+ if (fs5.existsSync(indexFile)) return indexFile;
2017
2475
  }
2018
2476
  return null;
2019
2477
  }
2020
- function isModuleRequest(url) {
2478
+ function isModuleRequest(url, destination) {
2021
2479
  const cleanUrl = url.split("?")[0];
2022
2480
  if (/\.(ts|tsx|jsx|js|mjs|vue|css|json)$/.test(cleanUrl)) return true;
2023
2481
  if (cleanUrl.startsWith("/@modules/")) return true;
2024
- if (!path4.extname(cleanUrl)) return true;
2482
+ if (isAssetFile(cleanUrl)) {
2483
+ const query = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
2484
+ const isExplicitAssetModule = /(?:^|&)(?:url|raw)(?:&|$)/.test(query);
2485
+ return isExplicitAssetModule || destination === "script";
2486
+ }
2487
+ if (!path5.extname(cleanUrl)) return true;
2025
2488
  return false;
2026
2489
  }
2027
2490
  function getHmrClientCode() {
2028
2491
  return `
2029
2492
  // Nasti HMR Client
2030
- const socket = new WebSocket(\`ws://\${location.host}\`, 'nasti-hmr');
2493
+ const socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
2494
+ const socket = new WebSocket(socketProtocol + '://' + location.host, 'nasti-hmr');
2031
2495
  const hotModulesMap = new Map();
2032
2496
  const disposeMap = new Map();
2033
2497
  const pruneMap = new Map();
2498
+ const dataMap = new Map();
2499
+ const customListenersMap = new Map();
2500
+ let updateQueue = [];
2501
+ let pendingUpdateQueue = false;
2034
2502
 
2035
2503
  socket.addEventListener('message', async ({ data }) => {
2036
2504
  const payload = JSON.parse(data);
2505
+ // \u9ED8\u8BA4\u6D4F\u89C8\u5668 client \u53EA\u6D88\u8D39\u81EA\u5DF1\u7684 HMR \u6D88\u606F\uFF1Bnative/worker \u73AF\u5883\u901A\u8FC7\u5404\u81EA\u7684
2506
+ // HotChannel \u6216 app-level HMR \u534F\u8C03\u5668\u5904\u7406\u540C\u4E00 transport \u4E0A\u7684\u547D\u540D\u6D88\u606F\u3002
2507
+ if (payload.environment && payload.environment !== 'client') return;
2037
2508
  switch (payload.type) {
2038
2509
  case 'connected':
2039
- console.log('[nasti] connected.');
2510
+ console.debug('[nasti] connected.');
2040
2511
  clearErrorOverlay();
2041
2512
  break;
2042
2513
  case 'update':
2043
2514
  try {
2044
- await Promise.all(payload.updates.map((update) => {
2045
- if (update.type === 'js-update') {
2046
- return fetchUpdate(update);
2047
- } else if (update.type === 'css-update') {
2048
- return updateCss(update.path);
2049
- }
2050
- }));
2515
+ // CSS \u5728 unbundled \u6A21\u5F0F\u4E0B\u4E5F\u662F\u4F1A\u6CE8\u5165 <style> \u7684 JS \u6A21\u5757\uFF0C\u548C\u666E\u901A JS
2516
+ // \u4E00\u6837\u91CD\u65B0 import \u624D\u80FD\u6267\u884C dispose/accept \u5E76\u4FDD\u6301\u9875\u9762\u72B6\u6001\u3002
2517
+ await Promise.all(payload.updates.map(queueUpdate));
2051
2518
  clearErrorOverlay();
2052
- console.log('[nasti] HMR update complete, reloading page');
2053
- location.reload();
2519
+ console.debug('[nasti] HMR update complete.');
2054
2520
  } catch (err) {
2055
2521
  console.error('[nasti] HMR update failed:', err);
2056
2522
  showErrorOverlay(err);
@@ -2061,11 +2527,34 @@ socket.addEventListener('message', async ({ data }) => {
2061
2527
  location.reload();
2062
2528
  break;
2063
2529
  case 'prune':
2064
- payload.paths.forEach((p) => {
2065
- const cb = pruneMap.get(p);
2066
- if (cb) cb();
2067
- });
2530
+ await Promise.all(payload.paths.map(async (path) => {
2531
+ const data = dataMap.get(path);
2532
+ const dispose = disposeMap.get(path);
2533
+ const prune = pruneMap.get(path);
2534
+ if (dispose) await dispose(data);
2535
+ if (prune) await prune(data);
2536
+ hotModulesMap.delete(path);
2537
+ disposeMap.delete(path);
2538
+ pruneMap.delete(path);
2539
+ dataMap.delete(path);
2540
+ clearCustomListeners(path);
2541
+ }));
2542
+ break;
2543
+ case 'custom': {
2544
+ const listenersByOwner = customListenersMap.get(payload.event);
2545
+ if (!listenersByOwner) break;
2546
+ const results = await Promise.allSettled(
2547
+ [...listenersByOwner.values()]
2548
+ .flatMap((listeners) => [...listeners])
2549
+ .map((listener) => Promise.resolve().then(() => listener(payload.data)))
2550
+ );
2551
+ for (const result of results) {
2552
+ if (result.status === 'rejected') {
2553
+ console.error('[nasti] custom HMR event listener failed:', result.reason);
2554
+ }
2555
+ }
2068
2556
  break;
2557
+ }
2069
2558
  case 'error':
2070
2559
  console.error('[nasti] error:', payload.err.message);
2071
2560
  showErrorOverlay(payload.err);
@@ -2073,33 +2562,64 @@ socket.addEventListener('message', async ({ data }) => {
2073
2562
  }
2074
2563
  });
2075
2564
 
2076
- // \u81EA\u52A8\u91CD\u8FDE\uFF08\u65AD\u7EBF\u65F6\u6307\u6570\u9000\u907F\uFF09
2565
+ // \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
2077
2566
  let reconnectTimer = 0;
2078
2567
  socket.addEventListener('close', () => {
2079
2568
  clearTimeout(reconnectTimer);
2080
2569
  reconnectTimer = setTimeout(() => location.reload(), 1000);
2081
2570
  });
2082
2571
 
2572
+ /**
2573
+ * \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
2574
+ * \u6539\u53D8\u6A21\u5757\u5E94\u7528\u987A\u5E8F\u3002\u8FD9\u4E0E Vite HMRClient \u7684 fetch/apply \u4E24\u9636\u6BB5\u4E00\u81F4\u3002
2575
+ */
2576
+ async function queueUpdate(update) {
2577
+ updateQueue.push(fetchUpdate(update));
2578
+ if (pendingUpdateQueue) return;
2579
+
2580
+ pendingUpdateQueue = true;
2581
+ await Promise.resolve();
2582
+ pendingUpdateQueue = false;
2583
+ const loading = updateQueue;
2584
+ updateQueue = [];
2585
+ const applyUpdates = await Promise.all(loading);
2586
+ for (const apply of applyUpdates) {
2587
+ if (apply) apply();
2588
+ }
2589
+ }
2590
+
2083
2591
  async function fetchUpdate(update) {
2084
2592
  const mod = hotModulesMap.get(update.path);
2085
- // \u5148\u8DD1 dispose\uFF08\u7ED9\u6A21\u5757\u673A\u4F1A\u6E05\u7406\u526F\u4F5C\u7528\uFF09
2086
- const dispose = disposeMap.get(update.path);
2087
- if (dispose) dispose();
2593
+ // \u5C1A\u672A\u5728\u5F53\u524D\u9875\u9762\u52A0\u8F7D\u7684\u52A8\u6001\u6A21\u5757\u4E0D\u9700\u8981\u66F4\u65B0\u3002
2594
+ if (!mod) return;
2088
2595
 
2089
- const newMod = await import(update.acceptedPath + '?t=' + update.timestamp);
2090
- if (mod) {
2091
- // \u590D\u5236\u56DE\u8C03\u6570\u7EC4\u907F\u514D\u56DE\u8C03\u5185\u90E8\u53C8\u4FEE\u6539 hotModulesMap \u9020\u6210\u8FED\u4EE3\u5F02\u5E38
2092
- [...mod.callbacks].forEach((cb) => cb(newMod));
2093
- }
2596
+ // \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
2597
+ const qualifiedCallbacks = mod.callbacks.filter(({ deps }) =>
2598
+ deps.includes(update.acceptedPath)
2599
+ );
2600
+ const isSelfUpdate = update.path === update.acceptedPath;
2601
+ if (!isSelfUpdate && qualifiedCallbacks.length === 0) return;
2602
+
2603
+ const dispose = disposeMap.get(update.acceptedPath);
2604
+ if (dispose) await dispose(dataMap.get(update.acceptedPath));
2605
+ const newMod = await import(appendTimestampQuery(update.acceptedPath, update.timestamp));
2606
+
2607
+ return () => {
2608
+ for (const { deps, fn } of qualifiedCallbacks) {
2609
+ fn(deps.map((dep) => dep === update.acceptedPath ? newMod : undefined));
2610
+ }
2611
+ const detail = isSelfUpdate
2612
+ ? update.path
2613
+ : update.acceptedPath + ' via ' + update.path;
2614
+ console.debug('[nasti] hot updated:', detail);
2615
+ };
2094
2616
  }
2095
2617
 
2096
- function updateCss(path) {
2097
- const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
2098
- if (el) {
2099
- return fetch(path + '?t=' + Date.now())
2100
- .then(r => r.text())
2101
- .then(css => { el.textContent = css; });
2102
- }
2618
+ function appendTimestampQuery(url, timestamp) {
2619
+ const hashIndex = url.indexOf('#');
2620
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : '';
2621
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
2622
+ return withoutHash + (withoutHash.includes('?') ? '&' : '?') + 't=' + timestamp + hash;
2103
2623
  }
2104
2624
 
2105
2625
  function clearErrorOverlay() {
@@ -2127,23 +2647,31 @@ function showErrorOverlay(err) {
2127
2647
  document.body.appendChild(overlay);
2128
2648
  }
2129
2649
 
2130
- /**
2131
- * \u751F\u6210 import.meta.hot \u7684 hot context\u3002
2132
- * \u5173\u952E\u7EA6\u675F\uFF1A\u540C\u4E00 ownerPath \u7684 accept \u56DE\u8C03\u5FC5\u987B\u66FF\u6362\uFF08\u4E0D\u662F append\uFF09\u3002
2133
- * \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
2134
- * \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
2135
- */
2136
2650
  export function createHotContext(ownerPath) {
2651
+ if (!dataMap.has(ownerPath)) dataMap.set(ownerPath, {});
2652
+
2653
+ // \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
2654
+ const existing = hotModulesMap.get(ownerPath);
2655
+ if (existing) existing.callbacks = [];
2656
+ clearCustomListeners(ownerPath);
2657
+
2658
+ const acceptDeps = (deps, callback = () => {}) => {
2659
+ const mod = hotModulesMap.get(ownerPath) || { id: ownerPath, callbacks: [] };
2660
+ mod.callbacks.push({ deps, fn: callback });
2661
+ hotModulesMap.set(ownerPath, mod);
2662
+ };
2663
+
2137
2664
  return {
2138
2665
  accept(deps, callback) {
2139
- // \u81EA\u63A5\u53D7: hot.accept() \u6216 hot.accept(callback)
2140
2666
  if (typeof deps === 'function' || deps === undefined) {
2141
- hotModulesMap.set(ownerPath, { callbacks: [deps || (() => {})] });
2142
- return;
2667
+ acceptDeps([ownerPath], ([mod]) => deps?.(mod));
2668
+ } else if (typeof deps === 'string') {
2669
+ acceptDeps([deps], ([mod]) => callback?.(mod));
2670
+ } else if (Array.isArray(deps)) {
2671
+ acceptDeps(deps, callback);
2672
+ } else {
2673
+ throw new Error('invalid hot.accept() usage');
2143
2674
  }
2144
- // \u4F9D\u8D56\u63A5\u53D7: hot.accept(deps, callback)\uFF0C\u591A\u6B21\u8C03\u7528\u8FFD\u52A0
2145
- const existing = hotModulesMap.get(ownerPath)?.callbacks ?? [];
2146
- hotModulesMap.set(ownerPath, { callbacks: [...existing, callback] });
2147
2675
  },
2148
2676
  prune(callback) {
2149
2677
  pruneMap.set(ownerPath, callback);
@@ -2151,24 +2679,116 @@ export function createHotContext(ownerPath) {
2151
2679
  dispose(callback) {
2152
2680
  disposeMap.set(ownerPath, callback);
2153
2681
  },
2682
+ on(event, callback) {
2683
+ let listenersByOwner = customListenersMap.get(event);
2684
+ if (!listenersByOwner) {
2685
+ listenersByOwner = new Map();
2686
+ customListenersMap.set(event, listenersByOwner);
2687
+ }
2688
+ let listeners = listenersByOwner.get(ownerPath);
2689
+ if (!listeners) {
2690
+ listeners = new Set();
2691
+ listenersByOwner.set(ownerPath, listeners);
2692
+ }
2693
+ listeners.add(callback);
2694
+ },
2695
+ off(event, callback) {
2696
+ const listenersByOwner = customListenersMap.get(event);
2697
+ const listeners = listenersByOwner?.get(ownerPath);
2698
+ listeners?.delete(callback);
2699
+ if (listeners?.size === 0) listenersByOwner.delete(ownerPath);
2700
+ if (listenersByOwner?.size === 0) customListenersMap.delete(event);
2701
+ },
2154
2702
  invalidate() {
2155
2703
  location.reload();
2156
2704
  },
2157
- data: {},
2705
+ data: dataMap.get(ownerPath),
2158
2706
  };
2159
2707
  }
2708
+
2709
+ function clearCustomListeners(ownerPath) {
2710
+ for (const [event, listenersByOwner] of customListenersMap) {
2711
+ listenersByOwner.delete(ownerPath);
2712
+ if (listenersByOwner.size === 0) customListenersMap.delete(event);
2713
+ }
2714
+ }
2160
2715
  `;
2161
2716
  }
2162
- var __dirname_esm, __require2, __refreshRuntimeCache, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
2717
+ var __dirname_esm, __require2, __refreshRuntimeCache, REACT_REFRESH_BOUNDARY_HELPERS, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
2163
2718
  var init_middleware = __esm({
2164
2719
  "src/server/middleware.ts"() {
2165
2720
  "use strict";
2166
2721
  init_transformer();
2167
2722
  init_html();
2168
2723
  init_env();
2169
- __dirname_esm = path4.dirname(fileURLToPath(import.meta.url));
2724
+ init_url();
2725
+ init_assets();
2726
+ __dirname_esm = path5.dirname(fileURLToPath(import.meta.url));
2170
2727
  __require2 = createRequire(import.meta.url);
2171
2728
  __refreshRuntimeCache = null;
2729
+ REACT_REFRESH_BOUNDARY_HELPERS = `
2730
+ function __nastiIsPlainObject(obj) {
2731
+ return Object.prototype.toString.call(obj) === '[object Object]' &&
2732
+ (obj.constructor === Object || obj.constructor === undefined);
2733
+ }
2734
+ function __nastiIsCompoundComponent(type) {
2735
+ if (!__nastiIsPlainObject(type)) return false;
2736
+ for (const key in type) {
2737
+ if (!isLikelyComponentType(type[key])) return false;
2738
+ }
2739
+ return true;
2740
+ }
2741
+ export function registerExportsForReactRefresh(filename, moduleExports) {
2742
+ for (const key in moduleExports) {
2743
+ if (key === '__esModule') continue;
2744
+ const value = moduleExports[key];
2745
+ if (isLikelyComponentType(value)) {
2746
+ register(value, filename + ' export ' + key);
2747
+ } else if (__nastiIsCompoundComponent(value)) {
2748
+ for (const subKey in value) {
2749
+ register(value[subKey], filename + ' export ' + key + '-' + subKey);
2750
+ }
2751
+ }
2752
+ }
2753
+ }
2754
+ let __nastiRefreshTimer;
2755
+ function __nastiEnqueueRefresh() {
2756
+ clearTimeout(__nastiRefreshTimer);
2757
+ __nastiRefreshTimer = setTimeout(() => performReactRefresh(), 16);
2758
+ }
2759
+ function __nastiCheckExports(ignored, exports, predicate) {
2760
+ for (const key in exports) {
2761
+ if (ignored.includes(key)) continue;
2762
+ if (!predicate(key, exports[key])) return key;
2763
+ }
2764
+ return true;
2765
+ }
2766
+ export function validateRefreshBoundaryAndEnqueueUpdate(id, prevExports, nextExports) {
2767
+ const ignored = window.__getReactRefreshIgnoredExports?.({ id }) ?? [];
2768
+ if (__nastiCheckExports(ignored, prevExports, (key) => key in nextExports) !== true) {
2769
+ return 'Could not Fast Refresh (export removed)';
2770
+ }
2771
+ if (__nastiCheckExports(ignored, nextExports, (key) => key in prevExports) !== true) {
2772
+ return 'Could not Fast Refresh (new export)';
2773
+ }
2774
+ let hasExports = false;
2775
+ const compatible = __nastiCheckExports(ignored, nextExports, (key, value) => {
2776
+ hasExports = true;
2777
+ return isLikelyComponentType(value) ||
2778
+ __nastiIsCompoundComponent(value) ||
2779
+ prevExports[key] === value;
2780
+ });
2781
+ if (!hasExports) {
2782
+ return 'Could not Fast Refresh (no exports)';
2783
+ }
2784
+ if (compatible === true) {
2785
+ __nastiEnqueueRefresh();
2786
+ return;
2787
+ }
2788
+ return 'Could not Fast Refresh ("' + compatible + '" export is incompatible)';
2789
+ }
2790
+ export const __hmr_import = (module) => import(module);
2791
+ `;
2172
2792
  REACT_REFRESH_GLOBAL_PREAMBLE = `
2173
2793
  import RefreshRuntime from "/@react-refresh";
2174
2794
  RefreshRuntime.injectIntoGlobalHook(window);
@@ -2184,31 +2804,40 @@ window.__vite_plugin_react_preamble_installed__ = true;
2184
2804
  });
2185
2805
 
2186
2806
  // src/server/hmr.ts
2187
- import path5 from "path";
2188
- import fs5 from "fs";
2807
+ import path6 from "path";
2808
+ import fs6 from "fs";
2189
2809
  import pc4 from "picocolors";
2190
- async function handleFileChange(file, server) {
2191
- const { moduleGraph, ws, config } = server;
2810
+ async function handleFileChange(file, server, environmentName = "client", timestamp = Date.now()) {
2811
+ const { config } = server;
2812
+ const environment = server.environments[environmentName];
2813
+ if (!environment) {
2814
+ throw new Error(`[nasti] unknown dev environment "${environmentName}"`);
2815
+ }
2816
+ const moduleGraph = environment.moduleGraph;
2192
2817
  const logger = config.logger;
2193
- const relativePath = "/" + path5.relative(config.root, file);
2194
- const shortFile = path5.relative(config.root, file);
2818
+ const relativePath = "/" + path6.relative(config.root, file);
2819
+ const shortFile = path6.relative(config.root, file);
2195
2820
  const mods = moduleGraph.getModulesByFile(file);
2196
2821
  if (!mods || mods.size === 0) {
2197
- return;
2822
+ return null;
2198
2823
  }
2199
2824
  const updates = [];
2200
- const timestamp = Date.now();
2825
+ const graph = moduleGraph;
2826
+ const invalidatedModules = /* @__PURE__ */ new Set();
2827
+ const affectedSet = /* @__PURE__ */ new Set();
2828
+ let fullReload = false;
2201
2829
  for (const mod of mods) {
2202
- moduleGraph.invalidateModule(mod);
2830
+ graph.invalidateModuleAndImporters(mod, timestamp, invalidatedModules);
2203
2831
  const ctx = {
2204
2832
  file,
2205
2833
  timestamp,
2206
2834
  modules: [mod],
2207
- read: () => fs5.readFileSync(file, "utf-8"),
2208
- server
2835
+ read: () => fs6.readFileSync(file, "utf-8"),
2836
+ server,
2837
+ environment
2209
2838
  };
2210
2839
  let affectedModules = [mod];
2211
- for (const plugin of config.plugins) {
2840
+ for (const plugin of environment.plugins) {
2212
2841
  if (plugin.handleHotUpdate) {
2213
2842
  const result = await plugin.handleHotUpdate(ctx);
2214
2843
  if (result) {
@@ -2217,29 +2846,52 @@ async function handleFileChange(file, server) {
2217
2846
  }
2218
2847
  }
2219
2848
  for (const affected of affectedModules) {
2220
- const boundaries = moduleGraph.getHmrBoundaries(affected);
2849
+ affectedSet.add(affected);
2850
+ graph.invalidateModuleAndImporters(affected, timestamp, invalidatedModules);
2851
+ const boundaries = graph.getHmrBoundaries(affected);
2221
2852
  if (boundaries.length === 0) {
2222
- logger.info(pc4.green("page reload ") + pc4.dim(shortFile), { timestamp: true });
2223
- ws.send({ type: "full-reload", path: relativePath });
2224
- return;
2853
+ fullReload = true;
2854
+ continue;
2225
2855
  }
2226
- for (const { boundary } of boundaries) {
2227
- updates.push({
2856
+ for (const { boundary, acceptedVia } of boundaries) {
2857
+ const update = {
2228
2858
  type: boundary.type === "css" ? "css-update" : "js-update",
2229
2859
  path: boundary.url,
2230
- acceptedPath: affected.url,
2860
+ acceptedPath: acceptedVia.url,
2231
2861
  timestamp
2232
- });
2862
+ };
2863
+ if (!updates.some(
2864
+ (existing) => existing.type === update.type && existing.path === update.path && existing.acceptedPath === update.acceptedPath
2865
+ )) {
2866
+ updates.push(update);
2867
+ }
2233
2868
  }
2234
2869
  }
2235
2870
  }
2236
- if (updates.length > 0) {
2871
+ const transformed = await Promise.all(
2872
+ [...affectedSet].map(async (module) => ({
2873
+ module,
2874
+ result: await environment.transformRequest(module.url)
2875
+ }))
2876
+ );
2877
+ const logPrefix = environmentName === "client" ? "" : `[${environmentName}] `;
2878
+ if (fullReload) {
2879
+ logger.info(pc4.green(`${logPrefix}reload `) + pc4.dim(shortFile), { timestamp: true });
2880
+ environment.hot.send({ type: "full-reload", path: relativePath });
2881
+ } else if (updates.length > 0) {
2237
2882
  logger.info(
2238
- updates.map((u) => pc4.green("hmr update ") + pc4.dim(u.path)).join("\n"),
2883
+ updates.map((u) => pc4.green(`${logPrefix}hmr update `) + pc4.dim(u.path)).join("\n"),
2239
2884
  { timestamp: true }
2240
2885
  );
2241
- ws.send({ type: "update", updates });
2886
+ environment.hot.send({ type: "update", updates });
2242
2887
  }
2888
+ return {
2889
+ environment,
2890
+ modules: [...affectedSet],
2891
+ updates,
2892
+ transformed,
2893
+ fullReload
2894
+ };
2243
2895
  }
2244
2896
  var init_hmr = __esm({
2245
2897
  "src/server/hmr.ts"() {
@@ -2248,12 +2900,12 @@ var init_hmr = __esm({
2248
2900
  });
2249
2901
 
2250
2902
  // src/plugins/resolve.ts
2251
- import path6 from "path";
2252
- import fs6 from "fs";
2903
+ import path7 from "path";
2904
+ import fs7 from "fs";
2253
2905
  import { createRequire as createRequire2 } from "module";
2254
2906
  function resolvePlugin(config) {
2255
2907
  const { alias, extensions } = config.resolve;
2256
- const require2 = createRequire2(path6.resolve(config.root, "package.json"));
2908
+ const require2 = createRequire2(path7.resolve(config.root, "package.json"));
2257
2909
  const aliasEntries = Object.entries(alias).sort(
2258
2910
  ([a], [b]) => b.length - a.length
2259
2911
  );
@@ -2261,10 +2913,10 @@ function resolvePlugin(config) {
2261
2913
  if (config.framework === "vue") {
2262
2914
  try {
2263
2915
  const vuePkgJson = require2.resolve("vue/package.json", { paths: [config.root] });
2264
- const vueDir = path6.dirname(vuePkgJson);
2265
- const mod = JSON.parse(fs6.readFileSync(vuePkgJson, "utf-8")).module;
2266
- const entry = path6.join(vueDir, mod ?? "dist/vue.runtime.esm-bundler.js");
2267
- if (fs6.existsSync(entry)) vueRuntimeEntry = entry;
2916
+ const vueDir = path7.dirname(vuePkgJson);
2917
+ const mod = JSON.parse(fs7.readFileSync(vuePkgJson, "utf-8")).module;
2918
+ const entry = path7.join(vueDir, mod ?? "dist/vue.runtime.esm-bundler.js");
2919
+ if (fs7.existsSync(entry)) vueRuntimeEntry = entry;
2268
2920
  } catch {
2269
2921
  }
2270
2922
  }
@@ -2276,32 +2928,33 @@ function resolvePlugin(config) {
2276
2928
  if (source === key || source.startsWith(key + "/")) {
2277
2929
  const aliasBase = resolveAliasTarget2(value, config.root);
2278
2930
  const sub = source.slice(key.length).replace(/^\//, "");
2279
- const target = sub ? path6.join(aliasBase, sub) : aliasBase;
2931
+ const target = sub ? path7.join(aliasBase, sub) : aliasBase;
2280
2932
  const resolved = tryResolveFile(target, extensions);
2281
2933
  if (resolved) return resolved;
2282
2934
  break;
2283
2935
  }
2284
2936
  }
2285
2937
  if (source.startsWith("/") && !source.startsWith("//")) {
2286
- const rootRelative = path6.join(config.root, source.slice(1));
2938
+ const rootRelative = path7.join(config.root, source.slice(1));
2287
2939
  const resolved = tryResolveFile(rootRelative, extensions);
2288
2940
  if (resolved) return resolved;
2289
2941
  }
2290
- if (path6.isAbsolute(source) && fs6.existsSync(source)) {
2942
+ if (path7.isAbsolute(source) && fs7.existsSync(source)) {
2291
2943
  const resolved = tryResolveFile(source, extensions);
2292
2944
  if (resolved) return resolved;
2293
2945
  }
2294
2946
  if (source.startsWith(".")) {
2295
- const dir = importer ? path6.dirname(importer) : config.root;
2296
- const absolute = path6.resolve(dir, source);
2947
+ const dir = importer ? path7.dirname(importer) : config.root;
2948
+ const absolute = path7.resolve(dir, source);
2297
2949
  const resolved = tryResolveFile(absolute, extensions);
2298
2950
  if (resolved) return resolved;
2299
2951
  }
2300
2952
  if (!source.startsWith("/") && !source.startsWith(".")) {
2301
2953
  if (vueRuntimeEntry && source === "vue") return vueRuntimeEntry;
2954
+ if (config.command === "build") return null;
2302
2955
  try {
2303
2956
  const resolved = require2.resolve(source, {
2304
- paths: [importer ? path6.dirname(importer) : config.root]
2957
+ paths: [importer ? path7.dirname(importer) : config.root]
2305
2958
  });
2306
2959
  return resolved;
2307
2960
  } catch {
@@ -2312,34 +2965,34 @@ function resolvePlugin(config) {
2312
2965
  },
2313
2966
  load(id) {
2314
2967
  if (id.startsWith("\0")) return null;
2315
- if (!fs6.existsSync(id)) return null;
2968
+ if (!fs7.existsSync(id)) return null;
2316
2969
  if (id.endsWith(".json")) {
2317
- const content = fs6.readFileSync(id, "utf-8");
2970
+ const content = fs7.readFileSync(id, "utf-8");
2318
2971
  return `export default ${content}`;
2319
2972
  }
2320
- return fs6.readFileSync(id, "utf-8");
2973
+ return null;
2321
2974
  }
2322
2975
  };
2323
2976
  }
2324
2977
  function resolveAliasTarget2(value, root) {
2325
- if (path6.isAbsolute(value) && fs6.existsSync(value)) return value;
2326
- if (value.startsWith("/")) return path6.join(root, value.slice(1));
2327
- return path6.resolve(root, value);
2978
+ if (path7.isAbsolute(value) && fs7.existsSync(value)) return value;
2979
+ if (value.startsWith("/")) return path7.join(root, value.slice(1));
2980
+ return path7.resolve(root, value);
2328
2981
  }
2329
2982
  function tryResolveFile(file, extensions) {
2330
- if (fs6.existsSync(file) && fs6.statSync(file).isFile()) {
2983
+ if (fs7.existsSync(file) && fs7.statSync(file).isFile()) {
2331
2984
  return file;
2332
2985
  }
2333
2986
  for (const ext of extensions) {
2334
2987
  const withExt = file + ext;
2335
- if (fs6.existsSync(withExt) && fs6.statSync(withExt).isFile()) {
2988
+ if (fs7.existsSync(withExt) && fs7.statSync(withExt).isFile()) {
2336
2989
  return withExt;
2337
2990
  }
2338
2991
  }
2339
- if (fs6.existsSync(file) && fs6.statSync(file).isDirectory()) {
2992
+ if (fs7.existsSync(file) && fs7.statSync(file).isDirectory()) {
2340
2993
  for (const ext of extensions) {
2341
- const indexFile = path6.join(file, "index" + ext);
2342
- if (fs6.existsSync(indexFile)) {
2994
+ const indexFile = path7.join(file, "index" + ext);
2995
+ if (fs7.existsSync(indexFile)) {
2343
2996
  return indexFile;
2344
2997
  }
2345
2998
  }
@@ -2387,27 +3040,27 @@ var require_process = __commonJS({
2387
3040
  var require_filesystem = __commonJS({
2388
3041
  "node_modules/detect-libc/lib/filesystem.js"(exports, module) {
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, module]) => [id, { ...module }])
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
  }
@@ -3284,7 +3956,7 @@ var init_css_engine = __esm({
3284
3956
  });
3285
3957
 
3286
3958
  // src/plugins/tailwind.ts
3287
- import path7 from "path";
3959
+ import path8 from "path";
3288
3960
  import { createRequire as createRequire3 } from "module";
3289
3961
  import { pathToFileURL as pathToFileURL3 } from "url";
3290
3962
  function hasTailwindDirectives(css) {
@@ -3294,7 +3966,7 @@ function hasTailwindDirectives(css) {
3294
3966
  }
3295
3967
  async function loadTailwind(projectRoot) {
3296
3968
  if (cached && cachedRoot === projectRoot) return cached;
3297
- const req = createRequire3(path7.join(projectRoot, "package.json"));
3969
+ const req = createRequire3(path8.join(projectRoot, "package.json"));
3298
3970
  let nodePath;
3299
3971
  let oxidePath;
3300
3972
  try {
@@ -3315,7 +3987,7 @@ async function compileTailwind(css, fromFile, projectRoot) {
3315
3987
  const { node, oxide } = await loadTailwind(projectRoot);
3316
3988
  const dependencies = [];
3317
3989
  const compiler2 = await node.compile(css, {
3318
- base: path7.dirname(fromFile),
3990
+ base: path8.dirname(fromFile),
3319
3991
  from: fromFile,
3320
3992
  onDependency: (p) => dependencies.push(p)
3321
3993
  });
@@ -3337,7 +4009,8 @@ var init_tailwind = __esm({
3337
4009
  });
3338
4010
 
3339
4011
  // src/plugins/css.ts
3340
- import path8 from "path";
4012
+ import path9 from "path";
4013
+ import { SourceMapGenerator } from "source-map-js";
3341
4014
  function cssPlugin(config, engine, consumer = "client") {
3342
4015
  return {
3343
4016
  name: "nasti:css",
@@ -3357,15 +4030,29 @@ function cssPlugin(config, engine, consumer = "client") {
3357
4030
  }
3358
4031
  const rewritten = rewriteCssUrls(cssSource, file, config.root);
3359
4032
  const escaped = JSON.stringify(rewritten);
4033
+ const normalizedId = normalizeCssModuleId(id);
4034
+ const cssModule = { id: normalizedId, source: code, code: rewritten };
4035
+ const map = config.build.sourcemap ? createIdentitySourceMap(code, id) : void 0;
4036
+ engine?.modules.set(normalizedId, cssModule);
4037
+ this.environment?.setCssModule?.(cssModule);
3360
4038
  if (query === "inline") {
3361
4039
  return { code: `export default ${escaped};
3362
- `, moduleType: "js" };
4040
+ `, map, moduleType: "js" };
3363
4041
  }
3364
4042
  if (consumer === "server") {
3365
4043
  return { code: `export default ${escaped};
3366
- `, moduleType: "js" };
4044
+ `, map, moduleType: "js" };
3367
4045
  }
3368
4046
  if (config.command === "serve") {
4047
+ if (config.build.css.inject === false) {
4048
+ return {
4049
+ code: `export default ${escaped};
4050
+ `,
4051
+ map,
4052
+ moduleType: "js",
4053
+ moduleSideEffects: "no-treeshake"
4054
+ };
4055
+ }
3369
4056
  return {
3370
4057
  code: `
3371
4058
  const css = ${escaped};
@@ -3389,16 +4076,18 @@ if (import.meta.hot) {
3389
4076
 
3390
4077
  export default css;
3391
4078
  `,
4079
+ map,
3392
4080
  // bundled dev(DevEngine)下该模块会进 Rolldown:不标 js 会按 .css
3393
4081
  // 扩展名走 CSS 管线触发 #4271 报错;unbundled 中间件忽略此字段
3394
4082
  moduleType: "js"
3395
4083
  };
3396
4084
  }
3397
4085
  if (engine) {
3398
- engine.styles.set(normalizeCssModuleId(id), rewritten);
4086
+ engine.styles.set(normalizedId, rewritten);
3399
4087
  return {
3400
4088
  code: `export default '';
3401
4089
  `,
4090
+ map,
3402
4091
  moduleType: "js",
3403
4092
  // 防止空 stub 被 tree-shake 出 chunk.moduleIds(css-post 靠它定位)
3404
4093
  moduleSideEffects: "no-treeshake"
@@ -3418,18 +4107,34 @@ document.head.appendChild(style);
3418
4107
 
3419
4108
  export default css;
3420
4109
  `,
4110
+ map,
3421
4111
  moduleType: "js"
3422
4112
  };
3423
4113
  }
3424
4114
  };
3425
4115
  }
4116
+ function createIdentitySourceMap(code, id) {
4117
+ const map = new SourceMapGenerator({ file: id });
4118
+ const lines = code.split("\n");
4119
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
4120
+ for (let column = 0; column <= lines[lineIndex].length; column++) {
4121
+ map.addMapping({
4122
+ generated: { line: lineIndex + 1, column },
4123
+ original: { line: lineIndex + 1, column },
4124
+ source: id
4125
+ });
4126
+ }
4127
+ }
4128
+ map.setSourceContent(id, code);
4129
+ return map.toJSON();
4130
+ }
3426
4131
  function rewriteCssUrls(css, from, root) {
3427
4132
  return css.replace(/url\(\s*['"]?([^'")\s]+)['"]?\s*\)/g, (match, url) => {
3428
4133
  if (url.startsWith("/") || url.startsWith("data:") || url.startsWith("http")) {
3429
4134
  return match;
3430
4135
  }
3431
- const resolved = path8.resolve(path8.dirname(from), url);
3432
- const relative = "/" + path8.relative(root, resolved).replace(/\\/g, "/");
4136
+ const resolved = path9.resolve(path9.dirname(from), url);
4137
+ const relative = "/" + path9.relative(root, resolved).replace(/\\/g, "/");
3433
4138
  return `url(${relative})`;
3434
4139
  });
3435
4140
  }
@@ -3445,19 +4150,27 @@ var init_css = __esm({
3445
4150
  function collectChunkCss(chunk, engine) {
3446
4151
  const ids = chunk.moduleIds ?? Object.keys(chunk.modules);
3447
4152
  let css = "";
4153
+ const moduleIds = [];
3448
4154
  for (const id of ids) {
3449
- const styles = engine.styles.get(normalizeCssModuleId(id));
3450
- if (styles) css += styles + "\n";
4155
+ const normalizedId = normalizeCssModuleId(id);
4156
+ const styles = engine.styles.get(normalizedId);
4157
+ if (styles) {
4158
+ css += styles + "\n";
4159
+ moduleIds.push(normalizedId);
4160
+ }
3451
4161
  }
3452
- return css;
4162
+ return { css, moduleIds };
3453
4163
  }
3454
4164
  function cssPostPlugin(config, engine) {
3455
4165
  return {
3456
4166
  name: "nasti:css-post",
3457
4167
  enforce: "post",
3458
4168
  async renderChunk(code, chunk) {
3459
- const css = collectChunkCss(chunk, engine);
4169
+ const { css, moduleIds } = collectChunkCss(chunk, engine);
3460
4170
  if (!css) return null;
4171
+ const ownership = { moduleIds, cssFileNames: [] };
4172
+ engine.chunks.set(chunk.fileName, ownership);
4173
+ if (config.build.css.emit === false) return null;
3461
4174
  if (!config.build.cssCodeSplit) {
3462
4175
  engine.pendingSingle.push(css);
3463
4176
  return null;
@@ -3470,6 +4183,7 @@ function cssPostPlugin(config, engine) {
3470
4183
  });
3471
4184
  const fileName = this.getFileName(ref);
3472
4185
  engine.allCss.push(fileName);
4186
+ ownership.cssFileNames.push(fileName);
3473
4187
  if (chunk.isEntry) {
3474
4188
  const key = chunk.facadeModuleId ?? chunk.name;
3475
4189
  const existing = engine.entryCss.get(key) ?? [];
@@ -3477,105 +4191,44 @@ function cssPostPlugin(config, engine) {
3477
4191
  engine.entryCss.set(key, existing);
3478
4192
  return null;
3479
4193
  }
4194
+ if (config.build.css.inject === false) return null;
3480
4195
  const href = JSON.stringify(config.base + fileName);
3481
4196
  const snippet = `
3482
4197
  ;(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){}})();`;
3483
4198
  return { code: code + snippet, map: null };
3484
4199
  },
3485
4200
  augmentChunkHash(chunk) {
3486
- const css = collectChunkCss(chunk, engine);
4201
+ const { css } = collectChunkCss(chunk, engine);
3487
4202
  return css || void 0;
3488
4203
  },
3489
4204
  async generateBundle() {
3490
4205
  if (config.build.cssCodeSplit || engine.pendingSingle.length === 0) return;
3491
- const merged = engine.pendingSingle.join("\n");
3492
- const finalCss = config.build.cssMinify ? await minifyCss(merged, config) : merged;
3493
- const ref = this.emitFile({ type: "asset", name: "style.css", source: finalCss });
3494
- const fileName = this.getFileName(ref);
3495
- engine.singleFileName = fileName;
3496
- engine.allCss.push(fileName);
3497
- }
3498
- };
3499
- }
3500
- var init_css_post = __esm({
3501
- "src/plugins/css-post.ts"() {
3502
- "use strict";
3503
- init_css_engine();
3504
- }
3505
- });
3506
-
3507
- // src/plugins/assets.ts
3508
- import path9 from "path";
3509
- import fs7 from "fs";
3510
- import crypto from "crypto";
3511
- function assetsPlugin(config) {
3512
- return {
3513
- name: "nasti:assets",
3514
- resolveId(source) {
3515
- if (source.endsWith("?url") || source.endsWith("?raw")) {
3516
- return source;
3517
- }
3518
- return null;
3519
- },
3520
- load(id) {
3521
- const ext = path9.extname(id.replace(/\?.*$/, ""));
3522
- if (id.endsWith("?raw")) {
3523
- const file = id.slice(0, -4);
3524
- if (fs7.existsSync(file)) {
3525
- const content = fs7.readFileSync(file, "utf-8");
3526
- return `export default ${JSON.stringify(content)}`;
3527
- }
3528
- }
3529
- if (id.endsWith("?url") || ASSET_EXTENSIONS.has(ext)) {
3530
- const file = id.replace(/\?.*$/, "");
3531
- if (!fs7.existsSync(file)) return null;
3532
- if (config.command === "serve") {
3533
- const url = "/" + path9.relative(config.root, file);
3534
- return `export default ${JSON.stringify(url)}`;
3535
- }
3536
- const content = fs7.readFileSync(file);
3537
- const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 8);
3538
- const basename = path9.basename(file, ext);
3539
- const hashedName = `${config.build.assetsDir}/${basename}.${hash}${ext}`;
3540
- return `export default ${JSON.stringify(config.base + hashedName)}`;
3541
- }
3542
- return null;
3543
- }
3544
- };
3545
- }
3546
- var ASSET_EXTENSIONS;
3547
- var init_assets = __esm({
3548
- "src/plugins/assets.ts"() {
3549
- "use strict";
3550
- ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
3551
- ".png",
3552
- ".jpg",
3553
- ".jpeg",
3554
- ".gif",
3555
- ".svg",
3556
- ".ico",
3557
- ".webp",
3558
- ".avif",
3559
- ".mp4",
3560
- ".webm",
3561
- ".ogg",
3562
- ".mp3",
3563
- ".wav",
3564
- ".flac",
3565
- ".aac",
3566
- ".woff",
3567
- ".woff2",
3568
- ".eot",
3569
- ".ttf",
3570
- ".otf",
3571
- ".pdf",
3572
- ".txt"
3573
- ]);
4206
+ const merged = engine.pendingSingle.join("\n");
4207
+ const finalCss = config.build.cssMinify ? await minifyCss(merged, config) : merged;
4208
+ const ref = this.emitFile({ type: "asset", name: "style.css", source: finalCss });
4209
+ const fileName = this.getFileName(ref);
4210
+ engine.singleFileName = fileName;
4211
+ engine.allCss.push(fileName);
4212
+ for (const ownership of engine.chunks.values()) {
4213
+ if (ownership.moduleIds.length > 0) ownership.cssFileNames.push(fileName);
4214
+ }
4215
+ }
4216
+ };
4217
+ }
4218
+ var init_css_post = __esm({
4219
+ "src/plugins/css-post.ts"() {
4220
+ "use strict";
4221
+ init_css_engine();
3574
4222
  }
3575
4223
  });
3576
4224
 
3577
4225
  // src/plugins/vue.ts
3578
4226
  import crypto2 from "crypto";
4227
+ import {
4228
+ SourceMapConsumer,
4229
+ SourceMapGenerator as SourceMapGenerator2,
4230
+ SourceNode
4231
+ } from "source-map-js";
3579
4232
  async function loadVueCompiler() {
3580
4233
  if (compiler) return compiler;
3581
4234
  try {
@@ -3585,9 +4238,10 @@ async function loadVueCompiler() {
3585
4238
  return null;
3586
4239
  }
3587
4240
  }
3588
- function vuePlugin(config) {
4241
+ function vuePlugin(config, environmentName = "client") {
3589
4242
  const isDev = config.command === "serve";
3590
4243
  const descriptorCache = /* @__PURE__ */ new Map();
4244
+ const vueOptions = config.environments[environmentName]?.vue ?? {};
3591
4245
  return {
3592
4246
  name: "nasti:vue",
3593
4247
  enforce: "pre",
@@ -3607,32 +4261,63 @@ function vuePlugin(config) {
3607
4261
  const sfc = await loadVueCompiler();
3608
4262
  if (!sfc) return null;
3609
4263
  const [, filePath, indexStr] = match;
3610
- let descriptor = descriptorCache.get(filePath);
3611
- if (!descriptor) {
4264
+ let cached2 = descriptorCache.get(filePath);
4265
+ if (!cached2) {
3612
4266
  try {
3613
- const fs12 = await import("fs");
3614
- const source = fs12.readFileSync(filePath, "utf-8");
3615
- const parsed = sfc.parse(source, { filename: filePath });
4267
+ const fs13 = await import("fs");
4268
+ const rawSource = fs13.readFileSync(filePath, "utf-8");
4269
+ const transformedSfc = await applySourceTransform(
4270
+ vueOptions.transformSfc,
4271
+ rawSource,
4272
+ { filename: filePath, environmentName, type: "sfc" }
4273
+ );
4274
+ const parsed = sfc.parse(transformedSfc.code, {
4275
+ ...vueOptions.parse,
4276
+ filename: filePath,
4277
+ sourceMap: true
4278
+ });
3616
4279
  if (parsed.errors.length) return null;
3617
- descriptor = parsed.descriptor;
3618
- descriptorCache.set(filePath, descriptor);
4280
+ cached2 = {
4281
+ descriptor: parsed.descriptor,
4282
+ sourceMap: transformedSfc.map
4283
+ };
4284
+ descriptorCache.set(filePath, cached2);
3619
4285
  } catch {
3620
4286
  return null;
3621
4287
  }
3622
4288
  }
4289
+ const { descriptor, sourceMap: sfcSourceMap } = cached2;
3623
4290
  const index2 = parseInt(indexStr ?? "0", 10);
3624
4291
  const style = descriptor.styles[index2];
3625
4292
  if (!style) return null;
3626
4293
  const scopeId = hashId(filePath);
4294
+ const transformedStyle = await applySourceTransform(
4295
+ vueOptions.transformStyle,
4296
+ style.content,
4297
+ { filename: filePath, environmentName, type: "style", index: index2 }
4298
+ );
4299
+ const wantsStyleSourceMap = !!config.build.sourcemap || transformedStyle.map != null || sfcSourceMap != null;
4300
+ const styleInputMap = wantsStyleSourceMap ? composeSourceMapChain(
4301
+ [transformedStyle.map, style.map, sfcSourceMap],
4302
+ { filename: filePath, environmentName, type: "style", index: index2 }
4303
+ ) : void 0;
3627
4304
  const result = await sfc.compileStyleAsync({
3628
- source: style.content,
4305
+ ...vueOptions.style,
4306
+ source: transformedStyle.code,
3629
4307
  filename: filePath,
3630
4308
  id: `data-v-${scopeId}`,
3631
4309
  scoped: style.scoped ?? false,
4310
+ inMap: styleInputMap,
3632
4311
  // <style lang="scss|less|stylus"> 需经对应预处理器(缺省 undefined = 纯 CSS)
3633
4312
  preprocessLang: style.lang
3634
4313
  });
3635
- return result.code;
4314
+ if (transformedStyle.map != null && result.map == null) {
4315
+ warnUnchainableMap(
4316
+ { filename: filePath, environmentName, type: "style", index: index2 },
4317
+ "compiler-sfc did not return a style map"
4318
+ );
4319
+ }
4320
+ return wantsStyleSourceMap ? { code: result.code, map: result.map } : result.code;
3636
4321
  },
3637
4322
  async transform(code, id) {
3638
4323
  if (!VUE_FILE_RE.test(id) && !VUE_QUERY_RE.test(id)) return null;
@@ -3644,57 +4329,144 @@ function vuePlugin(config) {
3644
4329
  if (VUE_QUERY_RE.test(id)) {
3645
4330
  return null;
3646
4331
  }
3647
- const { descriptor, errors } = sfc.parse(code, { filename: id });
4332
+ const transformedSfc = await applySourceTransform(
4333
+ vueOptions.transformSfc,
4334
+ code,
4335
+ { filename: id, environmentName, type: "sfc" }
4336
+ );
4337
+ code = transformedSfc.code;
4338
+ const { descriptor, errors } = sfc.parse(code, {
4339
+ ...vueOptions.parse,
4340
+ filename: id,
4341
+ sourceMap: true
4342
+ });
3648
4343
  if (errors.length) {
3649
- console.error(`[nasti:vue] Parse error in ${id}:`, errors[0].message);
4344
+ const firstError = errors[0];
4345
+ console.error(
4346
+ `[nasti:vue] Parse error in ${id}:`,
4347
+ typeof firstError === "string" ? firstError : firstError.message
4348
+ );
3650
4349
  return null;
3651
4350
  }
3652
- descriptorCache.set(id, descriptor);
4351
+ descriptorCache.set(id, {
4352
+ descriptor,
4353
+ sourceMap: transformedSfc.map
4354
+ });
3653
4355
  const scopeId = hashId(id);
4356
+ const wantsSourceMap = !!config.build.sourcemap || transformedSfc.map != null;
3654
4357
  let scriptCode = "";
4358
+ let scriptMap;
3655
4359
  if (descriptor.script || descriptor.scriptSetup) {
4360
+ const inlineTemplate = vueOptions.script?.inlineTemplate !== false;
3656
4361
  const compiled = sfc.compileScript(descriptor, {
4362
+ ...vueOptions.script,
3657
4363
  id: scopeId,
3658
4364
  isProd: !isDev,
3659
- inlineTemplate: true,
4365
+ inlineTemplate,
4366
+ sourceMap: wantsSourceMap,
3660
4367
  // 让 compileScript 产出 `const __sfc__ = ...`(而非默认的 `export default {...}`)。
3661
4368
  // 否则下方追加的 `__sfc__.render` / `__sfc__.__scopeId` / HMR 记录会引用一个
3662
4369
  // 不存在的 `__sfc__`,并与 compileScript 自带的 `export default` 形成双重默认导出。
3663
4370
  genDefaultAs: "__sfc__"
3664
4371
  });
3665
4372
  scriptCode = compiled.content;
4373
+ scriptMap = composeSourceMapChain(
4374
+ [compiled.map, transformedSfc.map],
4375
+ { filename: id, environmentName, type: "sfc" }
4376
+ );
4377
+ if (transformedSfc.map != null && scriptMap == null) {
4378
+ warnUnchainableMap(
4379
+ { filename: id, environmentName, type: "sfc" },
4380
+ "compiler-sfc did not return a script map"
4381
+ );
4382
+ }
3666
4383
  }
3667
4384
  let templateCode = "";
3668
- if (descriptor.template && !descriptor.scriptSetup) {
4385
+ let templateMap;
4386
+ const scriptSetupIsInline = !!descriptor.scriptSetup && vueOptions.script?.inlineTemplate !== false;
4387
+ if (descriptor.template && !scriptSetupIsInline) {
4388
+ const transformedTemplate = await applySourceTransform(
4389
+ vueOptions.transformTemplate,
4390
+ descriptor.template.content,
4391
+ { filename: id, environmentName, type: "template" }
4392
+ );
4393
+ const templateInputMap = composeSourceMapChain(
4394
+ [
4395
+ transformedTemplate.map,
4396
+ descriptor.template.map,
4397
+ transformedSfc.map
4398
+ ],
4399
+ { filename: id, environmentName, type: "template" }
4400
+ );
4401
+ const customCompilerOptions = vueOptions.template?.compilerOptions ?? {};
3669
4402
  const compiled = sfc.compileTemplate({
3670
- source: descriptor.template.content,
4403
+ ...vueOptions.template,
4404
+ source: transformedTemplate.code,
3671
4405
  filename: id,
3672
4406
  id: scopeId,
3673
- compilerOptions: { scopeId: `data-v-${scopeId}` }
4407
+ inMap: templateInputMap,
4408
+ compilerOptions: {
4409
+ ...customCompilerOptions,
4410
+ scopeId: `data-v-${scopeId}`
4411
+ }
3674
4412
  });
3675
4413
  templateCode = compiled.code;
4414
+ if (wantsSourceMap || transformedTemplate.map != null) {
4415
+ templateMap = compiled.map;
4416
+ }
4417
+ if (transformedTemplate.map != null && templateMap == null) {
4418
+ warnUnchainableMap(
4419
+ { filename: id, environmentName, type: "template" },
4420
+ "compiler-sfc did not return a template map"
4421
+ );
4422
+ }
3676
4423
  }
3677
- let output = scriptCode || "const __sfc__ = {}";
4424
+ const outputNode = new SourceNode();
4425
+ let hasMappedOutput = false;
4426
+ const append = (fragment, map) => {
4427
+ const normalizedMap = normalizeSourceMap(
4428
+ map,
4429
+ { filename: id, environmentName, type: "sfc" }
4430
+ );
4431
+ if (!normalizedMap) {
4432
+ outputNode.add(fragment);
4433
+ return;
4434
+ }
4435
+ try {
4436
+ outputNode.add(
4437
+ SourceNode.fromStringWithSourceMap(
4438
+ fragment,
4439
+ new SourceMapConsumer(normalizedMap)
4440
+ )
4441
+ );
4442
+ hasMappedOutput = true;
4443
+ } catch (error) {
4444
+ warnUnchainableMap(
4445
+ { filename: id, environmentName, type: "sfc" },
4446
+ `source-map assembly failed: ${error instanceof Error ? error.message : String(error)}`
4447
+ );
4448
+ outputNode.add(fragment);
4449
+ }
4450
+ };
4451
+ append(scriptCode || "const __sfc__ = {}", scriptMap);
3678
4452
  if (templateCode) {
3679
- output += `
3680
- ${templateCode}
3681
- `;
3682
- output += `
3683
- __sfc__.render = render
3684
- `;
4453
+ append("\n");
4454
+ append(templateCode, templateMap);
4455
+ append("\n");
4456
+ append("\n__sfc__.render = render\n");
3685
4457
  }
3686
4458
  if (descriptor.styles.length > 0) {
3687
4459
  for (let i = 0; i < descriptor.styles.length; i++) {
3688
- output += `
4460
+ append(`
3689
4461
  import "${id}?vue&type=style&index=${i}&lang.css"
3690
- `;
4462
+ `);
3691
4463
  }
3692
4464
  }
3693
- output += `
4465
+ append(`
3694
4466
  __sfc__.__scopeId = "data-v-${scopeId}"
3695
- `;
4467
+ `);
3696
4468
  if (isDev) {
3697
- output += `
4469
+ append(`
3698
4470
  __sfc__.__hmrId = ${JSON.stringify(scopeId)}
3699
4471
  if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') {
3700
4472
  __VUE_HMR_RUNTIME__.createRecord(__sfc__.__hmrId, __sfc__)
@@ -3708,17 +4480,34 @@ if (import.meta.hot) {
3708
4480
  }
3709
4481
  })
3710
4482
  }
3711
- `;
4483
+ `);
4484
+ }
4485
+ append("\nexport default __sfc__\n");
4486
+ const renderedOutput = outputNode.toStringWithSourceMap({ file: id });
4487
+ const output = renderedOutput.code;
4488
+ const outputMap = hasMappedOutput ? renderedOutput.map.toJSON() : void 0;
4489
+ if (transformedSfc.map != null && outputMap == null) {
4490
+ warnUnchainableMap(
4491
+ { filename: id, environmentName, type: "sfc" },
4492
+ "the compiled SFC output contained no chainable mappings"
4493
+ );
3712
4494
  }
3713
- output += `
3714
- export default __sfc__
3715
- `;
3716
4495
  const lang = descriptor.scriptSetup?.lang ?? descriptor.script?.lang;
3717
4496
  if (lang === "ts") {
3718
- const transpiled = transformCode(`${id}.ts`, output, { sourcemap: false });
3719
- return { code: transpiled.code };
4497
+ const transpiled = transformCode(`${id}.ts`, output, {
4498
+ sourcemap: wantsSourceMap,
4499
+ target: config.build.target
4500
+ });
4501
+ const transpiledMap = transpiled.map ? JSON.parse(transpiled.map) : void 0;
4502
+ return {
4503
+ code: transpiled.code,
4504
+ map: composeSourceMapChain(
4505
+ [transpiledMap, outputMap],
4506
+ { filename: id, environmentName, type: "sfc" }
4507
+ )
4508
+ };
3720
4509
  }
3721
- return { code: output };
4510
+ return { code: output, map: outputMap };
3722
4511
  },
3723
4512
  handleHotUpdate(ctx) {
3724
4513
  const { file, modules } = ctx;
@@ -3732,16 +4521,75 @@ export default __sfc__
3732
4521
  }
3733
4522
  };
3734
4523
  }
4524
+ async function applySourceTransform(transform2, source, context) {
4525
+ if (!transform2) return { code: source };
4526
+ const result = await transform2(source, context);
4527
+ return typeof result === "string" ? { code: result } : result;
4528
+ }
4529
+ function normalizeSourceMap(map, context) {
4530
+ if (map == null) return void 0;
4531
+ try {
4532
+ const value = typeof map === "string" ? JSON.parse(map) : map;
4533
+ if (value && typeof value === "object" && Array.isArray(value.sources) && Array.isArray(value.names) && typeof value.mappings === "string") {
4534
+ return value;
4535
+ }
4536
+ } catch {
4537
+ }
4538
+ warnUnchainableMap(context, "the provided map is not a valid source map");
4539
+ return void 0;
4540
+ }
4541
+ function composeSourceMapChain(maps, context) {
4542
+ const pending = maps.filter((map) => map != null);
4543
+ if (pending.length === 0) return void 0;
4544
+ let composed = normalizeSourceMap(pending.shift(), context);
4545
+ for (const map of pending) {
4546
+ const input = normalizeSourceMap(map, context);
4547
+ if (!input) continue;
4548
+ if (!composed) {
4549
+ composed = input;
4550
+ continue;
4551
+ }
4552
+ try {
4553
+ const consumer = new SourceMapConsumer(composed);
4554
+ if (consumer.sources.length !== 1) {
4555
+ warnUnchainableMap(
4556
+ context,
4557
+ "a generated map has multiple sources and cannot be chained safely"
4558
+ );
4559
+ continue;
4560
+ }
4561
+ const generator = SourceMapGenerator2.fromSourceMap(consumer);
4562
+ generator.applySourceMap(
4563
+ new SourceMapConsumer(input),
4564
+ consumer.sources[0]
4565
+ );
4566
+ composed = generator.toJSON();
4567
+ } catch (error) {
4568
+ warnUnchainableMap(
4569
+ context,
4570
+ `source-map composition failed: ${error instanceof Error ? error.message : String(error)}`
4571
+ );
4572
+ }
4573
+ }
4574
+ return composed;
4575
+ }
4576
+ function warnUnchainableMap(context, reason) {
4577
+ debug3?.(
4578
+ `source map warning for ${context.filename} (${context.type}, ${context.environmentName}): ${reason}`
4579
+ );
4580
+ }
3735
4581
  function hashId(filename) {
3736
4582
  return crypto2.createHash("sha256").update(filename).digest("hex").slice(0, 8);
3737
4583
  }
3738
- var VUE_FILE_RE, VUE_QUERY_RE, compiler;
4584
+ var VUE_FILE_RE, VUE_QUERY_RE, debug3, compiler;
3739
4585
  var init_vue = __esm({
3740
4586
  "src/plugins/vue.ts"() {
3741
4587
  "use strict";
3742
4588
  init_transformer();
4589
+ init_debug();
3743
4590
  VUE_FILE_RE = /\.vue$/;
3744
4591
  VUE_QUERY_RE = /\.vue\?vue&type=(script|template|style)(&index=\d+)?(&lang[.=]\w+)?/;
4592
+ debug3 = createDebugger("nasti:vue");
3745
4593
  compiler = null;
3746
4594
  }
3747
4595
  });
@@ -3749,16 +4597,27 @@ var init_vue = __esm({
3749
4597
  // src/plugins/builtins.ts
3750
4598
  function resolvePluginList(config, userPlugins, opts = {}) {
3751
4599
  const isServe = config.command === "serve";
4600
+ let environmentOptions;
4601
+ if (opts.environmentName) {
4602
+ environmentOptions = config.environments[opts.environmentName];
4603
+ if (!environmentOptions) {
4604
+ throw new Error(
4605
+ `[nasti] unknown environment "${opts.environmentName}" \u2014 declare it in config.environments`
4606
+ );
4607
+ }
4608
+ }
4609
+ const pluginConfig = environmentOptions ? { ...config, resolve: environmentOptions.resolve, build: environmentOptions.build } : config;
4610
+ const consumer = opts.consumer ?? environmentOptions?.consumer;
3752
4611
  return [
3753
4612
  // vuePlugin 排最前(enforce: 'pre' 语义):.vue 先编译成 JS 再走后续管道
3754
- ...config.framework === "vue" ? [vuePlugin(config)] : [],
3755
- resolvePlugin(config),
3756
- cssPlugin(config, opts.cssEngine, opts.consumer),
3757
- assetsPlugin(config),
3758
- ...isServe ? [htmlPlugin(config)] : [],
4613
+ ...config.framework === "vue" ? [vuePlugin(pluginConfig, opts.environmentName ?? "client")] : [],
4614
+ resolvePlugin(pluginConfig),
4615
+ cssPlugin(pluginConfig, opts.cssEngine, consumer),
4616
+ assetsPlugin(pluginConfig),
4617
+ ...isServe ? [htmlPlugin(pluginConfig)] : [],
3759
4618
  ...userPlugins,
3760
4619
  // cssPostPlugin 最后(enforce: 'post' 语义):renderChunk 聚合抽取
3761
- ...!isServe && opts.cssEngine ? [cssPostPlugin(config, opts.cssEngine)] : []
4620
+ ...!isServe && opts.cssEngine ? [cssPostPlugin(pluginConfig, opts.cssEngine)] : []
3762
4621
  ];
3763
4622
  }
3764
4623
  var init_builtins = __esm({
@@ -3791,14 +4650,14 @@ function createModuleRunner(environment) {
3791
4650
  }
3792
4651
  return new NastiModuleRunner(environment);
3793
4652
  }
3794
- var debug3, NODE_BUILTINS, NastiModuleRunner, AsyncFunction;
4653
+ var debug4, NODE_BUILTINS, NastiModuleRunner, AsyncFunction;
3795
4654
  var init_runnable_environment = __esm({
3796
4655
  "src/server/runnable-environment.ts"() {
3797
4656
  "use strict";
3798
4657
  init_transformer();
3799
4658
  init_env();
3800
4659
  init_debug();
3801
- debug3 = createDebugger("nasti:ssr");
4660
+ debug4 = createDebugger("nasti:ssr");
3802
4661
  NODE_BUILTINS = /* @__PURE__ */ new Set([...builtinModules, ...builtinModules.map((m) => `node:${m}`)]);
3803
4662
  NastiModuleRunner = class {
3804
4663
  environment;
@@ -3876,6 +4735,7 @@ var init_runnable_environment = __esm({
3876
4735
  if (shouldTransform(cleanId)) {
3877
4736
  const result = transformCode(cleanId, code, {
3878
4737
  sourcemap: false,
4738
+ target: this.environment.options.build.target,
3879
4739
  jsxRuntime: "automatic",
3880
4740
  jsxImportSource: this.config.framework === "vue" ? "vue" : "react"
3881
4741
  });
@@ -3892,7 +4752,7 @@ var init_runnable_environment = __esm({
3892
4752
  );
3893
4753
  }
3894
4754
  const runnerResult = await moduleRunnerTransform(resolvedId, code);
3895
- debug3?.(`fetchModule ${resolvedId} (${runnerResult.deps?.length ?? 0} deps)`);
4755
+ debug4?.(`fetchModule ${resolvedId} (${runnerResult.deps?.length ?? 0} deps)`);
3896
4756
  return { id: resolvedId, code: runnerResult.code };
3897
4757
  }
3898
4758
  completeExtension(id) {
@@ -4014,7 +4874,7 @@ async function tryNativeReporterPlugin(config, logger) {
4014
4874
  logInfo: (msg) => logger.info(msg)
4015
4875
  });
4016
4876
  } catch (err) {
4017
- debug4?.(`native viteReporterPlugin unavailable, falling back to JS table: ${err}`);
4877
+ debug5?.(`native viteReporterPlugin unavailable, falling back to JS table: ${err}`);
4018
4878
  return null;
4019
4879
  }
4020
4880
  }
@@ -4069,12 +4929,12 @@ function warnLargeChunks(output, config, logger) {
4069
4929
  )
4070
4930
  );
4071
4931
  }
4072
- var debug4, numberFormatter;
4932
+ var debug5, numberFormatter;
4073
4933
  var init_reporter = __esm({
4074
4934
  "src/build/reporter.ts"() {
4075
4935
  "use strict";
4076
4936
  init_debug();
4077
- debug4 = createDebugger("nasti:reporter");
4937
+ debug5 = createDebugger("nasti:reporter");
4078
4938
  numberFormatter = new Intl.NumberFormat("en", {
4079
4939
  maximumFractionDigits: 2,
4080
4940
  minimumFractionDigits: 2
@@ -4082,6 +4942,155 @@ var init_reporter = __esm({
4082
4942
  }
4083
4943
  });
4084
4944
 
4945
+ // src/core/build-app-context.ts
4946
+ import fs9 from "fs";
4947
+ import path12 from "path";
4948
+ function createBuildAppContext(config, results) {
4949
+ const output = [];
4950
+ const emitted = /* @__PURE__ */ new Set();
4951
+ const outDir = path12.resolve(config.root, config.build.outDir);
4952
+ let environmentArtifacts;
4953
+ return {
4954
+ config,
4955
+ results,
4956
+ get output() {
4957
+ return Object.freeze([...output]);
4958
+ },
4959
+ getResult(environmentName) {
4960
+ return results[environmentName];
4961
+ },
4962
+ getArtifact(environmentName, fileName) {
4963
+ const normalized = normalizeEnvironmentFileName(fileName);
4964
+ return results[environmentName]?.output.find(
4965
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalized
4966
+ );
4967
+ },
4968
+ getEntry(environmentName, entryName) {
4969
+ const result = results[environmentName];
4970
+ const fileName = result?.entries?.[entryName];
4971
+ if (!fileName) return void 0;
4972
+ return result.output.find(
4973
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalizeEnvironmentFileName(fileName)
4974
+ );
4975
+ },
4976
+ getManifest(environmentName) {
4977
+ return results[environmentName]?.manifest;
4978
+ },
4979
+ getChunk(environmentName, fileName) {
4980
+ const normalized = normalizeEnvironmentFileName(fileName);
4981
+ return results[environmentName]?.chunks?.[normalized];
4982
+ },
4983
+ getCss(environmentName) {
4984
+ return results[environmentName]?.css;
4985
+ },
4986
+ getSourceMap(environmentName, fileName) {
4987
+ const normalized = normalizeEnvironmentFileName(fileName);
4988
+ return results[environmentName]?.sourceMaps?.[normalized];
4989
+ },
4990
+ resolvePublicPath(environmentName, fileName) {
4991
+ const result = results[environmentName];
4992
+ if (!result) return void 0;
4993
+ const normalized = normalizeEnvironmentFileName(fileName);
4994
+ const base = result.publicPath ?? config.base;
4995
+ return joinPublicPath(base, normalized);
4996
+ },
4997
+ emitFile(file) {
4998
+ const fileName = normalizeAppFileName(file.fileName);
4999
+ const collisionKey = artifactCollisionKey(fileName);
5000
+ if (emitted.has(collisionKey)) {
5001
+ throw new Error(`[nasti] app artifact already emitted: ${fileName}`);
5002
+ }
5003
+ environmentArtifacts ??= collectEnvironmentArtifacts(config, results, outDir);
5004
+ if (environmentArtifacts.has(collisionKey)) {
5005
+ throw new Error(`[nasti] app artifact conflicts with environment output: ${fileName}`);
5006
+ }
5007
+ const target = path12.resolve(outDir, ...fileName.split("/"));
5008
+ const relative = path12.relative(outDir, target);
5009
+ if (relative.startsWith("..") || path12.isAbsolute(relative)) {
5010
+ throw new Error(`[nasti] app artifact must stay inside build.outDir: ${file.fileName}`);
5011
+ }
5012
+ assertNoSymlinkComponents(outDir, fileName);
5013
+ fs9.mkdirSync(path12.dirname(target), { recursive: true });
5014
+ fs9.writeFileSync(target, file.source);
5015
+ const artifact = {
5016
+ ...file,
5017
+ fileName,
5018
+ type: "asset"
5019
+ };
5020
+ emitted.add(collisionKey);
5021
+ output.push(artifact);
5022
+ return fileName;
5023
+ }
5024
+ };
5025
+ }
5026
+ function joinPublicPath(base, fileName) {
5027
+ return `${base.endsWith("/") ? base : `${base}/`}${fileName.replace(/^\//, "")}`;
5028
+ }
5029
+ function normalizeEnvironmentFileName(fileName) {
5030
+ return path12.posix.normalize(fileName.replace(/\\/g, "/").replace(/^\.\//, ""));
5031
+ }
5032
+ function isInvalidEnvironmentFileName(fileName) {
5033
+ return !fileName || fileName === "." || fileName === ".." || fileName.startsWith("../") || path12.posix.isAbsolute(fileName) || /^[A-Za-z]:\//.test(fileName);
5034
+ }
5035
+ function normalizeAppFileName(fileName) {
5036
+ const normalized = normalizeEnvironmentFileName(fileName);
5037
+ if (isInvalidEnvironmentFileName(normalized)) {
5038
+ throw new Error(`[nasti] invalid app artifact fileName: ${fileName}`);
5039
+ }
5040
+ return normalized;
5041
+ }
5042
+ function artifactCollisionKey(fileName) {
5043
+ return normalizeEnvironmentFileName(fileName).toLowerCase();
5044
+ }
5045
+ function collectEnvironmentArtifacts(config, results, appOutDir) {
5046
+ const occupied = /* @__PURE__ */ new Set();
5047
+ for (const [environmentName, result] of Object.entries(results)) {
5048
+ const environment = config.environments[environmentName];
5049
+ if (!environment) continue;
5050
+ const environmentOutDir = path12.resolve(config.root, environment.build.outDir);
5051
+ for (const artifact of result.output) {
5052
+ const artifactPath = path12.resolve(
5053
+ environmentOutDir,
5054
+ ...normalizeEnvironmentFileName(artifact.fileName).split("/")
5055
+ );
5056
+ const relative = path12.relative(appOutDir, artifactPath);
5057
+ if (!relative.startsWith("..") && !path12.isAbsolute(relative)) {
5058
+ occupied.add(artifactCollisionKey(relative));
5059
+ }
5060
+ }
5061
+ }
5062
+ return occupied;
5063
+ }
5064
+ function assertNoSymlinkComponents(outDir, fileName) {
5065
+ let current = outDir;
5066
+ for (const segment of fileName.split("/")) {
5067
+ current = path12.join(current, segment);
5068
+ let stats;
5069
+ try {
5070
+ stats = fs9.lstatSync(current);
5071
+ } catch (error) {
5072
+ if (error.code === "ENOENT") continue;
5073
+ throw error;
5074
+ }
5075
+ if (stats.isSymbolicLink()) {
5076
+ throw new Error(`[nasti] app artifact path cannot traverse a symlink: ${fileName}`);
5077
+ }
5078
+ }
5079
+ }
5080
+ function inferEnvironmentEntries(output) {
5081
+ const entries = {};
5082
+ for (const artifact of output) {
5083
+ if (artifact.type !== "chunk" || !artifact.isEntry || !artifact.name) continue;
5084
+ entries[artifact.name] = normalizeEnvironmentFileName(artifact.fileName);
5085
+ }
5086
+ return Object.keys(entries).length > 0 ? entries : void 0;
5087
+ }
5088
+ var init_build_app_context = __esm({
5089
+ "src/core/build-app-context.ts"() {
5090
+ "use strict";
5091
+ }
5092
+ });
5093
+
4085
5094
  // src/build/index.ts
4086
5095
  var build_exports = {};
4087
5096
  __export(build_exports, {
@@ -4091,8 +5100,8 @@ __export(build_exports, {
4091
5100
  resolveClientEntries: () => resolveClientEntries,
4092
5101
  toRolldownPlugins: () => toRolldownPlugins
4093
5102
  });
4094
- import path12 from "path";
4095
- import fs9 from "fs";
5103
+ import path13 from "path";
5104
+ import fs10 from "fs";
4096
5105
  import { builtinModules as builtinModules2 } from "module";
4097
5106
  import { rolldown } from "rolldown";
4098
5107
  import pc6 from "picocolors";
@@ -4100,9 +5109,14 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
4100
5109
  const config = environment.config;
4101
5110
  const envOptions = environment.options;
4102
5111
  const isServer = environment.consumer === "server";
4103
- const outDir = path12.resolve(config.root, envOptions.build.outDir);
5112
+ const outDir = path13.resolve(config.root, envOptions.build.outDir);
4104
5113
  const assetsDir = envOptions.build.assetsDir;
4105
- const { output: userOutput, transform: userTransform, ...restInputOptions } = envOptions.build.rolldownOptions;
5114
+ const {
5115
+ output: userOutput,
5116
+ transform: userTransform,
5117
+ resolve: userResolve,
5118
+ ...restInputOptions
5119
+ } = envOptions.build.rolldownOptions;
4106
5120
  const vueDefine = config.framework === "vue" ? {
4107
5121
  __VUE_OPTIONS_API__: "true",
4108
5122
  __VUE_PROD_DEVTOOLS__: "false",
@@ -4114,27 +5128,34 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
4114
5128
  const inputOptions = {
4115
5129
  ...restInputOptions,
4116
5130
  input: entryPoints,
4117
- transform: { ...userTransform, define: mergedDefine },
5131
+ transform: {
5132
+ ...userTransform,
5133
+ target: userTransform?.target ?? envOptions.build.target,
5134
+ define: mergedDefine
5135
+ },
4118
5136
  plugins: rolldownPlugins,
5137
+ // client/server consumer 都必须使用当前环境的 conditions/mainFields;Lynx
5138
+ // BG/MT 通常同为 client consumer,但仍可能声明不同的运行时条件。
5139
+ resolve: {
5140
+ ...userResolve ?? {},
5141
+ // Environment API 是条件解析的唯一高层入口,优先于继承来的底层选项。
5142
+ conditionNames: envOptions.resolve.conditions,
5143
+ mainFields: envOptions.resolve.mainFields
5144
+ },
4119
5145
  ...isServer ? {
4120
5146
  platform: restInputOptions.platform ?? "node",
4121
- resolve: {
4122
- conditionNames: envOptions.resolve.conditions,
4123
- mainFields: envOptions.resolve.mainFields,
4124
- ...restInputOptions.resolve
4125
- },
4126
5147
  // server 产物:node 内建恒外部化;bare specifier 默认外部化
4127
5148
  //(同 Vite ssr.external 默认 —— 依赖由 node_modules 运行时解析),
4128
5149
  // 相对/绝对/虚拟模块照常打包。需要内联依赖时经 rolldownOptions.external 覆盖。
4129
5150
  external: restInputOptions.external ?? ((id) => {
4130
5151
  if (NODE_BUILTINS2.has(id)) return true;
4131
- return !id.startsWith(".") && !path12.isAbsolute(id) && !id.startsWith("\0");
5152
+ return !id.startsWith(".") && !path13.isAbsolute(id) && !id.startsWith("\0");
4132
5153
  })
4133
5154
  } : {}
4134
5155
  };
4135
5156
  const outputOptions = isServer ? {
4136
5157
  format: "esm",
4137
- sourcemap: !!envOptions.build.sourcemap,
5158
+ sourcemap: envOptions.build.sourcemap,
4138
5159
  minify: !!envOptions.build.minify,
4139
5160
  entryFileNames: "[name].js",
4140
5161
  chunkFileNames: "chunks/[name]-[hash].js",
@@ -4143,7 +5164,7 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
4143
5164
  dir: outDir
4144
5165
  } : {
4145
5166
  format: "esm",
4146
- sourcemap: !!envOptions.build.sourcemap,
5167
+ sourcemap: envOptions.build.sourcemap,
4147
5168
  minify: !!envOptions.build.minify,
4148
5169
  entryFileNames: `${assetsDir}/[name].[hash].js`,
4149
5170
  chunkFileNames: `${assetsDir}/[name].[hash].js`,
@@ -4155,27 +5176,177 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
4155
5176
  };
4156
5177
  return { inputOptions, outputOptions, outDir };
4157
5178
  }
4158
- function toRolldownPlugins(plugins) {
5179
+ function toRolldownPlugins(plugins, environment) {
5180
+ const wrap = (hook) => {
5181
+ if (!hook) return hook;
5182
+ return function(...args) {
5183
+ return hook.apply(attachEnvironment(this, environment), args);
5184
+ };
5185
+ };
4159
5186
  return plugins.map((p) => ({
4160
5187
  name: p.name,
4161
- resolveId: p.resolveId,
4162
- load: p.load,
4163
- transform: p.transform,
4164
- buildStart: p.buildStart,
4165
- buildEnd: p.buildEnd,
5188
+ resolveId: wrap(p.resolveId),
5189
+ load: wrap(p.load),
5190
+ transform: wrap(p.transform),
5191
+ buildStart: wrap(p.buildStart),
5192
+ buildEnd: wrap(p.buildEnd),
4166
5193
  // closeBundle 在 bundle.close() 时触发 —— PWA manifest/SW 等终态产物依赖
4167
- closeBundle: p.closeBundle,
4168
- renderChunk: p.renderChunk,
4169
- augmentChunkHash: p.augmentChunkHash,
4170
- generateBundle: p.generateBundle
5194
+ closeBundle: wrap(p.closeBundle),
5195
+ renderChunk: wrap(p.renderChunk),
5196
+ augmentChunkHash: wrap(p.augmentChunkHash),
5197
+ generateBundle: wrap(p.generateBundle)
4171
5198
  }));
4172
5199
  }
5200
+ function attachEnvironment(context, environment) {
5201
+ if (context?.environment === environment) return context;
5202
+ try {
5203
+ Object.defineProperty(context, "environment", {
5204
+ configurable: true,
5205
+ enumerable: false,
5206
+ writable: false,
5207
+ value: environment
5208
+ });
5209
+ return context;
5210
+ } catch {
5211
+ return new Proxy(context, {
5212
+ get(target, property) {
5213
+ if (property === "environment") return environment;
5214
+ const value = Reflect.get(target, property, target);
5215
+ return typeof value === "function" ? value.bind(target) : value;
5216
+ },
5217
+ set(target, property, value) {
5218
+ return Reflect.set(target, property, value, target);
5219
+ }
5220
+ });
5221
+ }
5222
+ }
5223
+ function finalizeEnvironmentResult(environment, result) {
5224
+ const metadata = environment.getBuildMetadata();
5225
+ const inferredEntries = inferEnvironmentEntries(result.output);
5226
+ const entries = {
5227
+ ...inferredEntries,
5228
+ ...metadata.entries,
5229
+ ...result.entries
5230
+ };
5231
+ const normalizedEntries = Object.fromEntries(
5232
+ Object.entries(entries).map(([name, fileName]) => {
5233
+ const normalized = normalizeEnvironmentFileName(fileName);
5234
+ if (isInvalidEnvironmentFileName(normalized)) {
5235
+ throw new Error(
5236
+ `[nasti] environment "${environment.name}" returned invalid entry "${name}": ${fileName}`
5237
+ );
5238
+ }
5239
+ return [name, normalized];
5240
+ })
5241
+ );
5242
+ const inferredMetadata = inferOutputMetadata(environment, result.output);
5243
+ return {
5244
+ publicPath: environment.config.base,
5245
+ ...inferredMetadata,
5246
+ ...metadata,
5247
+ ...result,
5248
+ output: result.output,
5249
+ chunks: {
5250
+ ...inferredMetadata.chunks,
5251
+ ...metadata.chunks,
5252
+ ...result.chunks
5253
+ },
5254
+ assets: {
5255
+ ...inferredMetadata.assets,
5256
+ ...metadata.assets,
5257
+ ...result.assets
5258
+ },
5259
+ sourceMaps: {
5260
+ ...inferredMetadata.sourceMaps,
5261
+ ...metadata.sourceMaps,
5262
+ ...result.sourceMaps
5263
+ },
5264
+ ...Object.keys(normalizedEntries).length > 0 ? { entries: normalizedEntries } : {}
5265
+ };
5266
+ }
5267
+ function inferOutputMetadata(environment, output) {
5268
+ const chunks = {};
5269
+ const assets = {};
5270
+ const sourceMaps = {};
5271
+ const cssChunks = environment.getBuildMetadata().css?.chunks ?? {};
5272
+ const assetModules = environment.getAssetModules();
5273
+ const publicPath = environment.config.base;
5274
+ for (const artifact of output) {
5275
+ const fileName = normalizeEnvironmentFileName(artifact.fileName);
5276
+ if (artifact.map != null) sourceMaps[fileName] = artifact.map;
5277
+ if (artifact.type === "chunk") {
5278
+ const moduleIds = [...artifact.moduleIds ?? []];
5279
+ chunks[fileName] = {
5280
+ fileName,
5281
+ name: artifact.name ?? fileName,
5282
+ isEntry: !!artifact.isEntry,
5283
+ isDynamicEntry: !!artifact.isDynamicEntry,
5284
+ imports: [...artifact.imports ?? []],
5285
+ dynamicImports: [...artifact.dynamicImports ?? []],
5286
+ moduleIds,
5287
+ css: [...cssChunks[fileName]?.cssFileNames ?? []],
5288
+ assets: [
5289
+ ...new Set(
5290
+ moduleIds.map((id) => assetModules[id]).filter((asset) => !!asset)
5291
+ )
5292
+ ]
5293
+ };
5294
+ } else if (artifact.type === "asset") {
5295
+ assets[fileName] = {
5296
+ fileName,
5297
+ names: [...artifact.names ?? (artifact.name ? [artifact.name] : [])],
5298
+ publicPath: joinPublicPath(publicPath, fileName)
5299
+ };
5300
+ }
5301
+ }
5302
+ return { chunks, assets, sourceMaps };
5303
+ }
5304
+ function prepareBuildOutputDirectories(config, buildableNames) {
5305
+ const directories = /* @__PURE__ */ new Set();
5306
+ const protectedPaths = /* @__PURE__ */ new Set();
5307
+ const clientIsBuilt = buildableNames.includes("client");
5308
+ if (!clientIsBuilt && config.build.emptyOutDir) {
5309
+ directories.add(path13.resolve(config.root, config.build.outDir));
5310
+ }
5311
+ for (const name of buildableNames) {
5312
+ const environment = config.environments[name];
5313
+ const outDir = path13.resolve(config.root, environment.build.outDir);
5314
+ if (!environment.build.emptyOutDir) {
5315
+ protectedPaths.add(outDir);
5316
+ continue;
5317
+ }
5318
+ if (!environment.driver) directories.add(outDir);
5319
+ }
5320
+ const containsPath = (parent, child) => {
5321
+ const relative = path13.relative(parent, child);
5322
+ return relative === "" || !relative.startsWith("..") && !path13.isAbsolute(relative);
5323
+ };
5324
+ const roots = [...directories].filter(
5325
+ (directory) => ![...protectedPaths].some((protectedPath) => containsPath(directory, protectedPath))
5326
+ ).sort((a, b) => a.length - b.length).filter(
5327
+ (directory, index2, all) => !all.slice(0, index2).some((parent) => containsPath(parent, directory))
5328
+ );
5329
+ for (const directory of roots) {
5330
+ if (fs10.existsSync(directory)) fs10.rmSync(directory, { recursive: true, force: true });
5331
+ }
5332
+ }
5333
+ function assertDriverBuildResult(environment, result) {
5334
+ const output = result != null && typeof result === "object" ? result.output : void 0;
5335
+ const hasValidOutput = Array.isArray(output) && output.every(
5336
+ (artifact) => artifact != null && typeof artifact === "object" && typeof artifact.fileName === "string" && typeof artifact.type === "string"
5337
+ );
5338
+ if (!hasValidOutput) {
5339
+ throw new Error(
5340
+ `[nasti] environment "${environment.name}" driver "${environment.driver?.name}" returned an invalid build result; expected { output: EnvironmentBuildOutput[] }`
5341
+ );
5342
+ }
5343
+ }
4173
5344
  function resolveClientEntries(config, html) {
4174
5345
  const configuredEntries = config.environments.client?.entry ?? [];
4175
5346
  if (configuredEntries.length > 0) return configuredEntries;
4176
5347
  const entryPoints = [];
4177
5348
  const htmlFile = config.environments.client?.html;
4178
- const htmlDir = htmlFile ? path12.dirname(htmlFile) : config.root;
5349
+ const htmlDir = htmlFile ? path13.dirname(htmlFile) : config.root;
4179
5350
  if (html) {
4180
5351
  const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
4181
5352
  for (const match of scriptMatches) {
@@ -4183,7 +5354,7 @@ function resolveClientEntries(config, html) {
4183
5354
  if (src && !src.startsWith("http")) {
4184
5355
  const cleanSrc = src.split(/[?#]/, 1)[0];
4185
5356
  entryPoints.push(
4186
- cleanSrc.startsWith("/") ? path12.resolve(config.root, cleanSrc.replace(/^\//, "")) : path12.resolve(htmlDir, cleanSrc)
5357
+ cleanSrc.startsWith("/") ? path13.resolve(config.root, cleanSrc.replace(/^\//, "")) : path13.resolve(htmlDir, cleanSrc)
4187
5358
  );
4188
5359
  }
4189
5360
  }
@@ -4191,8 +5362,8 @@ function resolveClientEntries(config, html) {
4191
5362
  if (entryPoints.length === 0) {
4192
5363
  const fallbackEntries = ["src/main.ts", "src/main.tsx", "src/main.js", "src/index.ts", "src/index.tsx", "src/index.js"];
4193
5364
  for (const entry of fallbackEntries) {
4194
- const fullPath = path12.resolve(config.root, entry);
4195
- if (fs9.existsSync(fullPath)) {
5365
+ const fullPath = path13.resolve(config.root, entry);
5366
+ if (fs10.existsSync(fullPath)) {
4196
5367
  entryPoints.push(fullPath);
4197
5368
  break;
4198
5369
  }
@@ -4207,6 +5378,7 @@ function createOxcTransformPlugin(config, environment) {
4207
5378
  if (!shouldTransform(id)) return null;
4208
5379
  const result = transformCode(id, code, {
4209
5380
  sourcemap: !!environment.options.build.sourcemap,
5381
+ target: environment.options.build.target,
4210
5382
  jsxRuntime: "automatic",
4211
5383
  jsxImportSource: config.framework === "vue" ? "vue" : "react"
4212
5384
  });
@@ -4220,16 +5392,20 @@ async function build(inlineConfig = {}) {
4220
5392
  const startTime = performance.now();
4221
5393
  logger.info(
4222
5394
  pc6.cyan(`
4223
- nasti v${"2.3.1"} `) + pc6.green(`building for ${config.mode}...`)
4224
- );
4225
- debug5?.(`root: ${config.root}`);
4226
- const buildableNames = Object.keys(config.environments).filter(
4227
- (name) => name === "client" || config.environments[name].entry.length > 0 || !!config.environments[name].driver
5395
+ nasti v${"2.4.1"} `) + pc6.green(`building for ${config.mode}...`)
4228
5396
  );
5397
+ debug6?.(`root: ${config.root}`);
5398
+ const buildableNames = Object.keys(config.environments).filter((name) => {
5399
+ const environment = config.environments[name];
5400
+ if (!environment.buildEnabled) return false;
5401
+ return name === "client" || environment.entry.length > 0 || !!environment.driver;
5402
+ });
4229
5403
  buildableNames.sort((a, b) => a === "client" ? -1 : b === "client" ? 1 : 0);
5404
+ prepareBuildOutputDirectories(config, buildableNames);
4230
5405
  const environments = {};
4231
5406
  const environmentResults = {};
4232
5407
  const initializedEnvironments = [];
5408
+ const buildAppContext = createBuildAppContext(config, environmentResults);
4233
5409
  let clientOutput = [];
4234
5410
  let buildFailed = false;
4235
5411
  try {
@@ -4240,12 +5416,12 @@ nasti v${"2.3.1"} `) + pc6.green(`building for ${config.mode}...`)
4240
5416
  environmentResults[name] = built.result;
4241
5417
  if (name === "client") clientOutput = built.result.output;
4242
5418
  if (buildableNames.length > 1) {
4243
- debug5?.(`environment "${name}" built (${built.result.output.length} files)`);
5419
+ debug6?.(`environment "${name}" built (${built.result.output.length} files)`);
4244
5420
  }
4245
5421
  }
4246
5422
  const pluginApi = getPluginApi(config);
4247
5423
  for (const plugin of config.plugins) {
4248
- await plugin.afterBuildApp?.(environmentResults, pluginApi);
5424
+ await plugin.afterBuildApp?.(environmentResults, pluginApi, buildAppContext);
4249
5425
  }
4250
5426
  } catch (error) {
4251
5427
  buildFailed = true;
@@ -4272,22 +5448,31 @@ nasti v${"2.3.1"} `) + pc6.green(`building for ${config.mode}...`)
4272
5448
  }
4273
5449
  }
4274
5450
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
4275
- const totalSize = Object.values(environments).flat().reduce((sum, chunk) => {
5451
+ const allOutput = [...Object.values(environments).flat(), ...buildAppContext.output];
5452
+ const totalSize = allOutput.reduce((sum, chunk) => {
4276
5453
  const content = chunk.type === "chunk" ? chunk.code : chunk.source;
4277
5454
  if (content == null) return sum;
4278
5455
  return sum + (typeof content === "string" ? Buffer.byteLength(content) : content.byteLength);
4279
5456
  }, 0);
4280
- const fileCount = Object.values(environments).flat().length;
5457
+ const fileCount = allOutput.length;
4281
5458
  const envSuffix = buildableNames.length > 1 ? ` (${buildableNames.join(" + ")})` : "";
4282
5459
  logger.info(pc6.green(`\u2713 built in ${elapsed}s`) + pc6.dim(envSuffix));
4283
5460
  logger.info(pc6.dim(` ${fileCount} files, ${displaySize(totalSize)} total \u2192 ${config.build.outDir}/`));
4284
- return { output: clientOutput, environments, environmentResults };
5461
+ return {
5462
+ output: clientOutput,
5463
+ environments,
5464
+ environmentResults,
5465
+ appOutput: [...buildAppContext.output]
5466
+ };
4285
5467
  }
4286
5468
  async function buildClientEnvironment(config) {
4287
5469
  const logger = config.logger;
4288
- const outDir = path12.resolve(config.root, config.build.outDir);
5470
+ const outDir = path13.resolve(config.root, config.build.outDir);
4289
5471
  const cssEngine = createCssEngine();
4290
- const pluginList = resolvePluginList(config, config.plugins, { cssEngine });
5472
+ const pluginList = resolvePluginList(config, config.plugins, {
5473
+ cssEngine,
5474
+ environmentName: "client"
5475
+ });
4291
5476
  const clientEnv = new NastiEnvironment("client", config, {
4292
5477
  mode: "build",
4293
5478
  plugins: pluginList,
@@ -4302,13 +5487,11 @@ async function buildClientEnvironment(config) {
4302
5487
  );
4303
5488
  }
4304
5489
  const result = await clientEnv.driver.build(clientEnv.getDriverContext());
4305
- return { environment: clientEnv, result };
4306
- }
4307
- if (config.build.emptyOutDir && fs9.existsSync(outDir)) {
4308
- fs9.rmSync(outDir, { recursive: true, force: true });
5490
+ assertDriverBuildResult(clientEnv, result);
5491
+ return { environment: clientEnv, result: finalizeEnvironmentResult(clientEnv, result) };
4309
5492
  }
4310
- fs9.mkdirSync(outDir, { recursive: true });
4311
- const htmlFile = config.environments.client.html ?? path12.resolve(config.root, "index.html");
5493
+ fs10.mkdirSync(outDir, { recursive: true });
5494
+ const htmlFile = config.environments.client.html ?? path13.resolve(config.root, "index.html");
4312
5495
  const html = await readHtmlFile(config.root, htmlFile);
4313
5496
  const entryPoints = resolveClientEntries(config, html);
4314
5497
  if (entryPoints.length === 0) {
@@ -4318,7 +5501,7 @@ async function buildClientEnvironment(config) {
4318
5501
  const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
4319
5502
  const rolldownPlugins = [
4320
5503
  createOxcTransformPlugin(config, clientEnv),
4321
- ...toRolldownPlugins(allPlugins),
5504
+ ...toRolldownPlugins(allPlugins, clientEnv),
4322
5505
  ...nativeReporter ? [nativeReporter] : []
4323
5506
  ];
4324
5507
  const { inputOptions, outputOptions } = getRolldownOptions(
@@ -4329,6 +5512,7 @@ async function buildClientEnvironment(config) {
4329
5512
  const bundle2 = await rolldown(inputOptions);
4330
5513
  const { output } = await bundle2.write(outputOptions);
4331
5514
  await bundle2.close();
5515
+ clientEnv.setBuildMetadata({ css: getCssMetadata(cssEngine) });
4332
5516
  if (html) {
4333
5517
  let processedHtml = html;
4334
5518
  const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
@@ -4342,7 +5526,9 @@ async function buildClientEnvironment(config) {
4342
5526
  processedHtml = processHtml(processedHtml, result);
4343
5527
  }
4344
5528
  }
4345
- processedHtml = injectCssLinks(processedHtml, cssEngine, config);
5529
+ if (clientEnv.options.build.css.inject !== false) {
5530
+ processedHtml = injectCssLinks(processedHtml, cssEngine, config);
5531
+ }
4346
5532
  for (const chunk of output) {
4347
5533
  if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
4348
5534
  processedHtml = replaceEntryScript(
@@ -4355,13 +5541,16 @@ async function buildClientEnvironment(config) {
4355
5541
  );
4356
5542
  }
4357
5543
  }
4358
- fs9.writeFileSync(path12.resolve(outDir, "index.html"), processedHtml);
5544
+ fs10.writeFileSync(path13.resolve(outDir, "index.html"), processedHtml);
4359
5545
  }
4360
5546
  if (!nativeReporter && config.logLevel !== "silent") {
4361
5547
  reportBuildOutput(output, config, logger);
4362
5548
  }
4363
5549
  warnLargeChunks(output, config, logger);
4364
- return { environment: clientEnv, result: { output } };
5550
+ return {
5551
+ environment: clientEnv,
5552
+ result: finalizeEnvironmentResult(clientEnv, { output })
5553
+ };
4365
5554
  } catch (error) {
4366
5555
  try {
4367
5556
  await clientEnv.close();
@@ -4377,7 +5566,12 @@ async function buildClientEnvironment(config) {
4377
5566
  async function buildServerEnvironment(config, name) {
4378
5567
  const envOptions = config.environments[name];
4379
5568
  const logger = config.logger;
4380
- const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
5569
+ const cssEngine = envOptions.consumer === "client" ? createCssEngine() : void 0;
5570
+ const pluginList = resolvePluginList(config, config.plugins, {
5571
+ consumer: envOptions.consumer,
5572
+ environmentName: name,
5573
+ cssEngine
5574
+ });
4381
5575
  const environment = new NastiEnvironment(name, config, {
4382
5576
  mode: "build",
4383
5577
  plugins: pluginList,
@@ -4393,38 +5587,40 @@ async function buildServerEnvironment(config, name) {
4393
5587
  }
4394
5588
  try {
4395
5589
  const result = await environment.driver.build(environment.getDriverContext());
4396
- return { environment, result };
5590
+ assertDriverBuildResult(environment, result);
5591
+ return { environment, result: finalizeEnvironmentResult(environment, result) };
4397
5592
  } catch (error) {
4398
5593
  await environment.close();
4399
5594
  throw error;
4400
5595
  }
4401
5596
  }
4402
5597
  for (const entry of envOptions.entry) {
4403
- if (!fs9.existsSync(entry)) {
5598
+ if (!fs10.existsSync(entry)) {
4404
5599
  await environment.close();
4405
5600
  throw new Error(`[nasti] environment "${name}" entry not found: ${entry}`);
4406
5601
  }
4407
5602
  }
4408
5603
  const rolldownPlugins = [
4409
5604
  createOxcTransformPlugin(config, environment),
4410
- ...toRolldownPlugins(environment.plugins)
5605
+ ...toRolldownPlugins(environment.plugins, environment)
4411
5606
  ];
4412
5607
  const { inputOptions, outputOptions, outDir } = getRolldownOptions(
4413
5608
  environment,
4414
5609
  envOptions.entry,
4415
5610
  rolldownPlugins
4416
5611
  );
4417
- if (envOptions.build.emptyOutDir && fs9.existsSync(outDir)) {
4418
- fs9.rmSync(outDir, { recursive: true, force: true });
4419
- }
4420
- fs9.mkdirSync(outDir, { recursive: true });
5612
+ fs10.mkdirSync(outDir, { recursive: true });
4421
5613
  const bundle2 = await rolldown(inputOptions);
4422
5614
  const { output } = await bundle2.write(outputOptions);
4423
5615
  await bundle2.close();
5616
+ if (cssEngine) environment.setBuildMetadata({ css: getCssMetadata(cssEngine) });
4424
5617
  logger.info(
4425
- pc6.dim(` [${name}] `) + output.map((o) => path12.join(envOptions.build.outDir, o.fileName)).join(pc6.dim(", "))
5618
+ pc6.dim(` [${name}] `) + output.map((o) => path13.join(envOptions.build.outDir, o.fileName)).join(pc6.dim(", "))
4426
5619
  );
4427
- return { environment, result: { output } };
5620
+ return {
5621
+ environment,
5622
+ result: finalizeEnvironmentResult(environment, { output })
5623
+ };
4428
5624
  }
4429
5625
  function injectCssLinks(html, cssEngine, config) {
4430
5626
  const cssLinkTags = [];
@@ -4451,9 +5647,9 @@ function escapeRegExp(string) {
4451
5647
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4452
5648
  }
4453
5649
  function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, urlPrefix) {
4454
- const rootRelative = path12.relative(config.root, facadeModuleId).split(path12.sep).join("/");
4455
- const resolvedHtmlFile = path12.resolve(config.root, htmlFile);
4456
- const htmlRelative = path12.relative(path12.dirname(resolvedHtmlFile), facadeModuleId).split(path12.sep).join("/");
5650
+ const rootRelative = path13.relative(config.root, facadeModuleId).split(path13.sep).join("/");
5651
+ const resolvedHtmlFile = path13.resolve(config.root, htmlFile);
5652
+ const htmlRelative = path13.relative(path13.dirname(resolvedHtmlFile), facadeModuleId).split(path13.sep).join("/");
4457
5653
  const candidates = /* @__PURE__ */ new Set([
4458
5654
  rootRelative,
4459
5655
  `/${rootRelative}`,
@@ -4469,7 +5665,7 @@ function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, ur
4469
5665
  }
4470
5666
  return processed;
4471
5667
  }
4472
- var debug5, NODE_BUILTINS2;
5668
+ var debug6, NODE_BUILTINS2;
4473
5669
  var init_build = __esm({
4474
5670
  "src/build/index.ts"() {
4475
5671
  "use strict";
@@ -4483,7 +5679,8 @@ var init_build = __esm({
4483
5679
  init_reporter();
4484
5680
  init_debug();
4485
5681
  init_plugin_api();
4486
- debug5 = createDebugger("nasti:build");
5682
+ init_build_app_context();
5683
+ debug6 = createDebugger("nasti:build");
4487
5684
  NODE_BUILTINS2 = /* @__PURE__ */ new Set([...builtinModules2, ...builtinModules2.map((m) => `node:${m}`)]);
4488
5685
  }
4489
5686
  });
@@ -4493,7 +5690,7 @@ var dev_engine_exports = {};
4493
5690
  __export(dev_engine_exports, {
4494
5691
  createBundledDevServer: () => createBundledDevServer
4495
5692
  });
4496
- import path13 from "path";
5693
+ import path14 from "path";
4497
5694
  import crypto3 from "crypto";
4498
5695
  import { WebSocketServer as WsServer2 } from "ws";
4499
5696
  import pc7 from "picocolors";
@@ -4511,7 +5708,7 @@ async function createBundledDevServer(opts) {
4511
5708
  }
4512
5709
  } catch (err) {
4513
5710
  throw new Error(
4514
- `[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.`
5711
+ `[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.`
4515
5712
  );
4516
5713
  }
4517
5714
  const html = await readHtmlFile(config.root, config.environments.client?.html);
@@ -4529,7 +5726,7 @@ async function createBundledDevServer(opts) {
4529
5726
  createReactRefreshRuntimePlugin(entryPoints),
4530
5727
  createBundledOxcRefreshPlugin()
4531
5728
  ] : [],
4532
- ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins)),
5729
+ ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins, clientEnv)),
4533
5730
  ...useReactRefresh ? [
4534
5731
  refreshWrapperFn({
4535
5732
  cwd: config.root,
@@ -4564,7 +5761,7 @@ async function createBundledDevServer(opts) {
4564
5761
  for (const { clientId, update } of updates) {
4565
5762
  if (update.type === "Noop") continue;
4566
5763
  if (update.type === "FullReload") {
4567
- debug6?.(`full reload for ${clientId}: ${update.reason ?? ""}`);
5764
+ debug7?.(`full reload for ${clientId}: ${update.reason ?? ""}`);
4568
5765
  needsLatestOutput = true;
4569
5766
  continue;
4570
5767
  }
@@ -4578,7 +5775,7 @@ async function createBundledDevServer(opts) {
4578
5775
  }
4579
5776
  const url = `/${patchPath}`;
4580
5777
  logger.info(
4581
- pc7.green("hmr update ") + pc7.dim(changedFiles.map((f) => path13.relative(config.root, f)).join(", ")),
5778
+ pc7.green("hmr update ") + pc7.dim(changedFiles.map((f) => path14.relative(config.root, f)).join(", ")),
4582
5779
  { timestamp: true }
4583
5780
  );
4584
5781
  sendTo(clientId, { type: "hmr:update", path: url, url });
@@ -4614,7 +5811,7 @@ async function createBundledDevServer(opts) {
4614
5811
  },
4615
5812
  {
4616
5813
  watch: { skipWrite: true },
4617
- rebuildStrategy: "auto",
5814
+ rebuildStrategy: "never",
4618
5815
  onOutput(result) {
4619
5816
  if (result instanceof Error) {
4620
5817
  logger.error(pc7.red(`[bundled] build error: ${result.message}`), { error: result });
@@ -4630,7 +5827,13 @@ async function createBundledDevServer(opts) {
4630
5827
  memoryFiles.set(`${file.fileName}.map`, JSON.stringify(file.map));
4631
5828
  }
4632
5829
  }
4633
- debug6?.(`bundle output refreshed (${result.output.length} files)`);
5830
+ debug7?.(`bundle output refreshed (${result.output.length} files)`);
5831
+ },
5832
+ onAdditionalAssets(result) {
5833
+ for (const file of result.output) {
5834
+ const content = file.type === "chunk" ? file.code : file.source;
5835
+ if (content != null) memoryFiles.set(file.fileName, content);
5836
+ }
4634
5837
  },
4635
5838
  async onHmrUpdates(result) {
4636
5839
  if (result instanceof Error) {
@@ -4639,7 +5842,7 @@ async function createBundledDevServer(opts) {
4639
5842
  return;
4640
5843
  }
4641
5844
  const { updates, changedFiles } = result;
4642
- debug6?.(
5845
+ debug7?.(
4643
5846
  `onHmrUpdates(engine watcher): ${changedFiles.length} changed, ${updates.length} updates`
4644
5847
  );
4645
5848
  if (changedFiles.length === 0) return;
@@ -4658,24 +5861,29 @@ async function createBundledDevServer(opts) {
4658
5861
  if (!clientId) return;
4659
5862
  wss.handleUpgrade(req, socket, head, (ws) => {
4660
5863
  bundledClients.set(clientId, ws);
4661
- debug6?.(`bundled client connected: ${clientId}`);
4662
- ws.send(JSON.stringify({ type: "connected" }));
5864
+ debug7?.(`bundled client connected: ${clientId}`);
5865
+ void engine.registerClient(clientId).then(async () => {
5866
+ for (const fileName of entryFileNames.values()) {
5867
+ await engine.notifyPayloadDelivered(fileName);
5868
+ }
5869
+ ws.send(JSON.stringify({ type: "connected" }));
5870
+ }).catch((err) => {
5871
+ debug7?.(`registerClient failed for ${clientId}: ${err?.message ?? err}`);
5872
+ ws.close();
5873
+ });
4663
5874
  ws.on("message", async (raw) => {
4664
5875
  try {
4665
5876
  const msg = JSON.parse(String(raw));
4666
- if (msg.type === "hmr:module-registered" && Array.isArray(msg.modules)) {
4667
- await engine.registerModules(clientId, msg.modules);
4668
- debug6?.(`registered ${msg.modules.length} modules for ${clientId}`);
4669
- } else if (msg.type === "hmr:invalidate") {
5877
+ if (msg.type === "hmr:invalidate") {
4670
5878
  scheduleFullReload();
4671
5879
  }
4672
5880
  } catch (err) {
4673
- debug6?.(`bundled ws message error: ${err.message}`);
5881
+ debug7?.(`bundled ws message error: ${err.message}`);
4674
5882
  }
4675
5883
  });
4676
5884
  ws.on("close", () => {
4677
5885
  bundledClients.delete(clientId);
4678
- engine.removeClient(clientId).catch((err) => debug6?.(`removeClient failed for ${clientId}: ${err?.message ?? err}`));
5886
+ engine.removeClient(clientId).catch((err) => debug7?.(`removeClient failed for ${clientId}: ${err?.message ?? err}`));
4679
5887
  });
4680
5888
  });
4681
5889
  });
@@ -4692,10 +5900,18 @@ async function createBundledDevServer(opts) {
4692
5900
  res.end("// [nasti] lazy endpoint requires id & clientId");
4693
5901
  return;
4694
5902
  }
4695
- const code = await engine.compileEntry(id, clientId);
5903
+ const output = await engine.compileEntry(id, clientId);
5904
+ if (output.sourcemap && output.sourcemapFilename) {
5905
+ memoryFiles.set(output.sourcemapFilename, output.sourcemap);
5906
+ }
5907
+ res.once("finish", () => {
5908
+ void engine.notifyPayloadDelivered(output.filename).catch(
5909
+ (err) => debug7?.(`notifyPayloadDelivered failed: ${err?.message ?? err}`)
5910
+ );
5911
+ });
4696
5912
  res.setHeader("Content-Type", "application/javascript");
4697
5913
  res.setHeader("Cache-Control", "no-store");
4698
- res.end(code + "\n;export {}");
5914
+ res.end(output.code + "\n;export {}");
4699
5915
  return;
4700
5916
  }
4701
5917
  const patchHit = patches.get(pathname.replace(/^\//, ""));
@@ -4714,8 +5930,13 @@ async function createBundledDevServer(opts) {
4714
5930
  return;
4715
5931
  }
4716
5932
  res.setHeader("ETag", hit.etag);
4717
- res.setHeader("Content-Type", MIME_TYPES[path13.extname(fileName)] ?? "application/octet-stream");
5933
+ res.setHeader("Content-Type", MIME_TYPES[path14.extname(fileName)] ?? "application/octet-stream");
4718
5934
  res.setHeader("Cache-Control", "no-cache");
5935
+ res.once("finish", () => {
5936
+ void engine.notifyPayloadDelivered(fileName).catch(
5937
+ (err) => debug7?.(`notifyPayloadDelivered failed: ${err?.message ?? err}`)
5938
+ );
5939
+ });
4719
5940
  res.end(hit.content);
4720
5941
  return;
4721
5942
  }
@@ -4750,7 +5971,7 @@ function stripCatchAllLoad(plugins) {
4750
5971
  );
4751
5972
  }
4752
5973
  function createReactRefreshRuntimePlugin(entryPoints) {
4753
- const entryIds = new Set(entryPoints.map((p) => path13.resolve(p)));
5974
+ const entryIds = new Set(entryPoints.map((p) => path14.resolve(p)));
4754
5975
  return {
4755
5976
  name: "nasti:bundled-react-refresh",
4756
5977
  resolveId(source) {
@@ -4768,7 +5989,7 @@ function createReactRefreshRuntimePlugin(entryPoints) {
4768
5989
  return null;
4769
5990
  },
4770
5991
  transform(code, id) {
4771
- if (!entryIds.has(path13.resolve(id.split("?")[0]))) return null;
5992
+ if (!entryIds.has(path14.resolve(id.split("?")[0]))) return null;
4772
5993
  return { code: `import ${JSON.stringify(PREAMBLE_SPEC)};
4773
5994
  ${code}`, map: null };
4774
5995
  }
@@ -4815,7 +6036,7 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
4815
6036
  }
4816
6037
  return processed;
4817
6038
  }
4818
- var debug6, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
6039
+ var debug7, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
4819
6040
  var init_dev_engine = __esm({
4820
6041
  "src/server/bundled/dev-engine.ts"() {
4821
6042
  "use strict";
@@ -4824,7 +6045,7 @@ var init_dev_engine = __esm({
4824
6045
  init_transformer();
4825
6046
  init_middleware();
4826
6047
  init_debug();
4827
- debug6 = createDebugger("nasti:bundled");
6048
+ debug7 = createDebugger("nasti:bundled");
4828
6049
  MIME_TYPES = {
4829
6050
  ".js": "application/javascript",
4830
6051
  ".mjs": "application/javascript",
@@ -4939,7 +6160,7 @@ __export(server_exports, {
4939
6160
  createServer: () => createServer
4940
6161
  });
4941
6162
  import http from "http";
4942
- import path14 from "path";
6163
+ import path15 from "path";
4943
6164
  import os from "os";
4944
6165
  import connect from "connect";
4945
6166
  import sirv from "sirv";
@@ -4949,14 +6170,16 @@ async function createServer(inlineConfig = {}) {
4949
6170
  const startTime = performance.now();
4950
6171
  const config = await resolveConfig(inlineConfig, "serve");
4951
6172
  const logger = config.logger;
4952
- const allPlugins = resolvePluginList(config, config.plugins);
6173
+ const allPlugins = resolvePluginList(config, config.plugins, {
6174
+ environmentName: "client"
6175
+ });
4953
6176
  const configWithPlugins = { ...config, plugins: allPlugins };
4954
6177
  const app = connect();
4955
6178
  const httpServer = http.createServer(app);
4956
6179
  const ws = createWebSocketServer(httpServer);
4957
6180
  const pluginApi = getPluginApi(config);
4958
6181
  const clientEnv = new NastiEnvironment("client", config, {
4959
- hot: createWsHotChannel(ws),
6182
+ hot: createWsHotChannel(ws, "client"),
4960
6183
  mode: "dev",
4961
6184
  plugins: allPlugins,
4962
6185
  pluginApi
@@ -4966,15 +6189,45 @@ async function createServer(inlineConfig = {}) {
4966
6189
  for (const name of Object.keys(config.environments)) {
4967
6190
  if (name === "client") continue;
4968
6191
  const consumer = config.environments[name].consumer;
4969
- const envPlugins = resolvePluginList(config, config.plugins, { consumer });
6192
+ const envPlugins = resolvePluginList(config, config.plugins, {
6193
+ consumer,
6194
+ environmentName: name
6195
+ });
4970
6196
  environments[name] = new NastiEnvironment(name, config, {
6197
+ hot: consumer === "client" ? createWsHotChannel(ws, name) : void 0,
4971
6198
  mode: "dev",
4972
6199
  plugins: envPlugins,
4973
6200
  pluginApi
4974
6201
  });
4975
6202
  }
4976
6203
  for (const [name, environment] of Object.entries(environments)) {
4977
- if (name !== "client" && environment.options.driver) await environment.init();
6204
+ if (name === "client" || environment.consumer === "client" || environment.options.driver) {
6205
+ await environment.init();
6206
+ }
6207
+ }
6208
+ const transformContexts = /* @__PURE__ */ new Map();
6209
+ for (const environment of Object.values(environments)) {
6210
+ if (environment.consumer !== "client" || environment.driver) continue;
6211
+ const environmentConfig = {
6212
+ ...configWithPlugins,
6213
+ resolve: environment.options.resolve,
6214
+ build: environment.options.build,
6215
+ plugins: environment.plugins
6216
+ };
6217
+ const context = {
6218
+ config: environmentConfig,
6219
+ pluginContainer: environment.pluginContainer,
6220
+ moduleGraph: environment.moduleGraph,
6221
+ environment,
6222
+ envDefine: buildEnvDefine(
6223
+ loadEnv(environmentConfig.mode, environmentConfig.root, environmentConfig.envPrefix),
6224
+ environmentConfig.mode,
6225
+ ssrDefineOverrides(environment.consumer)
6226
+ ),
6227
+ onPrune: (paths) => environment.hot.send({ type: "prune", paths })
6228
+ };
6229
+ transformContexts.set(environment.name, context);
6230
+ environment.configureDevPipeline((url) => transformRequest(url, context));
4978
6231
  }
4979
6232
  let ssrRunner = null;
4980
6233
  async function getSsrRunner() {
@@ -4989,7 +6242,6 @@ async function createServer(inlineConfig = {}) {
4989
6242
  return ssrRunner;
4990
6243
  }
4991
6244
  const moduleGraph = clientEnv.moduleGraph;
4992
- const pluginContainer = clientEnv.pluginContainer;
4993
6245
  let bundledServer = null;
4994
6246
  if (config.experimental.bundledDev) {
4995
6247
  const { createBundledDevServer: createBundledDevServer2 } = await Promise.resolve().then(() => (init_dev_engine(), dev_engine_exports));
@@ -5001,14 +6253,14 @@ async function createServer(inlineConfig = {}) {
5001
6253
  app.use(bundledServer.middleware);
5002
6254
  }
5003
6255
  const ignoredSegments = /* @__PURE__ */ new Set(["node_modules", ".git", ".nasti"]);
5004
- const outDirAbs = path14.resolve(config.root, config.build.outDir);
6256
+ const outDirAbs = path15.resolve(config.root, config.build.outDir);
5005
6257
  const watcher = watch(config.root, {
5006
6258
  ignored: (filePath) => {
5007
6259
  if (filePath === config.root) return false;
5008
- if (filePath === outDirAbs || filePath.startsWith(outDirAbs + path14.sep)) return true;
5009
- const rel = path14.relative(config.root, filePath);
5010
- if (!rel || rel.startsWith("..") || path14.isAbsolute(rel)) return false;
5011
- for (const seg of rel.split(path14.sep)) {
6260
+ if (filePath === outDirAbs || filePath.startsWith(outDirAbs + path15.sep)) return true;
6261
+ const rel = path15.relative(config.root, filePath);
6262
+ if (!rel || rel.startsWith("..") || path15.isAbsolute(rel)) return false;
6263
+ for (const seg of rel.split(path15.sep)) {
5012
6264
  if (ignoredSegments.has(seg)) return true;
5013
6265
  }
5014
6266
  return false;
@@ -5018,6 +6270,15 @@ async function createServer(inlineConfig = {}) {
5018
6270
  let server;
5019
6271
  const environmentServices = {};
5020
6272
  let environmentDriversStarted = false;
6273
+ let devPipelinesStarted = false;
6274
+ const startDevPipelines = async () => {
6275
+ if (devPipelinesStarted) return;
6276
+ devPipelinesStarted = true;
6277
+ for (const environment of Object.values(environments)) {
6278
+ if (!transformContexts.has(environment.name)) continue;
6279
+ await environment.pluginContainer.buildStart();
6280
+ }
6281
+ };
5021
6282
  const logCloseError = (target, error) => {
5022
6283
  const normalized = error instanceof Error ? error : new Error(String(error));
5023
6284
  logger.error(`[nasti] failed to close ${target}`, { error: normalized });
@@ -5069,14 +6330,61 @@ async function createServer(inlineConfig = {}) {
5069
6330
  });
5070
6331
  }
5071
6332
  };
6333
+ const updateClientEnvironments = async (file) => {
6334
+ const timestamp = Date.now();
6335
+ const results = {};
6336
+ for (const environment of Object.values(environments)) {
6337
+ if (environment.consumer !== "client" || environment.driver) continue;
6338
+ try {
6339
+ const result = await handleFileChange(file, server, environment.name, timestamp);
6340
+ if (result) results[environment.name] = result;
6341
+ } catch (error) {
6342
+ const normalized = error instanceof Error ? error : new Error(String(error));
6343
+ logger.error(
6344
+ `[nasti] HMR failed for environment "${environment.name}": ${normalized.message}`,
6345
+ { error: normalized }
6346
+ );
6347
+ try {
6348
+ environment.hot.send({
6349
+ type: "error",
6350
+ err: { message: normalized.message, stack: normalized.stack }
6351
+ });
6352
+ } catch (channelError) {
6353
+ const channelFailure = channelError instanceof Error ? channelError : new Error(String(channelError));
6354
+ logger.error(
6355
+ `[nasti] failed to deliver HMR error to environment "${environment.name}"`,
6356
+ { error: channelFailure }
6357
+ );
6358
+ }
6359
+ }
6360
+ }
6361
+ if (Object.keys(results).length === 0) return;
6362
+ const context = {
6363
+ file,
6364
+ timestamp,
6365
+ environments: Object.freeze({ ...results }),
6366
+ server
6367
+ };
6368
+ for (const plugin of config.plugins) {
6369
+ await plugin.handleHotUpdateApp?.(context);
6370
+ }
6371
+ };
6372
+ const queueClientEnvironmentUpdate = (file) => {
6373
+ void updateClientEnvironments(file).catch((error) => {
6374
+ const normalized = error instanceof Error ? error : new Error(String(error));
6375
+ logger.error(`[nasti] multi-environment HMR failed: ${normalized.message}`, {
6376
+ error: normalized
6377
+ });
6378
+ });
6379
+ };
5072
6380
  watcher.on("change", (file) => {
5073
6381
  ssrRunner?.invalidateFile(file);
5074
- handleFileChange(file, server);
6382
+ queueClientEnvironmentUpdate(file);
5075
6383
  notifyEnvironmentDrivers(file, "change");
5076
6384
  });
5077
6385
  watcher.on("add", (file) => {
5078
6386
  ssrRunner?.invalidateFile(file);
5079
- handleFileChange(file, server);
6387
+ queueClientEnvironmentUpdate(file);
5080
6388
  notifyEnvironmentDrivers(file, "add");
5081
6389
  });
5082
6390
  watcher.on("unlink", (file) => {
@@ -5094,7 +6402,7 @@ async function createServer(inlineConfig = {}) {
5094
6402
  async listen(port) {
5095
6403
  const finalPort = port ?? config.server.port;
5096
6404
  const host = config.server.host === true ? "0.0.0.0" : config.server.host;
5097
- await pluginContainer.buildStart();
6405
+ await startDevPipelines();
5098
6406
  await startEnvironmentDrivers();
5099
6407
  return new Promise((resolve, reject) => {
5100
6408
  let currentPort = finalPort;
@@ -5109,7 +6417,7 @@ async function createServer(inlineConfig = {}) {
5109
6417
  const readyIn = Math.ceil(performance.now() - startTime);
5110
6418
  logger.info(
5111
6419
  `
5112
- ${pc8.cyan(pc8.bold("NASTI"))} ${pc8.cyan(`v${"2.3.1"}`)} ${pc8.dim("ready in")} ${pc8.bold(readyIn)} ${pc8.dim("ms")}
6420
+ ${pc8.cyan(pc8.bold("NASTI"))} ${pc8.cyan(`v${"2.4.1"}`)} ${pc8.dim("ready in")} ${pc8.bold(readyIn)} ${pc8.dim("ms")}
5113
6421
  `
5114
6422
  );
5115
6423
  printServerUrls(
@@ -5136,15 +6444,26 @@ async function createServer(inlineConfig = {}) {
5136
6444
  });
5137
6445
  },
5138
6446
  async transformRequest(url) {
5139
- const { transformRequest: transformRequest2 } = await Promise.resolve().then(() => (init_middleware(), middleware_exports));
5140
- return transformRequest2(url, { config: configWithPlugins, pluginContainer, moduleGraph });
6447
+ return clientEnv.transformRequest(url);
6448
+ },
6449
+ async transformEnvironmentRequest(environmentName, url) {
6450
+ const environment = environments[environmentName];
6451
+ if (!environment) {
6452
+ throw new Error(`[nasti] unknown dev environment "${environmentName}"`);
6453
+ }
6454
+ return environment.transformRequest(url);
5141
6455
  },
5142
6456
  async ssrLoadModule(url) {
5143
6457
  const runner = await getSsrRunner();
5144
6458
  return runner.import(url);
5145
6459
  },
5146
6460
  async close() {
5147
- await pluginContainer.buildEnd();
6461
+ if (devPipelinesStarted) {
6462
+ for (const environment of Object.values(environments).reverse()) {
6463
+ if (!transformContexts.has(environment.name)) continue;
6464
+ await environment.pluginContainer.buildEnd();
6465
+ }
6466
+ }
5148
6467
  await bundledServer?.close();
5149
6468
  let environmentCloseFailed = false;
5150
6469
  let firstEnvironmentCloseError;
@@ -5194,12 +6513,8 @@ async function createServer(inlineConfig = {}) {
5194
6513
  }
5195
6514
  throw error;
5196
6515
  }
5197
- app.use(transformMiddleware({
5198
- config: configWithPlugins,
5199
- pluginContainer,
5200
- moduleGraph
5201
- }));
5202
- const publicDir = path14.resolve(config.root, "public");
6516
+ app.use(transformMiddleware(transformContexts.get("client")));
6517
+ const publicDir = path15.resolve(config.root, "public");
5203
6518
  app.use(sirv(publicDir, { dev: true, etag: true }));
5204
6519
  app.use(sirv(config.root, { dev: true, etag: true }));
5205
6520
  const postMiddlewares = [];
@@ -5237,6 +6552,7 @@ var init_server = __esm({
5237
6552
  init_hmr();
5238
6553
  init_builtins();
5239
6554
  init_plugin_api();
6555
+ init_env();
5240
6556
  }
5241
6557
  });
5242
6558
 
@@ -5287,24 +6603,24 @@ __export(electron_exports, {
5287
6603
  detectInstalledElectron: () => detectInstalledElectron,
5288
6604
  normalizePreload: () => normalizePreload
5289
6605
  });
5290
- import path15 from "path";
5291
- import fs10 from "fs";
6606
+ import path16 from "path";
6607
+ import fs11 from "fs";
5292
6608
  import { rolldown as rolldown2 } from "rolldown";
5293
6609
  import pc9 from "picocolors";
5294
6610
  async function buildElectron(inlineConfig = {}) {
5295
6611
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
5296
6612
  const startTime = performance.now();
5297
6613
  assertElectronVersion(config);
5298
- console.log(pc9.cyan("\n\u26A1 nasti build (electron)") + pc9.dim(` v${"2.3.1"}`));
6614
+ console.log(pc9.cyan("\n\u26A1 nasti build (electron)") + pc9.dim(` v${"2.4.1"}`));
5299
6615
  console.log(pc9.dim(` root: ${config.root}`));
5300
6616
  console.log(pc9.dim(` mode: ${config.mode}`));
5301
6617
  console.log(pc9.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
5302
- const outDir = path15.resolve(config.root, config.build.outDir);
5303
- if (config.build.emptyOutDir && fs10.existsSync(outDir)) {
5304
- fs10.rmSync(outDir, { recursive: true, force: true });
6618
+ const outDir = path16.resolve(config.root, config.build.outDir);
6619
+ if (config.build.emptyOutDir && fs11.existsSync(outDir)) {
6620
+ fs11.rmSync(outDir, { recursive: true, force: true });
5305
6621
  }
5306
- fs10.mkdirSync(outDir, { recursive: true });
5307
- const rendererOutDir = path15.join(outDir, "renderer");
6622
+ fs11.mkdirSync(outDir, { recursive: true });
6623
+ const rendererOutDir = path16.join(outDir, "renderer");
5308
6624
  const { build: build2 } = await Promise.resolve().then(() => (init_build(), build_exports));
5309
6625
  await build2(createElectronRendererConfig(config, inlineConfig, {
5310
6626
  build: {
@@ -5313,8 +6629,8 @@ async function buildElectron(inlineConfig = {}) {
5313
6629
  emptyOutDir: false
5314
6630
  }
5315
6631
  }));
5316
- const mainEntry = path15.resolve(config.root, config.electron.main);
5317
- if (!fs10.existsSync(mainEntry)) {
6632
+ const mainEntry = path16.resolve(config.root, config.electron.main);
6633
+ if (!fs11.existsSync(mainEntry)) {
5318
6634
  throw new Error(
5319
6635
  `Electron main entry not found: ${config.electron.main}
5320
6636
  \u5728 nasti.config.ts \u7684 electron.main \u6307\u5B9A\u4E3B\u8FDB\u7A0B\u5165\u53E3\u6587\u4EF6\u3002`
@@ -5328,11 +6644,11 @@ async function buildElectron(inlineConfig = {}) {
5328
6644
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
5329
6645
  const preloadFiles = [];
5330
6646
  for (const entry of preloadEntries) {
5331
- if (!fs10.existsSync(entry)) {
6647
+ if (!fs11.existsSync(entry)) {
5332
6648
  console.warn(pc9.yellow(` \u26A0 preload entry not found, skipped: ${entry}`));
5333
6649
  continue;
5334
6650
  }
5335
- const base = path15.basename(entry).replace(/\.[^.]+$/, "");
6651
+ const base = path16.basename(entry).replace(/\.[^.]+$/, "");
5336
6652
  const out = outFileName(outDir, base, config.electron.preloadFormat);
5337
6653
  await bundleNode(config, entry, {
5338
6654
  outFile: out,
@@ -5344,10 +6660,10 @@ async function buildElectron(inlineConfig = {}) {
5344
6660
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
5345
6661
  console.log(pc9.green(`
5346
6662
  \u2713 Electron build complete in ${elapsed}s`));
5347
- console.log(pc9.dim(` renderer: ${path15.relative(config.root, rendererOutDir)}/`));
5348
- console.log(pc9.dim(` main: ${path15.relative(config.root, mainFile)}`));
6663
+ console.log(pc9.dim(` renderer: ${path16.relative(config.root, rendererOutDir)}/`));
6664
+ console.log(pc9.dim(` main: ${path16.relative(config.root, mainFile)}`));
5349
6665
  for (const pf of preloadFiles) {
5350
- console.log(pc9.dim(` preload: ${path15.relative(config.root, pf)}`));
6666
+ console.log(pc9.dim(` preload: ${path16.relative(config.root, pf)}`));
5351
6667
  }
5352
6668
  console.log();
5353
6669
  return { rendererOutDir, mainFile, preloadFiles };
@@ -5385,7 +6701,7 @@ async function bundleNode(config, entry, opts) {
5385
6701
  },
5386
6702
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5387
6703
  });
5388
- fs10.mkdirSync(path15.dirname(opts.outFile), { recursive: true });
6704
+ fs11.mkdirSync(path16.dirname(opts.outFile), { recursive: true });
5389
6705
  await bundle2.write({
5390
6706
  sourcemap: !!config.build.sourcemap,
5391
6707
  minify: !!config.build.minify,
@@ -5396,7 +6712,7 @@ async function bundleNode(config, entry, opts) {
5396
6712
  codeSplitting: false
5397
6713
  });
5398
6714
  await bundle2.close();
5399
- console.log(pc9.dim(` \u2713 ${opts.label} \u2192 ${path15.relative(config.root, opts.outFile)}`));
6715
+ console.log(pc9.dim(` \u2713 ${opts.label} \u2192 ${path16.relative(config.root, opts.outFile)}`));
5400
6716
  return opts.outFile;
5401
6717
  }
5402
6718
  function createElectronRendererConfig(config, inlineConfig = {}, overrides = {}) {
@@ -5420,11 +6736,11 @@ function createElectronRendererConfig(config, inlineConfig = {}, overrides = {})
5420
6736
  }
5421
6737
  function outFileName(outDir, base, format) {
5422
6738
  const ext = format === "cjs" ? ".cjs" : ".mjs";
5423
- return path15.join(outDir, base + ext);
6739
+ return path16.join(outDir, base + ext);
5424
6740
  }
5425
6741
  function normalizePreload(preload, root) {
5426
6742
  const list = Array.isArray(preload) ? preload : preload ? [preload] : [];
5427
- return list.map((p) => path15.resolve(root, p));
6743
+ return list.map((p) => path16.resolve(root, p));
5428
6744
  }
5429
6745
  function assertElectronVersion(config) {
5430
6746
  const min = config.electron.minVersion;
@@ -5439,9 +6755,9 @@ function assertElectronVersion(config) {
5439
6755
  }
5440
6756
  function detectInstalledElectron(root) {
5441
6757
  try {
5442
- const pkgPath = path15.resolve(root, "node_modules/electron/package.json");
5443
- if (!fs10.existsSync(pkgPath)) return null;
5444
- const pkg = JSON.parse(fs10.readFileSync(pkgPath, "utf-8"));
6758
+ const pkgPath = path16.resolve(root, "node_modules/electron/package.json");
6759
+ if (!fs11.existsSync(pkgPath)) return null;
6760
+ const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf-8"));
5445
6761
  const major = parseInt(String(pkg.version).split(".")[0], 10);
5446
6762
  return Number.isFinite(major) ? major : null;
5447
6763
  } catch {
@@ -5465,8 +6781,8 @@ __export(electron_dev_exports, {
5465
6781
  electronRendererDevPath: () => electronRendererDevPath,
5466
6782
  startElectronDev: () => startElectronDev
5467
6783
  });
5468
- import path16 from "path";
5469
- import fs11 from "fs";
6784
+ import path17 from "path";
6785
+ import fs12 from "fs";
5470
6786
  import { createRequire as createRequire5 } from "module";
5471
6787
  import { spawn } from "child_process";
5472
6788
  import chokidar from "chokidar";
@@ -5476,7 +6792,7 @@ async function startElectronDev(inlineConfig = {}) {
5476
6792
  const { noSpawn, ...rest } = inlineConfig;
5477
6793
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
5478
6794
  warnElectronVersion(config);
5479
- console.log(pc10.cyan("\n\u26A1 nasti electron dev") + pc10.dim(` v${"2.3.1"}`));
6795
+ console.log(pc10.cyan("\n\u26A1 nasti electron dev") + pc10.dim(` v${"2.4.1"}`));
5480
6796
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5481
6797
  const server = await createServer2({
5482
6798
  ...rest,
@@ -5486,11 +6802,11 @@ async function startElectronDev(inlineConfig = {}) {
5486
6802
  await server.listen();
5487
6803
  const devUrl = `http://localhost:${server.config.server.port}` + electronRendererDevPath(config.electron.renderer);
5488
6804
  console.log(pc10.dim(` renderer: ${devUrl}`));
5489
- const stageDir = path16.resolve(config.root, ".nasti");
5490
- fs11.mkdirSync(stageDir, { recursive: true });
5491
- const mainEntry = path16.resolve(config.root, config.electron.main);
6805
+ const stageDir = path17.resolve(config.root, ".nasti");
6806
+ fs12.mkdirSync(stageDir, { recursive: true });
6807
+ const mainEntry = path17.resolve(config.root, config.electron.main);
5492
6808
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
5493
- const builtMainFile = path16.join(stageDir, "main" + extFor(config.electron.mainFormat));
6809
+ const builtMainFile = path17.join(stageDir, "main" + extFor(config.electron.mainFormat));
5494
6810
  const builtPreloadFiles = [];
5495
6811
  const compileAll = async () => {
5496
6812
  await compileNode(config, mainEntry, {
@@ -5500,9 +6816,9 @@ async function startElectronDev(inlineConfig = {}) {
5500
6816
  });
5501
6817
  builtPreloadFiles.length = 0;
5502
6818
  for (const entry of preloadEntries) {
5503
- if (!fs11.existsSync(entry)) continue;
5504
- const base = path16.basename(entry).replace(/\.[^.]+$/, "");
5505
- const out = path16.join(stageDir, base + extFor(config.electron.preloadFormat));
6819
+ if (!fs12.existsSync(entry)) continue;
6820
+ const base = path17.basename(entry).replace(/\.[^.]+$/, "");
6821
+ const out = path17.join(stageDir, base + extFor(config.electron.preloadFormat));
5506
6822
  await compileNode(config, entry, {
5507
6823
  outFile: out,
5508
6824
  format: config.electron.preloadFormat,
@@ -5541,7 +6857,7 @@ async function startElectronDev(inlineConfig = {}) {
5541
6857
  };
5542
6858
  spawnElectron();
5543
6859
  if (config.electron.autoRestart) {
5544
- const watchTargets = [mainEntry, ...preloadEntries].filter(fs11.existsSync);
6860
+ const watchTargets = [mainEntry, ...preloadEntries].filter(fs12.existsSync);
5545
6861
  const watcher = chokidar.watch(watchTargets, { ignoreInitial: true });
5546
6862
  let restarting = null;
5547
6863
  let pending = false;
@@ -5621,7 +6937,7 @@ async function compileNode(config, entry, opts) {
5621
6937
  platform: "node",
5622
6938
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5623
6939
  });
5624
- fs11.mkdirSync(path16.dirname(opts.outFile), { recursive: true });
6940
+ fs12.mkdirSync(path17.dirname(opts.outFile), { recursive: true });
5625
6941
  await bundle2.write({
5626
6942
  file: opts.outFile,
5627
6943
  format: opts.format === "cjs" ? "cjs" : "esm",
@@ -5634,18 +6950,18 @@ async function compileNode(config, entry, opts) {
5634
6950
  await bundle2.close();
5635
6951
  }
5636
6952
  function electronRendererDevPath(renderer) {
5637
- const normalized = renderer.split(path16.sep).join("/").replace(/^\.?\//, "");
6953
+ const normalized = renderer.split(path17.sep).join("/").replace(/^\.?\//, "");
5638
6954
  return normalized === "index.html" ? "/" : `/${normalized}`;
5639
6955
  }
5640
6956
  function resolveElectronBinary(config) {
5641
- if (config.electron.electronPath && fs11.existsSync(config.electron.electronPath)) {
6957
+ if (config.electron.electronPath && fs12.existsSync(config.electron.electronPath)) {
5642
6958
  return config.electron.electronPath;
5643
6959
  }
5644
6960
  try {
5645
- const require2 = createRequire5(path16.resolve(config.root, "package.json"));
6961
+ const require2 = createRequire5(path17.resolve(config.root, "package.json"));
5646
6962
  const pathFile = require2.resolve("electron");
5647
6963
  const electronModule = require2(pathFile);
5648
- if (typeof electronModule === "string" && fs11.existsSync(electronModule)) {
6964
+ if (typeof electronModule === "string" && fs12.existsSync(electronModule)) {
5649
6965
  return electronModule;
5650
6966
  }
5651
6967
  } catch {
@@ -5820,20 +7136,20 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
5820
7136
  const logger = createCliLogger(options);
5821
7137
  try {
5822
7138
  const http2 = await import("http");
5823
- const path17 = await import("path");
7139
+ const path18 = await import("path");
5824
7140
  const os2 = await import("os");
5825
7141
  const sirv2 = (await import("sirv")).default;
5826
7142
  const connect2 = (await import("connect")).default;
5827
7143
  const { printServerUrls: printServerUrls2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
5828
- const resolvedRoot = path17.resolve(root ?? ".");
5829
- const outDir = path17.resolve(resolvedRoot, options.outDir);
7144
+ const resolvedRoot = path18.resolve(root ?? ".");
7145
+ const outDir = path18.resolve(resolvedRoot, options.outDir);
5830
7146
  const app = connect2();
5831
7147
  app.use(sirv2(outDir, { single: true, etag: true, gzip: true, brotli: true }));
5832
7148
  const port = options.port;
5833
7149
  const host = options.host === true ? "0.0.0.0" : options.host ?? "localhost";
5834
7150
  http2.createServer(app).listen(port, host, () => {
5835
7151
  logger.info(`
5836
- ${pc11.cyan(pc11.bold("NASTI"))} ${pc11.cyan(`v${"2.3.1"}`)} ${pc11.dim("preview")}
7152
+ ${pc11.cyan(pc11.bold("NASTI"))} ${pc11.cyan(`v${"2.4.1"}`)} ${pc11.dim("preview")}
5837
7153
  `);
5838
7154
  printServerUrls2(
5839
7155
  {
@@ -5850,6 +7166,6 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
5850
7166
  }
5851
7167
  });
5852
7168
  cli.help();
5853
- cli.version("2.3.1");
7169
+ cli.version("2.4.1");
5854
7170
  cli.parse();
5855
7171
  //# sourceMappingURL=cli.js.map