@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/index.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) => (path18) => {
9
- var fn = map[path18];
8
+ var __glob = (map) => (path19) => {
9
+ var fn = map[path19];
10
10
  if (fn) return fn();
11
- throw new Error("Module not found in bundle: " + path18);
11
+ throw new Error("Module not found in bundle: " + path19);
12
12
  };
13
13
  var __esm = (fn, res) => function __init() {
14
14
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -221,6 +221,92 @@ var init_logger = __esm({
221
221
  }
222
222
  });
223
223
 
224
+ // src/core/plugin-api.ts
225
+ function orderPlugins(plugins) {
226
+ 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);
227
+ const indexesByName = /* @__PURE__ */ new Map();
228
+ baseline.forEach((plugin, index2) => {
229
+ const indexes = indexesByName.get(plugin.name) ?? [];
230
+ indexes.push(index2);
231
+ indexesByName.set(plugin.name, indexes);
232
+ });
233
+ const edges = baseline.map(() => /* @__PURE__ */ new Set());
234
+ const indegree = baseline.map(() => 0);
235
+ const addEdge = (from, to) => {
236
+ if (from === to || edges[from].has(to)) return;
237
+ edges[from].add(to);
238
+ indegree[to]++;
239
+ };
240
+ baseline.forEach((plugin, current) => {
241
+ for (const dependency of plugin.pre ?? []) {
242
+ for (const before of indexesByName.get(dependency) ?? []) addEdge(before, current);
243
+ }
244
+ for (const dependency of plugin.post ?? []) {
245
+ for (const after of indexesByName.get(dependency) ?? []) addEdge(current, after);
246
+ }
247
+ });
248
+ const ready = indegree.map((degree, index2) => ({ degree, index: index2 })).filter(({ degree }) => degree === 0).map(({ index: index2 }) => index2);
249
+ const ordered = [];
250
+ while (ready.length > 0) {
251
+ ready.sort((a, b) => a - b);
252
+ const current = ready.shift();
253
+ ordered.push(baseline[current]);
254
+ for (const next of edges[current]) {
255
+ indegree[next]--;
256
+ if (indegree[next] === 0) ready.push(next);
257
+ }
258
+ }
259
+ if (ordered.length !== baseline.length) {
260
+ const cyclic = baseline.filter((_, index2) => indegree[index2] > 0).map((plugin) => plugin.name);
261
+ throw new Error(
262
+ `[nasti] circular plugin setup dependency: ${[...new Set(cyclic)].join(", ")}`
263
+ );
264
+ }
265
+ return ordered;
266
+ }
267
+ async function setupPluginApi(config, plugins) {
268
+ const exposed = /* @__PURE__ */ new Map();
269
+ const api = {
270
+ config,
271
+ logger: config.logger,
272
+ expose(key, value) {
273
+ if (exposed.has(key) && exposed.get(key) !== value) {
274
+ throw new Error(`[nasti] plugin API key already exposed: ${String(key)}`);
275
+ }
276
+ exposed.set(key, value);
277
+ },
278
+ useExposed(key) {
279
+ return exposed.get(key);
280
+ }
281
+ };
282
+ apiByConfig.set(config, api);
283
+ for (const plugin of plugins) {
284
+ await plugin.setup?.(api);
285
+ }
286
+ return api;
287
+ }
288
+ function getPluginApi(config) {
289
+ const api = apiByConfig.get(config);
290
+ if (!api) {
291
+ throw new Error(
292
+ "[nasti] internal: plugin API requested before setup; getPluginApi requires the original ResolvedConfig reference registered by setupPluginApi, not a shallow copy"
293
+ );
294
+ }
295
+ return api;
296
+ }
297
+ function enforceRank(plugin) {
298
+ if (plugin.enforce === "pre") return 0;
299
+ if (plugin.enforce === "post") return 2;
300
+ return 1;
301
+ }
302
+ var apiByConfig;
303
+ var init_plugin_api = __esm({
304
+ "src/core/plugin-api.ts"() {
305
+ "use strict";
306
+ apiByConfig = /* @__PURE__ */ new WeakMap();
307
+ }
308
+ });
309
+
224
310
  // src/config/index.ts
225
311
  function loadTsconfigPaths(root) {
226
312
  const tsconfigPath = import_node_path.default.resolve(root, "tsconfig.json");
@@ -258,6 +344,43 @@ async function loadConfigFromFile(root) {
258
344
  }
259
345
  return {};
260
346
  }
