@nasti-toolchain/nasti 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -5,10 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __glob = (map) => (path17) => {
9
- var fn = map[path17];
8
+ var __glob = (map) => (path18) => {
9
+ var fn = map[path18];
10
10
  if (fn) return fn();
11
- throw new Error("Module not found in bundle: " + path17);
11
+ throw new Error("Module not found in bundle: " + path18);
12
12
  };
13
13
  var __esm = (fn, res) => function __init() {
14
14
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -226,6 +226,92 @@ var init_defaults = __esm({
226
226
  }
227
227
  });
228
228
 
229
+ // src/core/plugin-api.ts
230
+ function orderPlugins(plugins) {
231
+ const baseline = plugins.map((plugin, index2) => ({ plugin, index: index2 })).sort((a, b) => enforceRank(a.plugin) - enforceRank(b.plugin) || a.index - b.index).map(({ plugin }) => plugin);
232
+ const indexesByName = /* @__PURE__ */ new Map();
233
+ baseline.forEach((plugin, index2) => {
234
+ const indexes = indexesByName.get(plugin.name) ?? [];
235
+ indexes.push(index2);
236
+ indexesByName.set(plugin.name, indexes);
237
+ });
238
+ const edges = baseline.map(() => /* @__PURE__ */ new Set());
239
+ const indegree = baseline.map(() => 0);
240
+ const addEdge = (from, to) => {
241
+ if (from === to || edges[from].has(to)) return;
242
+ edges[from].add(to);
243
+ indegree[to]++;
244
+ };
245
+ baseline.forEach((plugin, current) => {
246
+ for (const dependency of plugin.pre ?? []) {
247
+ for (const before of indexesByName.get(dependency) ?? []) addEdge(before, current);
248
+ }
249
+ for (const dependency of plugin.post ?? []) {
250
+ for (const after of indexesByName.get(dependency) ?? []) addEdge(current, after);
251
+ }
252
+ });
253
+ const ready = indegree.map((degree, index2) => ({ degree, index: index2 })).filter(({ degree }) => degree === 0).map(({ index: index2 }) => index2);
254
+ const ordered = [];
255
+ while (ready.length > 0) {
256
+ ready.sort((a, b) => a - b);
257
+ const current = ready.shift();
258
+ ordered.push(baseline[current]);
259
+ for (const next of edges[current]) {
260
+ indegree[next]--;
261
+ if (indegree[next] === 0) ready.push(next);
262
+ }
263
+ }
264
+ if (ordered.length !== baseline.length) {
265
+ const cyclic = baseline.filter((_, index2) => indegree[index2] > 0).map((plugin) => plugin.name);
266
+ throw new Error(
267
+ `[nasti] circular plugin setup dependency: ${[...new Set(cyclic)].join(", ")}`
268
+ );
269
+ }
270
+ return ordered;
271
+ }
272
+ async function setupPluginApi(config, plugins) {
273
+ const exposed = /* @__PURE__ */ new Map();
274
+ const api = {
275
+ config,
276
+ logger: config.logger,
277
+ expose(key, value) {
278
+ if (exposed.has(key) && exposed.get(key) !== value) {
279
+ throw new Error(`[nasti] plugin API key already exposed: ${String(key)}`);
280
+ }
281
+ exposed.set(key, value);
282
+ },
283
+ useExposed(key) {
284
+ return exposed.get(key);
285
+ }
286
+ };
287
+ apiByConfig.set(config, api);
288
+ for (const plugin of plugins) {
289
+ await plugin.setup?.(api);
290
+ }
291
+ return api;
292
+ }
293
+ function getPluginApi(config) {
294
+ const api = apiByConfig.get(config);
295
+ if (!api) {
296
+ throw new Error(
297
+ "[nasti] internal: plugin API requested before setup; getPluginApi requires the original ResolvedConfig reference registered by setupPluginApi, not a shallow copy"
298
+ );
299
+ }
300
+ return api;
301
+ }
302
+ function enforceRank(plugin) {
303
+ if (plugin.enforce === "pre") return 0;
304
+ if (plugin.enforce === "post") return 2;
305
+ return 1;
306
+ }
307
+ var apiByConfig;
308
+ var init_plugin_api = __esm({
309
+ "src/core/plugin-api.ts"() {
310
+ "use strict";
311
+ apiByConfig = /* @__PURE__ */ new WeakMap();
312
+ }
313
+ });
314
+
229
315
  // src/config/index.ts
230
316
  function loadTsconfigPaths(root) {
231
317
  const tsconfigPath = import_node_path.default.resolve(root, "tsconfig.json");
@@ -260,6 +346,43 @@ async function loadConfigFromFile(root) {
260
346
  }
261
347
  return {};
262
348
  }
349
+ function detectFramework(root) {
350
+ const sourceRoot = import_node_path.default.resolve(root, "src");
351
+ if (containsVueFile(sourceRoot)) return "vue";
352
+ const packagePath = import_node_path.default.resolve(root, "package.json");
353
+ if (import_node_fs.default.existsSync(packagePath)) {
354
+ try {
355
+ const pkg = JSON.parse(import_node_fs.default.readFileSync(packagePath, "utf-8"));
356
+ const dependencies = {
357
+ ...pkg.dependencies ?? {},
358
+ ...pkg.devDependencies ?? {},
359
+ ...pkg.peerDependencies ?? {},
360
+ ...pkg.optionalDependencies ?? {}
361
+ };
362
+ const hasVue = "vue" in dependencies || "@vue/runtime-dom" in dependencies;
363
+ const hasReact = "react" in dependencies || "react-dom" in dependencies;
364
+ if (hasVue && !hasReact) return "vue";
365
+ if (hasReact) return "react";
366
+ if (hasVue) return "vue";
367
+ } catch {
368
+ }
369
+ }
370
+ return "react";
371
+ }
372
+ function containsVueFile(dir, depth = 0) {
373
+ if (depth > 5 || !import_node_fs.default.existsSync(dir)) return false;
374
+ try {
375
+ for (const entry of import_node_fs.default.readdirSync(dir, { withFileTypes: true })) {
376
+ if (entry.isFile() && entry.name.endsWith(".vue")) return true;
377
+ if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "dist" && containsVueFile(import_node_path.default.join(dir, entry.name), depth + 1)) {
378
+ return true;
379
+ }
380
+ }
381
+ } catch {
382
+ return false;
383
+ }
384
+ return false;
385
+ }
263
386
  async function loadTsConfig(filePath) {
264
387
  const { transformSync: transformSync2 } = await import("oxc-transform");
265
388
  const code = import_node_fs.default.readFileSync(filePath, "utf-8");
@@ -306,7 +429,7 @@ async function resolveConfig(inlineConfig = {}, command) {
306
429
  base: merged.base ?? defaults.base,
307
430
  mode,
308
431
  target: merged.target ?? defaults.target,
309
- framework: merged.framework ?? defaults.framework,
432
+ framework: (merged.framework ?? defaults.framework) === "auto" ? detectFramework(root) : merged.framework,
310
433
  command,
311
434
  resolve: {
312
435
  // tsconfig paths 优先级最低:tsconfig < defaults < user config
@@ -353,7 +476,13 @@ async function resolveConfig(inlineConfig = {}, command) {
353
476
  if (envOptions.build) Object.assign(resolved.build, envOptions.build);
354
477
  resolved.environments.client = {
355
478
  consumer,
356
- entry: [],
479
+ buildEnabled: envOptions.buildEnabled ?? true,
480
+ entry: normalizeEnvironmentEntries(envOptions.entry, root),
481
+ html: import_node_path.default.resolve(
482
+ root,
483
+ envOptions.html ?? (resolved.target === "electron" ? resolved.electron.renderer : "index.html")
484
+ ),
485
+ driver: envOptions.driver,
357
486
  // 同引用 —— 精确镜像(assertClientEnvironmentMirror 校验)
358
487
  resolve: resolved.resolve,
359
488
  build: resolved.build
@@ -362,7 +491,10 @@ async function resolveConfig(inlineConfig = {}, command) {
362
491
  }
363
492
  resolved.environments[name] = {
364
493
  consumer,
365
- entry: (Array.isArray(envOptions.entry) ? envOptions.entry : envOptions.entry ? [envOptions.entry] : []).map((e) => import_node_path.default.resolve(root, e)),
494
+ buildEnabled: envOptions.buildEnabled ?? true,
495
+ entry: normalizeEnvironmentEntries(envOptions.entry, root),
496
+ html: envOptions.consumer === "client" && envOptions.html ? import_node_path.default.resolve(root, envOptions.html) : void 0,
497
+ driver: envOptions.driver,
366
498
  resolve: {
367
499
  alias: { ...resolved.resolve.alias, ...envOptions.resolve?.alias },
368
500
  extensions: envOptions.resolve?.extensions ?? [...resolved.resolve.extensions],
@@ -381,12 +513,13 @@ async function resolveConfig(inlineConfig = {}, command) {
381
513
  };
382
514
  }
383
515
  assertClientEnvironmentMirror(resolved);
384
- const filteredPlugins = rawPlugins.filter((p) => {
516
+ const filteredPlugins = orderPlugins(rawPlugins.filter((p) => {
385
517
  if (!p.apply) return true;
386
518
  if (typeof p.apply === "function") return p.apply(resolved, env);
387
519
  return p.apply === command;
388
- });
520
+ }));
389
521
  resolved.plugins = filteredPlugins;
522
+ await setupPluginApi(resolved, filteredPlugins);
390
523
  if (resolved.target === "electron") {
391
524
  const autoExternal = detectNativeDeps(root);
392
525
  if (autoExternal.length > 0) {
@@ -402,6 +535,10 @@ async function resolveConfig(inlineConfig = {}, command) {
402
535
  }
403
536
  return resolved;
404
537
  }
538
+ function normalizeEnvironmentEntries(entry, root) {
539
+ const entries = Array.isArray(entry) ? entry : entry ? [entry] : [];
540
+ return entries.map((item) => import_node_path.default.resolve(root, item));
541
+ }
405
542
  function detectNativeDeps(root) {
406
543
  const result = /* @__PURE__ */ new Set();
407
544
  const pkgJsonPath = import_node_path.default.resolve(root, "package.json");
@@ -522,6 +659,7 @@ var init_config = __esm({
522
659
  import_node_fs = __toESM(require("fs"), 1);
523
660
  init_defaults();
524
661
  init_logger();
662
+ init_plugin_api();
525
663
  CONFIG_FILES = [
526
664
  "nasti.config.ts",
527
665
  "nasti.config.js",
@@ -532,21 +670,11 @@ var init_config = __esm({
532
670
  });
533
671
 
534
672
  // src/core/plugin-container.ts
535
- function sortPlugins(plugins) {
536
- const pre = [];
537
- const normal = [];
538
- const post = [];
539
- for (const plugin of plugins) {
540
- if (plugin.enforce === "pre") pre.push(plugin);
541
- else if (plugin.enforce === "post") post.push(plugin);
542
- else normal.push(plugin);
543
- }
544
- return [...pre, ...normal, ...post];
545
- }
546
673
  var PluginContainer;
547
674
  var init_plugin_container = __esm({
548
675
  "src/core/plugin-container.ts"() {
549
676
  "use strict";
677
+ init_plugin_api();
550
678
  PluginContainer = class {
551
679
  plugins;
552
680
  config;
@@ -557,7 +685,7 @@ var init_plugin_container = __esm({
557
685
  constructor(config, environment) {
558
686
  this.config = config;
559
687
  this.environment = environment;
560
- this.plugins = sortPlugins(config.plugins);
688
+ this.plugins = orderPlugins(config.plugins);
561
689
  this.ctx = this.createContext();
562
690
  }
563
691
  createContext() {
@@ -656,17 +784,35 @@ var init_plugin_container = __esm({
656
784
  }
657
785
  });
658
786
 
787
+ // src/core/url.ts
788
+ function removeTimestampQuery(url) {
789
+ const hashIndex = url.indexOf("#");
790
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
791
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
792
+ const queryIndex = withoutHash.indexOf("?");
793
+ if (queryIndex < 0) return url;
794
+ const pathname = withoutHash.slice(0, queryIndex);
795
+ const query = withoutHash.slice(queryIndex + 1).split("&").filter((part) => !/^t=\d+$/.test(part)).join("&");
796
+ return pathname + (query ? `?${query}` : "") + hash;
797
+ }
798
+ var init_url = __esm({
799
+ "src/core/url.ts"() {
800
+ "use strict";
801
+ }
802
+ });
803
+
659
804
  // src/core/module-graph.ts
660
805
  var ModuleGraph;
661
806
  var init_module_graph = __esm({
662
807
  "src/core/module-graph.ts"() {
663
808
  "use strict";
809
+ init_url();
664
810
  ModuleGraph = class {
665
811
  urlToModuleMap = /* @__PURE__ */ new Map();
666
812
  idToModuleMap = /* @__PURE__ */ new Map();
667
813
  fileToModulesMap = /* @__PURE__ */ new Map();
668
814
  getModuleByUrl(url) {
669
- return this.urlToModuleMap.get(url);
815
+ return this.urlToModuleMap.get(removeTimestampQuery(url));
670
816
  }
671
817
  getModuleById(id) {
672
818
  return this.idToModuleMap.get(id);
@@ -675,10 +821,11 @@ var init_module_graph = __esm({
675
821
  return this.fileToModulesMap.get(file);
676
822
  }
677
823
  async ensureEntryFromUrl(url) {
678
- let mod = this.urlToModuleMap.get(url);
824
+ const normalizedUrl = removeTimestampQuery(url);
825
+ let mod = this.urlToModuleMap.get(normalizedUrl);
679
826
  if (mod) return mod;
680
- mod = this.createModule(url);
681
- this.urlToModuleMap.set(url, mod);
827
+ mod = this.createModule(normalizedUrl);
828
+ this.urlToModuleMap.set(normalizedUrl, mod);
682
829
  return mod;
683
830
  }
684
831
  createModule(url, id) {
@@ -692,6 +839,7 @@ var init_module_graph = __esm({
692
839
  acceptedHmrDeps: /* @__PURE__ */ new Set(),
693
840
  transformResult: null,
694
841
  lastHMRTimestamp: 0,
842
+ invalidationVersion: 0,
695
843
  isSelfAccepting: false
696
844
  };
697
845
  this.idToModuleMap.set(mod.id, mod);
@@ -734,10 +882,64 @@ var init_module_graph = __esm({
734
882
  }
735
883
  }
736
884
  }
885
+ /**
886
+ * 用一次转换得到的信息原子更新 import 与 HMR accept 关系。
887
+ * 依赖节点会在真正被浏览器请求前预先创建,这样入口模块先转换时也能建立完整图。
888
+ */
889
+ async updateModuleInfo(mod, importedUrls, acceptedUrls, isSelfAccepting, expectedInvalidationVersion) {
890
+ const importedModules = await Promise.all(
891
+ [...importedUrls].map((url) => this.ensureEntryFromUrl(url))
892
+ );
893
+ const acceptedModules = await Promise.all(
894
+ [...acceptedUrls].map((url) => this.ensureEntryFromUrl(url))
895
+ );
896
+ if (expectedInvalidationVersion !== void 0 && mod.invalidationVersion !== expectedInvalidationVersion) {
897
+ return null;
898
+ }
899
+ const previousImports = new Set(mod.importedModules);
900
+ for (const imported of previousImports) {
901
+ imported.importers.delete(mod);
902
+ }
903
+ mod.importedModules.clear();
904
+ mod.acceptedHmrDeps.clear();
905
+ for (const imported of importedModules) {
906
+ mod.importedModules.add(imported);
907
+ imported.importers.add(mod);
908
+ }
909
+ for (const accepted of acceptedModules) {
910
+ mod.acceptedHmrDeps.add(accepted);
911
+ }
912
+ mod.isSelfAccepting = isSelfAccepting;
913
+ const pruned = /* @__PURE__ */ new Set();
914
+ for (const imported of previousImports) {
915
+ if (!mod.importedModules.has(imported) && imported.importers.size === 0) {
916
+ pruned.add(imported);
917
+ }
918
+ }
919
+ return pruned;
920
+ }
737
921
  /** 使模块的转换缓存失效 */
738
- invalidateModule(mod) {
922
+ invalidateModule(mod, timestamp = Date.now()) {
739
923
  mod.transformResult = null;
740
- mod.lastHMRTimestamp = Date.now();
924
+ mod.lastHMRTimestamp = timestamp;
925
+ mod.invalidationVersion++;
926
+ }
927
+ /**
928
+ * 仅失效到 HMR 边界:显式接受依赖的模块本身不会重执行;自接受模块需要失效,
929
+ * 但不再继续影响其 importer。这样既能传播依赖时间戳,也不会隐式重复副作用。
930
+ */
931
+ invalidateModuleAndImporters(mod, timestamp = Date.now(), seen = /* @__PURE__ */ new Set()) {
932
+ if (seen.has(mod)) return;
933
+ seen.add(mod);
934
+ this.invalidateModule(mod, timestamp);
935
+ for (const importer of mod.importers) {
936
+ if (importer.acceptedHmrDeps.has(mod)) continue;
937
+ if (importer.isSelfAccepting) {
938
+ this.invalidateModule(importer, timestamp);
939
+ continue;
940
+ }
941
+ this.invalidateModuleAndImporters(importer, timestamp, seen);
942
+ }
741
943
  }
742
944
  /** 使所有模块缓存失效 */
743
945
  invalidateAll() {
@@ -748,34 +950,32 @@ var init_module_graph = __esm({
748
950
  /** 获取 HMR 传播边界 - 从变更模块向上遍历找到接受更新的边界 */
749
951
  getHmrBoundaries(mod) {
750
952
  const boundaries = [];
751
- const visited = /* @__PURE__ */ new Set();
752
- const propagate = (node, via) => {
753
- if (visited.has(node)) return true;
754
- visited.add(node);
755
- if (node.isSelfAccepting) {
756
- boundaries.push({ boundary: node, acceptedVia: via });
757
- return true;
953
+ const traversed = /* @__PURE__ */ new Set();
954
+ const addBoundary = (boundary, acceptedVia) => {
955
+ if (!boundaries.some(
956
+ (item) => item.boundary === boundary && item.acceptedVia === acceptedVia
957
+ )) {
958
+ boundaries.push({ boundary, acceptedVia });
758
959
  }
759
- if (node.acceptedHmrDeps.has(via)) {
760
- boundaries.push({ boundary: node, acceptedVia: via });
960
+ };
961
+ const propagate = (node) => {
962
+ if (traversed.has(node)) return true;
963
+ traversed.add(node);
964
+ if (node.isSelfAccepting) {
965
+ addBoundary(node, node);
761
966
  return true;
762
967
  }
763
968
  if (node.importers.size === 0) return false;
764
969
  for (const importer of node.importers) {
765
- if (!propagate(importer, node)) return false;
970
+ if (importer.acceptedHmrDeps.has(node)) {
971
+ addBoundary(importer, node);
972
+ continue;
973
+ }
974
+ if (!propagate(importer)) return false;
766
975
  }
767
976
  return true;
768
977
  };
769
- if (mod.isSelfAccepting) {
770
- boundaries.push({ boundary: mod, acceptedVia: mod });
771
- return boundaries;
772
- }
773
- for (const importer of mod.importers) {
774
- if (!propagate(importer, mod)) {
775
- return [];
776
- }
777
- }
778
- return boundaries;
978
+ return propagate(mod) ? boundaries : [];
779
979
  }
780
980
  };
781
981
  }
@@ -896,6 +1096,7 @@ var init_environment = __esm({
896
1096
  init_module_graph();
897
1097
  init_hot_channel();
898
1098
  init_debug();
1099
+ init_plugin_api();
899
1100
  debug = createDebugger("nasti:environment");
900
1101
  NastiEnvironment = class {
901
1102
  name;
@@ -904,6 +1105,7 @@ var init_environment = __esm({
904
1105
  config;
905
1106
  options;
906
1107
  hot;
1108
+ driver;
907
1109
  /** applyToEnvironment 过滤后的插件(init() 后可用) */
908
1110
  plugins = [];
909
1111
  /** per-env 插件容器(init() 后可用;dev 管线使用) */
@@ -911,6 +1113,8 @@ var init_environment = __esm({
911
1113
  /** per-env 模块图(dev 管线使用) */
912
1114
  moduleGraph;
913
1115
  candidatePlugins;
1116
+ pluginApi;
1117
+ buildMetadata = {};
914
1118
  initialized = false;
915
1119
  constructor(name, config, init = {}) {
916
1120
  const options = config.environments[name];
@@ -927,6 +1131,7 @@ var init_environment = __esm({
927
1131
  this.hot = init.hot ?? createNoopHotChannel();
928
1132
  this.moduleGraph = new ModuleGraph();
929
1133
  this.candidatePlugins = init.plugins ?? config.plugins;
1134
+ this.pluginApi = init.pluginApi ?? getPluginApi(config);
930
1135
  }
931
1136
  /** 过滤插件并建 per-env PluginContainer */
932
1137
  async init() {
@@ -937,10 +1142,57 @@ var init_environment = __esm({
937
1142
  { ...this.config, plugins: this.plugins },
938
1143
  this
939
1144
  );
1145
+ if (this.options.driver) {
1146
+ const claimed = [];
1147
+ for (const plugin of this.plugins) {
1148
+ const driver = await plugin.createEnvironmentDriver?.(this, this.pluginApi);
1149
+ if (driver) claimed.push({ plugin, driver });
1150
+ }
1151
+ if (claimed.length === 0) {
1152
+ throw new Error(
1153
+ `[nasti] environment "${this.name}" requested driver "${this.options.driver}", but no plugin provided it`
1154
+ );
1155
+ }
1156
+ if (claimed.length > 1) {
1157
+ throw new Error(
1158
+ `[nasti] environment "${this.name}" was claimed by multiple drivers: ` + claimed.map(({ plugin, driver }) => `${plugin.name} (${driver.name})`).join(", ")
1159
+ );
1160
+ }
1161
+ this.driver = claimed[0].driver;
1162
+ debug?.(`env "${this.name}" uses driver "${this.driver.name}"`);
1163
+ }
940
1164
  debug?.(`env "${this.name}" initialized (${this.plugins.length} plugins)`);
941
1165
  }
1166
+ getDriverContext() {
1167
+ return {
1168
+ environment: this,
1169
+ config: this.config,
1170
+ api: this.pluginApi,
1171
+ logger: this.config.logger
1172
+ };
1173
+ }
1174
+ setBuildMetadata(metadata) {
1175
+ const { entries: currentEntries, ...currentMetadata } = this.buildMetadata;
1176
+ const { entries, ...nextMetadata } = metadata;
1177
+ this.buildMetadata = {
1178
+ ...currentMetadata,
1179
+ ...nextMetadata,
1180
+ ...currentEntries || entries ? { entries: { ...currentEntries, ...entries } } : {}
1181
+ };
1182
+ }
1183
+ getBuildMetadata() {
1184
+ const { entries, ...metadata } = this.buildMetadata;
1185
+ return {
1186
+ ...metadata,
1187
+ ...entries ? { entries: { ...entries } } : {}
1188
+ };
1189
+ }
942
1190
  async close() {
943
- await this.hot.close?.();
1191
+ try {
1192
+ await this.driver?.close?.(this.getDriverContext());
1193
+ } finally {
1194
+ await this.hot.close?.();
1195
+ }
944
1196
  }
945
1197
  };
946
1198
  }
@@ -1005,7 +1257,8 @@ function transformCode(filename, code, options = {}) {
1005
1257
  importSource: options.jsxImportSource ?? "react",
1006
1258
  refresh: options.reactRefresh ?? false
1007
1259
  } : void 0,
1008
- sourcemap: options.sourcemap ?? true
1260
+ sourcemap: options.sourcemap ?? true,
1261
+ target: options.target
1009
1262
  });
1010
1263
  if (result.errors && result.errors.length > 0) {
1011
1264
  const msg = result.errors.map((e) => e.message ?? String(e)).join("\n");
@@ -1036,7 +1289,7 @@ function htmlPlugin(config) {
1036
1289
  transformIndexHtml(html) {
1037
1290
  const tags = [];
1038
1291
  if (config.command === "serve") {
1039
- const isReactLike = config.framework === "react" || config.framework === "auto";
1292
+ const isReactLike = config.framework === "react";
1040
1293
  if (isReactLike) {
1041
1294
  tags.push({
1042
1295
  tag: "script",
@@ -1090,8 +1343,8 @@ function serializeTag(tag) {
1090
1343
  }
1091
1344
  return ` <${tag.tag}${attrs}>${children}</${tag.tag}>`;
1092
1345
  }
1093
- async function readHtmlFile(root) {
1094
- const htmlPath = import_node_path2.default.resolve(root, "index.html");
1346
+ async function readHtmlFile(root, htmlFile = "index.html") {
1347
+ const htmlPath = import_node_path2.default.isAbsolute(htmlFile) ? htmlFile : import_node_path2.default.resolve(root, htmlFile);
1095
1348
  if (!import_node_fs2.default.existsSync(htmlPath)) return null;
1096
1349
  return import_node_fs2.default.readFileSync(htmlPath, "utf-8");
1097
1350
  }
@@ -1184,8 +1437,10 @@ __export(middleware_exports, {
1184
1437
  transformMiddleware: () => transformMiddleware,
1185
1438
  transformRequest: () => transformRequest
1186
1439
  });
1187
- function getReactRefreshRuntimeEsm() {
1188
- if (__refreshRuntimeCache) return __refreshRuntimeCache;
1440
+ function getReactRefreshRuntimeEsm(includeBoundaryHelpers = false) {
1441
+ if (__refreshRuntimeCache) {
1442
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
1443
+ }
1189
1444
  let cjsPath;
1190
1445
  try {
1191
1446
  const pkgPath = __require.resolve("react-refresh/package.json");
@@ -1220,7 +1475,7 @@ export const findAffectedHostInstances = __rt.findAffectedHostInstances;
1220
1475
  export const collectCustomHooksForSignature = __rt.collectCustomHooksForSignature;
1221
1476
  export default __rt;
1222
1477
  `;
1223
- return __refreshRuntimeCache;
1478
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
1224
1479
  }
1225
1480
  function buildReactRefreshWrapper(moduleUrl, transformedCode) {
1226
1481
  const urlLit = JSON.stringify(moduleUrl);
@@ -1246,22 +1501,40 @@ window.$RefreshReg$ = prevRefreshReg;
1246
1501
  window.$RefreshSig$ = prevRefreshSig;
1247
1502
 
1248
1503
  if (__nasti_hot__) {
1249
- __nasti_hot__.accept(() => {
1250
- clearTimeout(window.__nasti_refresh_timer__);
1251
- window.__nasti_refresh_timer__ = setTimeout(() => {
1252
- RefreshRuntime.performReactRefresh();
1253
- }, 30);
1504
+ let __nasti_current_exports__;
1505
+ __nasti_hot__.accept((nextExports) => {
1506
+ if (!nextExports) return;
1507
+ if (!__nasti_current_exports__) {
1508
+ __nasti_hot__.invalidate('Could not Fast Refresh (previous exports unavailable)');
1509
+ return;
1510
+ }
1511
+ const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate(
1512
+ ${urlLit},
1513
+ __nasti_current_exports__,
1514
+ nextExports,
1515
+ );
1516
+ if (invalidateMessage) __nasti_hot__.invalidate(invalidateMessage);
1517
+ });
1518
+ RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
1519
+ __nasti_current_exports__ = currentExports;
1520
+ RefreshRuntime.registerExportsForReactRefresh(${urlLit}, currentExports);
1254
1521
  });
1255
1522
  }
1256
1523
  `;
1257
1524
  }
1258
1525
  function injectImportMetaHot(code, moduleUrl) {
1259
- if (!/\bimport\.meta\.hot\b/.test(code)) return code;
1526
+ const hotRE = /\bimport\.meta\.hot\b/g;
1527
+ const matches = [...maskStringsAndComments(code).matchAll(hotRE)];
1528
+ if (matches.length === 0) return code;
1529
+ for (const match of matches.reverse()) {
1530
+ const start = match.index;
1531
+ code = code.slice(0, start) + "__nasti_hot__" + code.slice(start + match[0].length);
1532
+ }
1260
1533
  const urlLit = JSON.stringify(moduleUrl);
1261
1534
  const header = `import { createHotContext as __nasti_createHotContext__ } from "/@nasti/client";
1262
1535
  const __nasti_hot__ = __nasti_createHotContext__(${urlLit});
1263
1536
  `;
1264
- return header + code.replace(/\bimport\.meta\.hot\b/g, "__nasti_hot__");
1537
+ return header + code;
1265
1538
  }
1266
1539
  function transformMiddleware(ctx) {
1267
1540
  ctx.envDefine = buildEnvDefine(
@@ -1288,7 +1561,10 @@ function transformMiddleware(ctx) {
1288
1561
  return;
1289
1562
  }
1290
1563
  if (url === "/" || url.endsWith(".html")) {
1291
- const html = await readHtmlFile(ctx.config.root);
1564
+ const html = await readHtmlFile(
1565
+ ctx.config.root,
1566
+ ctx.config.environments.client?.html
1567
+ );
1292
1568
  if (html) {
1293
1569
  let processedHtml = html;
1294
1570
  for (const plugin of ctx.config.plugins) {
@@ -1334,13 +1610,14 @@ function transformMiddleware(ctx) {
1334
1610
  }
1335
1611
  async function transformRequest(url, ctx) {
1336
1612
  const { config, pluginContainer, moduleGraph } = ctx;
1613
+ url = removeTimestampQuery(url);
1337
1614
  const cleanReqUrl = url.split("?")[0];
1338
1615
  const cached2 = moduleGraph.getModuleByUrl(url);
1339
1616
  if (cached2?.transformResult) {
1340
1617
  return cached2.transformResult;
1341
1618
  }
1342
1619
  if (cleanReqUrl === "/@react-refresh") {
1343
- return { code: getReactRefreshRuntimeEsm() };
1620
+ return { code: getReactRefreshRuntimeEsm(true) };
1344
1621
  }
1345
1622
  if (cleanReqUrl.startsWith("/@modules/") && url.includes("?")) {
1346
1623
  const idParam = new URLSearchParams(url.slice(url.indexOf("?") + 1)).get("id");
@@ -1376,6 +1653,8 @@ async function transformRequest(url, ctx) {
1376
1653
  }
1377
1654
  const rawQuery = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
1378
1655
  if (rawQuery && !/^t=\d+$/.test(rawQuery)) {
1656
+ const mod2 = await moduleGraph.ensureEntryFromUrl(url);
1657
+ const transformVersion2 = mod2.invalidationVersion;
1379
1658
  const loaded = await pluginContainer.load(url);
1380
1659
  if (loaded != null) {
1381
1660
  let code2 = typeof loaded === "string" ? loaded : loaded.code;
@@ -1383,16 +1662,28 @@ async function transformRequest(url, ctx) {
1383
1662
  if (transformed != null) {
1384
1663
  code2 = typeof transformed === "string" ? transformed : transformed.code;
1385
1664
  }
1386
- const mod2 = await moduleGraph.ensureEntryFromUrl(url);
1387
- moduleGraph.registerModule(mod2, cleanReqUrl);
1388
- code2 = injectImportMetaHot(code2, url);
1665
+ const parentFile = resolveUrlToFile(cleanReqUrl, config.root) ?? cleanReqUrl;
1666
+ moduleGraph.registerModule(mod2, parentFile);
1667
+ const hotInfo2 = rewriteHotAcceptDeps(code2, config, parentFile);
1668
+ code2 = injectImportMetaHot(hotInfo2.code, url);
1389
1669
  code2 = replaceEnvInCode(code2, ctx.envDefine ?? buildEnvDefine(
1390
1670
  loadEnv(config.mode, config.root, config.envPrefix),
1391
1671
  config.mode
1392
1672
  ));
1393
- code2 = rewriteImports(code2, config, cleanReqUrl);
1673
+ const importedUrls2 = /* @__PURE__ */ new Set();
1674
+ code2 = rewriteImports(code2, config, parentFile, importedUrls2, moduleGraph);
1675
+ const pruned2 = await moduleGraph.updateModuleInfo(
1676
+ mod2,
1677
+ importedUrls2,
1678
+ hotInfo2.acceptedUrls,
1679
+ hotInfo2.isSelfAccepting,
1680
+ transformVersion2
1681
+ );
1394
1682
  const transformResult2 = { code: code2 };
1395
- mod2.transformResult = transformResult2;
1683
+ if (pruned2) {
1684
+ if (pruned2.size > 0) ctx.onPrune?.([...pruned2].map((item) => item.url));
1685
+ mod2.transformResult = transformResult2;
1686
+ }
1396
1687
  return transformResult2;
1397
1688
  }
1398
1689
  }
@@ -1400,6 +1691,7 @@ async function transformRequest(url, ctx) {
1400
1691
  if (!filePath || !import_node_fs4.default.existsSync(filePath)) return null;
1401
1692
  const mod = await moduleGraph.ensureEntryFromUrl(url);
1402
1693
  moduleGraph.registerModule(mod, filePath);
1694
+ const transformVersion = mod.invalidationVersion;
1403
1695
  if (cleanReqUrl.startsWith("/@modules/")) {
1404
1696
  const code2 = await bundlePackageAsEsm(filePath, config.root);
1405
1697
  const transformResult2 = { code: code2 };
@@ -1426,9 +1718,10 @@ async function transformRequest(url, ctx) {
1426
1718
  if (useRefresh) {
1427
1719
  code = buildReactRefreshWrapper(stableUrl, code);
1428
1720
  wrappedWithRefresh = true;
1429
- mod.isSelfAccepting = true;
1430
1721
  }
1431
1722
  }
1723
+ const hotInfo = rewriteHotAcceptDeps(code, config, filePath);
1724
+ code = hotInfo.code;
1432
1725
  if (!wrappedWithRefresh) {
1433
1726
  code = injectImportMetaHot(code, stableUrl);
1434
1727
  }
@@ -1437,9 +1730,20 @@ async function transformRequest(url, ctx) {
1437
1730
  config.mode
1438
1731
  );
1439
1732
  code = replaceEnvInCode(code, envDefine);
1440
- code = rewriteImports(code, config, filePath);
1733
+ const importedUrls = /* @__PURE__ */ new Set();
1734
+ code = rewriteImports(code, config, filePath, importedUrls, moduleGraph);
1735
+ const pruned = await moduleGraph.updateModuleInfo(
1736
+ mod,
1737
+ importedUrls,
1738
+ hotInfo.acceptedUrls,
1739
+ wrappedWithRefresh || hotInfo.isSelfAccepting,
1740
+ transformVersion
1741
+ );
1441
1742
  const transformResult = { code };
1442
- mod.transformResult = transformResult;
1743
+ if (pruned) {
1744
+ if (pruned.size > 0) ctx.onPrune?.([...pruned].map((item) => item.url));
1745
+ mod.transformResult = transformResult;
1746
+ }
1443
1747
  return transformResult;
1444
1748
  }
1445
1749
  async function loadVirtualModule(spec, ctx) {
@@ -1644,49 +1948,202 @@ async function injectCjsNamedExports(code, entryFile) {
1644
1948
  return code;
1645
1949
  }
1646
1950
  }
1647
- function rewriteImports(code, config, filePath) {
1951
+ function rewriteImports(code, config, filePath, importedUrls, moduleGraph) {
1952
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
1953
+ const transformSpec = (spec) => {
1954
+ const resolved = removeTimestampQuery(resolveSpec(spec));
1955
+ importedUrls?.add(resolved);
1956
+ const timestamp = moduleGraph?.getModuleByUrl(resolved)?.lastHMRTimestamp ?? 0;
1957
+ return timestamp > 0 ? appendTimestampQuery(resolved, timestamp) : resolved;
1958
+ };
1959
+ return code.replace(
1960
+ /\bfrom\s+(['"])([^'"]+)\1/g,
1961
+ (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
1962
+ ).replace(
1963
+ /\bimport\s+(['"])([^'"]+)\1/g,
1964
+ (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
1965
+ ).replace(
1966
+ /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
1967
+ (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
1968
+ );
1969
+ }
1970
+ function createModuleSpecifierResolver(config, filePath) {
1648
1971
  const root = config.root;
1649
1972
  const fileDir = import_node_path4.default.dirname(filePath);
1650
1973
  const aliasEntries = Object.entries(config.resolve.alias).sort(
1651
1974
  ([a], [b]) => b.length - a.length
1652
1975
  );
1653
1976
  const toRootUrl = (abs) => "/" + import_node_path4.default.relative(root, abs).replace(/\\/g, "/");
1654
- const transformSpec = (spec) => {
1655
- const suffixMatch = spec.match(/[?#].*$/);
1977
+ return (specifier) => {
1978
+ const suffixMatch = specifier.match(/[?#].*$/);
1656
1979
  const suffix = suffixMatch ? suffixMatch[0] : "";
1657
- const baseSpec = suffix ? spec.slice(0, -suffix.length) : spec;
1980
+ const baseSpec = suffix ? specifier.slice(0, -suffix.length) : specifier;
1658
1981
  for (const [key, value] of aliasEntries) {
1659
1982
  if (baseSpec === key || baseSpec.startsWith(key + "/")) {
1660
1983
  const aliasBase = resolveAliasTarget(value, root);
1661
1984
  const sub = baseSpec.slice(key.length).replace(/^\//, "");
1662
1985
  const target = sub ? import_node_path4.default.join(aliasBase, sub) : aliasBase;
1663
1986
  const resolved = tryResolveDiskPath(target);
1664
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
1987
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1665
1988
  }
1666
1989
  }
1667
1990
  if (baseSpec.startsWith("./") || baseSpec.startsWith("../")) {
1668
- const target = import_node_path4.default.resolve(fileDir, baseSpec);
1669
- const resolved = tryResolveDiskPath(target);
1670
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
1991
+ const resolved = tryResolveDiskPath(import_node_path4.default.resolve(fileDir, baseSpec));
1992
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1671
1993
  }
1672
1994
  if (baseSpec.startsWith("/") && !baseSpec.startsWith("/@")) {
1673
- const target = import_node_path4.default.join(root, baseSpec.replace(/^\//, ""));
1674
- const resolved = tryResolveDiskPath(target);
1675
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
1995
+ const resolved = tryResolveDiskPath(import_node_path4.default.join(root, baseSpec.replace(/^\//, "")));
1996
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1676
1997
  }
1677
- if (baseSpec.startsWith("/")) return spec;
1678
- return `/@modules/${spec}`;
1998
+ if (baseSpec.startsWith("/")) return specifier;
1999
+ return `/@modules/${specifier}`;
1679
2000
  };
1680
- return code.replace(
1681
- /\bfrom\s+(['"])([^'"]+)\1/g,
1682
- (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
1683
- ).replace(
1684
- /\bimport\s+(['"])([^'"]+)\1/g,
1685
- (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
1686
- ).replace(
1687
- /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
1688
- (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
1689
- );
2001
+ }
2002
+ function rewriteHotAcceptDeps(code, config, filePath) {
2003
+ const acceptedUrls = /* @__PURE__ */ new Set();
2004
+ const edits = [];
2005
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
2006
+ const acceptRE = /(?:\bimport\.meta\.hot|\b__nasti_hot__)(?:(?:\?\.)|\.)accept\s*\(/g;
2007
+ const searchableCode = maskStringsAndComments(code);
2008
+ let isSelfAccepting = false;
2009
+ let match;
2010
+ while (match = acceptRE.exec(searchableCode)) {
2011
+ let cursor = match.index + match[0].length;
2012
+ const skipTrivia = () => {
2013
+ while (cursor < code.length) {
2014
+ if (/\s/.test(code[cursor])) {
2015
+ cursor++;
2016
+ continue;
2017
+ }
2018
+ if (code[cursor] === "/" && code[cursor + 1] === "/") {
2019
+ cursor += 2;
2020
+ while (cursor < code.length && code[cursor] !== "\n") cursor++;
2021
+ continue;
2022
+ }
2023
+ if (code[cursor] === "/" && code[cursor + 1] === "*") {
2024
+ cursor += 2;
2025
+ while (cursor < code.length && !(code[cursor] === "*" && code[cursor + 1] === "/")) cursor++;
2026
+ cursor += 2;
2027
+ continue;
2028
+ }
2029
+ break;
2030
+ }
2031
+ };
2032
+ skipTrivia();
2033
+ const first = code[cursor];
2034
+ if (!first || first === ")" || first !== "[" && first !== "'" && first !== '"' && first !== "`") {
2035
+ isSelfAccepting = true;
2036
+ continue;
2037
+ }
2038
+ const readLiteral = () => {
2039
+ const quote = code[cursor];
2040
+ if (quote !== "'" && quote !== '"' && quote !== "`") return;
2041
+ const start = cursor;
2042
+ cursor++;
2043
+ let raw = "";
2044
+ while (cursor < code.length) {
2045
+ const char = code[cursor];
2046
+ if (char === "\\") {
2047
+ raw += code[cursor + 1] ?? "";
2048
+ cursor += 2;
2049
+ continue;
2050
+ }
2051
+ if (char === quote) {
2052
+ cursor++;
2053
+ const resolved = removeTimestampQuery(resolveSpec(raw));
2054
+ acceptedUrls.add(resolved);
2055
+ edits.push({ start, end: cursor, value: JSON.stringify(resolved) });
2056
+ return;
2057
+ }
2058
+ if (quote === "`" && char === "$" && code[cursor + 1] === "{") return;
2059
+ raw += char;
2060
+ cursor++;
2061
+ }
2062
+ };
2063
+ if (first === "[") {
2064
+ cursor++;
2065
+ while (cursor < code.length) {
2066
+ skipTrivia();
2067
+ if (code[cursor] === ",") {
2068
+ cursor++;
2069
+ skipTrivia();
2070
+ }
2071
+ if (code[cursor] === "]") break;
2072
+ const before = cursor;
2073
+ readLiteral();
2074
+ if (cursor === before) break;
2075
+ }
2076
+ } else {
2077
+ readLiteral();
2078
+ }
2079
+ }
2080
+ for (const edit of edits.sort((a, b) => b.start - a.start)) {
2081
+ code = code.slice(0, edit.start) + edit.value + code.slice(edit.end);
2082
+ }
2083
+ return { code, acceptedUrls, isSelfAccepting };
2084
+ }
2085
+ function maskStringsAndComments(code) {
2086
+ const masked = code.split("");
2087
+ let state = "code";
2088
+ const isRegexStart = (index2) => {
2089
+ let previous = index2 - 1;
2090
+ while (previous >= 0 && /\s/.test(code[previous])) previous--;
2091
+ return previous < 0 || "=(:,!&|?{};[]+-*%^~<>".includes(code[previous]);
2092
+ };
2093
+ for (let i = 0; i < code.length; i++) {
2094
+ const char = code[i];
2095
+ const next = code[i + 1];
2096
+ if (state === "code") {
2097
+ if (char === "'") state = "single";
2098
+ else if (char === '"') state = "double";
2099
+ else if (char === "`") state = "template";
2100
+ else if (char === "/" && next === "/") state = "line-comment";
2101
+ else if (char === "/" && next === "*") state = "block-comment";
2102
+ else if (char === "/" && isRegexStart(i)) state = "regex";
2103
+ else continue;
2104
+ masked[i] = " ";
2105
+ continue;
2106
+ }
2107
+ if (state === "line-comment") {
2108
+ if (char === "\n") {
2109
+ state = "code";
2110
+ } else {
2111
+ masked[i] = " ";
2112
+ }
2113
+ continue;
2114
+ }
2115
+ if (state === "block-comment") {
2116
+ masked[i] = char === "\n" ? "\n" : " ";
2117
+ if (char === "*" && next === "/") {
2118
+ masked[i + 1] = " ";
2119
+ i++;
2120
+ state = "code";
2121
+ }
2122
+ continue;
2123
+ }
2124
+ if (state === "regex" || state === "regex-class") {
2125
+ masked[i] = char === "\n" ? "\n" : " ";
2126
+ if (char === "\\") {
2127
+ if (i + 1 < code.length) masked[++i] = " ";
2128
+ } else if (state === "regex" && char === "[") {
2129
+ state = "regex-class";
2130
+ } else if (state === "regex-class" && char === "]") {
2131
+ state = "regex";
2132
+ } else if (state === "regex" && char === "/") {
2133
+ state = "code";
2134
+ }
2135
+ continue;
2136
+ }
2137
+ masked[i] = char === "\n" ? "\n" : " ";
2138
+ if (char === "\\") {
2139
+ if (i + 1 < code.length) masked[++i] = " ";
2140
+ continue;
2141
+ }
2142
+ if (state === "single" && char === "'" || state === "double" && char === '"' || state === "template" && char === "`") {
2143
+ state = "code";
2144
+ }
2145
+ }
2146
+ return masked.join("");
1690
2147
  }
1691
2148
  function resolveAliasTarget(value, root) {
1692
2149
  if (import_node_path4.default.isAbsolute(value) && import_node_fs4.default.existsSync(value)) return value;
@@ -1711,6 +2168,12 @@ function isUnderRoot(abs, root) {
1711
2168
  const rel = import_node_path4.default.relative(root, abs);
1712
2169
  return !!rel && !rel.startsWith("..") && !import_node_path4.default.isAbsolute(rel);
1713
2170
  }
2171
+ function appendTimestampQuery(url, timestamp) {
2172
+ const hashIndex = url.indexOf("#");
2173
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
2174
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
2175
+ return `${withoutHash}${withoutHash.includes("?") ? "&" : "?"}t=${timestamp}${hash}`;
2176
+ }
1714
2177
  function externalSpecToModuleUrl(spec, baseDir, root) {
1715
2178
  const resolved = resolveNodeModule(baseDir, spec);
1716
2179
  if (!resolved) return `/@modules/${spec}`;
@@ -1854,30 +2317,29 @@ function isModuleRequest(url) {
1854
2317
  function getHmrClientCode() {
1855
2318
  return `
1856
2319
  // Nasti HMR Client
1857
- const socket = new WebSocket(\`ws://\${location.host}\`, 'nasti-hmr');
2320
+ const socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
2321
+ const socket = new WebSocket(socketProtocol + '://' + location.host, 'nasti-hmr');
1858
2322
  const hotModulesMap = new Map();
1859
2323
  const disposeMap = new Map();
1860
2324
  const pruneMap = new Map();
2325
+ const dataMap = new Map();
2326
+ let updateQueue = [];
2327
+ let pendingUpdateQueue = false;
1861
2328
 
1862
2329
  socket.addEventListener('message', async ({ data }) => {
1863
2330
  const payload = JSON.parse(data);
1864
2331
  switch (payload.type) {
1865
2332
  case 'connected':
1866
- console.log('[nasti] connected.');
2333
+ console.debug('[nasti] connected.');
1867
2334
  clearErrorOverlay();
1868
2335
  break;
1869
2336
  case 'update':
1870
2337
  try {
1871
- await Promise.all(payload.updates.map((update) => {
1872
- if (update.type === 'js-update') {
1873
- return fetchUpdate(update);
1874
- } else if (update.type === 'css-update') {
1875
- return updateCss(update.path);
1876
- }
1877
- }));
2338
+ // CSS \u5728 unbundled \u6A21\u5F0F\u4E0B\u4E5F\u662F\u4F1A\u6CE8\u5165 <style> \u7684 JS \u6A21\u5757\uFF0C\u548C\u666E\u901A JS
2339
+ // \u4E00\u6837\u91CD\u65B0 import \u624D\u80FD\u6267\u884C dispose/accept \u5E76\u4FDD\u6301\u9875\u9762\u72B6\u6001\u3002
2340
+ await Promise.all(payload.updates.map(queueUpdate));
1878
2341
  clearErrorOverlay();
1879
- console.log('[nasti] HMR update complete, reloading page');
1880
- location.reload();
2342
+ console.debug('[nasti] HMR update complete.');
1881
2343
  } catch (err) {
1882
2344
  console.error('[nasti] HMR update failed:', err);
1883
2345
  showErrorOverlay(err);
@@ -1888,10 +2350,17 @@ socket.addEventListener('message', async ({ data }) => {
1888
2350
  location.reload();
1889
2351
  break;
1890
2352
  case 'prune':
1891
- payload.paths.forEach((p) => {
1892
- const cb = pruneMap.get(p);
1893
- if (cb) cb();
1894
- });
2353
+ await Promise.all(payload.paths.map(async (path) => {
2354
+ const data = dataMap.get(path);
2355
+ const dispose = disposeMap.get(path);
2356
+ const prune = pruneMap.get(path);
2357
+ if (dispose) await dispose(data);
2358
+ if (prune) await prune(data);
2359
+ hotModulesMap.delete(path);
2360
+ disposeMap.delete(path);
2361
+ pruneMap.delete(path);
2362
+ dataMap.delete(path);
2363
+ }));
1895
2364
  break;
1896
2365
  case 'error':
1897
2366
  console.error('[nasti] error:', payload.err.message);
@@ -1900,33 +2369,64 @@ socket.addEventListener('message', async ({ data }) => {
1900
2369
  }
1901
2370
  });
1902
2371
 
1903
- // \u81EA\u52A8\u91CD\u8FDE\uFF08\u65AD\u7EBF\u65F6\u6307\u6570\u9000\u907F\uFF09
2372
+ // \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
1904
2373
  let reconnectTimer = 0;
1905
2374
  socket.addEventListener('close', () => {
1906
2375
  clearTimeout(reconnectTimer);
1907
2376
  reconnectTimer = setTimeout(() => location.reload(), 1000);
1908
2377
  });
1909
2378
 
2379
+ /**
2380
+ * \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
2381
+ * \u6539\u53D8\u6A21\u5757\u5E94\u7528\u987A\u5E8F\u3002\u8FD9\u4E0E Vite HMRClient \u7684 fetch/apply \u4E24\u9636\u6BB5\u4E00\u81F4\u3002
2382
+ */
2383
+ async function queueUpdate(update) {
2384
+ updateQueue.push(fetchUpdate(update));
2385
+ if (pendingUpdateQueue) return;
2386
+
2387
+ pendingUpdateQueue = true;
2388
+ await Promise.resolve();
2389
+ pendingUpdateQueue = false;
2390
+ const loading = updateQueue;
2391
+ updateQueue = [];
2392
+ const applyUpdates = await Promise.all(loading);
2393
+ for (const apply of applyUpdates) {
2394
+ if (apply) apply();
2395
+ }
2396
+ }
2397
+
1910
2398
  async function fetchUpdate(update) {
1911
2399
  const mod = hotModulesMap.get(update.path);
1912
- // \u5148\u8DD1 dispose\uFF08\u7ED9\u6A21\u5757\u673A\u4F1A\u6E05\u7406\u526F\u4F5C\u7528\uFF09
1913
- const dispose = disposeMap.get(update.path);
1914
- if (dispose) dispose();
2400
+ // \u5C1A\u672A\u5728\u5F53\u524D\u9875\u9762\u52A0\u8F7D\u7684\u52A8\u6001\u6A21\u5757\u4E0D\u9700\u8981\u66F4\u65B0\u3002
2401
+ if (!mod) return;
1915
2402
 
1916
- const newMod = await import(update.acceptedPath + '?t=' + update.timestamp);
1917
- if (mod) {
1918
- // \u590D\u5236\u56DE\u8C03\u6570\u7EC4\u907F\u514D\u56DE\u8C03\u5185\u90E8\u53C8\u4FEE\u6539 hotModulesMap \u9020\u6210\u8FED\u4EE3\u5F02\u5E38
1919
- [...mod.callbacks].forEach((cb) => cb(newMod));
1920
- }
2403
+ // \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
2404
+ const qualifiedCallbacks = mod.callbacks.filter(({ deps }) =>
2405
+ deps.includes(update.acceptedPath)
2406
+ );
2407
+ const isSelfUpdate = update.path === update.acceptedPath;
2408
+ if (!isSelfUpdate && qualifiedCallbacks.length === 0) return;
2409
+
2410
+ const dispose = disposeMap.get(update.acceptedPath);
2411
+ if (dispose) await dispose(dataMap.get(update.acceptedPath));
2412
+ const newMod = await import(appendTimestampQuery(update.acceptedPath, update.timestamp));
2413
+
2414
+ return () => {
2415
+ for (const { deps, fn } of qualifiedCallbacks) {
2416
+ fn(deps.map((dep) => dep === update.acceptedPath ? newMod : undefined));
2417
+ }
2418
+ const detail = isSelfUpdate
2419
+ ? update.path
2420
+ : update.acceptedPath + ' via ' + update.path;
2421
+ console.debug('[nasti] hot updated:', detail);
2422
+ };
1921
2423
  }
1922
2424
 
1923
- function updateCss(path) {
1924
- const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
1925
- if (el) {
1926
- return fetch(path + '?t=' + Date.now())
1927
- .then(r => r.text())
1928
- .then(css => { el.textContent = css; });
1929
- }
2425
+ function appendTimestampQuery(url, timestamp) {
2426
+ const hashIndex = url.indexOf('#');
2427
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : '';
2428
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
2429
+ return withoutHash + (withoutHash.includes('?') ? '&' : '?') + 't=' + timestamp + hash;
1930
2430
  }
1931
2431
 
1932
2432
  function clearErrorOverlay() {
@@ -1954,23 +2454,30 @@ function showErrorOverlay(err) {
1954
2454
  document.body.appendChild(overlay);
1955
2455
  }
1956
2456
 
1957
- /**
1958
- * \u751F\u6210 import.meta.hot \u7684 hot context\u3002
1959
- * \u5173\u952E\u7EA6\u675F\uFF1A\u540C\u4E00 ownerPath \u7684 accept \u56DE\u8C03\u5FC5\u987B\u66FF\u6362\uFF08\u4E0D\u662F append\uFF09\u3002
1960
- * \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
1961
- * \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
1962
- */
1963
2457
  export function createHotContext(ownerPath) {
2458
+ if (!dataMap.has(ownerPath)) dataMap.set(ownerPath, {});
2459
+
2460
+ // \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
2461
+ const existing = hotModulesMap.get(ownerPath);
2462
+ if (existing) existing.callbacks = [];
2463
+
2464
+ const acceptDeps = (deps, callback = () => {}) => {
2465
+ const mod = hotModulesMap.get(ownerPath) || { id: ownerPath, callbacks: [] };
2466
+ mod.callbacks.push({ deps, fn: callback });
2467
+ hotModulesMap.set(ownerPath, mod);
2468
+ };
2469
+
1964
2470
  return {
1965
2471
  accept(deps, callback) {
1966
- // \u81EA\u63A5\u53D7: hot.accept() \u6216 hot.accept(callback)
1967
2472
  if (typeof deps === 'function' || deps === undefined) {
1968
- hotModulesMap.set(ownerPath, { callbacks: [deps || (() => {})] });
1969
- return;
2473
+ acceptDeps([ownerPath], ([mod]) => deps?.(mod));
2474
+ } else if (typeof deps === 'string') {
2475
+ acceptDeps([deps], ([mod]) => callback?.(mod));
2476
+ } else if (Array.isArray(deps)) {
2477
+ acceptDeps(deps, callback);
2478
+ } else {
2479
+ throw new Error('invalid hot.accept() usage');
1970
2480
  }
1971
- // \u4F9D\u8D56\u63A5\u53D7: hot.accept(deps, callback)\uFF0C\u591A\u6B21\u8C03\u7528\u8FFD\u52A0
1972
- const existing = hotModulesMap.get(ownerPath)?.callbacks ?? [];
1973
- hotModulesMap.set(ownerPath, { callbacks: [...existing, callback] });
1974
2481
  },
1975
2482
  prune(callback) {
1976
2483
  pruneMap.set(ownerPath, callback);
@@ -1981,12 +2488,12 @@ export function createHotContext(ownerPath) {
1981
2488
  invalidate() {
1982
2489
  location.reload();
1983
2490
  },
1984
- data: {},
2491
+ data: dataMap.get(ownerPath),
1985
2492
  };
1986
2493
  }
1987
2494
  `;
1988
2495
  }
1989
- var import_node_path4, import_node_fs4, import_node_module, import_node_url2, import_picocolors3, import_meta, __dirname_esm, __require, __refreshRuntimeCache, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
2496
+ var import_node_path4, import_node_fs4, import_node_module, import_node_url2, import_picocolors3, import_meta, __dirname_esm, __require, __refreshRuntimeCache, REACT_REFRESH_BOUNDARY_HELPERS, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
1990
2497
  var init_middleware = __esm({
1991
2498
  "src/server/middleware.ts"() {
1992
2499
  "use strict";
@@ -1998,10 +2505,74 @@ var init_middleware = __esm({
1998
2505
  init_transformer();
1999
2506
  init_html();
2000
2507
  init_env();
2508
+ init_url();
2001
2509
  import_meta = {};
2002
2510
  __dirname_esm = import_node_path4.default.dirname((0, import_node_url2.fileURLToPath)(import_meta.url));
2003
2511
  __require = (0, import_node_module.createRequire)(import_meta.url);
2004
2512
  __refreshRuntimeCache = null;
2513
+ REACT_REFRESH_BOUNDARY_HELPERS = `
2514
+ function __nastiIsPlainObject(obj) {
2515
+ return Object.prototype.toString.call(obj) === '[object Object]' &&
2516
+ (obj.constructor === Object || obj.constructor === undefined);
2517
+ }
2518
+ function __nastiIsCompoundComponent(type) {
2519
+ if (!__nastiIsPlainObject(type)) return false;
2520
+ for (const key in type) {
2521
+ if (!isLikelyComponentType(type[key])) return false;
2522
+ }
2523
+ return true;
2524
+ }
2525
+ export function registerExportsForReactRefresh(filename, moduleExports) {
2526
+ for (const key in moduleExports) {
2527
+ if (key === '__esModule') continue;
2528
+ const value = moduleExports[key];
2529
+ if (isLikelyComponentType(value)) {
2530
+ register(value, filename + ' export ' + key);
2531
+ } else if (__nastiIsCompoundComponent(value)) {
2532
+ for (const subKey in value) {
2533
+ register(value[subKey], filename + ' export ' + key + '-' + subKey);
2534
+ }
2535
+ }
2536
+ }
2537
+ }
2538
+ let __nastiRefreshTimer;
2539
+ function __nastiEnqueueRefresh() {
2540
+ clearTimeout(__nastiRefreshTimer);
2541
+ __nastiRefreshTimer = setTimeout(() => performReactRefresh(), 16);
2542
+ }
2543
+ function __nastiCheckExports(ignored, exports, predicate) {
2544
+ for (const key in exports) {
2545
+ if (ignored.includes(key)) continue;
2546
+ if (!predicate(key, exports[key])) return key;
2547
+ }
2548
+ return true;
2549
+ }
2550
+ export function validateRefreshBoundaryAndEnqueueUpdate(id, prevExports, nextExports) {
2551
+ const ignored = window.__getReactRefreshIgnoredExports?.({ id }) ?? [];
2552
+ if (__nastiCheckExports(ignored, prevExports, (key) => key in nextExports) !== true) {
2553
+ return 'Could not Fast Refresh (export removed)';
2554
+ }
2555
+ if (__nastiCheckExports(ignored, nextExports, (key) => key in prevExports) !== true) {
2556
+ return 'Could not Fast Refresh (new export)';
2557
+ }
2558
+ let hasExports = false;
2559
+ const compatible = __nastiCheckExports(ignored, nextExports, (key, value) => {
2560
+ hasExports = true;
2561
+ return isLikelyComponentType(value) ||
2562
+ __nastiIsCompoundComponent(value) ||
2563
+ prevExports[key] === value;
2564
+ });
2565
+ if (!hasExports) {
2566
+ return 'Could not Fast Refresh (no exports)';
2567
+ }
2568
+ if (compatible === true) {
2569
+ __nastiEnqueueRefresh();
2570
+ return;
2571
+ }
2572
+ return 'Could not Fast Refresh ("' + compatible + '" export is incompatible)';
2573
+ }
2574
+ export const __hmr_import = (module) => import(module);
2575
+ `;
2005
2576
  REACT_REFRESH_GLOBAL_PREAMBLE = `
2006
2577
  import RefreshRuntime from "/@react-refresh";
2007
2578
  RefreshRuntime.injectIntoGlobalHook(window);
@@ -2028,8 +2599,10 @@ async function handleFileChange(file, server) {
2028
2599
  }
2029
2600
  const updates = [];
2030
2601
  const timestamp = Date.now();
2602
+ const graph = moduleGraph;
2603
+ const invalidatedModules = /* @__PURE__ */ new Set();
2031
2604
  for (const mod of mods) {
2032
- moduleGraph.invalidateModule(mod);
2605
+ graph.invalidateModuleAndImporters(mod, timestamp, invalidatedModules);
2033
2606
  const ctx = {
2034
2607
  file,
2035
2608
  timestamp,
@@ -2047,19 +2620,25 @@ async function handleFileChange(file, server) {
2047
2620
  }
2048
2621
  }
2049
2622
  for (const affected of affectedModules) {
2050
- const boundaries = moduleGraph.getHmrBoundaries(affected);
2623
+ graph.invalidateModuleAndImporters(affected, timestamp, invalidatedModules);
2624
+ const boundaries = graph.getHmrBoundaries(affected);
2051
2625
  if (boundaries.length === 0) {
2052
2626
  logger.info(import_picocolors4.default.green("page reload ") + import_picocolors4.default.dim(shortFile), { timestamp: true });
2053
2627
  ws.send({ type: "full-reload", path: relativePath });
2054
2628
  return;
2055
2629
  }
2056
- for (const { boundary } of boundaries) {
2057
- updates.push({
2630
+ for (const { boundary, acceptedVia } of boundaries) {
2631
+ const update = {
2058
2632
  type: boundary.type === "css" ? "css-update" : "js-update",
2059
2633
  path: boundary.url,
2060
- acceptedPath: affected.url,
2634
+ acceptedPath: acceptedVia.url,
2061
2635
  timestamp
2062
- });
2636
+ };
2637
+ if (!updates.some(
2638
+ (existing) => existing.type === update.type && existing.path === update.path && existing.acceptedPath === update.acceptedPath
2639
+ )) {
2640
+ updates.push(update);
2641
+ }
2063
2642
  }
2064
2643
  }
2065
2644
  }
@@ -2130,6 +2709,7 @@ function resolvePlugin(config) {
2130
2709
  }
2131
2710
  if (!source.startsWith("/") && !source.startsWith(".")) {
2132
2711
  if (vueRuntimeEntry && source === "vue") return vueRuntimeEntry;
2712
+ if (config.command === "build") return null;
2133
2713
  try {
2134
2714
  const resolved = require2.resolve(source, {
2135
2715
  paths: [importer ? import_node_path6.default.dirname(importer) : config.root]
@@ -2222,27 +2802,27 @@ var require_process = __commonJS({
2222
2802
  var require_filesystem = __commonJS({
2223
2803
  "node_modules/detect-libc/lib/filesystem.js"(exports2, module2) {
2224
2804
  "use strict";
2225
- var fs12 = require("fs");
2805
+ var fs13 = require("fs");
2226
2806
  var LDD_PATH = "/usr/bin/ldd";
2227
2807
  var SELF_PATH = "/proc/self/exe";
2228
2808
  var MAX_LENGTH = 2048;
2229
- var readFileSync = (path17) => {
2230
- const fd = fs12.openSync(path17, "r");
2809
+ var readFileSync = (path18) => {
2810
+ const fd = fs13.openSync(path18, "r");
2231
2811
  const buffer = Buffer.alloc(MAX_LENGTH);
2232
- const bytesRead = fs12.readSync(fd, buffer, 0, MAX_LENGTH, 0);
2233
- fs12.close(fd, () => {
2812
+ const bytesRead = fs13.readSync(fd, buffer, 0, MAX_LENGTH, 0);
2813
+ fs13.close(fd, () => {
2234
2814
  });
2235
2815
  return buffer.subarray(0, bytesRead);
2236
2816
  };
2237
- var readFile = (path17) => new Promise((resolve, reject) => {
2238
- fs12.open(path17, "r", (err, fd) => {
2817
+ var readFile = (path18) => new Promise((resolve, reject) => {
2818
+ fs13.open(path18, "r", (err, fd) => {
2239
2819
  if (err) {
2240
2820
  reject(err);
2241
2821
  } else {
2242
2822
  const buffer = Buffer.alloc(MAX_LENGTH);
2243
- fs12.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
2823
+ fs13.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
2244
2824
  resolve(buffer.subarray(0, bytesRead));
2245
- fs12.close(fd, () => {
2825
+ fs13.close(fd, () => {
2246
2826
  });
2247
2827
  });
2248
2828
  }
@@ -2354,11 +2934,11 @@ var require_detect_libc = __commonJS({
2354
2934
  }
2355
2935
  return null;
2356
2936
  };
2357
- var familyFromInterpreterPath = (path17) => {
2358
- if (path17) {
2359
- if (path17.includes("/ld-musl-")) {
2937
+ var familyFromInterpreterPath = (path18) => {
2938
+ if (path18) {
2939
+ if (path18.includes("/ld-musl-")) {
2360
2940
  return MUSL;
2361
- } else if (path17.includes("/ld-linux-")) {
2941
+ } else if (path18.includes("/ld-linux-")) {
2362
2942
  return GLIBC;
2363
2943
  }
2364
2944
  }
@@ -2405,8 +2985,8 @@ var require_detect_libc = __commonJS({
2405
2985
  cachedFamilyInterpreter = null;
2406
2986
  try {
2407
2987
  const selfContent = await readFile(SELF_PATH);
2408
- const path17 = interpreterPath(selfContent);
2409
- cachedFamilyInterpreter = familyFromInterpreterPath(path17);
2988
+ const path18 = interpreterPath(selfContent);
2989
+ cachedFamilyInterpreter = familyFromInterpreterPath(path18);
2410
2990
  } catch (e) {
2411
2991
  }
2412
2992
  return cachedFamilyInterpreter;
@@ -2418,8 +2998,8 @@ var require_detect_libc = __commonJS({
2418
2998
  cachedFamilyInterpreter = null;
2419
2999
  try {
2420
3000
  const selfContent = readFileSync(SELF_PATH);
2421
- const path17 = interpreterPath(selfContent);
2422
- cachedFamilyInterpreter = familyFromInterpreterPath(path17);
3001
+ const path18 = interpreterPath(selfContent);
3002
+ cachedFamilyInterpreter = familyFromInterpreterPath(path18);
2423
3003
  } catch (e) {
2424
3004
  }
2425
3005
  return cachedFamilyInterpreter;
@@ -3445,8 +4025,8 @@ function vuePlugin(config) {
3445
4025
  let descriptor = descriptorCache.get(filePath);
3446
4026
  if (!descriptor) {
3447
4027
  try {
3448
- const fs12 = await import("fs");
3449
- const source = fs12.readFileSync(filePath, "utf-8");
4028
+ const fs13 = await import("fs");
4029
+ const source = fs13.readFileSync(filePath, "utf-8");
3450
4030
  const parsed = sfc.parse(source, { filename: filePath });
3451
4031
  if (parsed.errors.length) return null;
3452
4032
  descriptor = parsed.descriptor;
@@ -3585,16 +4165,27 @@ var init_vue = __esm({
3585
4165
  // src/plugins/builtins.ts
3586
4166
  function resolvePluginList(config, userPlugins, opts = {}) {
3587
4167
  const isServe = config.command === "serve";
4168
+ let environmentOptions;
4169
+ if (opts.environmentName) {
4170
+ environmentOptions = config.environments[opts.environmentName];
4171
+ if (!environmentOptions) {
4172
+ throw new Error(
4173
+ `[nasti] unknown environment "${opts.environmentName}" \u2014 declare it in config.environments`
4174
+ );
4175
+ }
4176
+ }
4177
+ const pluginConfig = environmentOptions ? { ...config, resolve: environmentOptions.resolve, build: environmentOptions.build } : config;
4178
+ const consumer = opts.consumer ?? environmentOptions?.consumer;
3588
4179
  return [
3589
4180
  // vuePlugin 排最前(enforce: 'pre' 语义):.vue 先编译成 JS 再走后续管道
3590
- ...config.framework === "vue" ? [vuePlugin(config)] : [],
3591
- resolvePlugin(config),
3592
- cssPlugin(config, opts.cssEngine, opts.consumer),
3593
- assetsPlugin(config),
3594
- ...isServe ? [htmlPlugin(config)] : [],
4181
+ ...config.framework === "vue" ? [vuePlugin(pluginConfig)] : [],
4182
+ resolvePlugin(pluginConfig),
4183
+ cssPlugin(pluginConfig, opts.cssEngine, consumer),
4184
+ assetsPlugin(pluginConfig),
4185
+ ...isServe ? [htmlPlugin(pluginConfig)] : [],
3595
4186
  ...userPlugins,
3596
4187
  // cssPostPlugin 最后(enforce: 'post' 语义):renderChunk 聚合抽取
3597
- ...!isServe && opts.cssEngine ? [cssPostPlugin(config, opts.cssEngine)] : []
4188
+ ...!isServe && opts.cssEngine ? [cssPostPlugin(pluginConfig, opts.cssEngine)] : []
3598
4189
  ];
3599
4190
  }
3600
4191
  var init_builtins = __esm({
@@ -3918,11 +4509,141 @@ var init_reporter = __esm({
3918
4509
  }
3919
4510
  });
3920
4511
 
4512
+ // src/core/build-app-context.ts
4513
+ function createBuildAppContext(config, results) {
4514
+ const output = [];
4515
+ const emitted = /* @__PURE__ */ new Set();
4516
+ const outDir = import_node_path12.default.resolve(config.root, config.build.outDir);
4517
+ let environmentArtifacts;
4518
+ return {
4519
+ config,
4520
+ results,
4521
+ get output() {
4522
+ return Object.freeze([...output]);
4523
+ },
4524
+ getResult(environmentName) {
4525
+ return results[environmentName];
4526
+ },
4527
+ getArtifact(environmentName, fileName) {
4528
+ const normalized = normalizeEnvironmentFileName(fileName);
4529
+ return results[environmentName]?.output.find(
4530
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalized
4531
+ );
4532
+ },
4533
+ getEntry(environmentName, entryName) {
4534
+ const result = results[environmentName];
4535
+ const fileName = result?.entries?.[entryName];
4536
+ if (!fileName) return void 0;
4537
+ return result.output.find(
4538
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalizeEnvironmentFileName(fileName)
4539
+ );
4540
+ },
4541
+ getManifest(environmentName) {
4542
+ return results[environmentName]?.manifest;
4543
+ },
4544
+ emitFile(file) {
4545
+ const fileName = normalizeAppFileName(file.fileName);
4546
+ const collisionKey = artifactCollisionKey(fileName);
4547
+ if (emitted.has(collisionKey)) {
4548
+ throw new Error(`[nasti] app artifact already emitted: ${fileName}`);
4549
+ }
4550
+ environmentArtifacts ??= collectEnvironmentArtifacts(config, results, outDir);
4551
+ if (environmentArtifacts.has(collisionKey)) {
4552
+ throw new Error(`[nasti] app artifact conflicts with environment output: ${fileName}`);
4553
+ }
4554
+ const target = import_node_path12.default.resolve(outDir, ...fileName.split("/"));
4555
+ const relative = import_node_path12.default.relative(outDir, target);
4556
+ if (relative.startsWith("..") || import_node_path12.default.isAbsolute(relative)) {
4557
+ throw new Error(`[nasti] app artifact must stay inside build.outDir: ${file.fileName}`);
4558
+ }
4559
+ assertNoSymlinkComponents(outDir, fileName);
4560
+ import_node_fs9.default.mkdirSync(import_node_path12.default.dirname(target), { recursive: true });
4561
+ import_node_fs9.default.writeFileSync(target, file.source);
4562
+ const artifact = {
4563
+ ...file,
4564
+ fileName,
4565
+ type: "asset"
4566
+ };
4567
+ emitted.add(collisionKey);
4568
+ output.push(artifact);
4569
+ return fileName;
4570
+ }
4571
+ };
4572
+ }
4573
+ function normalizeEnvironmentFileName(fileName) {
4574
+ return import_node_path12.default.posix.normalize(fileName.replace(/\\/g, "/").replace(/^\.\//, ""));
4575
+ }
4576
+ function isInvalidEnvironmentFileName(fileName) {
4577
+ return !fileName || fileName === "." || fileName === ".." || fileName.startsWith("../") || import_node_path12.default.posix.isAbsolute(fileName) || /^[A-Za-z]:\//.test(fileName);
4578
+ }
4579
+ function normalizeAppFileName(fileName) {
4580
+ const normalized = normalizeEnvironmentFileName(fileName);
4581
+ if (isInvalidEnvironmentFileName(normalized)) {
4582
+ throw new Error(`[nasti] invalid app artifact fileName: ${fileName}`);
4583
+ }
4584
+ return normalized;
4585
+ }
4586
+ function artifactCollisionKey(fileName) {
4587
+ return normalizeEnvironmentFileName(fileName).toLowerCase();
4588
+ }
4589
+ function collectEnvironmentArtifacts(config, results, appOutDir) {
4590
+ const occupied = /* @__PURE__ */ new Set();
4591
+ for (const [environmentName, result] of Object.entries(results)) {
4592
+ const environment = config.environments[environmentName];
4593
+ if (!environment) continue;
4594
+ const environmentOutDir = import_node_path12.default.resolve(config.root, environment.build.outDir);
4595
+ for (const artifact of result.output) {
4596
+ const artifactPath = import_node_path12.default.resolve(
4597
+ environmentOutDir,
4598
+ ...normalizeEnvironmentFileName(artifact.fileName).split("/")
4599
+ );
4600
+ const relative = import_node_path12.default.relative(appOutDir, artifactPath);
4601
+ if (!relative.startsWith("..") && !import_node_path12.default.isAbsolute(relative)) {
4602
+ occupied.add(artifactCollisionKey(relative));
4603
+ }
4604
+ }
4605
+ }
4606
+ return occupied;
4607
+ }
4608
+ function assertNoSymlinkComponents(outDir, fileName) {
4609
+ let current = outDir;
4610
+ for (const segment of fileName.split("/")) {
4611
+ current = import_node_path12.default.join(current, segment);
4612
+ let stats;
4613
+ try {
4614
+ stats = import_node_fs9.default.lstatSync(current);
4615
+ } catch (error) {
4616
+ if (error.code === "ENOENT") continue;
4617
+ throw error;
4618
+ }
4619
+ if (stats.isSymbolicLink()) {
4620
+ throw new Error(`[nasti] app artifact path cannot traverse a symlink: ${fileName}`);
4621
+ }
4622
+ }
4623
+ }
4624
+ function inferEnvironmentEntries(output) {
4625
+ const entries = {};
4626
+ for (const artifact of output) {
4627
+ if (artifact.type !== "chunk" || !artifact.isEntry || !artifact.name) continue;
4628
+ entries[artifact.name] = normalizeEnvironmentFileName(artifact.fileName);
4629
+ }
4630
+ return Object.keys(entries).length > 0 ? entries : void 0;
4631
+ }
4632
+ var import_node_fs9, import_node_path12;
4633
+ var init_build_app_context = __esm({
4634
+ "src/core/build-app-context.ts"() {
4635
+ "use strict";
4636
+ import_node_fs9 = __toESM(require("fs"), 1);
4637
+ import_node_path12 = __toESM(require("path"), 1);
4638
+ }
4639
+ });
4640
+
3921
4641
  // src/build/index.ts
3922
4642
  var build_exports = {};
3923
4643
  __export(build_exports, {
3924
4644
  build: () => build,
3925
4645
  getRolldownOptions: () => getRolldownOptions,
4646
+ replaceEntryScript: () => replaceEntryScript,
3926
4647
  resolveClientEntries: () => resolveClientEntries,
3927
4648
  toRolldownPlugins: () => toRolldownPlugins
3928
4649
  });
@@ -3930,9 +4651,14 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
3930
4651
  const config = environment.config;
3931
4652
  const envOptions = environment.options;
3932
4653
  const isServer = environment.consumer === "server";
3933
- const outDir = import_node_path12.default.resolve(config.root, envOptions.build.outDir);
4654
+ const outDir = import_node_path13.default.resolve(config.root, envOptions.build.outDir);
3934
4655
  const assetsDir = envOptions.build.assetsDir;
3935
- const { output: userOutput, transform: userTransform, ...restInputOptions } = envOptions.build.rolldownOptions;
4656
+ const {
4657
+ output: userOutput,
4658
+ transform: userTransform,
4659
+ resolve: userResolve,
4660
+ ...restInputOptions
4661
+ } = envOptions.build.rolldownOptions;
3936
4662
  const vueDefine = config.framework === "vue" ? {
3937
4663
  __VUE_OPTIONS_API__: "true",
3938
4664
  __VUE_PROD_DEVTOOLS__: "false",
@@ -3946,19 +4672,22 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
3946
4672
  input: entryPoints,
3947
4673
  transform: { ...userTransform, define: mergedDefine },
3948
4674
  plugins: rolldownPlugins,
4675
+ // client/server consumer 都必须使用当前环境的 conditions/mainFields;Lynx
4676
+ // BG/MT 通常同为 client consumer,但仍可能声明不同的运行时条件。
4677
+ resolve: {
4678
+ ...userResolve ?? {},
4679
+ // Environment API 是条件解析的唯一高层入口,优先于继承来的底层选项。
4680
+ conditionNames: envOptions.resolve.conditions,
4681
+ mainFields: envOptions.resolve.mainFields
4682
+ },
3949
4683
  ...isServer ? {
3950
4684
  platform: restInputOptions.platform ?? "node",
3951
- resolve: {
3952
- conditionNames: envOptions.resolve.conditions,
3953
- mainFields: envOptions.resolve.mainFields,
3954
- ...restInputOptions.resolve
3955
- },
3956
4685
  // server 产物:node 内建恒外部化;bare specifier 默认外部化
3957
4686
  //(同 Vite ssr.external 默认 —— 依赖由 node_modules 运行时解析),
3958
4687
  // 相对/绝对/虚拟模块照常打包。需要内联依赖时经 rolldownOptions.external 覆盖。
3959
4688
  external: restInputOptions.external ?? ((id) => {
3960
4689
  if (NODE_BUILTINS2.has(id)) return true;
3961
- return !id.startsWith(".") && !import_node_path12.default.isAbsolute(id) && !id.startsWith("\0");
4690
+ return !id.startsWith(".") && !import_node_path13.default.isAbsolute(id) && !id.startsWith("\0");
3962
4691
  })
3963
4692
  } : {}
3964
4693
  };
@@ -3985,37 +4714,139 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
3985
4714
  };
3986
4715
  return { inputOptions, outputOptions, outDir };
3987
4716
  }
3988
- function toRolldownPlugins(plugins) {
4717
+ function toRolldownPlugins(plugins, environment) {
4718
+ const wrap = (hook) => {
4719
+ if (!hook) return hook;
4720
+ return function(...args) {
4721
+ return hook.apply(attachEnvironment(this, environment), args);
4722
+ };
4723
+ };
3989
4724
  return plugins.map((p) => ({
3990
4725
  name: p.name,
3991
- resolveId: p.resolveId,
3992
- load: p.load,
3993
- transform: p.transform,
3994
- buildStart: p.buildStart,
3995
- buildEnd: p.buildEnd,
4726
+ resolveId: wrap(p.resolveId),
4727
+ load: wrap(p.load),
4728
+ transform: wrap(p.transform),
4729
+ buildStart: wrap(p.buildStart),
4730
+ buildEnd: wrap(p.buildEnd),
3996
4731
  // closeBundle 在 bundle.close() 时触发 —— PWA manifest/SW 等终态产物依赖
3997
- closeBundle: p.closeBundle,
3998
- renderChunk: p.renderChunk,
3999
- augmentChunkHash: p.augmentChunkHash,
4000
- generateBundle: p.generateBundle
4732
+ closeBundle: wrap(p.closeBundle),
4733
+ renderChunk: wrap(p.renderChunk),
4734
+ augmentChunkHash: wrap(p.augmentChunkHash),
4735
+ generateBundle: wrap(p.generateBundle)
4001
4736
  }));
4002
4737
  }
4738
+ function attachEnvironment(context, environment) {
4739
+ if (context?.environment === environment) return context;
4740
+ try {
4741
+ Object.defineProperty(context, "environment", {
4742
+ configurable: true,
4743
+ enumerable: false,
4744
+ writable: false,
4745
+ value: environment
4746
+ });
4747
+ return context;
4748
+ } catch {
4749
+ return new Proxy(context, {
4750
+ get(target, property) {
4751
+ if (property === "environment") return environment;
4752
+ const value = Reflect.get(target, property, target);
4753
+ return typeof value === "function" ? value.bind(target) : value;
4754
+ },
4755
+ set(target, property, value) {
4756
+ return Reflect.set(target, property, value, target);
4757
+ }
4758
+ });
4759
+ }
4760
+ }
4761
+ function finalizeEnvironmentResult(environment, result) {
4762
+ const metadata = environment.getBuildMetadata();
4763
+ const inferredEntries = inferEnvironmentEntries(result.output);
4764
+ const entries = {
4765
+ ...inferredEntries,
4766
+ ...metadata.entries,
4767
+ ...result.entries
4768
+ };
4769
+ const normalizedEntries = Object.fromEntries(
4770
+ Object.entries(entries).map(([name, fileName]) => {
4771
+ const normalized = normalizeEnvironmentFileName(fileName);
4772
+ if (isInvalidEnvironmentFileName(normalized)) {
4773
+ throw new Error(
4774
+ `[nasti] environment "${environment.name}" returned invalid entry "${name}": ${fileName}`
4775
+ );
4776
+ }
4777
+ return [name, normalized];
4778
+ })
4779
+ );
4780
+ return {
4781
+ ...metadata,
4782
+ ...result,
4783
+ output: result.output,
4784
+ ...Object.keys(normalizedEntries).length > 0 ? { entries: normalizedEntries } : {}
4785
+ };
4786
+ }
4787
+ function prepareBuildOutputDirectories(config, buildableNames) {
4788
+ const directories = /* @__PURE__ */ new Set();
4789
+ const protectedPaths = /* @__PURE__ */ new Set();
4790
+ const clientIsBuilt = buildableNames.includes("client");
4791
+ if (!clientIsBuilt && config.build.emptyOutDir) {
4792
+ directories.add(import_node_path13.default.resolve(config.root, config.build.outDir));
4793
+ }
4794
+ for (const name of buildableNames) {
4795
+ const environment = config.environments[name];
4796
+ const outDir = import_node_path13.default.resolve(config.root, environment.build.outDir);
4797
+ if (!environment.build.emptyOutDir) {
4798
+ protectedPaths.add(outDir);
4799
+ continue;
4800
+ }
4801
+ if (!environment.driver) directories.add(outDir);
4802
+ }
4803
+ const containsPath = (parent, child) => {
4804
+ const relative = import_node_path13.default.relative(parent, child);
4805
+ return relative === "" || !relative.startsWith("..") && !import_node_path13.default.isAbsolute(relative);
4806
+ };
4807
+ const roots = [...directories].filter(
4808
+ (directory) => ![...protectedPaths].some((protectedPath) => containsPath(directory, protectedPath))
4809
+ ).sort((a, b) => a.length - b.length).filter(
4810
+ (directory, index2, all) => !all.slice(0, index2).some((parent) => containsPath(parent, directory))
4811
+ );
4812
+ for (const directory of roots) {
4813
+ if (import_node_fs10.default.existsSync(directory)) import_node_fs10.default.rmSync(directory, { recursive: true, force: true });
4814
+ }
4815
+ }
4816
+ function assertDriverBuildResult(environment, result) {
4817
+ const output = result != null && typeof result === "object" ? result.output : void 0;
4818
+ const hasValidOutput = Array.isArray(output) && output.every(
4819
+ (artifact) => artifact != null && typeof artifact === "object" && typeof artifact.fileName === "string" && typeof artifact.type === "string"
4820
+ );
4821
+ if (!hasValidOutput) {
4822
+ throw new Error(
4823
+ `[nasti] environment "${environment.name}" driver "${environment.driver?.name}" returned an invalid build result; expected { output: EnvironmentBuildOutput[] }`
4824
+ );
4825
+ }
4826
+ }
4003
4827
  function resolveClientEntries(config, html) {
4828
+ const configuredEntries = config.environments.client?.entry ?? [];
4829
+ if (configuredEntries.length > 0) return configuredEntries;
4004
4830
  const entryPoints = [];
4831
+ const htmlFile = config.environments.client?.html;
4832
+ const htmlDir = htmlFile ? import_node_path13.default.dirname(htmlFile) : config.root;
4005
4833
  if (html) {
4006
4834
  const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
4007
4835
  for (const match of scriptMatches) {
4008
4836
  const src = match[1];
4009
4837
  if (src && !src.startsWith("http")) {
4010
- entryPoints.push(import_node_path12.default.resolve(config.root, src.replace(/^\//, "")));
4838
+ const cleanSrc = src.split(/[?#]/, 1)[0];
4839
+ entryPoints.push(
4840
+ cleanSrc.startsWith("/") ? import_node_path13.default.resolve(config.root, cleanSrc.replace(/^\//, "")) : import_node_path13.default.resolve(htmlDir, cleanSrc)
4841
+ );
4011
4842
  }
4012
4843
  }
4013
4844
  }
4014
4845
  if (entryPoints.length === 0) {
4015
4846
  const fallbackEntries = ["src/main.ts", "src/main.tsx", "src/main.js", "src/index.ts", "src/index.tsx", "src/index.js"];
4016
4847
  for (const entry of fallbackEntries) {
4017
- const fullPath = import_node_path12.default.resolve(config.root, entry);
4018
- if (import_node_fs9.default.existsSync(fullPath)) {
4848
+ const fullPath = import_node_path13.default.resolve(config.root, entry);
4849
+ if (import_node_fs10.default.existsSync(fullPath)) {
4019
4850
  entryPoints.push(fullPath);
4020
4851
  break;
4021
4852
  }
@@ -4043,130 +4874,229 @@ async function build(inlineConfig = {}) {
4043
4874
  const startTime = performance.now();
4044
4875
  logger.info(
4045
4876
  import_picocolors6.default.cyan(`
4046
- nasti v${"2.2.0"} `) + import_picocolors6.default.green(`building for ${config.mode}...`)
4877
+ nasti v${"2.4.0"} `) + import_picocolors6.default.green(`building for ${config.mode}...`)
4047
4878
  );
4048
4879
  debug5?.(`root: ${config.root}`);
4049
- const buildableNames = Object.keys(config.environments).filter(
4050
- (name) => name === "client" || config.environments[name].entry.length > 0
4051
- );
4880
+ const buildableNames = Object.keys(config.environments).filter((name) => {
4881
+ const environment = config.environments[name];
4882
+ if (!environment.buildEnabled) return false;
4883
+ return name === "client" || environment.entry.length > 0 || !!environment.driver;
4884
+ });
4052
4885
  buildableNames.sort((a, b) => a === "client" ? -1 : b === "client" ? 1 : 0);
4886
+ prepareBuildOutputDirectories(config, buildableNames);
4053
4887
  const environments = {};
4888
+ const environmentResults = {};
4889
+ const initializedEnvironments = [];
4890
+ const buildAppContext = createBuildAppContext(config, environmentResults);
4054
4891
  let clientOutput = [];
4055
- for (const name of buildableNames) {
4056
- const output = name === "client" ? await buildClientEnvironment(config) : await buildServerEnvironment(config, name);
4057
- environments[name] = output;
4058
- if (name === "client") clientOutput = output;
4059
- if (buildableNames.length > 1) {
4060
- debug5?.(`environment "${name}" built (${output.length} files)`);
4892
+ let buildFailed = false;
4893
+ try {
4894
+ for (const name of buildableNames) {
4895
+ const built = name === "client" ? await buildClientEnvironment(config) : await buildServerEnvironment(config, name);
4896
+ initializedEnvironments.push(built.environment);
4897
+ environments[name] = built.result.output;
4898
+ environmentResults[name] = built.result;
4899
+ if (name === "client") clientOutput = built.result.output;
4900
+ if (buildableNames.length > 1) {
4901
+ debug5?.(`environment "${name}" built (${built.result.output.length} files)`);
4902
+ }
4903
+ }
4904
+ const pluginApi = getPluginApi(config);
4905
+ for (const plugin of config.plugins) {
4906
+ await plugin.afterBuildApp?.(environmentResults, pluginApi, buildAppContext);
4907
+ }
4908
+ } catch (error) {
4909
+ buildFailed = true;
4910
+ throw error;
4911
+ } finally {
4912
+ let closeFailed = false;
4913
+ let firstCloseError;
4914
+ for (const environment of [...initializedEnvironments].reverse()) {
4915
+ try {
4916
+ await environment.close();
4917
+ } catch (error) {
4918
+ if (!closeFailed) {
4919
+ closeFailed = true;
4920
+ firstCloseError = error;
4921
+ }
4922
+ const closeError = error instanceof Error ? error : new Error(String(error));
4923
+ logger.error(`[nasti] failed to close environment "${environment.name}"`, {
4924
+ error: closeError
4925
+ });
4926
+ }
4927
+ }
4928
+ if (closeFailed && !buildFailed) {
4929
+ throw firstCloseError;
4061
4930
  }
4062
4931
  }
4063
4932
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
4064
- const totalSize = Object.values(environments).flat().reduce((sum, chunk) => {
4933
+ const allOutput = [...Object.values(environments).flat(), ...buildAppContext.output];
4934
+ const totalSize = allOutput.reduce((sum, chunk) => {
4065
4935
  const content = chunk.type === "chunk" ? chunk.code : chunk.source;
4066
4936
  if (content == null) return sum;
4067
4937
  return sum + (typeof content === "string" ? Buffer.byteLength(content) : content.byteLength);
4068
4938
  }, 0);
4069
- const fileCount = Object.values(environments).flat().length;
4939
+ const fileCount = allOutput.length;
4070
4940
  const envSuffix = buildableNames.length > 1 ? ` (${buildableNames.join(" + ")})` : "";
4071
4941
  logger.info(import_picocolors6.default.green(`\u2713 built in ${elapsed}s`) + import_picocolors6.default.dim(envSuffix));
4072
4942
  logger.info(import_picocolors6.default.dim(` ${fileCount} files, ${displaySize(totalSize)} total \u2192 ${config.build.outDir}/`));
4073
- return { output: clientOutput, environments };
4943
+ return {
4944
+ output: clientOutput,
4945
+ environments,
4946
+ environmentResults,
4947
+ appOutput: [...buildAppContext.output]
4948
+ };
4074
4949
  }
4075
4950
  async function buildClientEnvironment(config) {
4076
4951
  const logger = config.logger;
4077
- const outDir = import_node_path12.default.resolve(config.root, config.build.outDir);
4078
- if (config.build.emptyOutDir && import_node_fs9.default.existsSync(outDir)) {
4079
- import_node_fs9.default.rmSync(outDir, { recursive: true, force: true });
4080
- }
4081
- import_node_fs9.default.mkdirSync(outDir, { recursive: true });
4082
- const html = await readHtmlFile(config.root);
4083
- const entryPoints = resolveClientEntries(config, html);
4084
- if (entryPoints.length === 0) {
4085
- throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
4086
- }
4952
+ const outDir = import_node_path13.default.resolve(config.root, config.build.outDir);
4087
4953
  const cssEngine = createCssEngine();
4088
- const pluginList = resolvePluginList(config, config.plugins, { cssEngine });
4089
- const clientEnv = new NastiEnvironment("client", { ...config, plugins: pluginList }, {
4954
+ const pluginList = resolvePluginList(config, config.plugins, {
4955
+ cssEngine,
4956
+ environmentName: "client"
4957
+ });
4958
+ const clientEnv = new NastiEnvironment("client", config, {
4090
4959
  mode: "build",
4091
- plugins: pluginList
4960
+ plugins: pluginList,
4961
+ pluginApi: getPluginApi(config)
4092
4962
  });
4093
4963
  await clientEnv.init();
4094
- const allPlugins = clientEnv.plugins;
4095
- const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
4096
- const rolldownPlugins = [
4097
- createOxcTransformPlugin(config, clientEnv),
4098
- ...toRolldownPlugins(allPlugins),
4099
- ...nativeReporter ? [nativeReporter] : []
4100
- ];
4101
- const { inputOptions, outputOptions } = getRolldownOptions(clientEnv, entryPoints, rolldownPlugins);
4102
- const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
4103
- const { output } = await bundle2.write(outputOptions);
4104
- await bundle2.close();
4105
- if (html) {
4106
- let processedHtml = html;
4107
- const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
4108
- for (const p of htmlPlugins) {
4109
- const result = await p.transformIndexHtml(processedHtml);
4110
- if (typeof result === "string") {
4111
- processedHtml = result;
4112
- } else if (result && "html" in result) {
4113
- processedHtml = processHtml(result.html, result.tags);
4114
- } else if (Array.isArray(result)) {
4115
- processedHtml = processHtml(processedHtml, result);
4116
- }
4117
- }
4118
- processedHtml = injectCssLinks(processedHtml, cssEngine, config);
4119
- for (const chunk of output) {
4120
- if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
4121
- const originalEntry = import_node_path12.default.relative(config.root, chunk.facadeModuleId);
4122
- processedHtml = processedHtml.replace(
4123
- new RegExp(`(src=["'])/?(${escapeRegExp(originalEntry)})(["'])`, "g"),
4124
- `$1${config.base}${chunk.fileName}$3`
4964
+ try {
4965
+ if (clientEnv.driver) {
4966
+ if (!clientEnv.driver.build) {
4967
+ throw new Error(
4968
+ `[nasti] environment "client" driver "${clientEnv.driver.name}" does not implement build()`
4125
4969
  );
4126
4970
  }
4971
+ const result = await clientEnv.driver.build(clientEnv.getDriverContext());
4972
+ assertDriverBuildResult(clientEnv, result);
4973
+ return { environment: clientEnv, result: finalizeEnvironmentResult(clientEnv, result) };
4127
4974
  }
4128
- import_node_fs9.default.writeFileSync(import_node_path12.default.resolve(outDir, "index.html"), processedHtml);
4129
- }
4130
- if (!nativeReporter && config.logLevel !== "silent") {
4131
- reportBuildOutput(output, config, logger);
4975
+ import_node_fs10.default.mkdirSync(outDir, { recursive: true });
4976
+ const htmlFile = config.environments.client.html ?? import_node_path13.default.resolve(config.root, "index.html");
4977
+ const html = await readHtmlFile(config.root, htmlFile);
4978
+ const entryPoints = resolveClientEntries(config, html);
4979
+ if (entryPoints.length === 0) {
4980
+ throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
4981
+ }
4982
+ const allPlugins = clientEnv.plugins;
4983
+ const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
4984
+ const rolldownPlugins = [
4985
+ createOxcTransformPlugin(config, clientEnv),
4986
+ ...toRolldownPlugins(allPlugins, clientEnv),
4987
+ ...nativeReporter ? [nativeReporter] : []
4988
+ ];
4989
+ const { inputOptions, outputOptions } = getRolldownOptions(
4990
+ clientEnv,
4991
+ entryPoints,
4992
+ rolldownPlugins
4993
+ );
4994
+ const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
4995
+ const { output } = await bundle2.write(outputOptions);
4996
+ await bundle2.close();
4997
+ if (html) {
4998
+ let processedHtml = html;
4999
+ const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
5000
+ for (const p of htmlPlugins) {
5001
+ const result = await p.transformIndexHtml(processedHtml);
5002
+ if (typeof result === "string") {
5003
+ processedHtml = result;
5004
+ } else if (result && "html" in result) {
5005
+ processedHtml = processHtml(result.html, result.tags);
5006
+ } else if (Array.isArray(result)) {
5007
+ processedHtml = processHtml(processedHtml, result);
5008
+ }
5009
+ }
5010
+ processedHtml = injectCssLinks(processedHtml, cssEngine, config);
5011
+ for (const chunk of output) {
5012
+ if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
5013
+ processedHtml = replaceEntryScript(
5014
+ processedHtml,
5015
+ chunk.facadeModuleId,
5016
+ chunk.fileName,
5017
+ config,
5018
+ htmlFile,
5019
+ config.base
5020
+ );
5021
+ }
5022
+ }
5023
+ import_node_fs10.default.writeFileSync(import_node_path13.default.resolve(outDir, "index.html"), processedHtml);
5024
+ }
5025
+ if (!nativeReporter && config.logLevel !== "silent") {
5026
+ reportBuildOutput(output, config, logger);
5027
+ }
5028
+ warnLargeChunks(output, config, logger);
5029
+ return {
5030
+ environment: clientEnv,
5031
+ result: finalizeEnvironmentResult(clientEnv, { output })
5032
+ };
5033
+ } catch (error) {
5034
+ try {
5035
+ await clientEnv.close();
5036
+ } catch (closeError) {
5037
+ const normalized = closeError instanceof Error ? closeError : new Error(String(closeError));
5038
+ logger.error("[nasti] failed to close client environment after build failure", {
5039
+ error: normalized
5040
+ });
5041
+ }
5042
+ throw error;
4132
5043
  }
4133
- warnLargeChunks(output, config, logger);
4134
- return output;
4135
5044
  }
4136
5045
  async function buildServerEnvironment(config, name) {
4137
5046
  const envOptions = config.environments[name];
4138
5047
  const logger = config.logger;
5048
+ const pluginList = resolvePluginList(config, config.plugins, {
5049
+ consumer: envOptions.consumer,
5050
+ environmentName: name
5051
+ });
5052
+ const environment = new NastiEnvironment(name, config, {
5053
+ mode: "build",
5054
+ plugins: pluginList,
5055
+ pluginApi: getPluginApi(config)
5056
+ });
5057
+ await environment.init();
5058
+ if (environment.driver) {
5059
+ if (!environment.driver.build) {
5060
+ await environment.close();
5061
+ throw new Error(
5062
+ `[nasti] environment "${name}" driver "${environment.driver.name}" does not implement build()`
5063
+ );
5064
+ }
5065
+ try {
5066
+ const result = await environment.driver.build(environment.getDriverContext());
5067
+ assertDriverBuildResult(environment, result);
5068
+ return { environment, result: finalizeEnvironmentResult(environment, result) };
5069
+ } catch (error) {
5070
+ await environment.close();
5071
+ throw error;
5072
+ }
5073
+ }
4139
5074
  for (const entry of envOptions.entry) {
4140
- if (!import_node_fs9.default.existsSync(entry)) {
5075
+ if (!import_node_fs10.default.existsSync(entry)) {
5076
+ await environment.close();
4141
5077
  throw new Error(`[nasti] environment "${name}" entry not found: ${entry}`);
4142
5078
  }
4143
5079
  }
4144
- const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
4145
- const environment = new NastiEnvironment(name, { ...config, plugins: pluginList }, {
4146
- mode: "build",
4147
- plugins: pluginList
4148
- });
4149
- await environment.init();
4150
5080
  const rolldownPlugins = [
4151
5081
  createOxcTransformPlugin(config, environment),
4152
- ...toRolldownPlugins(environment.plugins)
5082
+ ...toRolldownPlugins(environment.plugins, environment)
4153
5083
  ];
4154
5084
  const { inputOptions, outputOptions, outDir } = getRolldownOptions(
4155
5085
  environment,
4156
5086
  envOptions.entry,
4157
5087
  rolldownPlugins
4158
5088
  );
4159
- if (envOptions.build.emptyOutDir && import_node_fs9.default.existsSync(outDir)) {
4160
- import_node_fs9.default.rmSync(outDir, { recursive: true, force: true });
4161
- }
4162
- import_node_fs9.default.mkdirSync(outDir, { recursive: true });
5089
+ import_node_fs10.default.mkdirSync(outDir, { recursive: true });
4163
5090
  const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
4164
5091
  const { output } = await bundle2.write(outputOptions);
4165
5092
  await bundle2.close();
4166
5093
  logger.info(
4167
- import_picocolors6.default.dim(` [${name}] `) + output.map((o) => import_node_path12.default.join(envOptions.build.outDir, o.fileName)).join(import_picocolors6.default.dim(", "))
5094
+ import_picocolors6.default.dim(` [${name}] `) + output.map((o) => import_node_path13.default.join(envOptions.build.outDir, o.fileName)).join(import_picocolors6.default.dim(", "))
4168
5095
  );
4169
- return output;
5096
+ return {
5097
+ environment,
5098
+ result: finalizeEnvironmentResult(environment, { output })
5099
+ };
4170
5100
  }
4171
5101
  function injectCssLinks(html, cssEngine, config) {
4172
5102
  const cssLinkTags = [];
@@ -4192,12 +5122,31 @@ function injectCssLinks(html, cssEngine, config) {
4192
5122
  function escapeRegExp(string) {
4193
5123
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4194
5124
  }
4195
- var import_node_path12, import_node_fs9, import_node_module5, import_rolldown, import_picocolors6, debug5, NODE_BUILTINS2;
5125
+ function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, urlPrefix) {
5126
+ const rootRelative = import_node_path13.default.relative(config.root, facadeModuleId).split(import_node_path13.default.sep).join("/");
5127
+ const resolvedHtmlFile = import_node_path13.default.resolve(config.root, htmlFile);
5128
+ const htmlRelative = import_node_path13.default.relative(import_node_path13.default.dirname(resolvedHtmlFile), facadeModuleId).split(import_node_path13.default.sep).join("/");
5129
+ const candidates = /* @__PURE__ */ new Set([
5130
+ rootRelative,
5131
+ `/${rootRelative}`,
5132
+ htmlRelative,
5133
+ `./${htmlRelative}`
5134
+ ]);
5135
+ let processed = html;
5136
+ for (const candidate of candidates) {
5137
+ processed = processed.replace(
5138
+ new RegExp(`(src=["'])${escapeRegExp(candidate)}([?#][^"']*)?(["'])`, "g"),
5139
+ `$1${urlPrefix}${fileName}$3`
5140
+ );
5141
+ }
5142
+ return processed;
5143
+ }
5144
+ var import_node_path13, import_node_fs10, import_node_module5, import_rolldown, import_picocolors6, debug5, NODE_BUILTINS2;
4196
5145
  var init_build = __esm({
4197
5146
  "src/build/index.ts"() {
4198
5147
  "use strict";
4199
- import_node_path12 = __toESM(require("path"), 1);
4200
- import_node_fs9 = __toESM(require("fs"), 1);
5148
+ import_node_path13 = __toESM(require("path"), 1);
5149
+ import_node_fs10 = __toESM(require("fs"), 1);
4201
5150
  import_node_module5 = require("module");
4202
5151
  import_rolldown = require("rolldown");
4203
5152
  init_config();
@@ -4209,6 +5158,8 @@ var init_build = __esm({
4209
5158
  init_env();
4210
5159
  init_reporter();
4211
5160
  init_debug();
5161
+ init_plugin_api();
5162
+ init_build_app_context();
4212
5163
  import_picocolors6 = __toESM(require("picocolors"), 1);
4213
5164
  debug5 = createDebugger("nasti:build");
4214
5165
  NODE_BUILTINS2 = /* @__PURE__ */ new Set([...import_node_module5.builtinModules, ...import_node_module5.builtinModules.map((m) => `node:${m}`)]);
@@ -4237,7 +5188,7 @@ async function createBundledDevServer(opts) {
4237
5188
  `[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.`
4238
5189
  );
4239
5190
  }
4240
- const html = await readHtmlFile(config.root);
5191
+ const html = await readHtmlFile(config.root, config.environments.client?.html);
4241
5192
  const entryPoints = resolveClientEntries(config, html);
4242
5193
  if (entryPoints.length === 0) {
4243
5194
  throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
@@ -4252,7 +5203,7 @@ async function createBundledDevServer(opts) {
4252
5203
  createReactRefreshRuntimePlugin(entryPoints),
4253
5204
  createBundledOxcRefreshPlugin()
4254
5205
  ] : [],
4255
- ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins)),
5206
+ ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins, clientEnv)),
4256
5207
  ...useReactRefresh ? [
4257
5208
  refreshWrapperFn({
4258
5209
  cwd: config.root,
@@ -4301,7 +5252,7 @@ async function createBundledDevServer(opts) {
4301
5252
  }
4302
5253
  const url = `/${patchPath}`;
4303
5254
  logger.info(
4304
- import_picocolors7.default.green("hmr update ") + import_picocolors7.default.dim(changedFiles.map((f) => import_node_path13.default.relative(config.root, f)).join(", ")),
5255
+ import_picocolors7.default.green("hmr update ") + import_picocolors7.default.dim(changedFiles.map((f) => import_node_path14.default.relative(config.root, f)).join(", ")),
4305
5256
  { timestamp: true }
4306
5257
  );
4307
5258
  sendTo(clientId, { type: "hmr:update", path: url, url });
@@ -4437,13 +5388,13 @@ async function createBundledDevServer(opts) {
4437
5388
  return;
4438
5389
  }
4439
5390
  res.setHeader("ETag", hit.etag);
4440
- res.setHeader("Content-Type", MIME_TYPES[import_node_path13.default.extname(fileName)] ?? "application/octet-stream");
5391
+ res.setHeader("Content-Type", MIME_TYPES[import_node_path14.default.extname(fileName)] ?? "application/octet-stream");
4441
5392
  res.setHeader("Cache-Control", "no-cache");
4442
5393
  res.end(hit.content);
4443
5394
  return;
4444
5395
  }
4445
5396
  if (pathname === "/" || pathname.endsWith(".html")) {
4446
- const rawHtml = await readHtmlFile(config.root);
5397
+ const rawHtml = await readHtmlFile(config.root, config.environments.client?.html);
4447
5398
  if (rawHtml) {
4448
5399
  res.setHeader("Content-Type", "text/html");
4449
5400
  res.setHeader("Cache-Control", "no-store");
@@ -4473,7 +5424,7 @@ function stripCatchAllLoad(plugins) {
4473
5424
  );
4474
5425
  }
4475
5426
  function createReactRefreshRuntimePlugin(entryPoints) {
4476
- const entryIds = new Set(entryPoints.map((p) => import_node_path13.default.resolve(p)));
5427
+ const entryIds = new Set(entryPoints.map((p) => import_node_path14.default.resolve(p)));
4477
5428
  return {
4478
5429
  name: "nasti:bundled-react-refresh",
4479
5430
  resolveId(source) {
@@ -4491,7 +5442,7 @@ function createReactRefreshRuntimePlugin(entryPoints) {
4491
5442
  return null;
4492
5443
  },
4493
5444
  transform(code, id) {
4494
- if (!entryIds.has(import_node_path13.default.resolve(id.split("?")[0]))) return null;
5445
+ if (!entryIds.has(import_node_path14.default.resolve(id.split("?")[0]))) return null;
4495
5446
  return { code: `import ${JSON.stringify(PREAMBLE_SPEC)};
4496
5447
  ${code}`, map: null };
4497
5448
  }
@@ -4527,19 +5478,22 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
4527
5478
  }
4528
5479
  }
4529
5480
  for (const [facadeModuleId, fileName] of entryFileNames) {
4530
- const originalEntry = import_node_path13.default.relative(config.root, facadeModuleId);
4531
- processed = processed.replace(
4532
- new RegExp(`(src=["'])/?(${originalEntry.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})(["'])`, "g"),
4533
- `$1/${fileName}$3`
5481
+ processed = replaceEntryScript(
5482
+ processed,
5483
+ facadeModuleId,
5484
+ fileName,
5485
+ config,
5486
+ config.environments.client?.html ?? "index.html",
5487
+ "/"
4534
5488
  );
4535
5489
  }
4536
5490
  return processed;
4537
5491
  }
4538
- var import_node_path13, import_node_crypto3, import_ws2, import_picocolors7, debug6, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
5492
+ var import_node_path14, import_node_crypto3, import_ws2, import_picocolors7, debug6, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
4539
5493
  var init_dev_engine = __esm({
4540
5494
  "src/server/bundled/dev-engine.ts"() {
4541
5495
  "use strict";
4542
- import_node_path13 = __toESM(require("path"), 1);
5496
+ import_node_path14 = __toESM(require("path"), 1);
4543
5497
  import_node_crypto3 = __toESM(require("crypto"), 1);
4544
5498
  import_ws2 = require("ws");
4545
5499
  import_picocolors7 = __toESM(require("picocolors"), 1);
@@ -4666,27 +5620,38 @@ async function createServer(inlineConfig = {}) {
4666
5620
  const startTime = performance.now();
4667
5621
  const config = await resolveConfig(inlineConfig, "serve");
4668
5622
  const logger = config.logger;
4669
- const allPlugins = resolvePluginList(config, config.plugins);
5623
+ const allPlugins = resolvePluginList(config, config.plugins, {
5624
+ environmentName: "client"
5625
+ });
4670
5626
  const configWithPlugins = { ...config, plugins: allPlugins };
4671
5627
  const app = (0, import_connect.default)();
4672
5628
  const httpServer = import_node_http.default.createServer(app);
4673
5629
  const ws = createWebSocketServer(httpServer);
4674
- const clientEnv = new NastiEnvironment("client", configWithPlugins, {
5630
+ const pluginApi = getPluginApi(config);
5631
+ const clientEnv = new NastiEnvironment("client", config, {
4675
5632
  hot: createWsHotChannel(ws),
4676
5633
  mode: "dev",
4677
- plugins: allPlugins
5634
+ plugins: allPlugins,
5635
+ pluginApi
4678
5636
  });
4679
5637
  await clientEnv.init();
4680
5638
  const environments = { client: clientEnv };
4681
5639
  for (const name of Object.keys(config.environments)) {
4682
5640
  if (name === "client") continue;
4683
5641
  const consumer = config.environments[name].consumer;
4684
- const envPlugins = resolvePluginList(config, config.plugins, { consumer });
4685
- environments[name] = new NastiEnvironment(name, { ...config, plugins: envPlugins }, {
5642
+ const envPlugins = resolvePluginList(config, config.plugins, {
5643
+ consumer,
5644
+ environmentName: name
5645
+ });
5646
+ environments[name] = new NastiEnvironment(name, config, {
4686
5647
  mode: "dev",
4687
- plugins: envPlugins
5648
+ plugins: envPlugins,
5649
+ pluginApi
4688
5650
  });
4689
5651
  }
5652
+ for (const [name, environment] of Object.entries(environments)) {
5653
+ if (name !== "client" && environment.options.driver) await environment.init();
5654
+ }
4690
5655
  let ssrRunner = null;
4691
5656
  async function getSsrRunner() {
4692
5657
  if (ssrRunner) return ssrRunner;
@@ -4711,23 +5676,15 @@ async function createServer(inlineConfig = {}) {
4711
5676
  });
4712
5677
  app.use(bundledServer.middleware);
4713
5678
  }
4714
- app.use(transformMiddleware({
4715
- config: configWithPlugins,
4716
- pluginContainer,
4717
- moduleGraph
4718
- }));
4719
- const publicDir = import_node_path14.default.resolve(config.root, "public");
4720
- app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
4721
- app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
4722
5679
  const ignoredSegments = /* @__PURE__ */ new Set(["node_modules", ".git", ".nasti"]);
4723
- const outDirAbs = import_node_path14.default.resolve(config.root, config.build.outDir);
5680
+ const outDirAbs = import_node_path15.default.resolve(config.root, config.build.outDir);
4724
5681
  const watcher = (0, import_chokidar.watch)(config.root, {
4725
5682
  ignored: (filePath) => {
4726
5683
  if (filePath === config.root) return false;
4727
- if (filePath === outDirAbs || filePath.startsWith(outDirAbs + import_node_path14.default.sep)) return true;
4728
- const rel = import_node_path14.default.relative(config.root, filePath);
4729
- if (!rel || rel.startsWith("..") || import_node_path14.default.isAbsolute(rel)) return false;
4730
- for (const seg of rel.split(import_node_path14.default.sep)) {
5684
+ if (filePath === outDirAbs || filePath.startsWith(outDirAbs + import_node_path15.default.sep)) return true;
5685
+ const rel = import_node_path15.default.relative(config.root, filePath);
5686
+ if (!rel || rel.startsWith("..") || import_node_path15.default.isAbsolute(rel)) return false;
5687
+ for (const seg of rel.split(import_node_path15.default.sep)) {
4731
5688
  if (ignoredSegments.has(seg)) return true;
4732
5689
  }
4733
5690
  return false;
@@ -4735,13 +5692,72 @@ async function createServer(inlineConfig = {}) {
4735
5692
  ignoreInitial: true
4736
5693
  });
4737
5694
  let server;
5695
+ const environmentServices = {};
5696
+ let environmentDriversStarted = false;
5697
+ const logCloseError = (target, error) => {
5698
+ const normalized = error instanceof Error ? error : new Error(String(error));
5699
+ logger.error(`[nasti] failed to close ${target}`, { error: normalized });
5700
+ };
5701
+ const startEnvironmentDrivers = async () => {
5702
+ if (environmentDriversStarted) return;
5703
+ environmentDriversStarted = true;
5704
+ const started = [];
5705
+ const attempted = [];
5706
+ try {
5707
+ for (const [name, environment] of Object.entries(environments)) {
5708
+ if (!environment.driver?.serve) continue;
5709
+ attempted.push(environment);
5710
+ const result = await environment.driver.serve({
5711
+ ...environment.getDriverContext(),
5712
+ server
5713
+ });
5714
+ started.push({ name, environment, service: result ?? {} });
5715
+ }
5716
+ for (const { name, service } of started) {
5717
+ environmentServices[name] = service;
5718
+ if (service.middleware) app.use(service.middleware);
5719
+ }
5720
+ } catch (error) {
5721
+ environmentDriversStarted = false;
5722
+ for (const { name } of started) {
5723
+ delete environmentServices[name];
5724
+ }
5725
+ for (const environment of attempted.reverse()) {
5726
+ try {
5727
+ await environment.driver?.close?.(environment.getDriverContext());
5728
+ } catch (closeError) {
5729
+ logCloseError(`environment driver "${environment.driver.name}"`, closeError);
5730
+ }
5731
+ }
5732
+ throw error;
5733
+ }
5734
+ };
5735
+ const notifyEnvironmentDrivers = (file, event) => {
5736
+ for (const environment of Object.values(environments)) {
5737
+ if (!environment.driver?.watchChange) continue;
5738
+ void Promise.resolve(
5739
+ environment.driver.watchChange(file, event, environment.getDriverContext())
5740
+ ).catch((error) => {
5741
+ logger.error(
5742
+ `[nasti] environment driver "${environment.driver.name}" watchChange failed`,
5743
+ { error }
5744
+ );
5745
+ });
5746
+ }
5747
+ };
4738
5748
  watcher.on("change", (file) => {
4739
5749
  ssrRunner?.invalidateFile(file);
4740
5750
  handleFileChange(file, server);
5751
+ notifyEnvironmentDrivers(file, "change");
4741
5752
  });
4742
5753
  watcher.on("add", (file) => {
4743
5754
  ssrRunner?.invalidateFile(file);
4744
5755
  handleFileChange(file, server);
5756
+ notifyEnvironmentDrivers(file, "add");
5757
+ });
5758
+ watcher.on("unlink", (file) => {
5759
+ ssrRunner?.invalidateFile(file);
5760
+ notifyEnvironmentDrivers(file, "unlink");
4745
5761
  });
4746
5762
  server = {
4747
5763
  config: configWithPlugins,
@@ -4750,10 +5766,12 @@ async function createServer(inlineConfig = {}) {
4750
5766
  watcher,
4751
5767
  ws,
4752
5768
  environments,
5769
+ environmentServices,
4753
5770
  async listen(port) {
4754
5771
  const finalPort = port ?? config.server.port;
4755
5772
  const host = config.server.host === true ? "0.0.0.0" : config.server.host;
4756
5773
  await pluginContainer.buildStart();
5774
+ await startEnvironmentDrivers();
4757
5775
  return new Promise((resolve, reject) => {
4758
5776
  let currentPort = finalPort;
4759
5777
  const onListening = () => {
@@ -4761,15 +5779,20 @@ async function createServer(inlineConfig = {}) {
4761
5779
  config.server.port = actualPort;
4762
5780
  const localUrl = `http://localhost:${actualPort}/`;
4763
5781
  const networkUrl = host === "0.0.0.0" ? `http://${getNetworkAddress()}:${actualPort}/` : null;
5782
+ const driverLocalUrls = Object.values(environmentServices).flatMap((service) => service.localUrls ?? []);
5783
+ const driverNetworkUrls = Object.values(environmentServices).flatMap((service) => service.networkUrls ?? []);
4764
5784
  logger.clearScreen("info");
4765
5785
  const readyIn = Math.ceil(performance.now() - startTime);
4766
5786
  logger.info(
4767
5787
  `
4768
- ${import_picocolors8.default.cyan(import_picocolors8.default.bold("NASTI"))} ${import_picocolors8.default.cyan(`v${"2.2.0"}`)} ${import_picocolors8.default.dim("ready in")} ${import_picocolors8.default.bold(readyIn)} ${import_picocolors8.default.dim("ms")}
5788
+ ${import_picocolors8.default.cyan(import_picocolors8.default.bold("NASTI"))} ${import_picocolors8.default.cyan(`v${"2.4.0"}`)} ${import_picocolors8.default.dim("ready in")} ${import_picocolors8.default.bold(readyIn)} ${import_picocolors8.default.dim("ms")}
4769
5789
  `
4770
5790
  );
4771
5791
  printServerUrls(
4772
- { local: [localUrl], network: networkUrl ? [networkUrl] : [] },
5792
+ {
5793
+ local: [localUrl, ...driverLocalUrls],
5794
+ network: [...networkUrl ? [networkUrl] : [], ...driverNetworkUrls]
5795
+ },
4773
5796
  logger.info
4774
5797
  );
4775
5798
  logger.info("");
@@ -4790,7 +5813,12 @@ async function createServer(inlineConfig = {}) {
4790
5813
  },
4791
5814
  async transformRequest(url) {
4792
5815
  const { transformRequest: transformRequest2 } = await Promise.resolve().then(() => (init_middleware(), middleware_exports));
4793
- return transformRequest2(url, { config: configWithPlugins, pluginContainer, moduleGraph });
5816
+ return transformRequest2(url, {
5817
+ config: configWithPlugins,
5818
+ pluginContainer,
5819
+ moduleGraph,
5820
+ onPrune: (paths) => ws.send({ type: "prune", paths })
5821
+ });
4794
5822
  },
4795
5823
  async ssrLoadModule(url) {
4796
5824
  const runner = await getSsrRunner();
@@ -4799,11 +5827,63 @@ async function createServer(inlineConfig = {}) {
4799
5827
  async close() {
4800
5828
  await pluginContainer.buildEnd();
4801
5829
  await bundledServer?.close();
4802
- watcher.close();
5830
+ let environmentCloseFailed = false;
5831
+ let firstEnvironmentCloseError;
5832
+ for (const environment of Object.values(environments).reverse()) {
5833
+ try {
5834
+ await environment.close();
5835
+ } catch (error) {
5836
+ if (!environmentCloseFailed) {
5837
+ environmentCloseFailed = true;
5838
+ firstEnvironmentCloseError = error;
5839
+ }
5840
+ logCloseError(`environment "${environment.name}"`, error);
5841
+ }
5842
+ }
5843
+ await watcher.close();
4803
5844
  ws.close();
4804
5845
  httpServer.close();
5846
+ if (environmentCloseFailed) {
5847
+ throw firstEnvironmentCloseError;
5848
+ }
4805
5849
  }
4806
5850
  };
5851
+ try {
5852
+ await startEnvironmentDrivers();
5853
+ } catch (error) {
5854
+ if (bundledServer) {
5855
+ try {
5856
+ await bundledServer.close();
5857
+ } catch (closeError) {
5858
+ logCloseError("bundled dev server after driver startup failure", closeError);
5859
+ }
5860
+ }
5861
+ try {
5862
+ await watcher.close();
5863
+ } catch (closeError) {
5864
+ logCloseError("file watcher after driver startup failure", closeError);
5865
+ }
5866
+ try {
5867
+ ws.close();
5868
+ } catch (closeError) {
5869
+ logCloseError("WebSocket server after driver startup failure", closeError);
5870
+ }
5871
+ try {
5872
+ httpServer.close();
5873
+ } catch (closeError) {
5874
+ logCloseError("HTTP server after driver startup failure", closeError);
5875
+ }
5876
+ throw error;
5877
+ }
5878
+ app.use(transformMiddleware({
5879
+ config: configWithPlugins,
5880
+ pluginContainer,
5881
+ moduleGraph,
5882
+ onPrune: (paths) => ws.send({ type: "prune", paths })
5883
+ }));
5884
+ const publicDir = import_node_path15.default.resolve(config.root, "public");
5885
+ app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
5886
+ app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
4807
5887
  const postMiddlewares = [];
4808
5888
  for (const plugin of allPlugins) {
4809
5889
  if (plugin.configureServer) {
@@ -4827,12 +5907,12 @@ function getNetworkAddress() {
4827
5907
  }
4828
5908
  return "localhost";
4829
5909
  }
4830
- var import_node_http, import_node_path14, import_node_os, import_connect, import_sirv, import_chokidar, import_picocolors8;
5910
+ var import_node_http, import_node_path15, import_node_os, import_connect, import_sirv, import_chokidar, import_picocolors8;
4831
5911
  var init_server = __esm({
4832
5912
  "src/server/index.ts"() {
4833
5913
  "use strict";
4834
5914
  import_node_http = __toESM(require("http"), 1);
4835
- import_node_path14 = __toESM(require("path"), 1);
5915
+ import_node_path15 = __toESM(require("path"), 1);
4836
5916
  import_node_os = __toESM(require("os"), 1);
4837
5917
  import_connect = __toESM(require("connect"), 1);
4838
5918
  import_sirv = __toESM(require("sirv"), 1);
@@ -4846,6 +5926,7 @@ var init_server = __esm({
4846
5926
  init_middleware();
4847
5927
  init_hmr();
4848
5928
  init_builtins();
5929
+ init_plugin_api();
4849
5930
  }
4850
5931
  });
4851
5932
 
@@ -4892,6 +5973,7 @@ var init_electron = __esm({
4892
5973
  var electron_exports = {};
4893
5974
  __export(electron_exports, {
4894
5975
  buildElectron: () => buildElectron,
5976
+ createElectronRendererConfig: () => createElectronRendererConfig,
4895
5977
  detectInstalledElectron: () => detectInstalledElectron,
4896
5978
  normalizePreload: () => normalizePreload
4897
5979
  });
@@ -4899,28 +5981,26 @@ async function buildElectron(inlineConfig = {}) {
4899
5981
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
4900
5982
  const startTime = performance.now();
4901
5983
  assertElectronVersion(config);
4902
- console.log(import_picocolors9.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors9.default.dim(` v${"2.2.0"}`));
5984
+ console.log(import_picocolors9.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors9.default.dim(` v${"2.4.0"}`));
4903
5985
  console.log(import_picocolors9.default.dim(` root: ${config.root}`));
4904
5986
  console.log(import_picocolors9.default.dim(` mode: ${config.mode}`));
4905
5987
  console.log(import_picocolors9.default.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
4906
- const outDir = import_node_path15.default.resolve(config.root, config.build.outDir);
4907
- if (config.build.emptyOutDir && import_node_fs10.default.existsSync(outDir)) {
4908
- import_node_fs10.default.rmSync(outDir, { recursive: true, force: true });
5988
+ const outDir = import_node_path16.default.resolve(config.root, config.build.outDir);
5989
+ if (config.build.emptyOutDir && import_node_fs11.default.existsSync(outDir)) {
5990
+ import_node_fs11.default.rmSync(outDir, { recursive: true, force: true });
4909
5991
  }
4910
- import_node_fs10.default.mkdirSync(outDir, { recursive: true });
4911
- const rendererOutDir = import_node_path15.default.join(outDir, "renderer");
5992
+ import_node_fs11.default.mkdirSync(outDir, { recursive: true });
5993
+ const rendererOutDir = import_node_path16.default.join(outDir, "renderer");
4912
5994
  const { build: build2 } = await Promise.resolve().then(() => (init_build(), build_exports));
4913
- await build2({
4914
- ...inlineConfig,
4915
- target: "web",
5995
+ await build2(createElectronRendererConfig(config, inlineConfig, {
4916
5996
  build: {
4917
5997
  ...inlineConfig.build,
4918
5998
  outDir: rendererOutDir,
4919
5999
  emptyOutDir: false
4920
6000
  }
4921
- });
4922
- const mainEntry = import_node_path15.default.resolve(config.root, config.electron.main);
4923
- if (!import_node_fs10.default.existsSync(mainEntry)) {
6001
+ }));
6002
+ const mainEntry = import_node_path16.default.resolve(config.root, config.electron.main);
6003
+ if (!import_node_fs11.default.existsSync(mainEntry)) {
4924
6004
  throw new Error(
4925
6005
  `Electron main entry not found: ${config.electron.main}
4926
6006
  \u5728 nasti.config.ts \u7684 electron.main \u6307\u5B9A\u4E3B\u8FDB\u7A0B\u5165\u53E3\u6587\u4EF6\u3002`
@@ -4934,11 +6014,11 @@ async function buildElectron(inlineConfig = {}) {
4934
6014
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
4935
6015
  const preloadFiles = [];
4936
6016
  for (const entry of preloadEntries) {
4937
- if (!import_node_fs10.default.existsSync(entry)) {
6017
+ if (!import_node_fs11.default.existsSync(entry)) {
4938
6018
  console.warn(import_picocolors9.default.yellow(` \u26A0 preload entry not found, skipped: ${entry}`));
4939
6019
  continue;
4940
6020
  }
4941
- const base = import_node_path15.default.basename(entry).replace(/\.[^.]+$/, "");
6021
+ const base = import_node_path16.default.basename(entry).replace(/\.[^.]+$/, "");
4942
6022
  const out = outFileName(outDir, base, config.electron.preloadFormat);
4943
6023
  await bundleNode(config, entry, {
4944
6024
  outFile: out,
@@ -4950,10 +6030,10 @@ async function buildElectron(inlineConfig = {}) {
4950
6030
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
4951
6031
  console.log(import_picocolors9.default.green(`
4952
6032
  \u2713 Electron build complete in ${elapsed}s`));
4953
- console.log(import_picocolors9.default.dim(` renderer: ${import_node_path15.default.relative(config.root, rendererOutDir)}/`));
4954
- console.log(import_picocolors9.default.dim(` main: ${import_node_path15.default.relative(config.root, mainFile)}`));
6033
+ console.log(import_picocolors9.default.dim(` renderer: ${import_node_path16.default.relative(config.root, rendererOutDir)}/`));
6034
+ console.log(import_picocolors9.default.dim(` main: ${import_node_path16.default.relative(config.root, mainFile)}`));
4955
6035
  for (const pf of preloadFiles) {
4956
- console.log(import_picocolors9.default.dim(` preload: ${import_node_path15.default.relative(config.root, pf)}`));
6036
+ console.log(import_picocolors9.default.dim(` preload: ${import_node_path16.default.relative(config.root, pf)}`));
4957
6037
  }
4958
6038
  console.log();
4959
6039
  return { rendererOutDir, mainFile, preloadFiles };
@@ -4972,7 +6052,8 @@ async function bundleNode(config, entry, opts) {
4972
6052
  const result = transformCode(id, code, {
4973
6053
  sourcemap: !!config.build.sourcemap,
4974
6054
  jsxRuntime: "automatic",
4975
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
6055
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
6056
+ target: config.electron.nodeTarget
4976
6057
  });
4977
6058
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
4978
6059
  }
@@ -4983,10 +6064,14 @@ async function bundleNode(config, entry, opts) {
4983
6064
  ...restInputOptions,
4984
6065
  input: entry,
4985
6066
  platform: "node",
4986
- transform: { ...userTransform, define: mergedDefine },
6067
+ transform: {
6068
+ ...userTransform,
6069
+ target: config.electron.nodeTarget,
6070
+ define: mergedDefine
6071
+ },
4987
6072
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
4988
6073
  });
4989
- import_node_fs10.default.mkdirSync(import_node_path15.default.dirname(opts.outFile), { recursive: true });
6074
+ import_node_fs11.default.mkdirSync(import_node_path16.default.dirname(opts.outFile), { recursive: true });
4990
6075
  await bundle2.write({
4991
6076
  sourcemap: !!config.build.sourcemap,
4992
6077
  minify: !!config.build.minify,
@@ -4997,16 +6082,35 @@ async function bundleNode(config, entry, opts) {
4997
6082
  codeSplitting: false
4998
6083
  });
4999
6084
  await bundle2.close();
5000
- console.log(import_picocolors9.default.dim(` \u2713 ${opts.label} \u2192 ${import_node_path15.default.relative(config.root, opts.outFile)}`));
6085
+ console.log(import_picocolors9.default.dim(` \u2713 ${opts.label} \u2192 ${import_node_path16.default.relative(config.root, opts.outFile)}`));
5001
6086
  return opts.outFile;
5002
6087
  }
6088
+ function createElectronRendererConfig(config, inlineConfig = {}, overrides = {}) {
6089
+ const inlineClient = inlineConfig.environments?.client ?? {};
6090
+ return {
6091
+ ...inlineConfig,
6092
+ ...overrides,
6093
+ root: config.root,
6094
+ mode: config.mode,
6095
+ target: "web",
6096
+ framework: config.framework,
6097
+ base: config.base === "/" ? "./" : config.base,
6098
+ environments: {
6099
+ ...inlineConfig.environments ?? {},
6100
+ client: {
6101
+ ...inlineClient,
6102
+ html: config.electron.renderer
6103
+ }
6104
+ }
6105
+ };
6106
+ }
5003
6107
  function outFileName(outDir, base, format) {
5004
6108
  const ext = format === "cjs" ? ".cjs" : ".mjs";
5005
- return import_node_path15.default.join(outDir, base + ext);
6109
+ return import_node_path16.default.join(outDir, base + ext);
5006
6110
  }
5007
6111
  function normalizePreload(preload, root) {
5008
6112
  const list = Array.isArray(preload) ? preload : preload ? [preload] : [];
5009
- return list.map((p) => import_node_path15.default.resolve(root, p));
6113
+ return list.map((p) => import_node_path16.default.resolve(root, p));
5010
6114
  }
5011
6115
  function assertElectronVersion(config) {
5012
6116
  const min = config.electron.minVersion;
@@ -5021,21 +6125,21 @@ function assertElectronVersion(config) {
5021
6125
  }
5022
6126
  function detectInstalledElectron(root) {
5023
6127
  try {
5024
- const pkgPath = import_node_path15.default.resolve(root, "node_modules/electron/package.json");
5025
- if (!import_node_fs10.default.existsSync(pkgPath)) return null;
5026
- const pkg = JSON.parse(import_node_fs10.default.readFileSync(pkgPath, "utf-8"));
6128
+ const pkgPath = import_node_path16.default.resolve(root, "node_modules/electron/package.json");
6129
+ if (!import_node_fs11.default.existsSync(pkgPath)) return null;
6130
+ const pkg = JSON.parse(import_node_fs11.default.readFileSync(pkgPath, "utf-8"));
5027
6131
  const major = parseInt(String(pkg.version).split(".")[0], 10);
5028
6132
  return Number.isFinite(major) ? major : null;
5029
6133
  } catch {
5030
6134
  return null;
5031
6135
  }
5032
6136
  }
5033
- var import_node_path15, import_node_fs10, import_rolldown2, import_picocolors9;
6137
+ var import_node_path16, import_node_fs11, import_rolldown2, import_picocolors9;
5034
6138
  var init_electron2 = __esm({
5035
6139
  "src/build/electron.ts"() {
5036
6140
  "use strict";
5037
- import_node_path15 = __toESM(require("path"), 1);
5038
- import_node_fs10 = __toESM(require("fs"), 1);
6141
+ import_node_path16 = __toESM(require("path"), 1);
6142
+ import_node_fs11 = __toESM(require("fs"), 1);
5039
6143
  import_rolldown2 = require("rolldown");
5040
6144
  import_picocolors9 = __toESM(require("picocolors"), 1);
5041
6145
  init_config();
@@ -5049,23 +6153,28 @@ var init_electron2 = __esm({
5049
6153
  // src/server/electron-dev.ts
5050
6154
  var electron_dev_exports = {};
5051
6155
  __export(electron_dev_exports, {
6156
+ electronRendererDevPath: () => electronRendererDevPath,
5052
6157
  startElectronDev: () => startElectronDev
5053
6158
  });
5054
6159
  async function startElectronDev(inlineConfig = {}) {
5055
6160
  const { noSpawn, ...rest } = inlineConfig;
5056
6161
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
5057
6162
  warnElectronVersion(config);
5058
- console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.2.0"}`));
6163
+ console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.4.0"}`));
5059
6164
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5060
- const server = await createServer2({ ...rest, target: "electron" });
6165
+ const server = await createServer2({
6166
+ ...rest,
6167
+ target: "electron",
6168
+ framework: config.framework
6169
+ });
5061
6170
  await server.listen();
5062
- const devUrl = `http://localhost:${server.config.server.port}/`;
6171
+ const devUrl = `http://localhost:${server.config.server.port}` + electronRendererDevPath(config.electron.renderer);
5063
6172
  console.log(import_picocolors10.default.dim(` renderer: ${devUrl}`));
5064
- const stageDir = import_node_path16.default.resolve(config.root, ".nasti");
5065
- import_node_fs11.default.mkdirSync(stageDir, { recursive: true });
5066
- const mainEntry = import_node_path16.default.resolve(config.root, config.electron.main);
6173
+ const stageDir = import_node_path17.default.resolve(config.root, ".nasti");
6174
+ import_node_fs12.default.mkdirSync(stageDir, { recursive: true });
6175
+ const mainEntry = import_node_path17.default.resolve(config.root, config.electron.main);
5067
6176
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
5068
- const builtMainFile = import_node_path16.default.join(stageDir, "main" + extFor(config.electron.mainFormat));
6177
+ const builtMainFile = import_node_path17.default.join(stageDir, "main" + extFor(config.electron.mainFormat));
5069
6178
  const builtPreloadFiles = [];
5070
6179
  const compileAll = async () => {
5071
6180
  await compileNode(config, mainEntry, {
@@ -5075,9 +6184,9 @@ async function startElectronDev(inlineConfig = {}) {
5075
6184
  });
5076
6185
  builtPreloadFiles.length = 0;
5077
6186
  for (const entry of preloadEntries) {
5078
- if (!import_node_fs11.default.existsSync(entry)) continue;
5079
- const base = import_node_path16.default.basename(entry).replace(/\.[^.]+$/, "");
5080
- const out = import_node_path16.default.join(stageDir, base + extFor(config.electron.preloadFormat));
6187
+ if (!import_node_fs12.default.existsSync(entry)) continue;
6188
+ const base = import_node_path17.default.basename(entry).replace(/\.[^.]+$/, "");
6189
+ const out = import_node_path17.default.join(stageDir, base + extFor(config.electron.preloadFormat));
5081
6190
  await compileNode(config, entry, {
5082
6191
  outFile: out,
5083
6192
  format: config.electron.preloadFormat,
@@ -5116,7 +6225,7 @@ async function startElectronDev(inlineConfig = {}) {
5116
6225
  };
5117
6226
  spawnElectron();
5118
6227
  if (config.electron.autoRestart) {
5119
- const watchTargets = [mainEntry, ...preloadEntries].filter(import_node_fs11.default.existsSync);
6228
+ const watchTargets = [mainEntry, ...preloadEntries].filter(import_node_fs12.default.existsSync);
5120
6229
  const watcher = import_chokidar2.default.watch(watchTargets, { ignoreInitial: true });
5121
6230
  let restarting = null;
5122
6231
  let pending = false;
@@ -5181,18 +6290,22 @@ async function compileNode(config, entry, opts) {
5181
6290
  const result = transformCode(id, code, {
5182
6291
  sourcemap: true,
5183
6292
  jsxRuntime: "automatic",
5184
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
6293
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
6294
+ target: config.electron.nodeTarget
5185
6295
  });
5186
6296
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
5187
6297
  }
5188
6298
  };
5189
6299
  const bundle2 = await (0, import_rolldown3.rolldown)({
5190
6300
  input: entry,
5191
- transform: { define: envDefine },
6301
+ transform: {
6302
+ target: config.electron.nodeTarget,
6303
+ define: envDefine
6304
+ },
5192
6305
  platform: "node",
5193
6306
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5194
6307
  });
5195
- import_node_fs11.default.mkdirSync(import_node_path16.default.dirname(opts.outFile), { recursive: true });
6308
+ import_node_fs12.default.mkdirSync(import_node_path17.default.dirname(opts.outFile), { recursive: true });
5196
6309
  await bundle2.write({
5197
6310
  file: opts.outFile,
5198
6311
  format: opts.format === "cjs" ? "cjs" : "esm",
@@ -5204,15 +6317,19 @@ async function compileNode(config, entry, opts) {
5204
6317
  });
5205
6318
  await bundle2.close();
5206
6319
  }
6320
+ function electronRendererDevPath(renderer) {
6321
+ const normalized = renderer.split(import_node_path17.default.sep).join("/").replace(/^\.?\//, "");
6322
+ return normalized === "index.html" ? "/" : `/${normalized}`;
6323
+ }
5207
6324
  function resolveElectronBinary(config) {
5208
- if (config.electron.electronPath && import_node_fs11.default.existsSync(config.electron.electronPath)) {
6325
+ if (config.electron.electronPath && import_node_fs12.default.existsSync(config.electron.electronPath)) {
5209
6326
  return config.electron.electronPath;
5210
6327
  }
5211
6328
  try {
5212
- const require2 = (0, import_node_module7.createRequire)(import_node_path16.default.resolve(config.root, "package.json"));
6329
+ const require2 = (0, import_node_module7.createRequire)(import_node_path17.default.resolve(config.root, "package.json"));
5213
6330
  const pathFile = require2.resolve("electron");
5214
6331
  const electronModule = require2(pathFile);
5215
- if (typeof electronModule === "string" && import_node_fs11.default.existsSync(electronModule)) {
6332
+ if (typeof electronModule === "string" && import_node_fs12.default.existsSync(electronModule)) {
5216
6333
  return electronModule;
5217
6334
  }
5218
6335
  } catch {
@@ -5237,12 +6354,12 @@ function warnElectronVersion(config) {
5237
6354
  );
5238
6355
  }
5239
6356
  }
5240
- var import_node_path16, import_node_fs11, import_node_module7, import_node_child_process, import_chokidar2, import_picocolors10, import_rolldown3;
6357
+ var import_node_path17, import_node_fs12, import_node_module7, import_node_child_process, import_chokidar2, import_picocolors10, import_rolldown3;
5241
6358
  var init_electron_dev = __esm({
5242
6359
  "src/server/electron-dev.ts"() {
5243
6360
  "use strict";
5244
- import_node_path16 = __toESM(require("path"), 1);
5245
- import_node_fs11 = __toESM(require("fs"), 1);
6361
+ import_node_path17 = __toESM(require("path"), 1);
6362
+ import_node_fs12 = __toESM(require("fs"), 1);
5246
6363
  import_node_module7 = require("module");
5247
6364
  import_node_child_process = require("child_process");
5248
6365
  import_chokidar2 = __toESM(require("chokidar"), 1);
@@ -5395,20 +6512,20 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
5395
6512
  const logger = createCliLogger(options);
5396
6513
  try {
5397
6514
  const http2 = await import("http");
5398
- const path17 = await import("path");
6515
+ const path18 = await import("path");
5399
6516
  const os2 = await import("os");
5400
6517
  const sirv2 = (await import("sirv")).default;
5401
6518
  const connect2 = (await import("connect")).default;
5402
6519
  const { printServerUrls: printServerUrls2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
5403
- const resolvedRoot = path17.resolve(root ?? ".");
5404
- const outDir = path17.resolve(resolvedRoot, options.outDir);
6520
+ const resolvedRoot = path18.resolve(root ?? ".");
6521
+ const outDir = path18.resolve(resolvedRoot, options.outDir);
5405
6522
  const app = connect2();
5406
6523
  app.use(sirv2(outDir, { single: true, etag: true, gzip: true, brotli: true }));
5407
6524
  const port = options.port;
5408
6525
  const host = options.host === true ? "0.0.0.0" : options.host ?? "localhost";
5409
6526
  http2.createServer(app).listen(port, host, () => {
5410
6527
  logger.info(`
5411
- ${import_picocolors11.default.cyan(import_picocolors11.default.bold("NASTI"))} ${import_picocolors11.default.cyan(`v${"2.2.0"}`)} ${import_picocolors11.default.dim("preview")}
6528
+ ${import_picocolors11.default.cyan(import_picocolors11.default.bold("NASTI"))} ${import_picocolors11.default.cyan(`v${"2.4.0"}`)} ${import_picocolors11.default.dim("preview")}
5412
6529
  `);
5413
6530
  printServerUrls2(
5414
6531
  {
@@ -5425,6 +6542,6 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
5425
6542
  }
5426
6543
  });
5427
6544
  cli.help();
5428
- cli.version("2.2.0");
6545
+ cli.version("2.4.0");
5429
6546
  cli.parse();
5430
6547
  //# sourceMappingURL=cli.cjs.map