347
+ function detectFramework(root) {
348
+ const sourceRoot = import_node_path.default.resolve(root, "src");
349
+ if (containsVueFile(sourceRoot)) return "vue";
350
+ const packagePath = import_node_path.default.resolve(root, "package.json");
351
+ if (import_node_fs.default.existsSync(packagePath)) {
352
+ try {
353
+ const pkg = JSON.parse(import_node_fs.default.readFileSync(packagePath, "utf-8"));
354
+ const dependencies = {
355
+ ...pkg.dependencies ?? {},
356
+ ...pkg.devDependencies ?? {},
357
+ ...pkg.peerDependencies ?? {},
358
+ ...pkg.optionalDependencies ?? {}
359
+ };
360
+ const hasVue = "vue" in dependencies || "@vue/runtime-dom" in dependencies;
361
+ const hasReact = "react" in dependencies || "react-dom" in dependencies;
362
+ if (hasVue && !hasReact) return "vue";
363
+ if (hasReact) return "react";
364
+ if (hasVue) return "vue";
365
+ } catch {
366
+ }
367
+ }
368
+ return "react";
369
+ }
370
+ function containsVueFile(dir, depth = 0) {
371
+ if (depth > 5 || !import_node_fs.default.existsSync(dir)) return false;
372
+ try {
373
+ for (const entry of import_node_fs.default.readdirSync(dir, { withFileTypes: true })) {
374
+ if (entry.isFile() && entry.name.endsWith(".vue")) return true;
375
+ if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "dist" && containsVueFile(import_node_path.default.join(dir, entry.name), depth + 1)) {
376
+ return true;
377
+ }
378
+ }
379
+ } catch {
380
+ return false;
381
+ }
382
+ return false;
383
+ }
261
384
  async function loadTsConfig(filePath) {
262
385
  const { transformSync: transformSync2 } = await import("oxc-transform");
263
386
  const code = import_node_fs.default.readFileSync(filePath, "utf-8");
@@ -304,7 +427,7 @@ async function resolveConfig(inlineConfig = {}, command) {
304
427
  base: merged.base ?? defaults.base,
305
428
  mode,
306
429
  target: merged.target ?? defaults.target,
307
- framework: merged.framework ?? defaults.framework,
430
+ framework: (merged.framework ?? defaults.framework) === "auto" ? detectFramework(root) : merged.framework,
308
431
  command,
309
432
  resolve: {
310
433
  // tsconfig paths 优先级最低:tsconfig < defaults < user config
@@ -351,7 +474,13 @@ async function resolveConfig(inlineConfig = {}, command) {
351
474
  if (envOptions.build) Object.assign(resolved.build, envOptions.build);
352
475
  resolved.environments.client = {
353
476
  consumer,
354
- entry: [],
477
+ buildEnabled: envOptions.buildEnabled ?? true,
478
+ entry: normalizeEnvironmentEntries(envOptions.entry, root),
479
+ html: import_node_path.default.resolve(
480
+ root,
481
+ envOptions.html ?? (resolved.target === "electron" ? resolved.electron.renderer : "index.html")
482
+ ),
483
+ driver: envOptions.driver,
355
484
  // 同引用 —— 精确镜像(assertClientEnvironmentMirror 校验)
356
485
  resolve: resolved.resolve,
357
486
  build: resolved.build
@@ -360,7 +489,10 @@ async function resolveConfig(inlineConfig = {}, command) {
360
489
  }
361
490
  resolved.environments[name] = {
362
491
  consumer,
363
- entry: (Array.isArray(envOptions.entry) ? envOptions.entry : envOptions.entry ? [envOptions.entry] : []).map((e) => import_node_path.default.resolve(root, e)),
492
+ buildEnabled: envOptions.buildEnabled ?? true,
493
+ entry: normalizeEnvironmentEntries(envOptions.entry, root),
494
+ html: envOptions.consumer === "client" && envOptions.html ? import_node_path.default.resolve(root, envOptions.html) : void 0,
495
+ driver: envOptions.driver,
364
496
  resolve: {
365
497
  alias: { ...resolved.resolve.alias, ...envOptions.resolve?.alias },
366
498
  extensions: envOptions.resolve?.extensions ?? [...resolved.resolve.extensions],
@@ -379,12 +511,13 @@ async function resolveConfig(inlineConfig = {}, command) {
379
511
  };
380
512
  }
381
513
  assertClientEnvironmentMirror(resolved);
382
- const filteredPlugins = rawPlugins.filter((p) => {
514
+ const filteredPlugins = orderPlugins(rawPlugins.filter((p) => {
383
515
  if (!p.apply) return true;
384
516
  if (typeof p.apply === "function") return p.apply(resolved, env);
385
517
  return p.apply === command;
386
- });
518
+ }));
387
519
  resolved.plugins = filteredPlugins;
520
+ await setupPluginApi(resolved, filteredPlugins);
388
521
  if (resolved.target === "electron") {
389
522
  const autoExternal = detectNativeDeps(root);
390
523
  if (autoExternal.length > 0) {
@@ -400,6 +533,10 @@ async function resolveConfig(inlineConfig = {}, command) {
400
533
  }
401
534
  return resolved;
402
535
  }
536
+ function normalizeEnvironmentEntries(entry, root) {
537
+ const entries = Array.isArray(entry) ? entry : entry ? [entry] : [];
538
+ return entries.map((item) => import_node_path.default.resolve(root, item));
539
+ }
403
540
  function detectNativeDeps(root) {
404
541
  const result = /* @__PURE__ */ new Set();
405
542
  const pkgJsonPath = import_node_path.default.resolve(root, "package.json");
@@ -520,6 +657,7 @@ var init_config = __esm({
520
657
  import_node_fs = __toESM(require("fs"), 1);
521
658
  init_defaults();
522
659
  init_logger();
660
+ init_plugin_api();
523
661
  CONFIG_FILES = [
524
662
  "nasti.config.ts",
525
663
  "nasti.config.js",
@@ -578,6 +716,7 @@ function resolvePlugin(config) {
578
716
  }
579
717
  if (!source.startsWith("/") && !source.startsWith(".")) {
580
718
  if (vueRuntimeEntry && source === "vue") return vueRuntimeEntry;
719
+ if (config.command === "build") return null;
581
720
  try {
582
721
  const resolved = require2.resolve(source, {
583
722
  paths: [importer ? import_node_path2.default.dirname(importer) : config.root]
@@ -713,27 +852,27 @@ var require_process = __commonJS({
713
852
  var require_filesystem = __commonJS({
714
853
  "node_modules/detect-libc/lib/filesystem.js"(exports2, module2) {
715
854
  "use strict";
716
- var fs13 = require("fs");
855
+ var fs14 = require("fs");
717
856
  var LDD_PATH = "/usr/bin/ldd";
718
857
  var SELF_PATH = "/proc/self/exe";
719
858
  var MAX_LENGTH = 2048;
720
- var readFileSync = (path18) => {
721
- const fd = fs13.openSync(path18, "r");
859
+ var readFileSync = (path19) => {
860
+ const fd = fs14.openSync(path19, "r");
722
861
  const buffer = Buffer.alloc(MAX_LENGTH);
723
- const bytesRead = fs13.readSync(fd, buffer, 0, MAX_LENGTH, 0);
724
- fs13.close(fd, () => {
862
+ const bytesRead = fs14.readSync(fd, buffer, 0, MAX_LENGTH, 0);
863
+ fs14.close(fd, () => {
725
864
  });
726
865
  return buffer.subarray(0, bytesRead);
727
866
  };
728
- var readFile = (path18) => new Promise((resolve, reject) => {
729
- fs13.open(path18, "r", (err, fd) => {
867
+ var readFile = (path19) => new Promise((resolve, reject) => {
868
+ fs14.open(path19, "r", (err, fd) => {
730
869
  if (err) {
731
870
  reject(err);
732
871
  } else {
733
872
  const buffer = Buffer.alloc(MAX_LENGTH);
734
- fs13.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
873
+ fs14.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
735
874
  resolve(buffer.subarray(0, bytesRead));
736
- fs13.close(fd, () => {
875
+ fs14.close(fd, () => {
737
876
  });
738
877
  });
739
878
  }
@@ -845,11 +984,11 @@ var require_detect_libc = __commonJS({
845
984
  }
846
985
  return null;
847
986
  };
848
- var familyFromInterpreterPath = (path18) => {
849
- if (path18) {
850
- if (path18.includes("/ld-musl-")) {
987
+ var familyFromInterpreterPath = (path19) => {
988
+ if (path19) {
989
+ if (path19.includes("/ld-musl-")) {
851
990
  return MUSL;
852
- } else if (path18.includes("/ld-linux-")) {
991
+ } else if (path19.includes("/ld-linux-")) {
853
992
  return GLIBC;
854
993
  }
855
994
  }
@@ -896,8 +1035,8 @@ var require_detect_libc = __commonJS({
896
1035
  cachedFamilyInterpreter = null;
897
1036
  try {
898
1037
  const selfContent = await readFile(SELF_PATH);
899
- const path18 = interpreterPath(selfContent);
900
- cachedFamilyInterpreter = familyFromInterpreterPath(path18);
1038
+ const path19 = interpreterPath(selfContent);
1039
+ cachedFamilyInterpreter = familyFromInterpreterPath(path19);
901
1040
  } catch (e) {
902
1041
  }
903
1042
  return cachedFamilyInterpreter;
@@ -909,8 +1048,8 @@ var require_detect_libc = __commonJS({
909
1048
  cachedFamilyInterpreter = null;
910
1049
  try {
911
1050
  const selfContent = readFileSync(SELF_PATH);
912
- const path18 = interpreterPath(selfContent);
913
- cachedFamilyInterpreter = familyFromInterpreterPath(path18);
1051
+ const path19 = interpreterPath(selfContent);
1052
+ cachedFamilyInterpreter = familyFromInterpreterPath(path19);
914
1053
  } catch (e) {
915
1054
  }
916
1055
  return cachedFamilyInterpreter;
@@ -1915,7 +2054,8 @@ function transformCode(filename, code, options = {}) {
1915
2054
  importSource: options.jsxImportSource ?? "react",
1916
2055
  refresh: options.reactRefresh ?? false
1917
2056
  } : void 0,
1918
- sourcemap: options.sourcemap ?? true
2057
+ sourcemap: options.sourcemap ?? true,
2058
+ target: options.target
1919
2059
  });
1920
2060
  if (result.errors && result.errors.length > 0) {
1921
2061
  const msg = result.errors.map((e) => e.message ?? String(e)).join("\n");
@@ -1973,8 +2113,8 @@ function vuePlugin(config) {
1973
2113
  let descriptor = descriptorCache.get(filePath);
1974
2114
  if (!descriptor) {
1975
2115
  try {
1976
- const fs13 = await import("fs");
1977
- const source = fs13.readFileSync(filePath, "utf-8");
2116
+ const fs14 = await import("fs");
2117
+ const source = fs14.readFileSync(filePath, "utf-8");
1978
2118
  const parsed = sfc.parse(source, { filename: filePath });
1979
2119
  if (parsed.errors.length) return null;
1980
2120
  descriptor = parsed.descriptor;
@@ -2118,7 +2258,7 @@ function htmlPlugin(config) {
2118
2258
  transformIndexHtml(html) {
2119
2259
  const tags = [];
2120
2260
  if (config.command === "serve") {
2121
- const isReactLike = config.framework === "react" || config.framework === "auto";
2261
+ const isReactLike = config.framework === "react";
2122
2262
  if (isReactLike) {
2123
2263
  tags.push({
2124
2264
  tag: "script",
@@ -2172,8 +2312,8 @@ function serializeTag(tag) {
2172
2312
  }
2173
2313
  return ` <${tag.tag}${attrs}>${children}</${tag.tag}>`;
2174
2314
  }
2175
- async function readHtmlFile(root) {
2176
- const htmlPath = import_node_path6.default.resolve(root, "index.html");
2315
+ async function readHtmlFile(root, htmlFile = "index.html") {
2316
+ const htmlPath = import_node_path6.default.isAbsolute(htmlFile) ? htmlFile : import_node_path6.default.resolve(root, htmlFile);
2177
2317
  if (!import_node_fs4.default.existsSync(htmlPath)) return null;
2178
2318
  return import_node_fs4.default.readFileSync(htmlPath, "utf-8");
2179
2319
  }
@@ -2196,16 +2336,27 @@ window.__vite_plugin_react_preamble_installed__ = true;
2196
2336
  // src/plugins/builtins.ts
2197
2337
  function resolvePluginList(config, userPlugins, opts = {}) {
2198
2338
  const isServe = config.command === "serve";
2339
+ let environmentOptions;
2340
+ if (opts.environmentName) {
2341
+ environmentOptions = config.environments[opts.environmentName];
2342
+ if (!environmentOptions) {
2343
+ throw new Error(
2344
+ `[nasti] unknown environment "${opts.environmentName}" \u2014 declare it in config.environments`
2345
+ );
2346
+ }
2347
+ }
2348
+ const pluginConfig = environmentOptions ? { ...config, resolve: environmentOptions.resolve, build: environmentOptions.build } : config;
2349
+ const consumer = opts.consumer ?? environmentOptions?.consumer;
2199
2350
  return [
2200
2351
  // vuePlugin 排最前(enforce: 'pre' 语义):.vue 先编译成 JS 再走后续管道
2201
- ...config.framework === "vue" ? [vuePlugin(config)] : [],
2202
- resolvePlugin(config),
2203
- cssPlugin(config, opts.cssEngine, opts.consumer),
2204
- assetsPlugin(config),
2205
- ...isServe ? [htmlPlugin(config)] : [],
2352
+ ...config.framework === "vue" ? [vuePlugin(pluginConfig)] : [],
2353
+ resolvePlugin(pluginConfig),
2354
+ cssPlugin(pluginConfig, opts.cssEngine, consumer),
2355
+ assetsPlugin(pluginConfig),
2356
+ ...isServe ? [htmlPlugin(pluginConfig)] : [],
2206
2357
  ...userPlugins,
2207
2358
  // cssPostPlugin 最后(enforce: 'post' 语义):renderChunk 聚合抽取
2208
- ...!isServe && opts.cssEngine ? [cssPostPlugin(config, opts.cssEngine)] : []
2359
+ ...!isServe && opts.cssEngine ? [cssPostPlugin(pluginConfig, opts.cssEngine)] : []
2209
2360
  ];
2210
2361
  }
2211
2362
  var init_builtins = __esm({
@@ -2221,21 +2372,11 @@ var init_builtins = __esm({
2221
2372
  });
2222
2373
 
2223
2374
  // src/core/plugin-container.ts
2224
- function sortPlugins(plugins) {
2225
- const pre = [];
2226
- const normal = [];
2227
- const post = [];
2228
- for (const plugin of plugins) {
2229
- if (plugin.enforce === "pre") pre.push(plugin);
2230
- else if (plugin.enforce === "post") post.push(plugin);
2231
- else normal.push(plugin);
2232
- }
2233
- return [...pre, ...normal, ...post];
2234
- }
2235
2375
  var PluginContainer;
2236
2376
  var init_plugin_container = __esm({
2237
2377
  "src/core/plugin-container.ts"() {
2238
2378
  "use strict";
2379
+ init_plugin_api();
2239
2380
  PluginContainer = class {
2240
2381
  plugins;
2241
2382
  config;
@@ -2246,7 +2387,7 @@ var init_plugin_container = __esm({
2246
2387
  constructor(config, environment) {
2247
2388
  this.config = config;
2248
2389
  this.environment = environment;
2249
- this.plugins = sortPlugins(config.plugins);
2390
+ this.plugins = orderPlugins(config.plugins);
2250
2391
  this.ctx = this.createContext();
2251
2392
  }
2252
2393
  createContext() {
@@ -2345,17 +2486,35 @@ var init_plugin_container = __esm({
2345
2486
  }
2346
2487
  });
2347
2488
 
2489
+ // src/core/url.ts
2490
+ function removeTimestampQuery(url) {
2491
+ const hashIndex = url.indexOf("#");
2492
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
2493
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
2494
+ const queryIndex = withoutHash.indexOf("?");
2495
+ if (queryIndex < 0) return url;
2496
+ const pathname = withoutHash.slice(0, queryIndex);
2497
+ const query = withoutHash.slice(queryIndex + 1).split("&").filter((part) => !/^t=\d+$/.test(part)).join("&");
2498
+ return pathname + (query ? `?${query}` : "") + hash;
2499
+ }
2500
+ var init_url = __esm({
2501
+ "src/core/url.ts"() {
2502
+ "use strict";
2503
+ }
2504
+ });
2505
+
2348
2506
  // src/core/module-graph.ts
2349
2507
  var ModuleGraph;
2350
2508
  var init_module_graph = __esm({
2351
2509
  "src/core/module-graph.ts"() {
2352
2510
  "use strict";
2511
+ init_url();
2353
2512
  ModuleGraph = class {
2354
2513
  urlToModuleMap = /* @__PURE__ */ new Map();
2355
2514
  idToModuleMap = /* @__PURE__ */ new Map();
2356
2515
  fileToModulesMap = /* @__PURE__ */ new Map();
2357
2516
  getModuleByUrl(url) {
2358
- return this.urlToModuleMap.get(url);
2517
+ return this.urlToModuleMap.get(removeTimestampQuery(url));
2359
2518
  }
2360
2519
  getModuleById(id) {
2361
2520
  return this.idToModuleMap.get(id);
@@ -2364,10 +2523,11 @@ var init_module_graph = __esm({
2364
2523
  return this.fileToModulesMap.get(file);
2365
2524
  }
2366
2525
  async ensureEntryFromUrl(url) {
2367
- let mod = this.urlToModuleMap.get(url);
2526
+ const normalizedUrl = removeTimestampQuery(url);
2527
+ let mod = this.urlToModuleMap.get(normalizedUrl);
2368
2528
  if (mod) return mod;
2369
- mod = this.createModule(url);
2370
- this.urlToModuleMap.set(url, mod);
2529
+ mod = this.createModule(normalizedUrl);
2530
+ this.urlToModuleMap.set(normalizedUrl, mod);
2371
2531
  return mod;
2372
2532
  }
2373
2533
  createModule(url, id) {
@@ -2381,6 +2541,7 @@ var init_module_graph = __esm({
2381
2541
  acceptedHmrDeps: /* @__PURE__ */ new Set(),
2382
2542
  transformResult: null,
2383
2543
  lastHMRTimestamp: 0,
2544
+ invalidationVersion: 0,
2384
2545
  isSelfAccepting: false
2385
2546
  };
2386
2547
  this.idToModuleMap.set(mod.id, mod);
@@ -2423,10 +2584,64 @@ var init_module_graph = __esm({
2423
2584
  }
2424
2585
  }
2425
2586
  }
2587
+ /**
2588
+ * 用一次转换得到的信息原子更新 import 与 HMR accept 关系。
2589
+ * 依赖节点会在真正被浏览器请求前预先创建,这样入口模块先转换时也能建立完整图。
2590
+ */
2591
+ async updateModuleInfo(mod, importedUrls, acceptedUrls, isSelfAccepting, expectedInvalidationVersion) {
2592
+ const importedModules = await Promise.all(
2593
+ [...importedUrls].map((url) => this.ensureEntryFromUrl(url))
2594
+ );
2595
+ const acceptedModules = await Promise.all(
2596
+ [...acceptedUrls].map((url) => this.ensureEntryFromUrl(url))
2597
+ );
2598
+ if (expectedInvalidationVersion !== void 0 && mod.invalidationVersion !== expectedInvalidationVersion) {
2599
+ return null;
2600
+ }
2601
+ const previousImports = new Set(mod.importedModules);
2602
+ for (const imported of previousImports) {
2603
+ imported.importers.delete(mod);
2604
+ }
2605
+ mod.importedModules.clear();
2606
+ mod.acceptedHmrDeps.clear();
2607
+ for (const imported of importedModules) {
2608
+ mod.importedModules.add(imported);
2609
+ imported.importers.add(mod);
2610
+ }
2611
+ for (const accepted of acceptedModules) {
2612
+ mod.acceptedHmrDeps.add(accepted);
2613
+ }
2614
+ mod.isSelfAccepting = isSelfAccepting;
2615
+ const pruned = /* @__PURE__ */ new Set();
2616
+ for (const imported of previousImports) {
2617
+ if (!mod.importedModules.has(imported) && imported.importers.size === 0) {
2618
+ pruned.add(imported);
2619
+ }
2620
+ }
2621
+ return pruned;
2622
+ }
2426
2623
  /** 使模块的转换缓存失效 */
2427
- invalidateModule(mod) {
2624
+ invalidateModule(mod, timestamp = Date.now()) {
2428
2625
  mod.transformResult = null;
2429
- mod.lastHMRTimestamp = Date.now();
2626
+ mod.lastHMRTimestamp = timestamp;
2627
+ mod.invalidationVersion++;
2628
+ }
2629
+ /**
2630
+ * 仅失效到 HMR 边界:显式接受依赖的模块本身不会重执行;自接受模块需要失效,
2631
+ * 但不再继续影响其 importer。这样既能传播依赖时间戳,也不会隐式重复副作用。
2632
+ */
2633
+ invalidateModuleAndImporters(mod, timestamp = Date.now(), seen = /* @__PURE__ */ new Set()) {
2634
+ if (seen.has(mod)) return;
2635
+ seen.add(mod);
2636
+ this.invalidateModule(mod, timestamp);
2637
+ for (const importer of mod.importers) {
2638
+ if (importer.acceptedHmrDeps.has(mod)) continue;
2639
+ if (importer.isSelfAccepting) {
2640
+ this.invalidateModule(importer, timestamp);
2641
+ continue;
2642
+ }
2643
+ this.invalidateModuleAndImporters(importer, timestamp, seen);
2644
+ }
2430
2645
  }
2431
2646
  /** 使所有模块缓存失效 */
2432
2647
  invalidateAll() {
@@ -2437,34 +2652,32 @@ var init_module_graph = __esm({
2437
2652
  /** 获取 HMR 传播边界 - 从变更模块向上遍历找到接受更新的边界 */
2438
2653
  getHmrBoundaries(mod) {
2439
2654
  const boundaries = [];
2440
- const visited = /* @__PURE__ */ new Set();
2441
- const propagate = (node, via) => {
2442
- if (visited.has(node)) return true;
2443
- visited.add(node);
2444
- if (node.isSelfAccepting) {
2445
- boundaries.push({ boundary: node, acceptedVia: via });
2446
- return true;
2655
+ const traversed = /* @__PURE__ */ new Set();
2656
+ const addBoundary = (boundary, acceptedVia) => {
2657
+ if (!boundaries.some(
2658
+ (item) => item.boundary === boundary && item.acceptedVia === acceptedVia
2659
+ )) {
2660
+ boundaries.push({ boundary, acceptedVia });
2447
2661
  }
2448
- if (node.acceptedHmrDeps.has(via)) {
2449
- boundaries.push({ boundary: node, acceptedVia: via });
2662
+ };
2663
+ const propagate = (node) => {
2664
+ if (traversed.has(node)) return true;
2665
+ traversed.add(node);
2666
+ if (node.isSelfAccepting) {
2667
+ addBoundary(node, node);
2450
2668
  return true;
2451
2669
  }
2452
2670
  if (node.importers.size === 0) return false;
2453
2671
  for (const importer of node.importers) {
2454
- if (!propagate(importer, node)) return false;
2672
+ if (importer.acceptedHmrDeps.has(node)) {
2673
+ addBoundary(importer, node);
2674
+ continue;
2675
+ }
2676
+ if (!propagate(importer)) return false;
2455
2677
  }
2456
2678
  return true;
2457
2679
  };
2458
- if (mod.isSelfAccepting) {
2459
- boundaries.push({ boundary: mod, acceptedVia: mod });
2460
- return boundaries;
2461
- }
2462
- for (const importer of mod.importers) {
2463
- if (!propagate(importer, mod)) {
2464
- return [];
2465
- }
2466
- }
2467
- return boundaries;
2680
+ return propagate(mod) ? boundaries : [];
2468
2681
  }
2469
2682
  };
2470
2683
  }
@@ -2542,6 +2755,7 @@ var init_environment = __esm({
2542
2755
  init_module_graph();
2543
2756
  init_hot_channel();
2544
2757
  init_debug();
2758
+ init_plugin_api();
2545
2759
  debug2 = createDebugger("nasti:environment");
2546
2760
  NastiEnvironment = class {
2547
2761
  name;
@@ -2550,6 +2764,7 @@ var init_environment = __esm({
2550
2764
  config;
2551
2765
  options;
2552
2766
  hot;
2767
+ driver;
2553
2768
  /** applyToEnvironment 过滤后的插件(init() 后可用) */
2554
2769
  plugins = [];
2555
2770
  /** per-env 插件容器(init() 后可用;dev 管线使用) */
@@ -2557,6 +2772,8 @@ var init_environment = __esm({
2557
2772
  /** per-env 模块图(dev 管线使用) */
2558
2773
  moduleGraph;
2559
2774
  candidatePlugins;
2775
+ pluginApi;
2776
+ buildMetadata = {};
2560
2777
  initialized = false;
2561
2778
  constructor(name, config, init = {}) {
2562
2779
  const options = config.environments[name];
@@ -2573,6 +2790,7 @@ var init_environment = __esm({
2573
2790
  this.hot = init.hot ?? createNoopHotChannel();
2574
2791
  this.moduleGraph = new ModuleGraph();
2575
2792
  this.candidatePlugins = init.plugins ?? config.plugins;
2793
+ this.pluginApi = init.pluginApi ?? getPluginApi(config);
2576
2794
  }
2577
2795
  /** 过滤插件并建 per-env PluginContainer */
2578
2796
  async init() {
@@ -2583,10 +2801,57 @@ var init_environment = __esm({
2583
2801
  { ...this.config, plugins: this.plugins },
2584
2802
  this
2585
2803
  );
2804
+ if (this.options.driver) {
2805
+ const claimed = [];
2806
+ for (const plugin of this.plugins) {
2807
+ const driver = await plugin.createEnvironmentDriver?.(this, this.pluginApi);
2808
+ if (driver) claimed.push({ plugin, driver });
2809
+ }
2810
+ if (claimed.length === 0) {
2811
+ throw new Error(
2812
+ `[nasti] environment "${this.name}" requested driver "${this.options.driver}", but no plugin provided it`
2813
+ );
2814
+ }
2815
+ if (claimed.length > 1) {
2816
+ throw new Error(
2817
+ `[nasti] environment "${this.name}" was claimed by multiple drivers: ` + claimed.map(({ plugin, driver }) => `${plugin.name} (${driver.name})`).join(", ")
2818
+ );
2819
+ }
2820
+ this.driver = claimed[0].driver;
2821
+ debug2?.(`env "${this.name}" uses driver "${this.driver.name}"`);
2822
+ }
2586
2823
  debug2?.(`env "${this.name}" initialized (${this.plugins.length} plugins)`);
2587
2824
  }
2825
+ getDriverContext() {
2826
+ return {
2827
+ environment: this,
2828
+ config: this.config,
2829
+ api: this.pluginApi,
2830
+ logger: this.config.logger
2831
+ };
2832
+ }
2833
+ setBuildMetadata(metadata) {
2834
+ const { entries: currentEntries, ...currentMetadata } = this.buildMetadata;
2835
+ const { entries, ...nextMetadata } = metadata;
2836
+ this.buildMetadata = {
2837
+ ...currentMetadata,
2838
+ ...nextMetadata,
2839
+ ...currentEntries || entries ? { entries: { ...currentEntries, ...entries } } : {}
2840
+ };
2841
+ }
2842
+ getBuildMetadata() {
2843
+ const { entries, ...metadata } = this.buildMetadata;
2844
+ return {
2845
+ ...metadata,
2846
+ ...entries ? { entries: { ...entries } } : {}
2847
+ };
2848
+ }
2588
2849
  async close() {
2589
- await this.hot.close?.();
2850
+ try {
2851
+ await this.driver?.close?.(this.getDriverContext());
2852
+ } finally {
2853
+ await this.hot.close?.();
2854
+ }
2590
2855
  }
2591
2856
  };
2592
2857
  }
@@ -2746,11 +3011,141 @@ var init_reporter = __esm({
2746
3011
  }
2747
3012
  });
2748
3013
 
3014
+ // src/core/build-app-context.ts
3015
+ function createBuildAppContext(config, results) {
3016
+ const output = [];
3017
+ const emitted = /* @__PURE__ */ new Set();
3018
+ const outDir = import_node_path9.default.resolve(config.root, config.build.outDir);
3019
+ let environmentArtifacts;
3020
+ return {
3021
+ config,
3022
+ results,
3023
+ get output() {
3024
+ return Object.freeze([...output]);
3025
+ },
3026
+ getResult(environmentName) {
3027
+ return results[environmentName];
3028
+ },
3029
+ getArtifact(environmentName, fileName) {
3030
+ const normalized = normalizeEnvironmentFileName(fileName);
3031
+ return results[environmentName]?.output.find(
3032
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalized
3033
+ );
3034
+ },
3035
+ getEntry(environmentName, entryName) {
3036
+ const result = results[environmentName];
3037
+ const fileName = result?.entries?.[entryName];
3038
+ if (!fileName) return void 0;
3039
+ return result.output.find(
3040
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalizeEnvironmentFileName(fileName)
3041
+ );
3042
+ },
3043
+ getManifest(environmentName) {
3044
+ return results[environmentName]?.manifest;
3045
+ },
3046
+ emitFile(file) {
3047
+ const fileName = normalizeAppFileName(file.fileName);
3048
+ const collisionKey = artifactCollisionKey(fileName);
3049
+ if (emitted.has(collisionKey)) {
3050
+ throw new Error(`[nasti] app artifact already emitted: ${fileName}`);
3051
+ }
3052
+ environmentArtifacts ??= collectEnvironmentArtifacts(config, results, outDir);
3053
+ if (environmentArtifacts.has(collisionKey)) {
3054
+ throw new Error(`[nasti] app artifact conflicts with environment output: ${fileName}`);
3055
+ }
3056
+ const target = import_node_path9.default.resolve(outDir, ...fileName.split("/"));
3057
+ const relative = import_node_path9.default.relative(outDir, target);
3058
+ if (relative.startsWith("..") || import_node_path9.default.isAbsolute(relative)) {
3059
+ throw new Error(`[nasti] app artifact must stay inside build.outDir: ${file.fileName}`);
3060
+ }
3061
+ assertNoSymlinkComponents(outDir, fileName);
3062
+ import_node_fs6.default.mkdirSync(import_node_path9.default.dirname(target), { recursive: true });
3063
+ import_node_fs6.default.writeFileSync(target, file.source);
3064
+ const artifact = {
3065
+ ...file,
3066
+ fileName,
3067
+ type: "asset"
3068
+ };
3069
+ emitted.add(collisionKey);
3070
+ output.push(artifact);
3071
+ return fileName;
3072
+ }
3073
+ };
3074
+ }
3075
+ function normalizeEnvironmentFileName(fileName) {
3076
+ return import_node_path9.default.posix.normalize(fileName.replace(/\\/g, "/").replace(/^\.\//, ""));
3077
+ }
3078
+ function isInvalidEnvironmentFileName(fileName) {
3079
+ return !fileName || fileName === "." || fileName === ".." || fileName.startsWith("../") || import_node_path9.default.posix.isAbsolute(fileName) || /^[A-Za-z]:\//.test(fileName);
3080
+ }
3081
+ function normalizeAppFileName(fileName) {
3082
+ const normalized = normalizeEnvironmentFileName(fileName);
3083
+ if (isInvalidEnvironmentFileName(normalized)) {
3084
+ throw new Error(`[nasti] invalid app artifact fileName: ${fileName}`);
3085
+ }
3086
+ return normalized;
3087
+ }
3088
+ function artifactCollisionKey(fileName) {
3089
+ return normalizeEnvironmentFileName(fileName).toLowerCase();
3090
+ }
3091
+ function collectEnvironmentArtifacts(config, results, appOutDir) {
3092
+ const occupied = /* @__PURE__ */ new Set();
3093
+ for (const [environmentName, result] of Object.entries(results)) {
3094
+ const environment = config.environments[environmentName];
3095
+ if (!environment) continue;
3096
+ const environmentOutDir = import_node_path9.default.resolve(config.root, environment.build.outDir);
3097
+ for (const artifact of result.output) {
3098
+ const artifactPath = import_node_path9.default.resolve(
3099
+ environmentOutDir,
3100
+ ...normalizeEnvironmentFileName(artifact.fileName).split("/")
3101
+ );
3102
+ const relative = import_node_path9.default.relative(appOutDir, artifactPath);
3103
+ if (!relative.startsWith("..") && !import_node_path9.default.isAbsolute(relative)) {
3104
+ occupied.add(artifactCollisionKey(relative));
3105
+ }
3106
+ }
3107
+ }
3108
+ return occupied;
3109
+ }
3110
+ function assertNoSymlinkComponents(outDir, fileName) {
3111
+ let current = outDir;
3112
+ for (const segment of fileName.split("/")) {
3113
+ current = import_node_path9.default.join(current, segment);
3114
+ let stats;
3115
+ try {
3116
+ stats = import_node_fs6.default.lstatSync(current);
3117
+ } catch (error) {
3118
+ if (error.code === "ENOENT") continue;
3119
+ throw error;
3120
+ }
3121
+ if (stats.isSymbolicLink()) {
3122
+ throw new Error(`[nasti] app artifact path cannot traverse a symlink: ${fileName}`);
3123
+ }
3124
+ }
3125
+ }
3126
+ function inferEnvironmentEntries(output) {
3127
+ const entries = {};
3128
+ for (const artifact of output) {
3129
+ if (artifact.type !== "chunk" || !artifact.isEntry || !artifact.name) continue;
3130
+ entries[artifact.name] = normalizeEnvironmentFileName(artifact.fileName);
3131
+ }
3132
+ return Object.keys(entries).length > 0 ? entries : void 0;
3133
+ }
3134
+ var import_node_fs6, import_node_path9;
3135
+ var init_build_app_context = __esm({
3136
+ "src/core/build-app-context.ts"() {
3137
+ "use strict";
3138
+ import_node_fs6 = __toESM(require("fs"), 1);
3139
+ import_node_path9 = __toESM(require("path"), 1);
3140
+ }
3141
+ });
3142
+
2749
3143
  // src/build/index.ts
2750
3144
  var build_exports = {};
2751
3145
  __export(build_exports, {
2752
3146
  build: () => build,
2753
3147
  getRolldownOptions: () => getRolldownOptions,
3148
+ replaceEntryScript: () => replaceEntryScript,
2754
3149
  resolveClientEntries: () => resolveClientEntries,
2755
3150
  toRolldownPlugins: () => toRolldownPlugins
2756
3151
  });
@@ -2758,9 +3153,14 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
2758
3153
  const config = environment.config;
2759
3154
  const envOptions = environment.options;
2760
3155
  const isServer = environment.consumer === "server";
2761
- const outDir = import_node_path9.default.resolve(config.root, envOptions.build.outDir);
3156
+ const outDir = import_node_path10.default.resolve(config.root, envOptions.build.outDir);
2762
3157
  const assetsDir = envOptions.build.assetsDir;
2763
- const { output: userOutput, transform: userTransform, ...restInputOptions } = envOptions.build.rolldownOptions;
3158
+ const {
3159
+ output: userOutput,
3160
+ transform: userTransform,
3161
+ resolve: userResolve,
3162
+ ...restInputOptions
3163
+ } = envOptions.build.rolldownOptions;
2764
3164
  const vueDefine = config.framework === "vue" ? {
2765
3165
  __VUE_OPTIONS_API__: "true",
2766
3166
  __VUE_PROD_DEVTOOLS__: "false",
@@ -2774,19 +3174,22 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
2774
3174
  input: entryPoints,
2775
3175
  transform: { ...userTransform, define: mergedDefine },
2776
3176
  plugins: rolldownPlugins,
3177
+ // client/server consumer 都必须使用当前环境的 conditions/mainFields;Lynx
3178
+ // BG/MT 通常同为 client consumer,但仍可能声明不同的运行时条件。
3179
+ resolve: {
3180
+ ...userResolve ?? {},
3181
+ // Environment API 是条件解析的唯一高层入口,优先于继承来的底层选项。
3182
+ conditionNames: envOptions.resolve.conditions,
3183
+ mainFields: envOptions.resolve.mainFields
3184
+ },
2777
3185
  ...isServer ? {
2778
3186
  platform: restInputOptions.platform ?? "node",
2779
- resolve: {
2780
- conditionNames: envOptions.resolve.conditions,
2781
- mainFields: envOptions.resolve.mainFields,
2782
- ...restInputOptions.resolve
2783
- },
2784
3187
  // server 产物:node 内建恒外部化;bare specifier 默认外部化
2785
3188
  //(同 Vite ssr.external 默认 —— 依赖由 node_modules 运行时解析),
2786
3189
  // 相对/绝对/虚拟模块照常打包。需要内联依赖时经 rolldownOptions.external 覆盖。
2787
3190
  external: restInputOptions.external ?? ((id) => {
2788
3191
  if (NODE_BUILTINS.has(id)) return true;
2789
- return !id.startsWith(".") && !import_node_path9.default.isAbsolute(id) && !id.startsWith("\0");
3192
+ return !id.startsWith(".") && !import_node_path10.default.isAbsolute(id) && !id.startsWith("\0");
2790
3193
  })
2791
3194
  } : {}
2792
3195
  };
@@ -2813,37 +3216,139 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
2813
3216
  };
2814
3217
  return { inputOptions, outputOptions, outDir };
2815
3218
  }
2816
- function toRolldownPlugins(plugins) {
3219
+ function toRolldownPlugins(plugins, environment) {
3220
+ const wrap = (hook) => {
3221
+ if (!hook) return hook;
3222
+ return function(...args) {
3223
+ return hook.apply(attachEnvironment(this, environment), args);
3224
+ };
3225
+ };
2817
3226
  return plugins.map((p) => ({
2818
3227
  name: p.name,
2819
- resolveId: p.resolveId,
2820
- load: p.load,
2821
- transform: p.transform,
2822
- buildStart: p.buildStart,
2823
- buildEnd: p.buildEnd,
3228
+ resolveId: wrap(p.resolveId),
3229
+ load: wrap(p.load),
3230
+ transform: wrap(p.transform),
3231
+ buildStart: wrap(p.buildStart),
3232
+ buildEnd: wrap(p.buildEnd),
2824
3233
  // closeBundle 在 bundle.close() 时触发 —— PWA manifest/SW 等终态产物依赖
2825
- closeBundle: p.closeBundle,
2826
- renderChunk: p.renderChunk,
2827
- augmentChunkHash: p.augmentChunkHash,
2828
- generateBundle: p.generateBundle
3234
+ closeBundle: wrap(p.closeBundle),
3235
+ renderChunk: wrap(p.renderChunk),
3236
+ augmentChunkHash: wrap(p.augmentChunkHash),
3237
+ generateBundle: wrap(p.generateBundle)
2829
3238
  }));
2830
3239
  }
3240
+ function attachEnvironment(context, environment) {
3241
+ if (context?.environment === environment) return context;
3242
+ try {
3243
+ Object.defineProperty(context, "environment", {
3244
+ configurable: true,
3245
+ enumerable: false,
3246
+ writable: false,
3247
+ value: environment
3248
+ });
3249
+ return context;
3250
+ } catch {
3251
+ return new Proxy(context, {
3252
+ get(target, property) {
3253
+ if (property === "environment") return environment;
3254
+ const value = Reflect.get(target, property, target);
3255
+ return typeof value === "function" ? value.bind(target) : value;
3256
+ },
3257
+ set(target, property, value) {
3258
+ return Reflect.set(target, property, value, target);
3259
+ }
3260
+ });
3261
+ }
3262
+ }
3263
+ function finalizeEnvironmentResult(environment, result) {
3264
+ const metadata = environment.getBuildMetadata();
3265
+ const inferredEntries = inferEnvironmentEntries(result.output);
3266
+ const entries = {
3267
+ ...inferredEntries,
3268
+ ...metadata.entries,
3269
+ ...result.entries
3270
+ };
3271
+ const normalizedEntries = Object.fromEntries(
3272
+ Object.entries(entries).map(([name, fileName]) => {
3273
+ const normalized = normalizeEnvironmentFileName(fileName);
3274
+ if (isInvalidEnvironmentFileName(normalized)) {
3275
+ throw new Error(
3276
+ `[nasti] environment "${environment.name}" returned invalid entry "${name}": ${fileName}`
3277
+ );
3278
+ }
3279
+ return [name, normalized];
3280
+ })
3281
+ );
3282
+ return {
3283
+ ...metadata,
3284
+ ...result,
3285
+ output: result.output,
3286
+ ...Object.keys(normalizedEntries).length > 0 ? { entries: normalizedEntries } : {}
3287
+ };
3288
+ }
3289
+ function prepareBuildOutputDirectories(config, buildableNames) {
3290
+ const directories = /* @__PURE__ */ new Set();
3291
+ const protectedPaths = /* @__PURE__ */ new Set();
3292
+ const clientIsBuilt = buildableNames.includes("client");
3293
+ if (!clientIsBuilt && config.build.emptyOutDir) {
3294
+ directories.add(import_node_path10.default.resolve(config.root, config.build.outDir));
3295
+ }
3296
+ for (const name of buildableNames) {
3297
+ const environment = config.environments[name];
3298
+ const outDir = import_node_path10.default.resolve(config.root, environment.build.outDir);
3299
+ if (!environment.build.emptyOutDir) {
3300
+ protectedPaths.add(outDir);
3301
+ continue;
3302
+ }
3303
+ if (!environment.driver) directories.add(outDir);
3304
+ }
3305
+ const containsPath = (parent, child) => {
3306
+ const relative = import_node_path10.default.relative(parent, child);
3307
+ return relative === "" || !relative.startsWith("..") && !import_node_path10.default.isAbsolute(relative);
3308
+ };
3309
+ const roots = [...directories].filter(
3310
+ (directory) => ![...protectedPaths].some((protectedPath) => containsPath(directory, protectedPath))
3311
+ ).sort((a, b) => a.length - b.length).filter(
3312
+ (directory, index2, all) => !all.slice(0, index2).some((parent) => containsPath(parent, directory))
3313
+ );
3314
+ for (const directory of roots) {
3315
+ if (import_node_fs7.default.existsSync(directory)) import_node_fs7.default.rmSync(directory, { recursive: true, force: true });
3316
+ }
3317
+ }
3318
+ function assertDriverBuildResult(environment, result) {
3319
+ const output = result != null && typeof result === "object" ? result.output : void 0;
3320
+ const hasValidOutput = Array.isArray(output) && output.every(
3321
+ (artifact) => artifact != null && typeof artifact === "object" && typeof artifact.fileName === "string" && typeof artifact.type === "string"
3322
+ );
3323
+ if (!hasValidOutput) {
3324
+ throw new Error(
3325
+ `[nasti] environment "${environment.name}" driver "${environment.driver?.name}" returned an invalid build result; expected { output: EnvironmentBuildOutput[] }`
3326
+ );
3327
+ }
3328
+ }
2831
3329
  function resolveClientEntries(config, html) {
3330
+ const configuredEntries = config.environments.client?.entry ?? [];
3331
+ if (configuredEntries.length > 0) return configuredEntries;
2832
3332
  const entryPoints = [];
3333
+ const htmlFile = config.environments.client?.html;
3334
+ const htmlDir = htmlFile ? import_node_path10.default.dirname(htmlFile) : config.root;
2833
3335
  if (html) {
2834
3336
  const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
2835
3337
  for (const match of scriptMatches) {
2836
3338
  const src = match[1];
2837
3339
  if (src && !src.startsWith("http")) {
2838
- entryPoints.push(import_node_path9.default.resolve(config.root, src.replace(/^\//, "")));
3340
+ const cleanSrc = src.split(/[?#]/, 1)[0];
3341
+ entryPoints.push(
3342
+ cleanSrc.startsWith("/") ? import_node_path10.default.resolve(config.root, cleanSrc.replace(/^\//, "")) : import_node_path10.default.resolve(htmlDir, cleanSrc)
3343
+ );
2839
3344
  }
2840
3345
  }
2841
3346
  }
2842
3347
  if (entryPoints.length === 0) {
2843
3348
  const fallbackEntries = ["src/main.ts", "src/main.tsx", "src/main.js", "src/index.ts", "src/index.tsx", "src/index.js"];
2844
3349
  for (const entry of fallbackEntries) {
2845
- const fullPath = import_node_path9.default.resolve(config.root, entry);
2846
- if (import_node_fs6.default.existsSync(fullPath)) {
3350
+ const fullPath = import_node_path10.default.resolve(config.root, entry);
3351
+ if (import_node_fs7.default.existsSync(fullPath)) {
2847
3352
  entryPoints.push(fullPath);
2848
3353
  break;
2849
3354
  }
@@ -2871,130 +3376,229 @@ async function build(inlineConfig = {}) {
2871
3376
  const startTime = performance.now();
2872
3377
  logger.info(
2873
3378
  import_picocolors4.default.cyan(`
2874
- nasti v${"2.2.0"} `) + import_picocolors4.default.green(`building for ${config.mode}...`)
3379
+ nasti v${"2.4.0"} `) + import_picocolors4.default.green(`building for ${config.mode}...`)
2875
3380
  );
2876
3381
  debug4?.(`root: ${config.root}`);
2877
- const buildableNames = Object.keys(config.environments).filter(
2878
- (name) => name === "client" || config.environments[name].entry.length > 0
2879
- );
3382
+ const buildableNames = Object.keys(config.environments).filter((name) => {
3383
+ const environment = config.environments[name];
3384
+ if (!environment.buildEnabled) return false;
3385
+ return name === "client" || environment.entry.length > 0 || !!environment.driver;
3386
+ });
2880
3387
  buildableNames.sort((a, b) => a === "client" ? -1 : b === "client" ? 1 : 0);
3388
+ prepareBuildOutputDirectories(config, buildableNames);
2881
3389
  const environments = {};
3390
+ const environmentResults = {};
3391
+ const initializedEnvironments = [];
3392
+ const buildAppContext = createBuildAppContext(config, environmentResults);
2882
3393
  let clientOutput = [];
2883
- for (const name of buildableNames) {
2884
- const output = name === "client" ? await buildClientEnvironment(config) : await buildServerEnvironment(config, name);
2885
- environments[name] = output;
2886
- if (name === "client") clientOutput = output;
2887
- if (buildableNames.length > 1) {
2888
- debug4?.(`environment "${name}" built (${output.length} files)`);
3394
+ let buildFailed = false;
3395
+ try {
3396
+ for (const name of buildableNames) {
3397
+ const built = name === "client" ? await buildClientEnvironment(config) : await buildServerEnvironment(config, name);
3398
+ initializedEnvironments.push(built.environment);
3399
+ environments[name] = built.result.output;
3400
+ environmentResults[name] = built.result;
3401
+ if (name === "client") clientOutput = built.result.output;
3402
+ if (buildableNames.length > 1) {
3403
+ debug4?.(`environment "${name}" built (${built.result.output.length} files)`);
3404
+ }
3405
+ }
3406
+ const pluginApi = getPluginApi(config);
3407
+ for (const plugin of config.plugins) {
3408
+ await plugin.afterBuildApp?.(environmentResults, pluginApi, buildAppContext);
3409
+ }
3410
+ } catch (error) {
3411
+ buildFailed = true;
3412
+ throw error;
3413
+ } finally {
3414
+ let closeFailed = false;
3415
+ let firstCloseError;
3416
+ for (const environment of [...initializedEnvironments].reverse()) {
3417
+ try {
3418
+ await environment.close();
3419
+ } catch (error) {
3420
+ if (!closeFailed) {
3421
+ closeFailed = true;
3422
+ firstCloseError = error;
3423
+ }
3424
+ const closeError = error instanceof Error ? error : new Error(String(error));
3425
+ logger.error(`[nasti] failed to close environment "${environment.name}"`, {
3426
+ error: closeError
3427
+ });
3428
+ }
3429
+ }
3430
+ if (closeFailed && !buildFailed) {
3431
+ throw firstCloseError;
2889
3432
  }
2890
3433
  }
2891
3434
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
2892
- const totalSize = Object.values(environments).flat().reduce((sum, chunk) => {
3435
+ const allOutput = [...Object.values(environments).flat(), ...buildAppContext.output];
3436
+ const totalSize = allOutput.reduce((sum, chunk) => {
2893
3437
  const content = chunk.type === "chunk" ? chunk.code : chunk.source;
2894
3438
  if (content == null) return sum;
2895
3439
  return sum + (typeof content === "string" ? Buffer.byteLength(content) : content.byteLength);
2896
3440
  }, 0);
2897
- const fileCount = Object.values(environments).flat().length;
3441
+ const fileCount = allOutput.length;
2898
3442
  const envSuffix = buildableNames.length > 1 ? ` (${buildableNames.join(" + ")})` : "";
2899
3443
  logger.info(import_picocolors4.default.green(`\u2713 built in ${elapsed}s`) + import_picocolors4.default.dim(envSuffix));
2900
3444
  logger.info(import_picocolors4.default.dim(` ${fileCount} files, ${displaySize(totalSize)} total \u2192 ${config.build.outDir}/`));
2901
- return { output: clientOutput, environments };
3445
+ return {
3446
+ output: clientOutput,
3447
+ environments,
3448
+ environmentResults,
3449
+ appOutput: [...buildAppContext.output]
3450
+ };
2902
3451
  }
2903
3452
  async function buildClientEnvironment(config) {
2904
3453
  const logger = config.logger;
2905
- const outDir = import_node_path9.default.resolve(config.root, config.build.outDir);
2906
- if (config.build.emptyOutDir && import_node_fs6.default.existsSync(outDir)) {
2907
- import_node_fs6.default.rmSync(outDir, { recursive: true, force: true });
2908
- }
2909
- import_node_fs6.default.mkdirSync(outDir, { recursive: true });
2910
- const html = await readHtmlFile(config.root);
2911
- const entryPoints = resolveClientEntries(config, html);
2912
- if (entryPoints.length === 0) {
2913
- throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
2914
- }
3454
+ const outDir = import_node_path10.default.resolve(config.root, config.build.outDir);
2915
3455
  const cssEngine = createCssEngine();
2916
- const pluginList = resolvePluginList(config, config.plugins, { cssEngine });
2917
- const clientEnv = new NastiEnvironment("client", { ...config, plugins: pluginList }, {
3456
+ const pluginList = resolvePluginList(config, config.plugins, {
3457
+ cssEngine,
3458
+ environmentName: "client"
3459
+ });
3460
+ const clientEnv = new NastiEnvironment("client", config, {
2918
3461
  mode: "build",
2919
- plugins: pluginList
3462
+ plugins: pluginList,
3463
+ pluginApi: getPluginApi(config)
2920
3464
  });
2921
3465
  await clientEnv.init();
2922
- const allPlugins = clientEnv.plugins;
2923
- const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
2924
- const rolldownPlugins = [
2925
- createOxcTransformPlugin(config, clientEnv),
2926
- ...toRolldownPlugins(allPlugins),
2927
- ...nativeReporter ? [nativeReporter] : []
2928
- ];
2929
- const { inputOptions, outputOptions } = getRolldownOptions(clientEnv, entryPoints, rolldownPlugins);
2930
- const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
2931
- const { output } = await bundle2.write(outputOptions);
2932
- await bundle2.close();
2933
- if (html) {
2934
- let processedHtml = html;
2935
- const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
2936
- for (const p of htmlPlugins) {
2937
- const result = await p.transformIndexHtml(processedHtml);
2938
- if (typeof result === "string") {
2939
- processedHtml = result;
2940
- } else if (result && "html" in result) {
2941
- processedHtml = processHtml(result.html, result.tags);
2942
- } else if (Array.isArray(result)) {
2943
- processedHtml = processHtml(processedHtml, result);
2944
- }
2945
- }
2946
- processedHtml = injectCssLinks(processedHtml, cssEngine, config);
2947
- for (const chunk of output) {
2948
- if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
2949
- const originalEntry = import_node_path9.default.relative(config.root, chunk.facadeModuleId);
2950
- processedHtml = processedHtml.replace(
2951
- new RegExp(`(src=["'])/?(${escapeRegExp(originalEntry)})(["'])`, "g"),
2952
- `$1${config.base}${chunk.fileName}$3`
3466
+ try {
3467
+ if (clientEnv.driver) {
3468
+ if (!clientEnv.driver.build) {
3469
+ throw new Error(
3470
+ `[nasti] environment "client" driver "${clientEnv.driver.name}" does not implement build()`
2953
3471
  );
2954
3472
  }
3473
+ const result = await clientEnv.driver.build(clientEnv.getDriverContext());
3474
+ assertDriverBuildResult(clientEnv, result);
3475
+ return { environment: clientEnv, result: finalizeEnvironmentResult(clientEnv, result) };
2955
3476
  }
2956
- import_node_fs6.default.writeFileSync(import_node_path9.default.resolve(outDir, "index.html"), processedHtml);
2957
- }
2958
- if (!nativeReporter && config.logLevel !== "silent") {
2959
- reportBuildOutput(output, config, logger);
3477
+ import_node_fs7.default.mkdirSync(outDir, { recursive: true });
3478
+ const htmlFile = config.environments.client.html ?? import_node_path10.default.resolve(config.root, "index.html");
3479
+ const html = await readHtmlFile(config.root, htmlFile);
3480
+ const entryPoints = resolveClientEntries(config, html);
3481
+ if (entryPoints.length === 0) {
3482
+ throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
3483
+ }
3484
+ const allPlugins = clientEnv.plugins;
3485
+ const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
3486
+ const rolldownPlugins = [
3487
+ createOxcTransformPlugin(config, clientEnv),
3488
+ ...toRolldownPlugins(allPlugins, clientEnv),
3489
+ ...nativeReporter ? [nativeReporter] : []
3490
+ ];
3491
+ const { inputOptions, outputOptions } = getRolldownOptions(
3492
+ clientEnv,
3493
+ entryPoints,
3494
+ rolldownPlugins
3495
+ );
3496
+ const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
3497
+ const { output } = await bundle2.write(outputOptions);
3498
+ await bundle2.close();
3499
+ if (html) {
3500
+ let processedHtml = html;
3501
+ const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
3502
+ for (const p of htmlPlugins) {
3503
+ const result = await p.transformIndexHtml(processedHtml);
3504
+ if (typeof result === "string") {
3505
+ processedHtml = result;
3506
+ } else if (result && "html" in result) {
3507
+ processedHtml = processHtml(result.html, result.tags);
3508
+ } else if (Array.isArray(result)) {
3509
+ processedHtml = processHtml(processedHtml, result);
3510
+ }
3511
+ }
3512
+ processedHtml = injectCssLinks(processedHtml, cssEngine, config);
3513
+ for (const chunk of output) {
3514
+ if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
3515
+ processedHtml = replaceEntryScript(
3516
+ processedHtml,
3517
+ chunk.facadeModuleId,
3518
+ chunk.fileName,
3519
+ config,
3520
+ htmlFile,
3521
+ config.base
3522
+ );
3523
+ }
3524
+ }
3525
+ import_node_fs7.default.writeFileSync(import_node_path10.default.resolve(outDir, "index.html"), processedHtml);
3526
+ }
3527
+ if (!nativeReporter && config.logLevel !== "silent") {
3528
+ reportBuildOutput(output, config, logger);
3529
+ }
3530
+ warnLargeChunks(output, config, logger);
3531
+ return {
3532
+ environment: clientEnv,
3533
+ result: finalizeEnvironmentResult(clientEnv, { output })
3534
+ };
3535
+ } catch (error) {
3536
+ try {
3537
+ await clientEnv.close();
3538
+ } catch (closeError) {
3539
+ const normalized = closeError instanceof Error ? closeError : new Error(String(closeError));
3540
+ logger.error("[nasti] failed to close client environment after build failure", {
3541
+ error: normalized
3542
+ });
3543
+ }
3544
+ throw error;
2960
3545
  }
2961
- warnLargeChunks(output, config, logger);
2962
- return output;
2963
3546
  }
2964
3547
  async function buildServerEnvironment(config, name) {
2965
3548
  const envOptions = config.environments[name];
2966
3549
  const logger = config.logger;
3550
+ const pluginList = resolvePluginList(config, config.plugins, {
3551
+ consumer: envOptions.consumer,
3552
+ environmentName: name
3553
+ });
3554
+ const environment = new NastiEnvironment(name, config, {
3555
+ mode: "build",
3556
+ plugins: pluginList,
3557
+ pluginApi: getPluginApi(config)
3558
+ });
3559
+ await environment.init();
3560
+ if (environment.driver) {
3561
+ if (!environment.driver.build) {
3562
+ await environment.close();
3563
+ throw new Error(
3564
+ `[nasti] environment "${name}" driver "${environment.driver.name}" does not implement build()`
3565
+ );
3566
+ }
3567
+ try {
3568
+ const result = await environment.driver.build(environment.getDriverContext());
3569
+ assertDriverBuildResult(environment, result);
3570
+ return { environment, result: finalizeEnvironmentResult(environment, result) };
3571
+ } catch (error) {
3572
+ await environment.close();
3573
+ throw error;
3574
+ }
3575
+ }
2967
3576
  for (const entry of envOptions.entry) {
2968
- if (!import_node_fs6.default.existsSync(entry)) {
3577
+ if (!import_node_fs7.default.existsSync(entry)) {
3578
+ await environment.close();
2969
3579
  throw new Error(`[nasti] environment "${name}" entry not found: ${entry}`);
2970
3580
  }
2971
3581
  }
2972
- const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
2973
- const environment = new NastiEnvironment(name, { ...config, plugins: pluginList }, {
2974
- mode: "build",
2975
- plugins: pluginList
2976
- });
2977
- await environment.init();
2978
3582
  const rolldownPlugins = [
2979
3583
  createOxcTransformPlugin(config, environment),
2980
- ...toRolldownPlugins(environment.plugins)
3584
+ ...toRolldownPlugins(environment.plugins, environment)
2981
3585
  ];
2982
3586
  const { inputOptions, outputOptions, outDir } = getRolldownOptions(
2983
3587
  environment,
2984
3588
  envOptions.entry,
2985
3589
  rolldownPlugins
2986
3590
  );
2987
- if (envOptions.build.emptyOutDir && import_node_fs6.default.existsSync(outDir)) {
2988
- import_node_fs6.default.rmSync(outDir, { recursive: true, force: true });
2989
- }
2990
- import_node_fs6.default.mkdirSync(outDir, { recursive: true });
3591
+ import_node_fs7.default.mkdirSync(outDir, { recursive: true });
2991
3592
  const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
2992
3593
  const { output } = await bundle2.write(outputOptions);
2993
3594
  await bundle2.close();
2994
3595
  logger.info(
2995
- import_picocolors4.default.dim(` [${name}] `) + output.map((o) => import_node_path9.default.join(envOptions.build.outDir, o.fileName)).join(import_picocolors4.default.dim(", "))
3596
+ import_picocolors4.default.dim(` [${name}] `) + output.map((o) => import_node_path10.default.join(envOptions.build.outDir, o.fileName)).join(import_picocolors4.default.dim(", "))
2996
3597
  );
2997
- return output;
3598
+ return {
3599
+ environment,
3600
+ result: finalizeEnvironmentResult(environment, { output })
3601
+ };
2998
3602
  }
2999
3603
  function injectCssLinks(html, cssEngine, config) {
3000
3604
  const cssLinkTags = [];
@@ -3020,12 +3624,31 @@ function injectCssLinks(html, cssEngine, config) {
3020
3624
  function escapeRegExp(string) {
3021
3625
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3022
3626
  }
3023
- var import_node_path9, import_node_fs6, import_node_module3, import_rolldown, import_picocolors4, debug4, NODE_BUILTINS;
3627
+ function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, urlPrefix) {
3628
+ const rootRelative = import_node_path10.default.relative(config.root, facadeModuleId).split(import_node_path10.default.sep).join("/");
3629
+ const resolvedHtmlFile = import_node_path10.default.resolve(config.root, htmlFile);
3630
+ const htmlRelative = import_node_path10.default.relative(import_node_path10.default.dirname(resolvedHtmlFile), facadeModuleId).split(import_node_path10.default.sep).join("/");
3631
+ const candidates = /* @__PURE__ */ new Set([
3632
+ rootRelative,
3633
+ `/${rootRelative}`,
3634
+ htmlRelative,
3635
+ `./${htmlRelative}`
3636
+ ]);
3637
+ let processed = html;
3638
+ for (const candidate of candidates) {
3639
+ processed = processed.replace(
3640
+ new RegExp(`(src=["'])${escapeRegExp(candidate)}([?#][^"']*)?(["'])`, "g"),
3641
+ `$1${urlPrefix}${fileName}$3`
3642
+ );
3643
+ }
3644
+ return processed;
3645
+ }
3646
+ var import_node_path10, import_node_fs7, import_node_module3, import_rolldown, import_picocolors4, debug4, NODE_BUILTINS;
3024
3647
  var init_build = __esm({
3025
3648
  "src/build/index.ts"() {
3026
3649
  "use strict";
3027
- import_node_path9 = __toESM(require("path"), 1);
3028
- import_node_fs6 = __toESM(require("fs"), 1);
3650
+ import_node_path10 = __toESM(require("path"), 1);
3651
+ import_node_fs7 = __toESM(require("fs"), 1);
3029
3652
  import_node_module3 = require("module");
3030
3653
  import_rolldown = require("rolldown");
3031
3654
  init_config();
@@ -3037,6 +3660,8 @@ var init_build = __esm({
3037
3660
  init_env();
3038
3661
  init_reporter();
3039
3662
  init_debug();
3663
+ init_plugin_api();
3664
+ init_build_app_context();
3040
3665
  import_picocolors4 = __toESM(require("picocolors"), 1);
3041
3666
  debug4 = createDebugger("nasti:build");
3042
3667
  NODE_BUILTINS = /* @__PURE__ */ new Set([...import_node_module3.builtinModules, ...import_node_module3.builtinModules.map((m) => `node:${m}`)]);
@@ -3096,15 +3721,17 @@ __export(middleware_exports, {
3096
3721
  transformMiddleware: () => transformMiddleware,
3097
3722
  transformRequest: () => transformRequest
3098
3723
  });
3099
- function getReactRefreshRuntimeEsm() {
3100
- if (__refreshRuntimeCache) return __refreshRuntimeCache;
3724
+ function getReactRefreshRuntimeEsm(includeBoundaryHelpers = false) {
3725
+ if (__refreshRuntimeCache) {
3726
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
3727
+ }
3101
3728
  let cjsPath;
3102
3729
  try {
3103
3730
  const pkgPath = __require.resolve("react-refresh/package.json");
3104
- cjsPath = import_node_path11.default.join(import_node_path11.default.dirname(pkgPath), "cjs", "react-refresh-runtime.development.js");
3731
+ cjsPath = import_node_path12.default.join(import_node_path12.default.dirname(pkgPath), "cjs", "react-refresh-runtime.development.js");
3105
3732
  } catch (err) {
3106
- cjsPath = import_node_path11.default.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
3107
- if (!import_node_fs8.default.existsSync(cjsPath)) {
3733
+ cjsPath = import_node_path12.default.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
3734
+ if (!import_node_fs9.default.existsSync(cjsPath)) {
3108
3735
  const origMsg = err instanceof Error ? err.message : String(err);
3109
3736
  throw new Error(
3110
3737
  `[nasti] Missing dependency "react-refresh". Install it with: npm install react-refresh
@@ -3112,7 +3739,7 @@ Original resolve error: ${origMsg}`
3112
3739
  );
3113
3740
  }
3114
3741
  }
3115
- const cjsSource = import_node_fs8.default.readFileSync(cjsPath, "utf-8");
3742
+ const cjsSource = import_node_fs9.default.readFileSync(cjsPath, "utf-8");
3116
3743
  __refreshRuntimeCache = `// Wrapped react-refresh runtime -> ESM
3117
3744
  const exports = {};
3118
3745
  const module = { exports };
@@ -3132,7 +3759,7 @@ export const findAffectedHostInstances = __rt.findAffectedHostInstances;
3132
3759
  export const collectCustomHooksForSignature = __rt.collectCustomHooksForSignature;
3133
3760
  export default __rt;
3134
3761
  `;
3135
- return __refreshRuntimeCache;
3762
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
3136
3763
  }
3137
3764
  function buildReactRefreshWrapper(moduleUrl, transformedCode) {
3138
3765
  const urlLit = JSON.stringify(moduleUrl);
@@ -3158,22 +3785,40 @@ window.$RefreshReg$ = prevRefreshReg;
3158
3785
  window.$RefreshSig$ = prevRefreshSig;
3159
3786
 
3160
3787
  if (__nasti_hot__) {
3161
- __nasti_hot__.accept(() => {
3162
- clearTimeout(window.__nasti_refresh_timer__);
3163
- window.__nasti_refresh_timer__ = setTimeout(() => {
3164
- RefreshRuntime.performReactRefresh();
3165
- }, 30);
3788
+ let __nasti_current_exports__;
3789
+ __nasti_hot__.accept((nextExports) => {
3790
+ if (!nextExports) return;
3791
+ if (!__nasti_current_exports__) {
3792
+ __nasti_hot__.invalidate('Could not Fast Refresh (previous exports unavailable)');
3793
+ return;
3794
+ }
3795
+ const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate(
3796
+ ${urlLit},
3797
+ __nasti_current_exports__,
3798
+ nextExports,
3799
+ );
3800
+ if (invalidateMessage) __nasti_hot__.invalidate(invalidateMessage);
3801
+ });
3802
+ RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
3803
+ __nasti_current_exports__ = currentExports;
3804
+ RefreshRuntime.registerExportsForReactRefresh(${urlLit}, currentExports);
3166
3805
  });
3167
3806
  }
3168
3807
  `;
3169
3808
  }
3170
3809
  function injectImportMetaHot(code, moduleUrl) {
3171
- if (!/\bimport\.meta\.hot\b/.test(code)) return code;
3810
+ const hotRE = /\bimport\.meta\.hot\b/g;
3811
+ const matches = [...maskStringsAndComments(code).matchAll(hotRE)];
3812
+ if (matches.length === 0) return code;
3813
+ for (const match of matches.reverse()) {
3814
+ const start = match.index;
3815
+ code = code.slice(0, start) + "__nasti_hot__" + code.slice(start + match[0].length);
3816
+ }
3172
3817
  const urlLit = JSON.stringify(moduleUrl);
3173
3818
  const header = `import { createHotContext as __nasti_createHotContext__ } from "/@nasti/client";
3174
3819
  const __nasti_hot__ = __nasti_createHotContext__(${urlLit});
3175
3820
  `;
3176
- return header + code.replace(/\bimport\.meta\.hot\b/g, "__nasti_hot__");
3821
+ return header + code;
3177
3822
  }
3178
3823
  function transformMiddleware(ctx) {
3179
3824
  ctx.envDefine = buildEnvDefine(
@@ -3200,7 +3845,10 @@ function transformMiddleware(ctx) {
3200
3845
  return;
3201
3846
  }
3202
3847
  if (url === "/" || url.endsWith(".html")) {
3203
- const html = await readHtmlFile(ctx.config.root);
3848
+ const html = await readHtmlFile(
3849
+ ctx.config.root,
3850
+ ctx.config.environments.client?.html
3851
+ );
3204
3852
  if (html) {
3205
3853
  let processedHtml = html;
3206
3854
  for (const plugin of ctx.config.plugins) {
@@ -3246,13 +3894,14 @@ function transformMiddleware(ctx) {
3246
3894
  }
3247
3895
  async function transformRequest(url, ctx) {
3248
3896
  const { config, pluginContainer, moduleGraph } = ctx;
3897
+ url = removeTimestampQuery(url);
3249
3898
  const cleanReqUrl = url.split("?")[0];
3250
3899
  const cached2 = moduleGraph.getModuleByUrl(url);
3251
3900
  if (cached2?.transformResult) {
3252
3901
  return cached2.transformResult;
3253
3902
  }
3254
3903
  if (cleanReqUrl === "/@react-refresh") {
3255
- return { code: getReactRefreshRuntimeEsm() };
3904
+ return { code: getReactRefreshRuntimeEsm(true) };
3256
3905
  }
3257
3906
  if (cleanReqUrl.startsWith("/@modules/") && url.includes("?")) {
3258
3907
  const idParam = new URLSearchParams(url.slice(url.indexOf("?") + 1)).get("id");
@@ -3260,8 +3909,8 @@ async function transformRequest(url, ctx) {
3260
3909
  let realIdValid = false;
3261
3910
  try {
3262
3911
  if (idParam) {
3263
- realId = import_node_fs8.default.realpathSync(idParam);
3264
- realIdValid = import_node_fs8.default.statSync(realId).isFile() && (realId.includes(`${import_node_path11.default.sep}node_modules${import_node_path11.default.sep}`) || isUnderRoot(realId, config.root));
3912
+ realId = import_node_fs9.default.realpathSync(idParam);
3913
+ realIdValid = import_node_fs9.default.statSync(realId).isFile() && (realId.includes(`${import_node_path12.default.sep}node_modules${import_node_path12.default.sep}`) || isUnderRoot(realId, config.root));
3265
3914
  }
3266
3915
  } catch {
3267
3916
  realId = null;
@@ -3288,6 +3937,8 @@ async function transformRequest(url, ctx) {
3288
3937
  }
3289
3938
  const rawQuery = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
3290
3939
  if (rawQuery && !/^t=\d+$/.test(rawQuery)) {
3940
+ const mod2 = await moduleGraph.ensureEntryFromUrl(url);
3941
+ const transformVersion2 = mod2.invalidationVersion;
3291
3942
  const loaded = await pluginContainer.load(url);
3292
3943
  if (loaded != null) {
3293
3944
  let code2 = typeof loaded === "string" ? loaded : loaded.code;
@@ -3295,30 +3946,43 @@ async function transformRequest(url, ctx) {
3295
3946
  if (transformed != null) {
3296
3947
  code2 = typeof transformed === "string" ? transformed : transformed.code;
3297
3948
  }
3298
- const mod2 = await moduleGraph.ensureEntryFromUrl(url);
3299
- moduleGraph.registerModule(mod2, cleanReqUrl);
3300
- code2 = injectImportMetaHot(code2, url);
3949
+ const parentFile = resolveUrlToFile(cleanReqUrl, config.root) ?? cleanReqUrl;
3950
+ moduleGraph.registerModule(mod2, parentFile);
3951
+ const hotInfo2 = rewriteHotAcceptDeps(code2, config, parentFile);
3952
+ code2 = injectImportMetaHot(hotInfo2.code, url);
3301
3953
  code2 = replaceEnvInCode(code2, ctx.envDefine ?? buildEnvDefine(
3302
3954
  loadEnv(config.mode, config.root, config.envPrefix),
3303
3955
  config.mode
3304
3956
  ));
3305
- code2 = rewriteImports(code2, config, cleanReqUrl);
3957
+ const importedUrls2 = /* @__PURE__ */ new Set();
3958
+ code2 = rewriteImports(code2, config, parentFile, importedUrls2, moduleGraph);
3959
+ const pruned2 = await moduleGraph.updateModuleInfo(
3960
+ mod2,
3961
+ importedUrls2,
3962
+ hotInfo2.acceptedUrls,
3963
+ hotInfo2.isSelfAccepting,
3964
+ transformVersion2
3965
+ );
3306
3966
  const transformResult2 = { code: code2 };
3307
- mod2.transformResult = transformResult2;
3967
+ if (pruned2) {
3968
+ if (pruned2.size > 0) ctx.onPrune?.([...pruned2].map((item) => item.url));
3969
+ mod2.transformResult = transformResult2;
3970
+ }
3308
3971
  return transformResult2;
3309
3972
  }
3310
3973
  }
3311
3974
  const filePath = resolveUrlToFile(url, config.root);
3312
- if (!filePath || !import_node_fs8.default.existsSync(filePath)) return null;
3975
+ if (!filePath || !import_node_fs9.default.existsSync(filePath)) return null;
3313
3976
  const mod = await moduleGraph.ensureEntryFromUrl(url);
3314
3977
  moduleGraph.registerModule(mod, filePath);
3978
+ const transformVersion = mod.invalidationVersion;
3315
3979
  if (cleanReqUrl.startsWith("/@modules/")) {
3316
3980
  const code2 = await bundlePackageAsEsm(filePath, config.root);
3317
3981
  const transformResult2 = { code: code2 };
3318
3982
  mod.transformResult = transformResult2;
3319
3983
  return transformResult2;
3320
3984
  }
3321
- let code = import_node_fs8.default.readFileSync(filePath, "utf-8");
3985
+ let code = import_node_fs9.default.readFileSync(filePath, "utf-8");
3322
3986
  const pluginResult = await pluginContainer.transform(code, filePath);
3323
3987
  if (pluginResult) {
3324
3988
  code = typeof pluginResult === "string" ? pluginResult : pluginResult.code;
@@ -3338,9 +4002,10 @@ async function transformRequest(url, ctx) {
3338
4002
  if (useRefresh) {
3339
4003
  code = buildReactRefreshWrapper(stableUrl, code);
3340
4004
  wrappedWithRefresh = true;
3341
- mod.isSelfAccepting = true;
3342
4005
  }
3343
4006
  }
4007
+ const hotInfo = rewriteHotAcceptDeps(code, config, filePath);
4008
+ code = hotInfo.code;
3344
4009
  if (!wrappedWithRefresh) {
3345
4010
  code = injectImportMetaHot(code, stableUrl);
3346
4011
  }
@@ -3349,9 +4014,20 @@ async function transformRequest(url, ctx) {
3349
4014
  config.mode
3350
4015
  );
3351
4016
  code = replaceEnvInCode(code, envDefine);
3352
- code = rewriteImports(code, config, filePath);
4017
+ const importedUrls = /* @__PURE__ */ new Set();
4018
+ code = rewriteImports(code, config, filePath, importedUrls, moduleGraph);
4019
+ const pruned = await moduleGraph.updateModuleInfo(
4020
+ mod,
4021
+ importedUrls,
4022
+ hotInfo.acceptedUrls,
4023
+ wrappedWithRefresh || hotInfo.isSelfAccepting,
4024
+ transformVersion
4025
+ );
3353
4026
  const transformResult = { code };
3354
- mod.transformResult = transformResult;
4027
+ if (pruned) {
4028
+ if (pruned.size > 0) ctx.onPrune?.([...pruned].map((item) => item.url));
4029
+ mod.transformResult = transformResult;
4030
+ }
3355
4031
  return transformResult;
3356
4032
  }
3357
4033
  async function loadVirtualModule(spec, ctx) {
@@ -3359,7 +4035,7 @@ async function loadVirtualModule(spec, ctx) {
3359
4035
  const resolved = await pluginContainer.resolveId(spec);
3360
4036
  if (resolved == null) return null;
3361
4037
  const resolvedId = typeof resolved === "string" ? resolved : resolved.id;
3362
- const looksVirtual = resolvedId.startsWith("\0") || !import_node_fs8.default.existsSync(resolvedId);
4038
+ const looksVirtual = resolvedId.startsWith("\0") || !import_node_fs9.default.existsSync(resolvedId);
3363
4039
  if (!looksVirtual) return null;
3364
4040
  const loadResult = await pluginContainer.load(resolvedId);
3365
4041
  if (loadResult == null) return null;
@@ -3372,7 +4048,7 @@ async function loadVirtualModule(spec, ctx) {
3372
4048
  loadEnv(config.mode, config.root, config.envPrefix),
3373
4049
  config.mode
3374
4050
  ));
3375
- const anchor = import_node_path11.default.join(config.root, "__nasti_virtual__.ts");
4051
+ const anchor = import_node_path12.default.join(config.root, "__nasti_virtual__.ts");
3376
4052
  code = rewriteImports(code, config, anchor);
3377
4053
  return { id: resolvedId, result: { code } };
3378
4054
  }
@@ -3398,7 +4074,7 @@ async function doBundlePackage(entryFile, root) {
3398
4074
  await bundle2.close();
3399
4075
  let code = result.output[0].code;
3400
4076
  code = code.replace(/process\.env\.NODE_ENV/g, '"development"');
3401
- const externalBaseDir = import_node_path11.default.dirname(entryFile);
4077
+ const externalBaseDir = import_node_path12.default.dirname(entryFile);
3402
4078
  code = code.replace(
3403
4079
  /^(import\b[^;'"]*?\bfrom\s+)(['"])([^'"./][^'"]*)(\2)/gm,
3404
4080
  (_, prefix, q, spec) => `${prefix}${q}${externalSpecToModuleUrl(spec, externalBaseDir, root)}${q}`
@@ -3416,16 +4092,16 @@ async function doBundlePackage(entryFile, root) {
3416
4092
  return code;
3417
4093
  }
3418
4094
  async function tryGenerateSubpathShim(entryFile, root) {
3419
- const NM = `${import_node_path11.default.sep}node_modules${import_node_path11.default.sep}`;
4095
+ const NM = `${import_node_path12.default.sep}node_modules${import_node_path12.default.sep}`;
3420
4096
  if (!entryFile.includes(NM)) return null;
3421
4097
  let pkgDir = null;
3422
4098
  let pkgName = null;
3423
- let dir = import_node_path11.default.dirname(entryFile);
4099
+ let dir = import_node_path12.default.dirname(entryFile);
3424
4100
  while (true) {
3425
- const pkgJsonPath = import_node_path11.default.join(dir, "package.json");
3426
- if (import_node_fs8.default.existsSync(pkgJsonPath)) {
4101
+ const pkgJsonPath = import_node_path12.default.join(dir, "package.json");
4102
+ if (import_node_fs9.default.existsSync(pkgJsonPath)) {
3427
4103
  try {
3428
- const pkg = JSON.parse(import_node_fs8.default.readFileSync(pkgJsonPath, "utf-8"));
4104
+ const pkg = JSON.parse(import_node_fs9.default.readFileSync(pkgJsonPath, "utf-8"));
3429
4105
  if (typeof pkg?.name === "string" && pkg.name) {
3430
4106
  pkgDir = dir;
3431
4107
  pkgName = pkg.name;
@@ -3434,16 +4110,16 @@ async function tryGenerateSubpathShim(entryFile, root) {
3434
4110
  } catch {
3435
4111
  }
3436
4112
  }
3437
- const parent = import_node_path11.default.dirname(dir);
4113
+ const parent = import_node_path12.default.dirname(dir);
3438
4114
  if (parent === dir) return null;
3439
4115
  dir = parent;
3440
4116
  if (!dir.includes(NM)) return null;
3441
4117
  }
3442
4118
  if (!pkgDir || !pkgName) return null;
3443
- const entryExt = import_node_path11.default.extname(entryFile);
4119
+ const entryExt = import_node_path12.default.extname(entryFile);
3444
4120
  const mainEntry = pickMainEntryByExtension(pkgDir, entryExt);
3445
4121
  if (!mainEntry) return null;
3446
- if (import_node_path11.default.resolve(mainEntry) === import_node_path11.default.resolve(entryFile)) return null;
4122
+ if (import_node_path12.default.resolve(mainEntry) === import_node_path12.default.resolve(entryFile)) return null;
3447
4123
  let mainNs;
3448
4124
  let subNs;
3449
4125
  try {
@@ -3467,7 +4143,7 @@ async function tryGenerateSubpathShim(entryFile, root) {
3467
4143
  if (mainNs["default"] !== subNs["default"]) return null;
3468
4144
  }
3469
4145
  const rootMain = resolveNodeModule(root, pkgName);
3470
- const mainEntryUrl = rootMain && rootMain.startsWith(pkgDir + import_node_path11.default.sep) ? `/@modules/${pkgName}` : `/@modules/${pkgName}?id=${encodeURIComponent(mainEntry)}`;
4146
+ const mainEntryUrl = rootMain && rootMain.startsWith(pkgDir + import_node_path12.default.sep) ? `/@modules/${pkgName}` : `/@modules/${pkgName}?id=${encodeURIComponent(mainEntry)}`;
3471
4147
  const lines = [
3472
4148
  `// Nasti subpath shim \u2192 ${pkgName} (avoid duplicate bundling)`,
3473
4149
  `import * as __pkg from "${mainEntryUrl}";`
@@ -3481,10 +4157,10 @@ async function tryGenerateSubpathShim(entryFile, root) {
3481
4157
  return lines.join("\n") + "\n";
3482
4158
  }
3483
4159
  function pickMainEntryByExtension(pkgDir, preferredExt) {
3484
- const pkgJsonPath = import_node_path11.default.join(pkgDir, "package.json");
4160
+ const pkgJsonPath = import_node_path12.default.join(pkgDir, "package.json");
3485
4161
  let pkg;
3486
4162
  try {
3487
- pkg = JSON.parse(import_node_fs8.default.readFileSync(pkgJsonPath, "utf-8"));
4163
+ pkg = JSON.parse(import_node_fs9.default.readFileSync(pkgJsonPath, "utf-8"));
3488
4164
  } catch {
3489
4165
  return null;
3490
4166
  }
@@ -3503,14 +4179,14 @@ function pickMainEntryByExtension(pkgDir, preferredExt) {
3503
4179
  if (typeof pkg.module === "string") candidates.push(pkg.module);
3504
4180
  if (typeof pkg.main === "string") candidates.push(pkg.main);
3505
4181
  for (const cand of candidates) {
3506
- if (import_node_path11.default.extname(cand) === preferredExt) {
3507
- const full = import_node_path11.default.resolve(pkgDir, cand);
3508
- if (import_node_fs8.default.existsSync(full)) return full;
4182
+ if (import_node_path12.default.extname(cand) === preferredExt) {
4183
+ const full = import_node_path12.default.resolve(pkgDir, cand);
4184
+ if (import_node_fs9.default.existsSync(full)) return full;
3509
4185
  }
3510
4186
  }
3511
4187
  for (const cand of candidates) {
3512
- const full = import_node_path11.default.resolve(pkgDir, cand);
3513
- if (import_node_fs8.default.existsSync(full)) return full;
4188
+ const full = import_node_path12.default.resolve(pkgDir, cand);
4189
+ if (import_node_fs9.default.existsSync(full)) return full;
3514
4190
  }
3515
4191
  return null;
3516
4192
  }
@@ -3556,72 +4232,231 @@ async function injectCjsNamedExports(code, entryFile) {
3556
4232
  return code;
3557
4233
  }
3558
4234
  }
3559
- function rewriteImports(code, config, filePath) {
4235
+ function rewriteImports(code, config, filePath, importedUrls, moduleGraph) {
4236
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
4237
+ const transformSpec = (spec) => {
4238
+ const resolved = removeTimestampQuery(resolveSpec(spec));
4239
+ importedUrls?.add(resolved);
4240
+ const timestamp = moduleGraph?.getModuleByUrl(resolved)?.lastHMRTimestamp ?? 0;
4241
+ return timestamp > 0 ? appendTimestampQuery(resolved, timestamp) : resolved;
4242
+ };
4243
+ return code.replace(
4244
+ /\bfrom\s+(['"])([^'"]+)\1/g,
4245
+ (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
4246
+ ).replace(
4247
+ /\bimport\s+(['"])([^'"]+)\1/g,
4248
+ (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
4249
+ ).replace(
4250
+ /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
4251
+ (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
4252
+ );
4253
+ }
4254
+ function createModuleSpecifierResolver(config, filePath) {
3560
4255
  const root = config.root;
3561
- const fileDir = import_node_path11.default.dirname(filePath);
4256
+ const fileDir = import_node_path12.default.dirname(filePath);
3562
4257
  const aliasEntries = Object.entries(config.resolve.alias).sort(
3563
4258
  ([a], [b]) => b.length - a.length
3564
4259
  );
3565
- const toRootUrl = (abs) => "/" + import_node_path11.default.relative(root, abs).replace(/\\/g, "/");
3566
- const transformSpec = (spec) => {
3567
- const suffixMatch = spec.match(/[?#].*$/);
4260
+ const toRootUrl = (abs) => "/" + import_node_path12.default.relative(root, abs).replace(/\\/g, "/");
4261
+ return (specifier) => {
4262
+ const suffixMatch = specifier.match(/[?#].*$/);
3568
4263
  const suffix = suffixMatch ? suffixMatch[0] : "";
3569
- const baseSpec = suffix ? spec.slice(0, -suffix.length) : spec;
4264
+ const baseSpec = suffix ? specifier.slice(0, -suffix.length) : specifier;
3570
4265
  for (const [key, value] of aliasEntries) {
3571
4266
  if (baseSpec === key || baseSpec.startsWith(key + "/")) {
3572
4267
  const aliasBase = resolveAliasTarget2(value, root);
3573
4268
  const sub = baseSpec.slice(key.length).replace(/^\//, "");
3574
- const target = sub ? import_node_path11.default.join(aliasBase, sub) : aliasBase;
4269
+ const target = sub ? import_node_path12.default.join(aliasBase, sub) : aliasBase;
3575
4270
  const resolved = tryResolveDiskPath(target);
3576
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
4271
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
3577
4272
  }
3578
4273
  }
3579
4274
  if (baseSpec.startsWith("./") || baseSpec.startsWith("../")) {
3580
- const target = import_node_path11.default.resolve(fileDir, baseSpec);
3581
- const resolved = tryResolveDiskPath(target);
3582
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
4275
+ const resolved = tryResolveDiskPath(import_node_path12.default.resolve(fileDir, baseSpec));
4276
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
3583
4277
  }
3584
4278
  if (baseSpec.startsWith("/") && !baseSpec.startsWith("/@")) {
3585
- const target = import_node_path11.default.join(root, baseSpec.replace(/^\//, ""));
3586
- const resolved = tryResolveDiskPath(target);
3587
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
4279
+ const resolved = tryResolveDiskPath(import_node_path12.default.join(root, baseSpec.replace(/^\//, "")));
4280
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
3588
4281
  }
3589
- if (baseSpec.startsWith("/")) return spec;
3590
- return `/@modules/${spec}`;
4282
+ if (baseSpec.startsWith("/")) return specifier;
4283
+ return `/@modules/${specifier}`;
3591
4284
  };
3592
- return code.replace(
3593
- /\bfrom\s+(['"])([^'"]+)\1/g,
3594
- (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
3595
- ).replace(
3596
- /\bimport\s+(['"])([^'"]+)\1/g,
3597
- (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
3598
- ).replace(
3599
- /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
3600
- (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
3601
- );
4285
+ }
4286
+ function rewriteHotAcceptDeps(code, config, filePath) {
4287
+ const acceptedUrls = /* @__PURE__ */ new Set();
4288
+ const edits = [];
4289
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
4290
+ const acceptRE = /(?:\bimport\.meta\.hot|\b__nasti_hot__)(?:(?:\?\.)|\.)accept\s*\(/g;
4291
+ const searchableCode = maskStringsAndComments(code);
4292
+ let isSelfAccepting = false;
4293
+ let match;
4294
+ while (match = acceptRE.exec(searchableCode)) {
4295
+ let cursor = match.index + match[0].length;
4296
+ const skipTrivia = () => {
4297
+ while (cursor < code.length) {
4298
+ if (/\s/.test(code[cursor])) {
4299
+ cursor++;
4300
+ continue;
4301
+ }
4302
+ if (code[cursor] === "/" && code[cursor + 1] === "/") {
4303
+ cursor += 2;
4304
+ while (cursor < code.length && code[cursor] !== "\n") cursor++;
4305
+ continue;
4306
+ }
4307
+ if (code[cursor] === "/" && code[cursor + 1] === "*") {
4308
+ cursor += 2;
4309
+ while (cursor < code.length && !(code[cursor] === "*" && code[cursor + 1] === "/")) cursor++;
4310
+ cursor += 2;
4311
+ continue;
4312
+ }
4313
+ break;
4314
+ }
4315
+ };
4316
+ skipTrivia();
4317
+ const first = code[cursor];
4318
+ if (!first || first === ")" || first !== "[" && first !== "'" && first !== '"' && first !== "`") {
4319
+ isSelfAccepting = true;
4320
+ continue;
4321
+ }
4322
+ const readLiteral = () => {
4323
+ const quote = code[cursor];
4324
+ if (quote !== "'" && quote !== '"' && quote !== "`") return;
4325
+ const start = cursor;
4326
+ cursor++;
4327
+ let raw = "";
4328
+ while (cursor < code.length) {
4329
+ const char = code[cursor];
4330
+ if (char === "\\") {
4331
+ raw += code[cursor + 1] ?? "";
4332
+ cursor += 2;
4333
+ continue;
4334
+ }
4335
+ if (char === quote) {
4336
+ cursor++;
4337
+ const resolved = removeTimestampQuery(resolveSpec(raw));
4338
+ acceptedUrls.add(resolved);
4339
+ edits.push({ start, end: cursor, value: JSON.stringify(resolved) });
4340
+ return;
4341
+ }
4342
+ if (quote === "`" && char === "$" && code[cursor + 1] === "{") return;
4343
+ raw += char;
4344
+ cursor++;
4345
+ }
4346
+ };
4347
+ if (first === "[") {
4348
+ cursor++;
4349
+ while (cursor < code.length) {
4350
+ skipTrivia();
4351
+ if (code[cursor] === ",") {
4352
+ cursor++;
4353
+ skipTrivia();
4354
+ }
4355
+ if (code[cursor] === "]") break;
4356
+ const before = cursor;
4357
+ readLiteral();
4358
+ if (cursor === before) break;
4359
+ }
4360
+ } else {
4361
+ readLiteral();
4362
+ }
4363
+ }
4364
+ for (const edit of edits.sort((a, b) => b.start - a.start)) {
4365
+ code = code.slice(0, edit.start) + edit.value + code.slice(edit.end);
4366
+ }
4367
+ return { code, acceptedUrls, isSelfAccepting };
4368
+ }
4369
+ function maskStringsAndComments(code) {
4370
+ const masked = code.split("");
4371
+ let state = "code";
4372
+ const isRegexStart = (index2) => {
4373
+ let previous = index2 - 1;
4374
+ while (previous >= 0 && /\s/.test(code[previous])) previous--;
4375
+ return previous < 0 || "=(:,!&|?{};[]+-*%^~<>".includes(code[previous]);
4376
+ };
4377
+ for (let i = 0; i < code.length; i++) {
4378
+ const char = code[i];
4379
+ const next = code[i + 1];
4380
+ if (state === "code") {
4381
+ if (char === "'") state = "single";
4382
+ else if (char === '"') state = "double";
4383
+ else if (char === "`") state = "template";
4384
+ else if (char === "/" && next === "/") state = "line-comment";
4385
+ else if (char === "/" && next === "*") state = "block-comment";
4386
+ else if (char === "/" && isRegexStart(i)) state = "regex";
4387
+ else continue;
4388
+ masked[i] = " ";
4389
+ continue;
4390
+ }
4391
+ if (state === "line-comment") {
4392
+ if (char === "\n") {
4393
+ state = "code";
4394
+ } else {
4395
+ masked[i] = " ";
4396
+ }
4397
+ continue;
4398
+ }
4399
+ if (state === "block-comment") {
4400
+ masked[i] = char === "\n" ? "\n" : " ";
4401
+ if (char === "*" && next === "/") {
4402
+ masked[i + 1] = " ";
4403
+ i++;
4404
+ state = "code";
4405
+ }
4406
+ continue;
4407
+ }
4408
+ if (state === "regex" || state === "regex-class") {
4409
+ masked[i] = char === "\n" ? "\n" : " ";
4410
+ if (char === "\\") {
4411
+ if (i + 1 < code.length) masked[++i] = " ";
4412
+ } else if (state === "regex" && char === "[") {
4413
+ state = "regex-class";
4414
+ } else if (state === "regex-class" && char === "]") {
4415
+ state = "regex";
4416
+ } else if (state === "regex" && char === "/") {
4417
+ state = "code";
4418
+ }
4419
+ continue;
4420
+ }
4421
+ masked[i] = char === "\n" ? "\n" : " ";
4422
+ if (char === "\\") {
4423
+ if (i + 1 < code.length) masked[++i] = " ";
4424
+ continue;
4425
+ }
4426
+ if (state === "single" && char === "'" || state === "double" && char === '"' || state === "template" && char === "`") {
4427
+ state = "code";
4428
+ }
4429
+ }
4430
+ return masked.join("");
3602
4431
  }
3603
4432
  function resolveAliasTarget2(value, root) {
3604
- if (import_node_path11.default.isAbsolute(value) && import_node_fs8.default.existsSync(value)) return value;
3605
- if (value.startsWith("/")) return import_node_path11.default.join(root, value.slice(1));
3606
- return import_node_path11.default.resolve(root, value);
4433
+ if (import_node_path12.default.isAbsolute(value) && import_node_fs9.default.existsSync(value)) return value;
4434
+ if (value.startsWith("/")) return import_node_path12.default.join(root, value.slice(1));
4435
+ return import_node_path12.default.resolve(root, value);
3607
4436
  }
3608
4437
  function tryResolveDiskPath(target) {
3609
- if (import_node_fs8.default.existsSync(target) && import_node_fs8.default.statSync(target).isFile()) return target;
4438
+ if (import_node_fs9.default.existsSync(target) && import_node_fs9.default.statSync(target).isFile()) return target;
3610
4439
  for (const ext of RESOLVE_EXTENSIONS) {
3611
4440
  const withExt = target + ext;
3612
- if (import_node_fs8.default.existsSync(withExt) && import_node_fs8.default.statSync(withExt).isFile()) return withExt;
4441
+ if (import_node_fs9.default.existsSync(withExt) && import_node_fs9.default.statSync(withExt).isFile()) return withExt;
3613
4442
  }
3614
- if (import_node_fs8.default.existsSync(target) && import_node_fs8.default.statSync(target).isDirectory()) {
4443
+ if (import_node_fs9.default.existsSync(target) && import_node_fs9.default.statSync(target).isDirectory()) {
3615
4444
  for (const ext of RESOLVE_EXTENSIONS) {
3616
- const idx = import_node_path11.default.join(target, "index" + ext);
3617
- if (import_node_fs8.default.existsSync(idx) && import_node_fs8.default.statSync(idx).isFile()) return idx;
4445
+ const idx = import_node_path12.default.join(target, "index" + ext);
4446
+ if (import_node_fs9.default.existsSync(idx) && import_node_fs9.default.statSync(idx).isFile()) return idx;
3618
4447
  }
3619
4448
  }
3620
4449
  return null;
3621
4450
  }
3622
4451
  function isUnderRoot(abs, root) {
3623
- const rel = import_node_path11.default.relative(root, abs);
3624
- return !!rel && !rel.startsWith("..") && !import_node_path11.default.isAbsolute(rel);
4452
+ const rel = import_node_path12.default.relative(root, abs);
4453
+ return !!rel && !rel.startsWith("..") && !import_node_path12.default.isAbsolute(rel);
4454
+ }
4455
+ function appendTimestampQuery(url, timestamp) {
4456
+ const hashIndex = url.indexOf("#");
4457
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
4458
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
4459
+ return `${withoutHash}${withoutHash.includes("?") ? "&" : "?"}t=${timestamp}${hash}`;
3625
4460
  }
3626
4461
  function externalSpecToModuleUrl(spec, baseDir, root) {
3627
4462
  const resolved = resolveNodeModule(baseDir, spec);
@@ -3634,7 +4469,7 @@ function resolveNodeModule(baseDir, moduleName) {
3634
4469
  const resolved = resolveNodeModuleEntry(baseDir, moduleName);
3635
4470
  if (!resolved) return null;
3636
4471
  try {
3637
- return import_node_fs8.default.realpathSync(resolved);
4472
+ return import_node_fs9.default.realpathSync(resolved);
3638
4473
  } catch {
3639
4474
  return resolved;
3640
4475
  }
@@ -3654,21 +4489,21 @@ function resolveNodeModuleEntry(root, moduleName) {
3654
4489
  let pkgDir = null;
3655
4490
  let dir = root;
3656
4491
  for (; ; ) {
3657
- const candidate = import_node_path11.default.join(dir, "node_modules", pkgName);
3658
- if (import_node_fs8.default.existsSync(candidate)) {
4492
+ const candidate = import_node_path12.default.join(dir, "node_modules", pkgName);
4493
+ if (import_node_fs9.default.existsSync(candidate)) {
3659
4494
  pkgDir = candidate;
3660
4495
  break;
3661
4496
  }
3662
- const parent = import_node_path11.default.dirname(dir);
4497
+ const parent = import_node_path12.default.dirname(dir);
3663
4498
  if (parent === dir) break;
3664
4499
  dir = parent;
3665
4500
  }
3666
4501
  if (!pkgDir) return null;
3667
- const pkgJsonPath = import_node_path11.default.join(pkgDir, "package.json");
3668
- if (!import_node_fs8.default.existsSync(pkgJsonPath)) return null;
4502
+ const pkgJsonPath = import_node_path12.default.join(pkgDir, "package.json");
4503
+ if (!import_node_fs9.default.existsSync(pkgJsonPath)) return null;
3669
4504
  let pkg;
3670
4505
  try {
3671
- pkg = JSON.parse(import_node_fs8.default.readFileSync(pkgJsonPath, "utf-8"));
4506
+ pkg = JSON.parse(import_node_fs9.default.readFileSync(pkgJsonPath, "utf-8"));
3672
4507
  } catch {
3673
4508
  return null;
3674
4509
  }
@@ -3681,32 +4516,32 @@ function resolveNodeModuleEntry(root, moduleName) {
3681
4516
  const subDirs = [""];
3682
4517
  for (const field of ["module", "main"]) {
3683
4518
  if (typeof pkg[field] === "string") {
3684
- const dir2 = import_node_path11.default.dirname(pkg[field]);
4519
+ const dir2 = import_node_path12.default.dirname(pkg[field]);
3685
4520
  if (dir2 && dir2 !== "." && !subDirs.includes(dir2)) subDirs.push(dir2);
3686
4521
  }
3687
4522
  }
3688
4523
  for (const dir2 of subDirs) {
3689
- const direct = import_node_path11.default.join(pkgDir, dir2, subpath);
3690
- if (import_node_fs8.default.existsSync(direct) && import_node_fs8.default.statSync(direct).isFile()) return direct;
4524
+ const direct = import_node_path12.default.join(pkgDir, dir2, subpath);
4525
+ if (import_node_fs9.default.existsSync(direct) && import_node_fs9.default.statSync(direct).isFile()) return direct;
3691
4526
  for (const ext of RESOLVE_EXTENSIONS) {
3692
- if (import_node_fs8.default.existsSync(direct + ext)) return direct + ext;
4527
+ if (import_node_fs9.default.existsSync(direct + ext)) return direct + ext;
3693
4528
  }
3694
4529
  }
3695
4530
  return null;
3696
4531
  }
3697
4532
  for (const field of ["module", "jsnext:main", "jsnext", "main"]) {
3698
4533
  if (typeof pkg[field] === "string") {
3699
- const entry = import_node_path11.default.join(pkgDir, pkg[field]);
3700
- if (import_node_fs8.default.existsSync(entry)) return entry;
4534
+ const entry = import_node_path12.default.join(pkgDir, pkg[field]);
4535
+ if (import_node_fs9.default.existsSync(entry)) return entry;
3701
4536
  }
3702
4537
  }
3703
- const indexFallback = import_node_path11.default.join(pkgDir, "index.js");
3704
- if (import_node_fs8.default.existsSync(indexFallback)) return indexFallback;
4538
+ const indexFallback = import_node_path12.default.join(pkgDir, "index.js");
4539
+ if (import_node_fs9.default.existsSync(indexFallback)) return indexFallback;
3705
4540
  return null;
3706
4541
  }
3707
4542
  function resolvePackageExports(exports2, key, pkgDir) {
3708
4543
  if (typeof exports2 === "string") {
3709
- return key === "." ? import_node_path11.default.join(pkgDir, exports2) : null;
4544
+ return key === "." ? import_node_path12.default.join(pkgDir, exports2) : null;
3710
4545
  }
3711
4546
  const entry = exports2[key];
3712
4547
  if (entry === void 0) {
@@ -3718,7 +4553,7 @@ function resolvePackageExports(exports2, key, pkgDir) {
3718
4553
  return resolveExportValue(entry, pkgDir);
3719
4554
  }
3720
4555
  function resolveExportValue(value, pkgDir) {
3721
- if (typeof value === "string") return import_node_path11.default.join(pkgDir, value);
4556
+ if (typeof value === "string") return import_node_path12.default.join(pkgDir, value);
3722
4557
  if (Array.isArray(value)) {
3723
4558
  for (const item of value) {
3724
4559
  const r = resolveExportValue(item, pkgDir);
@@ -3742,17 +4577,17 @@ function resolveUrlToFile(url, root) {
3742
4577
  const moduleName = cleanUrl.slice("/@modules/".length);
3743
4578
  return resolveNodeModule(root, moduleName);
3744
4579
  }
3745
- const filePath = import_node_path11.default.resolve(root, cleanUrl.replace(/^\//, ""));
3746
- if (import_node_fs8.default.existsSync(filePath) && import_node_fs8.default.statSync(filePath).isFile()) {
4580
+ const filePath = import_node_path12.default.resolve(root, cleanUrl.replace(/^\//, ""));
4581
+ if (import_node_fs9.default.existsSync(filePath) && import_node_fs9.default.statSync(filePath).isFile()) {
3747
4582
  return filePath;
3748
4583
  }
3749
4584
  for (const ext of RESOLVE_EXTENSIONS) {
3750
4585
  const withExt = filePath + ext;
3751
- if (import_node_fs8.default.existsSync(withExt)) return withExt;
4586
+ if (import_node_fs9.default.existsSync(withExt)) return withExt;
3752
4587
  }
3753
4588
  for (const ext of RESOLVE_EXTENSIONS) {
3754
- const indexFile = import_node_path11.default.join(filePath, "index" + ext);
3755
- if (import_node_fs8.default.existsSync(indexFile)) return indexFile;
4589
+ const indexFile = import_node_path12.default.join(filePath, "index" + ext);
4590
+ if (import_node_fs9.default.existsSync(indexFile)) return indexFile;
3756
4591
  }
3757
4592
  return null;
3758
4593
  }
@@ -3760,36 +4595,35 @@ function isModuleRequest(url) {
3760
4595
  const cleanUrl = url.split("?")[0];
3761
4596
  if (/\.(ts|tsx|jsx|js|mjs|vue|css|json)$/.test(cleanUrl)) return true;
3762
4597
  if (cleanUrl.startsWith("/@modules/")) return true;
3763
- if (!import_node_path11.default.extname(cleanUrl)) return true;
4598
+ if (!import_node_path12.default.extname(cleanUrl)) return true;
3764
4599
  return false;
3765
4600
  }
3766
4601
  function getHmrClientCode() {
3767
4602
  return `
3768
4603
  // Nasti HMR Client
3769
- const socket = new WebSocket(\`ws://\${location.host}\`, 'nasti-hmr');
4604
+ const socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
4605
+ const socket = new WebSocket(socketProtocol + '://' + location.host, 'nasti-hmr');
3770
4606
  const hotModulesMap = new Map();
3771
4607
  const disposeMap = new Map();
3772
4608
  const pruneMap = new Map();
4609
+ const dataMap = new Map();
4610
+ let updateQueue = [];
4611
+ let pendingUpdateQueue = false;
3773
4612
 
3774
4613
  socket.addEventListener('message', async ({ data }) => {
3775
4614
  const payload = JSON.parse(data);
3776
4615
  switch (payload.type) {
3777
4616
  case 'connected':
3778
- console.log('[nasti] connected.');
4617
+ console.debug('[nasti] connected.');
3779
4618
  clearErrorOverlay();
3780
4619
  break;
3781
4620
  case 'update':
3782
4621
  try {
3783
- await Promise.all(payload.updates.map((update) => {
3784
- if (update.type === 'js-update') {
3785
- return fetchUpdate(update);
3786
- } else if (update.type === 'css-update') {
3787
- return updateCss(update.path);
3788
- }
3789
- }));
4622
+ // CSS \u5728 unbundled \u6A21\u5F0F\u4E0B\u4E5F\u662F\u4F1A\u6CE8\u5165 <style> \u7684 JS \u6A21\u5757\uFF0C\u548C\u666E\u901A JS
4623
+ // \u4E00\u6837\u91CD\u65B0 import \u624D\u80FD\u6267\u884C dispose/accept \u5E76\u4FDD\u6301\u9875\u9762\u72B6\u6001\u3002
4624
+ await Promise.all(payload.updates.map(queueUpdate));
3790
4625
  clearErrorOverlay();
3791
- console.log('[nasti] HMR update complete, reloading page');
3792
- location.reload();
4626
+ console.debug('[nasti] HMR update complete.');
3793
4627
  } catch (err) {
3794
4628
  console.error('[nasti] HMR update failed:', err);
3795
4629
  showErrorOverlay(err);
@@ -3800,10 +4634,17 @@ socket.addEventListener('message', async ({ data }) => {
3800
4634
  location.reload();
3801
4635
  break;
3802
4636
  case 'prune':
3803
- payload.paths.forEach((p) => {
3804
- const cb = pruneMap.get(p);
3805
- if (cb) cb();
3806
- });
4637
+ await Promise.all(payload.paths.map(async (path) => {
4638
+ const data = dataMap.get(path);
4639
+ const dispose = disposeMap.get(path);
4640
+ const prune = pruneMap.get(path);
4641
+ if (dispose) await dispose(data);
4642
+ if (prune) await prune(data);
4643
+ hotModulesMap.delete(path);
4644
+ disposeMap.delete(path);
4645
+ pruneMap.delete(path);
4646
+ dataMap.delete(path);
4647
+ }));
3807
4648
  break;
3808
4649
  case 'error':
3809
4650
  console.error('[nasti] error:', payload.err.message);
@@ -3812,33 +4653,64 @@ socket.addEventListener('message', async ({ data }) => {
3812
4653
  }
3813
4654
  });
3814
4655
 
3815
- // \u81EA\u52A8\u91CD\u8FDE\uFF08\u65AD\u7EBF\u65F6\u6307\u6570\u9000\u907F\uFF09
4656
+ // \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
3816
4657
  let reconnectTimer = 0;
3817
4658
  socket.addEventListener('close', () => {
3818
4659
  clearTimeout(reconnectTimer);
3819
4660
  reconnectTimer = setTimeout(() => location.reload(), 1000);
3820
4661
  });
3821
4662
 
4663
+ /**
4664
+ * \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
4665
+ * \u6539\u53D8\u6A21\u5757\u5E94\u7528\u987A\u5E8F\u3002\u8FD9\u4E0E Vite HMRClient \u7684 fetch/apply \u4E24\u9636\u6BB5\u4E00\u81F4\u3002
4666
+ */
4667
+ async function queueUpdate(update) {
4668
+ updateQueue.push(fetchUpdate(update));
4669
+ if (pendingUpdateQueue) return;
4670
+
4671
+ pendingUpdateQueue = true;
4672
+ await Promise.resolve();
4673
+ pendingUpdateQueue = false;
4674
+ const loading = updateQueue;
4675
+ updateQueue = [];
4676
+ const applyUpdates = await Promise.all(loading);
4677
+ for (const apply of applyUpdates) {
4678
+ if (apply) apply();
4679
+ }
4680
+ }
4681
+
3822
4682
  async function fetchUpdate(update) {
3823
4683
  const mod = hotModulesMap.get(update.path);
3824
- // \u5148\u8DD1 dispose\uFF08\u7ED9\u6A21\u5757\u673A\u4F1A\u6E05\u7406\u526F\u4F5C\u7528\uFF09
3825
- const dispose = disposeMap.get(update.path);
3826
- if (dispose) dispose();
4684
+ // \u5C1A\u672A\u5728\u5F53\u524D\u9875\u9762\u52A0\u8F7D\u7684\u52A8\u6001\u6A21\u5757\u4E0D\u9700\u8981\u66F4\u65B0\u3002
4685
+ if (!mod) return;
3827
4686
 
3828
- const newMod = await import(update.acceptedPath + '?t=' + update.timestamp);
3829
- if (mod) {
3830
- // \u590D\u5236\u56DE\u8C03\u6570\u7EC4\u907F\u514D\u56DE\u8C03\u5185\u90E8\u53C8\u4FEE\u6539 hotModulesMap \u9020\u6210\u8FED\u4EE3\u5F02\u5E38
3831
- [...mod.callbacks].forEach((cb) => cb(newMod));
3832
- }
4687
+ // \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
4688
+ const qualifiedCallbacks = mod.callbacks.filter(({ deps }) =>
4689
+ deps.includes(update.acceptedPath)
4690
+ );
4691
+ const isSelfUpdate = update.path === update.acceptedPath;
4692
+ if (!isSelfUpdate && qualifiedCallbacks.length === 0) return;
4693
+
4694
+ const dispose = disposeMap.get(update.acceptedPath);
4695
+ if (dispose) await dispose(dataMap.get(update.acceptedPath));
4696
+ const newMod = await import(appendTimestampQuery(update.acceptedPath, update.timestamp));
4697
+
4698
+ return () => {
4699
+ for (const { deps, fn } of qualifiedCallbacks) {
4700
+ fn(deps.map((dep) => dep === update.acceptedPath ? newMod : undefined));
4701
+ }
4702
+ const detail = isSelfUpdate
4703
+ ? update.path
4704
+ : update.acceptedPath + ' via ' + update.path;
4705
+ console.debug('[nasti] hot updated:', detail);
4706
+ };
3833
4707
  }
3834
4708
 
3835
- function updateCss(path) {
3836
- const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
3837
- if (el) {
3838
- return fetch(path + '?t=' + Date.now())
3839
- .then(r => r.text())
3840
- .then(css => { el.textContent = css; });
3841
- }
4709
+ function appendTimestampQuery(url, timestamp) {
4710
+ const hashIndex = url.indexOf('#');
4711
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : '';
4712
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
4713
+ return withoutHash + (withoutHash.includes('?') ? '&' : '?') + 't=' + timestamp + hash;
3842
4714
  }
3843
4715
 
3844
4716
  function clearErrorOverlay() {
@@ -3866,23 +4738,30 @@ function showErrorOverlay(err) {
3866
4738
  document.body.appendChild(overlay);
3867
4739
  }
3868
4740
 
3869
- /**
3870
- * \u751F\u6210 import.meta.hot \u7684 hot context\u3002
3871
- * \u5173\u952E\u7EA6\u675F\uFF1A\u540C\u4E00 ownerPath \u7684 accept \u56DE\u8C03\u5FC5\u987B\u66FF\u6362\uFF08\u4E0D\u662F append\uFF09\u3002
3872
- * \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
3873
- * \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
3874
- */
3875
4741
  export function createHotContext(ownerPath) {
4742
+ if (!dataMap.has(ownerPath)) dataMap.set(ownerPath, {});
4743
+
4744
+ // \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
4745
+ const existing = hotModulesMap.get(ownerPath);
4746
+ if (existing) existing.callbacks = [];
4747
+
4748
+ const acceptDeps = (deps, callback = () => {}) => {
4749
+ const mod = hotModulesMap.get(ownerPath) || { id: ownerPath, callbacks: [] };
4750
+ mod.callbacks.push({ deps, fn: callback });
4751
+ hotModulesMap.set(ownerPath, mod);
4752
+ };
4753
+
3876
4754
  return {
3877
4755
  accept(deps, callback) {
3878
- // \u81EA\u63A5\u53D7: hot.accept() \u6216 hot.accept(callback)
3879
4756
  if (typeof deps === 'function' || deps === undefined) {
3880
- hotModulesMap.set(ownerPath, { callbacks: [deps || (() => {})] });
3881
- return;
4757
+ acceptDeps([ownerPath], ([mod]) => deps?.(mod));
4758
+ } else if (typeof deps === 'string') {
4759
+ acceptDeps([deps], ([mod]) => callback?.(mod));
4760
+ } else if (Array.isArray(deps)) {
4761
+ acceptDeps(deps, callback);
4762
+ } else {
4763
+ throw new Error('invalid hot.accept() usage');
3882
4764
  }
3883
- // \u4F9D\u8D56\u63A5\u53D7: hot.accept(deps, callback)\uFF0C\u591A\u6B21\u8C03\u7528\u8FFD\u52A0
3884
- const existing = hotModulesMap.get(ownerPath)?.callbacks ?? [];
3885
- hotModulesMap.set(ownerPath, { callbacks: [...existing, callback] });
3886
4765
  },
3887
4766
  prune(callback) {
3888
4767
  pruneMap.set(ownerPath, callback);
@@ -3893,27 +4772,91 @@ export function createHotContext(ownerPath) {
3893
4772
  invalidate() {
3894
4773
  location.reload();
3895
4774
  },
3896
- data: {},
4775
+ data: dataMap.get(ownerPath),
3897
4776
  };
3898
4777
  }
3899
4778
  `;
3900
4779
  }
3901
- var import_node_path11, import_node_fs8, import_node_module5, import_node_url3, import_picocolors6, import_meta, __dirname_esm, __require, __refreshRuntimeCache, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
4780
+ var import_node_path12, import_node_fs9, import_node_module5, import_node_url3, import_picocolors6, import_meta, __dirname_esm, __require, __refreshRuntimeCache, REACT_REFRESH_BOUNDARY_HELPERS, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
3902
4781
  var init_middleware = __esm({
3903
4782
  "src/server/middleware.ts"() {
3904
4783
  "use strict";
3905
- import_node_path11 = __toESM(require("path"), 1);
3906
- import_node_fs8 = __toESM(require("fs"), 1);
4784
+ import_node_path12 = __toESM(require("path"), 1);
4785
+ import_node_fs9 = __toESM(require("fs"), 1);
3907
4786
  import_node_module5 = require("module");
3908
4787
  import_node_url3 = require("url");
3909
4788
  import_picocolors6 = __toESM(require("picocolors"), 1);
3910
4789
  init_transformer();
3911
4790
  init_html();
3912
4791
  init_env();
4792
+ init_url();
3913
4793
  import_meta = {};
3914
- __dirname_esm = import_node_path11.default.dirname((0, import_node_url3.fileURLToPath)(import_meta.url));
4794
+ __dirname_esm = import_node_path12.default.dirname((0, import_node_url3.fileURLToPath)(import_meta.url));
3915
4795
  __require = (0, import_node_module5.createRequire)(import_meta.url);
3916
4796
  __refreshRuntimeCache = null;
4797
+ REACT_REFRESH_BOUNDARY_HELPERS = `
4798
+ function __nastiIsPlainObject(obj) {
4799
+ return Object.prototype.toString.call(obj) === '[object Object]' &&
4800
+ (obj.constructor === Object || obj.constructor === undefined);
4801
+ }
4802
+ function __nastiIsCompoundComponent(type) {
4803
+ if (!__nastiIsPlainObject(type)) return false;
4804
+ for (const key in type) {
4805
+ if (!isLikelyComponentType(type[key])) return false;
4806
+ }
4807
+ return true;
4808
+ }
4809
+ export function registerExportsForReactRefresh(filename, moduleExports) {
4810
+ for (const key in moduleExports) {
4811
+ if (key === '__esModule') continue;
4812
+ const value = moduleExports[key];
4813
+ if (isLikelyComponentType(value)) {
4814
+ register(value, filename + ' export ' + key);
4815
+ } else if (__nastiIsCompoundComponent(value)) {
4816
+ for (const subKey in value) {
4817
+ register(value[subKey], filename + ' export ' + key + '-' + subKey);
4818
+ }
4819
+ }
4820
+ }
4821
+ }
4822
+ let __nastiRefreshTimer;
4823
+ function __nastiEnqueueRefresh() {
4824
+ clearTimeout(__nastiRefreshTimer);
4825
+ __nastiRefreshTimer = setTimeout(() => performReactRefresh(), 16);
4826
+ }
4827
+ function __nastiCheckExports(ignored, exports, predicate) {
4828
+ for (const key in exports) {
4829
+ if (ignored.includes(key)) continue;
4830
+ if (!predicate(key, exports[key])) return key;
4831
+ }
4832
+ return true;
4833
+ }
4834
+ export function validateRefreshBoundaryAndEnqueueUpdate(id, prevExports, nextExports) {
4835
+ const ignored = window.__getReactRefreshIgnoredExports?.({ id }) ?? [];
4836
+ if (__nastiCheckExports(ignored, prevExports, (key) => key in nextExports) !== true) {
4837
+ return 'Could not Fast Refresh (export removed)';
4838
+ }
4839
+ if (__nastiCheckExports(ignored, nextExports, (key) => key in prevExports) !== true) {
4840
+ return 'Could not Fast Refresh (new export)';
4841
+ }
4842
+ let hasExports = false;
4843
+ const compatible = __nastiCheckExports(ignored, nextExports, (key, value) => {
4844
+ hasExports = true;
4845
+ return isLikelyComponentType(value) ||
4846
+ __nastiIsCompoundComponent(value) ||
4847
+ prevExports[key] === value;
4848
+ });
4849
+ if (!hasExports) {
4850
+ return 'Could not Fast Refresh (no exports)';
4851
+ }
4852
+ if (compatible === true) {
4853
+ __nastiEnqueueRefresh();
4854
+ return;
4855
+ }
4856
+ return 'Could not Fast Refresh ("' + compatible + '" export is incompatible)';
4857
+ }
4858
+ export const __hmr_import = (module) => import(module);
4859
+ `;
3917
4860
  REACT_REFRESH_GLOBAL_PREAMBLE = `
3918
4861
  import RefreshRuntime from "/@react-refresh";
3919
4862
  RefreshRuntime.injectIntoGlobalHook(window);
@@ -3932,21 +4875,23 @@ window.__vite_plugin_react_preamble_installed__ = true;
3932
4875
  async function handleFileChange(file, server) {
3933
4876
  const { moduleGraph, ws, config } = server;
3934
4877
  const logger = config.logger;
3935
- const relativePath = "/" + import_node_path12.default.relative(config.root, file);
3936
- const shortFile = import_node_path12.default.relative(config.root, file);
4878
+ const relativePath = "/" + import_node_path13.default.relative(config.root, file);
4879
+ const shortFile = import_node_path13.default.relative(config.root, file);
3937
4880
  const mods = moduleGraph.getModulesByFile(file);
3938
4881
  if (!mods || mods.size === 0) {
3939
4882
  return;
3940
4883
  }
3941
4884
  const updates = [];
3942
4885
  const timestamp = Date.now();
4886
+ const graph = moduleGraph;
4887
+ const invalidatedModules = /* @__PURE__ */ new Set();
3943
4888
  for (const mod of mods) {
3944
- moduleGraph.invalidateModule(mod);
4889
+ graph.invalidateModuleAndImporters(mod, timestamp, invalidatedModules);
3945
4890
  const ctx = {
3946
4891
  file,
3947
4892
  timestamp,
3948
4893
  modules: [mod],
3949
- read: () => import_node_fs9.default.readFileSync(file, "utf-8"),
4894
+ read: () => import_node_fs10.default.readFileSync(file, "utf-8"),
3950
4895
  server
3951
4896
  };
3952
4897
  let affectedModules = [mod];
@@ -3959,19 +4904,25 @@ async function handleFileChange(file, server) {
3959
4904
  }
3960
4905
  }
3961
4906
  for (const affected of affectedModules) {
3962
- const boundaries = moduleGraph.getHmrBoundaries(affected);
4907
+ graph.invalidateModuleAndImporters(affected, timestamp, invalidatedModules);
4908
+ const boundaries = graph.getHmrBoundaries(affected);
3963
4909
  if (boundaries.length === 0) {
3964
4910
  logger.info(import_picocolors7.default.green("page reload ") + import_picocolors7.default.dim(shortFile), { timestamp: true });
3965
4911
  ws.send({ type: "full-reload", path: relativePath });
3966
4912
  return;
3967
4913
  }
3968
- for (const { boundary } of boundaries) {
3969
- updates.push({
4914
+ for (const { boundary, acceptedVia } of boundaries) {
4915
+ const update = {
3970
4916
  type: boundary.type === "css" ? "css-update" : "js-update",
3971
4917
  path: boundary.url,
3972
- acceptedPath: affected.url,
4918
+ acceptedPath: acceptedVia.url,
3973
4919
  timestamp
3974
- });
4920
+ };
4921
+ if (!updates.some(
4922
+ (existing) => existing.type === update.type && existing.path === update.path && existing.acceptedPath === update.acceptedPath
4923
+ )) {
4924
+ updates.push(update);
4925
+ }
3975
4926
  }
3976
4927
  }
3977
4928
  }
@@ -3983,12 +4934,12 @@ async function handleFileChange(file, server) {
3983
4934
  ws.send({ type: "update", updates });
3984
4935
  }
3985
4936
  }
3986
- var import_node_path12, import_node_fs9, import_picocolors7;
4937
+ var import_node_path13, import_node_fs10, import_picocolors7;
3987
4938
  var init_hmr = __esm({
3988
4939
  "src/server/hmr.ts"() {
3989
4940
  "use strict";
3990
- import_node_path12 = __toESM(require("path"), 1);
3991
- import_node_fs9 = __toESM(require("fs"), 1);
4941
+ import_node_path13 = __toESM(require("path"), 1);
4942
+ import_node_fs10 = __toESM(require("fs"), 1);
3992
4943
  import_picocolors7 = __toESM(require("picocolors"), 1);
3993
4944
  }
3994
4945
  });
@@ -4007,12 +4958,12 @@ function createModuleRunner(environment) {
4007
4958
  }
4008
4959
  return new NastiModuleRunner(environment);
4009
4960
  }
4010
- var import_node_path13, import_node_fs10, import_node_module6, import_node_url4, debug5, NODE_BUILTINS3, NastiModuleRunner, AsyncFunction;
4961
+ var import_node_path14, import_node_fs11, import_node_module6, import_node_url4, debug5, NODE_BUILTINS3, NastiModuleRunner, AsyncFunction;
4011
4962
  var init_runnable_environment = __esm({
4012
4963
  "src/server/runnable-environment.ts"() {
4013
4964
  "use strict";
4014
- import_node_path13 = __toESM(require("path"), 1);
4015
- import_node_fs10 = __toESM(require("fs"), 1);
4965
+ import_node_path14 = __toESM(require("path"), 1);
4966
+ import_node_fs11 = __toESM(require("fs"), 1);
4016
4967
  import_node_module6 = require("module");
4017
4968
  import_node_url4 = require("url");
4018
4969
  init_transformer();
@@ -4034,7 +4985,7 @@ var init_runnable_environment = __esm({
4034
4985
  this.config.mode,
4035
4986
  ssrDefineOverrides(environment.consumer)
4036
4987
  );
4037
- this.require = (0, import_node_module6.createRequire)(import_node_path13.default.join(this.config.root, "package.json"));
4988
+ this.require = (0, import_node_module6.createRequire)(import_node_path14.default.join(this.config.root, "package.json"));
4038
4989
  const handlers = {
4039
4990
  fetchModule: async (id, importer) => this.fetchModule(id, importer),
4040
4991
  getBuiltins: () => [/^node:/, ...import_node_module6.builtinModules]
@@ -4058,9 +5009,9 @@ var init_runnable_environment = __esm({
4058
5009
  this.cache.clear();
4059
5010
  }
4060
5011
  resolveToId(rawUrl) {
4061
- if (import_node_path13.default.isAbsolute(rawUrl) && import_node_fs10.default.existsSync(rawUrl.split("?")[0])) return rawUrl;
5012
+ if (import_node_path14.default.isAbsolute(rawUrl) && import_node_fs11.default.existsSync(rawUrl.split("?")[0])) return rawUrl;
4062
5013
  const clean = rawUrl.replace(/^\//, "");
4063
- return import_node_path13.default.resolve(this.config.root, clean);
5014
+ return import_node_path14.default.resolve(this.config.root, clean);
4064
5015
  }
4065
5016
  /**
4066
5017
  * fetchModule(invoke 契约方法):环境管线产出 runner 可执行代码。
@@ -4069,14 +5020,14 @@ var init_runnable_environment = __esm({
4069
5020
  */
4070
5021
  async fetchModule(id, importer) {
4071
5022
  if (NODE_BUILTINS3.has(id)) return { externalize: id };
4072
- if (!id.startsWith(".") && !import_node_path13.default.isAbsolute(id) && !id.startsWith("\0")) {
5023
+ if (!id.startsWith(".") && !import_node_path14.default.isAbsolute(id) && !id.startsWith("\0")) {
4073
5024
  return { externalize: id };
4074
5025
  }
4075
5026
  const container = this.environment.pluginContainer;
4076
5027
  let resolvedId = id;
4077
5028
  if (id.startsWith(".") && importer) {
4078
5029
  const resolved = await container.resolveId(id, importer);
4079
- resolvedId = resolved ? typeof resolved === "string" ? resolved : resolved.id : import_node_path13.default.resolve(import_node_path13.default.dirname(importer.split("?")[0]), id);
5030
+ resolvedId = resolved ? typeof resolved === "string" ? resolved : resolved.id : import_node_path14.default.resolve(import_node_path14.default.dirname(importer.split("?")[0]), id);
4080
5031
  }
4081
5032
  resolvedId = this.completeExtension(resolvedId);
4082
5033
  const cleanId = resolvedId.split("?")[0];
@@ -4084,8 +5035,8 @@ var init_runnable_environment = __esm({
4084
5035
  const loaded = await container.load(resolvedId);
4085
5036
  if (loaded != null) {
4086
5037
  code = typeof loaded === "string" ? loaded : loaded.code;
4087
- } else if (import_node_fs10.default.existsSync(cleanId)) {
4088
- code = import_node_fs10.default.readFileSync(cleanId, "utf-8");
5038
+ } else if (import_node_fs11.default.existsSync(cleanId)) {
5039
+ code = import_node_fs11.default.readFileSync(cleanId, "utf-8");
4089
5040
  } else {
4090
5041
  throw new Error(`[nasti:ssr] cannot load module: ${resolvedId}`);
4091
5042
  }
@@ -4118,19 +5069,19 @@ var init_runnable_environment = __esm({
4118
5069
  completeExtension(id) {
4119
5070
  const clean = id.split("?")[0];
4120
5071
  const query = id.includes("?") ? id.slice(id.indexOf("?")) : "";
4121
- if (import_node_fs10.default.existsSync(clean) && import_node_fs10.default.statSync(clean).isFile()) return id;
5072
+ if (import_node_fs11.default.existsSync(clean) && import_node_fs11.default.statSync(clean).isFile()) return id;
4122
5073
  const jsMatch = clean.match(/^(.*)\.([mc]?)jsx?$/);
4123
5074
  if (jsMatch) {
4124
5075
  for (const tsExt of [`.${jsMatch[2]}ts`, `.${jsMatch[2]}tsx`]) {
4125
- if (import_node_fs10.default.existsSync(jsMatch[1] + tsExt)) return jsMatch[1] + tsExt + query;
5076
+ if (import_node_fs11.default.existsSync(jsMatch[1] + tsExt)) return jsMatch[1] + tsExt + query;
4126
5077
  }
4127
5078
  }
4128
5079
  for (const ext of this.config.resolve.extensions) {
4129
- if (import_node_fs10.default.existsSync(clean + ext)) return clean + ext + query;
5080
+ if (import_node_fs11.default.existsSync(clean + ext)) return clean + ext + query;
4130
5081
  }
4131
5082
  for (const ext of this.config.resolve.extensions) {
4132
- const indexPath = import_node_path13.default.join(clean, `index${ext}`);
4133
- if (import_node_fs10.default.existsSync(indexPath)) return indexPath;
5083
+ const indexPath = import_node_path14.default.join(clean, `index${ext}`);
5084
+ if (import_node_fs11.default.existsSync(indexPath)) return indexPath;
4134
5085
  }
4135
5086
  return id;
4136
5087
  }
@@ -4157,10 +5108,10 @@ var init_runnable_environment = __esm({
4157
5108
  return;
4158
5109
  }
4159
5110
  const ssrImport = async (dep) => {
4160
- if (NODE_BUILTINS3.has(dep) || !dep.startsWith(".") && !import_node_path13.default.isAbsolute(dep) && !dep.startsWith("\0")) {
5111
+ if (NODE_BUILTINS3.has(dep) || !dep.startsWith(".") && !import_node_path14.default.isAbsolute(dep) && !dep.startsWith("\0")) {
4161
5112
  return this.importExternal(dep);
4162
5113
  }
4163
- const depId = dep.startsWith(".") ? this.completeExtension(import_node_path13.default.resolve(import_node_path13.default.dirname(fetched.id.split("?")[0]), dep)) : this.completeExtension(dep);
5114
+ const depId = dep.startsWith(".") ? this.completeExtension(import_node_path14.default.resolve(import_node_path14.default.dirname(fetched.id.split("?")[0]), dep)) : this.completeExtension(dep);
4164
5115
  return this.instantiate(depId);
4165
5116
  };
4166
5117
  const ssrExportAll = (sourceModule) => {
@@ -4192,7 +5143,7 @@ var init_runnable_environment = __esm({
4192
5143
  }
4193
5144
  async importExternal(spec) {
4194
5145
  try {
4195
- return await (spec.startsWith("node:") || !import_node_path13.default.isAbsolute(spec) ? import(this.resolveExternalSpecifier(spec)) : import((0, import_node_url4.pathToFileURL)(spec).href));
5146
+ return await (spec.startsWith("node:") || !import_node_path14.default.isAbsolute(spec) ? import(this.resolveExternalSpecifier(spec)) : import((0, import_node_url4.pathToFileURL)(spec).href));
4196
5147
  } catch (err) {
4197
5148
  throw new Error(`[nasti:ssr] failed to import external "${spec}": ${err.message}`);
4198
5149
  }
@@ -4235,7 +5186,7 @@ async function createBundledDevServer(opts) {
4235
5186
  `[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.`
4236
5187
  );
4237
5188
  }
4238
- const html = await readHtmlFile(config.root);
5189
+ const html = await readHtmlFile(config.root, config.environments.client?.html);
4239
5190
  const entryPoints = resolveClientEntries(config, html);
4240
5191
  if (entryPoints.length === 0) {
4241
5192
  throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
@@ -4250,7 +5201,7 @@ async function createBundledDevServer(opts) {
4250
5201
  createReactRefreshRuntimePlugin(entryPoints),
4251
5202
  createBundledOxcRefreshPlugin()
4252
5203
  ] : [],
4253
- ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins)),
5204
+ ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins, clientEnv)),
4254
5205
  ...useReactRefresh ? [
4255
5206
  refreshWrapperFn({
4256
5207
  cwd: config.root,
@@ -4299,7 +5250,7 @@ async function createBundledDevServer(opts) {
4299
5250
  }
4300
5251
  const url = `/${patchPath}`;
4301
5252
  logger.info(
4302
- import_picocolors8.default.green("hmr update ") + import_picocolors8.default.dim(changedFiles.map((f) => import_node_path14.default.relative(config.root, f)).join(", ")),
5253
+ import_picocolors8.default.green("hmr update ") + import_picocolors8.default.dim(changedFiles.map((f) => import_node_path15.default.relative(config.root, f)).join(", ")),
4303
5254
  { timestamp: true }
4304
5255
  );
4305
5256
  sendTo(clientId, { type: "hmr:update", path: url, url });
@@ -4435,13 +5386,13 @@ async function createBundledDevServer(opts) {
4435
5386
  return;
4436
5387
  }
4437
5388
  res.setHeader("ETag", hit.etag);
4438
- res.setHeader("Content-Type", MIME_TYPES[import_node_path14.default.extname(fileName)] ?? "application/octet-stream");
5389
+ res.setHeader("Content-Type", MIME_TYPES[import_node_path15.default.extname(fileName)] ?? "application/octet-stream");
4439
5390
  res.setHeader("Cache-Control", "no-cache");
4440
5391
  res.end(hit.content);
4441
5392
  return;
4442
5393
  }
4443
5394
  if (pathname === "/" || pathname.endsWith(".html")) {
4444
- const rawHtml = await readHtmlFile(config.root);
5395
+ const rawHtml = await readHtmlFile(config.root, config.environments.client?.html);
4445
5396
  if (rawHtml) {
4446
5397
  res.setHeader("Content-Type", "text/html");
4447
5398
  res.setHeader("Cache-Control", "no-store");
@@ -4471,7 +5422,7 @@ function stripCatchAllLoad(plugins) {
4471
5422
  );
4472
5423
  }
4473
5424
  function createReactRefreshRuntimePlugin(entryPoints) {
4474
- const entryIds = new Set(entryPoints.map((p) => import_node_path14.default.resolve(p)));
5425
+ const entryIds = new Set(entryPoints.map((p) => import_node_path15.default.resolve(p)));
4475
5426
  return {
4476
5427
  name: "nasti:bundled-react-refresh",
4477
5428
  resolveId(source) {
@@ -4489,7 +5440,7 @@ function createReactRefreshRuntimePlugin(entryPoints) {
4489
5440
  return null;
4490
5441
  },
4491
5442
  transform(code, id) {
4492
- if (!entryIds.has(import_node_path14.default.resolve(id.split("?")[0]))) return null;
5443
+ if (!entryIds.has(import_node_path15.default.resolve(id.split("?")[0]))) return null;
4493
5444
  return { code: `import ${JSON.stringify(PREAMBLE_SPEC)};
4494
5445
  ${code}`, map: null };
4495
5446
  }
@@ -4525,19 +5476,22 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
4525
5476
  }
4526
5477
  }
4527
5478
  for (const [facadeModuleId, fileName] of entryFileNames) {
4528
- const originalEntry = import_node_path14.default.relative(config.root, facadeModuleId);
4529
- processed = processed.replace(
4530
- new RegExp(`(src=["'])/?(${originalEntry.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})(["'])`, "g"),
4531
- `$1/${fileName}$3`
5479
+ processed = replaceEntryScript(
5480
+ processed,
5481
+ facadeModuleId,
5482
+ fileName,
5483
+ config,
5484
+ config.environments.client?.html ?? "index.html",
5485
+ "/"
4532
5486
  );
4533
5487
  }
4534
5488
  return processed;
4535
5489
  }
4536
- var import_node_path14, import_node_crypto3, import_ws2, import_picocolors8, debug6, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
5490
+ var import_node_path15, import_node_crypto3, import_ws2, import_picocolors8, debug6, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
4537
5491
  var init_dev_engine = __esm({
4538
5492
  "src/server/bundled/dev-engine.ts"() {
4539
5493
  "use strict";
4540
- import_node_path14 = __toESM(require("path"), 1);
5494
+ import_node_path15 = __toESM(require("path"), 1);
4541
5495
  import_node_crypto3 = __toESM(require("crypto"), 1);
4542
5496
  import_ws2 = require("ws");
4543
5497
  import_picocolors8 = __toESM(require("picocolors"), 1);
@@ -4664,27 +5618,38 @@ async function createServer(inlineConfig = {}) {
4664
5618
  const startTime = performance.now();
4665
5619
  const config = await resolveConfig(inlineConfig, "serve");
4666
5620
  const logger = config.logger;
4667
- const allPlugins = resolvePluginList(config, config.plugins);
5621
+ const allPlugins = resolvePluginList(config, config.plugins, {
5622
+ environmentName: "client"
5623
+ });
4668
5624
  const configWithPlugins = { ...config, plugins: allPlugins };
4669
5625
  const app = (0, import_connect.default)();
4670
5626
  const httpServer = import_node_http.default.createServer(app);
4671
5627
  const ws = createWebSocketServer(httpServer);
4672
- const clientEnv = new NastiEnvironment("client", configWithPlugins, {
5628
+ const pluginApi = getPluginApi(config);
5629
+ const clientEnv = new NastiEnvironment("client", config, {
4673
5630
  hot: createWsHotChannel(ws),
4674
5631
  mode: "dev",
4675
- plugins: allPlugins
5632
+ plugins: allPlugins,
5633
+ pluginApi
4676
5634
  });
4677
5635
  await clientEnv.init();
4678
5636
  const environments = { client: clientEnv };
4679
5637
  for (const name of Object.keys(config.environments)) {
4680
5638
  if (name === "client") continue;
4681
5639
  const consumer = config.environments[name].consumer;
4682
- const envPlugins = resolvePluginList(config, config.plugins, { consumer });
4683
- environments[name] = new NastiEnvironment(name, { ...config, plugins: envPlugins }, {
5640
+ const envPlugins = resolvePluginList(config, config.plugins, {
5641
+ consumer,
5642
+ environmentName: name
5643
+ });
5644
+ environments[name] = new NastiEnvironment(name, config, {
4684
5645
  mode: "dev",
4685
- plugins: envPlugins
5646
+ plugins: envPlugins,
5647
+ pluginApi
4686
5648
  });
4687
5649
  }
5650
+ for (const [name, environment] of Object.entries(environments)) {
5651
+ if (name !== "client" && environment.options.driver) await environment.init();
5652
+ }
4688
5653
  let ssrRunner = null;
4689
5654
  async function getSsrRunner() {
4690
5655
  if (ssrRunner) return ssrRunner;
@@ -4709,23 +5674,15 @@ async function createServer(inlineConfig = {}) {
4709
5674
  });
4710
5675
  app.use(bundledServer.middleware);
4711
5676
  }
4712
- app.use(transformMiddleware({
4713
- config: configWithPlugins,
4714
- pluginContainer,
4715
- moduleGraph
4716
- }));
4717
- const publicDir = import_node_path15.default.resolve(config.root, "public");
4718
- app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
4719
- app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
4720
5677
  const ignoredSegments = /* @__PURE__ */ new Set(["node_modules", ".git", ".nasti"]);
4721
- const outDirAbs = import_node_path15.default.resolve(config.root, config.build.outDir);
5678
+ const outDirAbs = import_node_path16.default.resolve(config.root, config.build.outDir);
4722
5679
  const watcher = (0, import_chokidar.watch)(config.root, {
4723
5680
  ignored: (filePath) => {
4724
5681
  if (filePath === config.root) return false;
4725
- if (filePath === outDirAbs || filePath.startsWith(outDirAbs + import_node_path15.default.sep)) return true;
4726
- const rel = import_node_path15.default.relative(config.root, filePath);
4727
- if (!rel || rel.startsWith("..") || import_node_path15.default.isAbsolute(rel)) return false;
4728
- for (const seg of rel.split(import_node_path15.default.sep)) {
5682
+ if (filePath === outDirAbs || filePath.startsWith(outDirAbs + import_node_path16.default.sep)) return true;
5683
+ const rel = import_node_path16.default.relative(config.root, filePath);
5684
+ if (!rel || rel.startsWith("..") || import_node_path16.default.isAbsolute(rel)) return false;
5685
+ for (const seg of rel.split(import_node_path16.default.sep)) {
4729
5686
  if (ignoredSegments.has(seg)) return true;
4730
5687
  }
4731
5688
  return false;
@@ -4733,13 +5690,72 @@ async function createServer(inlineConfig = {}) {
4733
5690
  ignoreInitial: true
4734
5691
  });
4735
5692
  let server;
5693
+ const environmentServices = {};
5694
+ let environmentDriversStarted = false;
5695
+ const logCloseError = (target, error) => {
5696
+ const normalized = error instanceof Error ? error : new Error(String(error));
5697
+ logger.error(`[nasti] failed to close ${target}`, { error: normalized });
5698
+ };
5699
+ const startEnvironmentDrivers = async () => {
5700
+ if (environmentDriversStarted) return;
5701
+ environmentDriversStarted = true;
5702
+ const started = [];
5703
+ const attempted = [];
5704
+ try {
5705
+ for (const [name, environment] of Object.entries(environments)) {
5706
+ if (!environment.driver?.serve) continue;
5707
+ attempted.push(environment);
5708
+ const result = await environment.driver.serve({
5709
+ ...environment.getDriverContext(),
5710
+ server
5711
+ });
5712
+ started.push({ name, environment, service: result ?? {} });
5713
+ }
5714
+ for (const { name, service } of started) {
5715
+ environmentServices[name] = service;
5716
+ if (service.middleware) app.use(service.middleware);
5717
+ }
5718
+ } catch (error) {
5719
+ environmentDriversStarted = false;
5720
+ for (const { name } of started) {
5721
+ delete environmentServices[name];
5722
+ }
5723
+ for (const environment of attempted.reverse()) {
5724
+ try {
5725
+ await environment.driver?.close?.(environment.getDriverContext());
5726
+ } catch (closeError) {
5727
+ logCloseError(`environment driver "${environment.driver.name}"`, closeError);
5728
+ }
5729
+ }
5730
+ throw error;
5731
+ }
5732
+ };
5733
+ const notifyEnvironmentDrivers = (file, event) => {
5734
+ for (const environment of Object.values(environments)) {
5735
+ if (!environment.driver?.watchChange) continue;
5736
+ void Promise.resolve(
5737
+ environment.driver.watchChange(file, event, environment.getDriverContext())
5738
+ ).catch((error) => {
5739
+ logger.error(
5740
+ `[nasti] environment driver "${environment.driver.name}" watchChange failed`,
5741
+ { error }
5742
+ );
5743
+ });
5744
+ }
5745
+ };
4736
5746
  watcher.on("change", (file) => {
4737
5747
  ssrRunner?.invalidateFile(file);
4738
5748
  handleFileChange(file, server);
5749
+ notifyEnvironmentDrivers(file, "change");
4739
5750
  });
4740
5751
  watcher.on("add", (file) => {
4741
5752
  ssrRunner?.invalidateFile(file);
4742
5753
  handleFileChange(file, server);
5754
+ notifyEnvironmentDrivers(file, "add");
5755
+ });
5756
+ watcher.on("unlink", (file) => {
5757
+ ssrRunner?.invalidateFile(file);
5758
+ notifyEnvironmentDrivers(file, "unlink");
4743
5759
  });
4744
5760
  server = {
4745
5761
  config: configWithPlugins,
@@ -4748,10 +5764,12 @@ async function createServer(inlineConfig = {}) {
4748
5764
  watcher,
4749
5765
  ws,
4750
5766
  environments,
5767
+ environmentServices,
4751
5768
  async listen(port) {
4752
5769
  const finalPort = port ?? config.server.port;
4753
5770
  const host = config.server.host === true ? "0.0.0.0" : config.server.host;
4754
5771
  await pluginContainer.buildStart();
5772
+ await startEnvironmentDrivers();
4755
5773
  return new Promise((resolve, reject) => {
4756
5774
  let currentPort = finalPort;
4757
5775
  const onListening = () => {
@@ -4759,15 +5777,20 @@ async function createServer(inlineConfig = {}) {
4759
5777
  config.server.port = actualPort;
4760
5778
  const localUrl = `http://localhost:${actualPort}/`;
4761
5779
  const networkUrl = host === "0.0.0.0" ? `http://${getNetworkAddress()}:${actualPort}/` : null;
5780
+ const driverLocalUrls = Object.values(environmentServices).flatMap((service) => service.localUrls ?? []);
5781
+ const driverNetworkUrls = Object.values(environmentServices).flatMap((service) => service.networkUrls ?? []);
4762
5782
  logger.clearScreen("info");
4763
5783
  const readyIn = Math.ceil(performance.now() - startTime);
4764
5784
  logger.info(
4765
5785
  `
4766
- ${import_picocolors9.default.cyan(import_picocolors9.default.bold("NASTI"))} ${import_picocolors9.default.cyan(`v${"2.2.0"}`)} ${import_picocolors9.default.dim("ready in")} ${import_picocolors9.default.bold(readyIn)} ${import_picocolors9.default.dim("ms")}
5786
+ ${import_picocolors9.default.cyan(import_picocolors9.default.bold("NASTI"))} ${import_picocolors9.default.cyan(`v${"2.4.0"}`)} ${import_picocolors9.default.dim("ready in")} ${import_picocolors9.default.bold(readyIn)} ${import_picocolors9.default.dim("ms")}
4767
5787
  `
4768
5788
  );
4769
5789
  printServerUrls(
4770
- { local: [localUrl], network: networkUrl ? [networkUrl] : [] },
5790
+ {
5791
+ local: [localUrl, ...driverLocalUrls],
5792
+ network: [...networkUrl ? [networkUrl] : [], ...driverNetworkUrls]
5793
+ },
4771
5794
  logger.info
4772
5795
  );
4773
5796
  logger.info("");
@@ -4788,7 +5811,12 @@ async function createServer(inlineConfig = {}) {
4788
5811
  },
4789
5812
  async transformRequest(url) {
4790
5813
  const { transformRequest: transformRequest2 } = await Promise.resolve().then(() => (init_middleware(), middleware_exports));
4791
- return transformRequest2(url, { config: configWithPlugins, pluginContainer, moduleGraph });
5814
+ return transformRequest2(url, {
5815
+ config: configWithPlugins,
5816
+ pluginContainer,
5817
+ moduleGraph,
5818
+ onPrune: (paths) => ws.send({ type: "prune", paths })
5819
+ });
4792
5820
  },
4793
5821
  async ssrLoadModule(url) {
4794
5822
  const runner = await getSsrRunner();
@@ -4797,11 +5825,63 @@ async function createServer(inlineConfig = {}) {
4797
5825
  async close() {
4798
5826
  await pluginContainer.buildEnd();
4799
5827
  await bundledServer?.close();
4800
- watcher.close();
5828
+ let environmentCloseFailed = false;
5829
+ let firstEnvironmentCloseError;
5830
+ for (const environment of Object.values(environments).reverse()) {
5831
+ try {
5832
+ await environment.close();
5833
+ } catch (error) {
5834
+ if (!environmentCloseFailed) {
5835
+ environmentCloseFailed = true;
5836
+ firstEnvironmentCloseError = error;
5837
+ }
5838
+ logCloseError(`environment "${environment.name}"`, error);
5839
+ }
5840
+ }
5841
+ await watcher.close();
4801
5842
  ws.close();
4802
5843
  httpServer.close();
5844
+ if (environmentCloseFailed) {
5845
+ throw firstEnvironmentCloseError;
5846
+ }
4803
5847
  }
4804
5848
  };
5849
+ try {
5850
+ await startEnvironmentDrivers();
5851
+ } catch (error) {
5852
+ if (bundledServer) {
5853
+ try {
5854
+ await bundledServer.close();
5855
+ } catch (closeError) {
5856
+ logCloseError("bundled dev server after driver startup failure", closeError);
5857
+ }
5858
+ }
5859
+ try {
5860
+ await watcher.close();
5861
+ } catch (closeError) {
5862
+ logCloseError("file watcher after driver startup failure", closeError);
5863
+ }
5864
+ try {
5865
+ ws.close();
5866
+ } catch (closeError) {
5867
+ logCloseError("WebSocket server after driver startup failure", closeError);
5868
+ }
5869
+ try {
5870
+ httpServer.close();
5871
+ } catch (closeError) {
5872
+ logCloseError("HTTP server after driver startup failure", closeError);
5873
+ }
5874
+ throw error;
5875
+ }
5876
+ app.use(transformMiddleware({
5877
+ config: configWithPlugins,
5878
+ pluginContainer,
5879
+ moduleGraph,
5880
+ onPrune: (paths) => ws.send({ type: "prune", paths })
5881
+ }));
5882
+ const publicDir = import_node_path16.default.resolve(config.root, "public");
5883
+ app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
5884
+ app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
4805
5885
  const postMiddlewares = [];
4806
5886
  for (const plugin of allPlugins) {
4807
5887
  if (plugin.configureServer) {
@@ -4825,12 +5905,12 @@ function getNetworkAddress() {
4825
5905
  }
4826
5906
  return "localhost";
4827
5907
  }
4828
- var import_node_http, import_node_path15, import_node_os, import_connect, import_sirv, import_chokidar, import_picocolors9;
5908
+ var import_node_http, import_node_path16, import_node_os, import_connect, import_sirv, import_chokidar, import_picocolors9;
4829
5909
  var init_server = __esm({
4830
5910
  "src/server/index.ts"() {
4831
5911
  "use strict";
4832
5912
  import_node_http = __toESM(require("http"), 1);
4833
- import_node_path15 = __toESM(require("path"), 1);
5913
+ import_node_path16 = __toESM(require("path"), 1);
4834
5914
  import_node_os = __toESM(require("os"), 1);
4835
5915
  import_connect = __toESM(require("connect"), 1);
4836
5916
  import_sirv = __toESM(require("sirv"), 1);
@@ -4844,6 +5924,7 @@ var init_server = __esm({
4844
5924
  init_middleware();
4845
5925
  init_hmr();
4846
5926
  init_builtins();
5927
+ init_plugin_api();
4847
5928
  }
4848
5929
  });
4849
5930
 
@@ -4856,12 +5937,15 @@ __export(src_exports, {
4856
5937
  buildElectron: () => buildElectron,
4857
5938
  buildEnvDefine: () => buildEnvDefine,
4858
5939
  createDebugger: () => createDebugger,
5940
+ createElectronRendererConfig: () => createElectronRendererConfig,
4859
5941
  createLogger: () => createLogger,
4860
5942
  createNoopHotChannel: () => createNoopHotChannel,
4861
5943
  createServer: () => createServer,
4862
5944
  createWsHotChannel: () => createWsHotChannel,
4863
5945
  defineConfig: () => defineConfig,
5946
+ detectFramework: () => detectFramework,
4864
5947
  electronPlugin: () => electronPlugin,
5948
+ electronRendererDevPath: () => electronRendererDevPath,
4865
5949
  loadEnv: () => loadEnv,
4866
5950
  monacoEditorPlugin: () => monacoEditorPlugin,
4867
5951
  printServerUrls: () => printServerUrls,
@@ -4875,8 +5959,8 @@ init_config();
4875
5959
  init_build();
4876
5960
 
4877
5961
  // src/build/electron.ts
4878
- var import_node_path10 = __toESM(require("path"), 1);
4879
- var import_node_fs7 = __toESM(require("fs"), 1);
5962
+ var import_node_path11 = __toESM(require("path"), 1);
5963
+ var import_node_fs8 = __toESM(require("fs"), 1);
4880
5964
  var import_rolldown2 = require("rolldown");
4881
5965
  var import_picocolors5 = __toESM(require("picocolors"), 1);
4882
5966
  init_config();
@@ -4922,28 +6006,26 @@ async function buildElectron(inlineConfig = {}) {
4922
6006
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
4923
6007
  const startTime = performance.now();
4924
6008
  assertElectronVersion(config);
4925
- console.log(import_picocolors5.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors5.default.dim(` v${"2.2.0"}`));
6009
+ console.log(import_picocolors5.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors5.default.dim(` v${"2.4.0"}`));
4926
6010
  console.log(import_picocolors5.default.dim(` root: ${config.root}`));
4927
6011
  console.log(import_picocolors5.default.dim(` mode: ${config.mode}`));
4928
6012
  console.log(import_picocolors5.default.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
4929
- const outDir = import_node_path10.default.resolve(config.root, config.build.outDir);
4930
- if (config.build.emptyOutDir && import_node_fs7.default.existsSync(outDir)) {
4931
- import_node_fs7.default.rmSync(outDir, { recursive: true, force: true });
6013
+ const outDir = import_node_path11.default.resolve(config.root, config.build.outDir);
6014
+ if (config.build.emptyOutDir && import_node_fs8.default.existsSync(outDir)) {
6015
+ import_node_fs8.default.rmSync(outDir, { recursive: true, force: true });
4932
6016
  }
4933
- import_node_fs7.default.mkdirSync(outDir, { recursive: true });
4934
- const rendererOutDir = import_node_path10.default.join(outDir, "renderer");
6017
+ import_node_fs8.default.mkdirSync(outDir, { recursive: true });
6018
+ const rendererOutDir = import_node_path11.default.join(outDir, "renderer");
4935
6019
  const { build: build2 } = await Promise.resolve().then(() => (init_build(), build_exports));
4936
- await build2({
4937
- ...inlineConfig,
4938
- target: "web",
6020
+ await build2(createElectronRendererConfig(config, inlineConfig, {
4939
6021
  build: {
4940
6022
  ...inlineConfig.build,
4941
6023
  outDir: rendererOutDir,
4942
6024
  emptyOutDir: false
4943
6025
  }
4944
- });
4945
- const mainEntry = import_node_path10.default.resolve(config.root, config.electron.main);
4946
- if (!import_node_fs7.default.existsSync(mainEntry)) {
6026
+ }));
6027
+ const mainEntry = import_node_path11.default.resolve(config.root, config.electron.main);
6028
+ if (!import_node_fs8.default.existsSync(mainEntry)) {
4947
6029
  throw new Error(
4948
6030
  `Electron main entry not found: ${config.electron.main}
4949
6031
  \u5728 nasti.config.ts \u7684 electron.main \u6307\u5B9A\u4E3B\u8FDB\u7A0B\u5165\u53E3\u6587\u4EF6\u3002`
@@ -4957,11 +6039,11 @@ async function buildElectron(inlineConfig = {}) {
4957
6039
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
4958
6040
  const preloadFiles = [];
4959
6041
  for (const entry of preloadEntries) {
4960
- if (!import_node_fs7.default.existsSync(entry)) {
6042
+ if (!import_node_fs8.default.existsSync(entry)) {
4961
6043
  console.warn(import_picocolors5.default.yellow(` \u26A0 preload entry not found, skipped: ${entry}`));
4962
6044
  continue;
4963
6045
  }
4964
- const base = import_node_path10.default.basename(entry).replace(/\.[^.]+$/, "");
6046
+ const base = import_node_path11.default.basename(entry).replace(/\.[^.]+$/, "");
4965
6047
  const out = outFileName(outDir, base, config.electron.preloadFormat);
4966
6048
  await bundleNode(config, entry, {
4967
6049
  outFile: out,
@@ -4973,10 +6055,10 @@ async function buildElectron(inlineConfig = {}) {
4973
6055
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
4974
6056
  console.log(import_picocolors5.default.green(`
4975
6057
  \u2713 Electron build complete in ${elapsed}s`));
4976
- console.log(import_picocolors5.default.dim(` renderer: ${import_node_path10.default.relative(config.root, rendererOutDir)}/`));
4977
- console.log(import_picocolors5.default.dim(` main: ${import_node_path10.default.relative(config.root, mainFile)}`));
6058
+ console.log(import_picocolors5.default.dim(` renderer: ${import_node_path11.default.relative(config.root, rendererOutDir)}/`));
6059
+ console.log(import_picocolors5.default.dim(` main: ${import_node_path11.default.relative(config.root, mainFile)}`));
4978
6060
  for (const pf of preloadFiles) {
4979
- console.log(import_picocolors5.default.dim(` preload: ${import_node_path10.default.relative(config.root, pf)}`));
6061
+ console.log(import_picocolors5.default.dim(` preload: ${import_node_path11.default.relative(config.root, pf)}`));
4980
6062
  }
4981
6063
  console.log();
4982
6064
  return { rendererOutDir, mainFile, preloadFiles };
@@ -4995,7 +6077,8 @@ async function bundleNode(config, entry, opts) {
4995
6077
  const result = transformCode(id, code, {
4996
6078
  sourcemap: !!config.build.sourcemap,
4997
6079
  jsxRuntime: "automatic",
4998
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
6080
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
6081
+ target: config.electron.nodeTarget
4999
6082
  });
5000
6083
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
5001
6084
  }
@@ -5006,10 +6089,14 @@ async function bundleNode(config, entry, opts) {
5006
6089
  ...restInputOptions,
5007
6090
  input: entry,
5008
6091
  platform: "node",
5009
- transform: { ...userTransform, define: mergedDefine },
6092
+ transform: {
6093
+ ...userTransform,
6094
+ target: config.electron.nodeTarget,
6095
+ define: mergedDefine
6096
+ },
5010
6097
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5011
6098
  });
5012
- import_node_fs7.default.mkdirSync(import_node_path10.default.dirname(opts.outFile), { recursive: true });
6099
+ import_node_fs8.default.mkdirSync(import_node_path11.default.dirname(opts.outFile), { recursive: true });
5013
6100
  await bundle2.write({
5014
6101
  sourcemap: !!config.build.sourcemap,
5015
6102
  minify: !!config.build.minify,
@@ -5020,16 +6107,35 @@ async function bundleNode(config, entry, opts) {
5020
6107
  codeSplitting: false
5021
6108
  });
5022
6109
  await bundle2.close();
5023
- console.log(import_picocolors5.default.dim(` \u2713 ${opts.label} \u2192 ${import_node_path10.default.relative(config.root, opts.outFile)}`));
6110
+ console.log(import_picocolors5.default.dim(` \u2713 ${opts.label} \u2192 ${import_node_path11.default.relative(config.root, opts.outFile)}`));
5024
6111
  return opts.outFile;
5025
6112
  }
6113
+ function createElectronRendererConfig(config, inlineConfig = {}, overrides = {}) {
6114
+ const inlineClient = inlineConfig.environments?.client ?? {};
6115
+ return {
6116
+ ...inlineConfig,
6117
+ ...overrides,
6118
+ root: config.root,
6119
+ mode: config.mode,
6120
+ target: "web",
6121
+ framework: config.framework,
6122
+ base: config.base === "/" ? "./" : config.base,
6123
+ environments: {
6124
+ ...inlineConfig.environments ?? {},
6125
+ client: {
6126
+ ...inlineClient,
6127
+ html: config.electron.renderer
6128
+ }
6129
+ }
6130
+ };
6131
+ }
5026
6132
  function outFileName(outDir, base, format) {
5027
6133
  const ext = format === "cjs" ? ".cjs" : ".mjs";
5028
- return import_node_path10.default.join(outDir, base + ext);
6134
+ return import_node_path11.default.join(outDir, base + ext);
5029
6135
  }
5030
6136
  function normalizePreload(preload, root) {
5031
6137
  const list = Array.isArray(preload) ? preload : preload ? [preload] : [];
5032
- return list.map((p) => import_node_path10.default.resolve(root, p));
6138
+ return list.map((p) => import_node_path11.default.resolve(root, p));
5033
6139
  }
5034
6140
  function assertElectronVersion(config) {
5035
6141
  const min = config.electron.minVersion;
@@ -5044,9 +6150,9 @@ function assertElectronVersion(config) {
5044
6150
  }
5045
6151
  function detectInstalledElectron(root) {
5046
6152
  try {
5047
- const pkgPath = import_node_path10.default.resolve(root, "node_modules/electron/package.json");
5048
- if (!import_node_fs7.default.existsSync(pkgPath)) return null;
5049
- const pkg = JSON.parse(import_node_fs7.default.readFileSync(pkgPath, "utf-8"));
6153
+ const pkgPath = import_node_path11.default.resolve(root, "node_modules/electron/package.json");
6154
+ if (!import_node_fs8.default.existsSync(pkgPath)) return null;
6155
+ const pkg = JSON.parse(import_node_fs8.default.readFileSync(pkgPath, "utf-8"));
5050
6156
  const major = parseInt(String(pkg.version).split(".")[0], 10);
5051
6157
  return Number.isFinite(major) ? major : null;
5052
6158
  } catch {
@@ -5058,8 +6164,8 @@ function detectInstalledElectron(root) {
5058
6164
  init_server();
5059
6165
 
5060
6166
  // src/server/electron-dev.ts
5061
- var import_node_path16 = __toESM(require("path"), 1);
5062
- var import_node_fs11 = __toESM(require("fs"), 1);
6167
+ var import_node_path17 = __toESM(require("path"), 1);
6168
+ var import_node_fs12 = __toESM(require("fs"), 1);
5063
6169
  var import_node_module7 = require("module");
5064
6170
  var import_node_child_process = require("child_process");
5065
6171
  var import_chokidar2 = __toESM(require("chokidar"), 1);
@@ -5073,17 +6179,21 @@ async function startElectronDev(inlineConfig = {}) {
5073
6179
  const { noSpawn, ...rest } = inlineConfig;
5074
6180
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
5075
6181
  warnElectronVersion(config);
5076
- console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.2.0"}`));
6182
+ console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.4.0"}`));
5077
6183
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5078
- const server = await createServer2({ ...rest, target: "electron" });
6184
+ const server = await createServer2({
6185
+ ...rest,
6186
+ target: "electron",
6187
+ framework: config.framework
6188
+ });
5079
6189
  await server.listen();
5080
- const devUrl = `http://localhost:${server.config.server.port}/`;
6190
+ const devUrl = `http://localhost:${server.config.server.port}` + electronRendererDevPath(config.electron.renderer);
5081
6191
  console.log(import_picocolors10.default.dim(` renderer: ${devUrl}`));
5082
- const stageDir = import_node_path16.default.resolve(config.root, ".nasti");
5083
- import_node_fs11.default.mkdirSync(stageDir, { recursive: true });
5084
- const mainEntry = import_node_path16.default.resolve(config.root, config.electron.main);
6192
+ const stageDir = import_node_path17.default.resolve(config.root, ".nasti");
6193
+ import_node_fs12.default.mkdirSync(stageDir, { recursive: true });
6194
+ const mainEntry = import_node_path17.default.resolve(config.root, config.electron.main);
5085
6195
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
5086
- const builtMainFile = import_node_path16.default.join(stageDir, "main" + extFor(config.electron.mainFormat));
6196
+ const builtMainFile = import_node_path17.default.join(stageDir, "main" + extFor(config.electron.mainFormat));
5087
6197
  const builtPreloadFiles = [];
5088
6198
  const compileAll = async () => {
5089
6199
  await compileNode(config, mainEntry, {
@@ -5093,9 +6203,9 @@ async function startElectronDev(inlineConfig = {}) {
5093
6203
  });
5094
6204
  builtPreloadFiles.length = 0;
5095
6205
  for (const entry of preloadEntries) {
5096
- if (!import_node_fs11.default.existsSync(entry)) continue;
5097
- const base = import_node_path16.default.basename(entry).replace(/\.[^.]+$/, "");
5098
- const out = import_node_path16.default.join(stageDir, base + extFor(config.electron.preloadFormat));
6206
+ if (!import_node_fs12.default.existsSync(entry)) continue;
6207
+ const base = import_node_path17.default.basename(entry).replace(/\.[^.]+$/, "");
6208
+ const out = import_node_path17.default.join(stageDir, base + extFor(config.electron.preloadFormat));
5099
6209
  await compileNode(config, entry, {
5100
6210
  outFile: out,
5101
6211
  format: config.electron.preloadFormat,
@@ -5134,7 +6244,7 @@ async function startElectronDev(inlineConfig = {}) {
5134
6244
  };
5135
6245
  spawnElectron();
5136
6246
  if (config.electron.autoRestart) {
5137
- const watchTargets = [mainEntry, ...preloadEntries].filter(import_node_fs11.default.existsSync);
6247
+ const watchTargets = [mainEntry, ...preloadEntries].filter(import_node_fs12.default.existsSync);
5138
6248
  const watcher = import_chokidar2.default.watch(watchTargets, { ignoreInitial: true });
5139
6249
  let restarting = null;
5140
6250
  let pending = false;
@@ -5199,18 +6309,22 @@ async function compileNode(config, entry, opts) {
5199
6309
  const result = transformCode(id, code, {
5200
6310
  sourcemap: true,
5201
6311
  jsxRuntime: "automatic",
5202
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
6312
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
6313
+ target: config.electron.nodeTarget
5203
6314
  });
5204
6315
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
5205
6316
  }
5206
6317
  };
5207
6318
  const bundle2 = await (0, import_rolldown3.rolldown)({
5208
6319
  input: entry,
5209
- transform: { define: envDefine },
6320
+ transform: {
6321
+ target: config.electron.nodeTarget,
6322
+ define: envDefine
6323
+ },
5210
6324
  platform: "node",
5211
6325
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5212
6326
  });
5213
- import_node_fs11.default.mkdirSync(import_node_path16.default.dirname(opts.outFile), { recursive: true });
6327
+ import_node_fs12.default.mkdirSync(import_node_path17.default.dirname(opts.outFile), { recursive: true });
5214
6328
  await bundle2.write({
5215
6329
  file: opts.outFile,
5216
6330
  format: opts.format === "cjs" ? "cjs" : "esm",
@@ -5222,15 +6336,19 @@ async function compileNode(config, entry, opts) {
5222
6336
  });
5223
6337
  await bundle2.close();
5224
6338
  }
6339
+ function electronRendererDevPath(renderer) {
6340
+ const normalized = renderer.split(import_node_path17.default.sep).join("/").replace(/^\.?\//, "");
6341
+ return normalized === "index.html" ? "/" : `/${normalized}`;
6342
+ }
5225
6343
  function resolveElectronBinary(config) {
5226
- if (config.electron.electronPath && import_node_fs11.default.existsSync(config.electron.electronPath)) {
6344
+ if (config.electron.electronPath && import_node_fs12.default.existsSync(config.electron.electronPath)) {
5227
6345
  return config.electron.electronPath;
5228
6346
  }
5229
6347
  try {
5230
- const require2 = (0, import_node_module7.createRequire)(import_node_path16.default.resolve(config.root, "package.json"));
6348
+ const require2 = (0, import_node_module7.createRequire)(import_node_path17.default.resolve(config.root, "package.json"));
5231
6349
  const pathFile = require2.resolve("electron");
5232
6350
  const electronModule = require2(pathFile);
5233
- if (typeof electronModule === "string" && import_node_fs11.default.existsSync(electronModule)) {
6351
+ if (typeof electronModule === "string" && import_node_fs12.default.existsSync(electronModule)) {
5234
6352
  return electronModule;
5235
6353
  }
5236
6354
  } catch {
@@ -5257,8 +6375,8 @@ function warnElectronVersion(config) {
5257
6375
  }
5258
6376
 
5259
6377
  // src/plugins/monaco-editor.ts
5260
- var import_node_path17 = __toESM(require("path"), 1);
5261
- var import_node_fs12 = __toESM(require("fs"), 1);
6378
+ var import_node_path18 = __toESM(require("path"), 1);
6379
+ var import_node_fs13 = __toESM(require("fs"), 1);
5262
6380
  var import_node_crypto4 = __toESM(require("crypto"), 1);
5263
6381
  var import_node_module8 = require("module");
5264
6382
  var DEFAULT_WORKERS = {
@@ -5279,9 +6397,9 @@ function normalizePublicPath(p) {
5279
6397
  }
5280
6398
  function readMonacoVersion(root) {
5281
6399
  try {
5282
- const require2 = (0, import_node_module8.createRequire)(import_node_path17.default.resolve(root, "package.json"));
6400
+ const require2 = (0, import_node_module8.createRequire)(import_node_path18.default.resolve(root, "package.json"));
5283
6401
  const pkgJsonPath = require2.resolve("monaco-editor/package.json", { paths: [root] });
5284
- const pkg = JSON.parse(import_node_fs12.default.readFileSync(pkgJsonPath, "utf-8"));
6402
+ const pkg = JSON.parse(import_node_fs13.default.readFileSync(pkgJsonPath, "utf-8"));
5285
6403
  return typeof pkg.version === "string" ? pkg.version : "unknown";
5286
6404
  } catch {
5287
6405
  return "unknown";
@@ -5301,20 +6419,20 @@ function monacoEditorPlugin(options = {}) {
5301
6419
  let cacheDir = "";
5302
6420
  const building = /* @__PURE__ */ new Map();
5303
6421
  async function buildWorker(worker) {
5304
- const cacheFile = import_node_path17.default.join(cacheDir, `${worker.label}.worker.js`);
5305
- if (import_node_fs12.default.existsSync(cacheFile)) return cacheFile;
6422
+ const cacheFile = import_node_path18.default.join(cacheDir, `${worker.label}.worker.js`);
6423
+ if (import_node_fs13.default.existsSync(cacheFile)) return cacheFile;
5306
6424
  const existing = building.get(worker.label);
5307
6425
  if (existing) return existing;
5308
6426
  const task = (async () => {
5309
6427
  const { rolldown: rolldown4 } = await import("rolldown");
5310
- const require2 = (0, import_node_module8.createRequire)(import_node_path17.default.resolve(resolvedConfig.root, "package.json"));
6428
+ const require2 = (0, import_node_module8.createRequire)(import_node_path18.default.resolve(resolvedConfig.root, "package.json"));
5311
6429
  let entry;
5312
6430
  try {
5313
6431
  entry = require2.resolve(worker.entry, { paths: [resolvedConfig.root] });
5314
6432
  } catch {
5315
6433
  entry = require2.resolve(worker.entry + ".js", { paths: [resolvedConfig.root] });
5316
6434
  }
5317
- import_node_fs12.default.mkdirSync(cacheDir, { recursive: true });
6435
+ import_node_fs13.default.mkdirSync(cacheDir, { recursive: true });
5318
6436
  const bundle2 = await rolldown4({
5319
6437
  input: entry,
5320
6438
  platform: "browser"
@@ -5374,12 +6492,12 @@ function monacoEditorPlugin(options = {}) {
5374
6492
  resolvedConfig = config;
5375
6493
  const version = readMonacoVersion(config.root);
5376
6494
  const key = import_node_crypto4.default.createHash("sha1").update(version + "|" + publicPath).digest("hex").slice(0, 8);
5377
- cacheDir = import_node_path17.default.resolve(config.root, "node_modules/.nasti/monaco", key);
6495
+ cacheDir = import_node_path18.default.resolve(config.root, "node_modules/.nasti/monaco", key);
5378
6496
  },
5379
6497
  async configureServer(server) {
5380
6498
  const shouldBuild = !isCDN(publicPath) || forceBuildCDN;
5381
6499
  const watcher = server.watcher;
5382
- const monacoDir = import_node_path17.default.resolve(resolvedConfig.root, "node_modules/monaco-editor");
6500
+ const monacoDir = import_node_path18.default.resolve(resolvedConfig.root, "node_modules/monaco-editor");
5383
6501
  try {
5384
6502
  watcher?.unwatch?.(monacoDir);
5385
6503
  } catch {
@@ -5409,7 +6527,7 @@ function monacoEditorPlugin(options = {}) {
5409
6527
  const file = await buildWorker(worker);
5410
6528
  res.setHeader("Content-Type", "application/javascript; charset=utf-8");
5411
6529
  res.setHeader("Cache-Control", "public, max-age=604800, immutable");
5412
- import_node_fs12.default.createReadStream(file).pipe(res);
6530
+ import_node_fs13.default.createReadStream(file).pipe(res);
5413
6531
  } catch (e) {
5414
6532
  res.statusCode = 500;
5415
6533
  res.end(`Monaco worker build failed: ${e.message}`);
@@ -5444,16 +6562,16 @@ self.monaco = monaco;`,
5444
6562
  resolvedConfig.root,
5445
6563
  resolvedConfig.build.outDir,
5446
6564
  resolvedConfig.base
5447
- ) : isCDN(publicPath) ? import_node_path17.default.resolve(resolvedConfig.root, resolvedConfig.build.outDir, "monaco") : import_node_path17.default.resolve(
6565
+ ) : isCDN(publicPath) ? import_node_path18.default.resolve(resolvedConfig.root, resolvedConfig.build.outDir, "monaco") : import_node_path18.default.resolve(
5448
6566
  resolvedConfig.root,
5449
6567
  resolvedConfig.build.outDir,
5450
6568
  publicPath.replace(/^\//, "")
5451
6569
  );
5452
- import_node_fs12.default.mkdirSync(outDir, { recursive: true });
6570
+ import_node_fs13.default.mkdirSync(outDir, { recursive: true });
5453
6571
  for (const worker of workers) {
5454
6572
  try {
5455
6573
  const cacheFile = await buildWorker(worker);
5456
- import_node_fs12.default.copyFileSync(cacheFile, import_node_path17.default.join(outDir, `${worker.label}.worker.js`));
6574
+ import_node_fs13.default.copyFileSync(cacheFile, import_node_path18.default.join(outDir, `${worker.label}.worker.js`));
5457
6575
  } catch (e) {
5458
6576
  throw new Error(
5459
6577
  `[nasti:monaco-editor] worker build failed for "${worker.label}": ${e.message}
@@ -5479,12 +6597,15 @@ init_env();
5479
6597
  buildElectron,
5480
6598
  buildEnvDefine,
5481
6599
  createDebugger,
6600
+ createElectronRendererConfig,
5482
6601
  createLogger,
5483
6602
  createNoopHotChannel,
5484
6603
  createServer,
5485
6604
  createWsHotChannel,
5486
6605
  defineConfig,
6606
+ detectFramework,
5487
6607
  electronPlugin,
6608
+ electronRendererDevPath,
5488
6609
  loadEnv,
5489
6610
  monacoEditorPlugin,
5490
6611
  printServerUrls,