@nasti-toolchain/nasti 2.3.1 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10,10 +10,10 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
10
10
  if (typeof require !== "undefined") return require.apply(this, arguments);
11
11
  throw Error('Dynamic require of "' + x + '" is not supported');
12
12
  });
13
- var __glob = (map) => (path18) => {
14
- var fn = map[path18];
13
+ var __glob = (map) => (path19) => {
14
+ var fn = map[path19];
15
15
  if (fn) return fn();
16
- throw new Error("Module not found in bundle: " + path18);
16
+ throw new Error("Module not found in bundle: " + path19);
17
17
  };
18
18
  var __esm = (fn, res) => function __init() {
19
19
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -70,7 +70,10 @@ var init_defaults = __esm({
70
70
  target: "es2022",
71
71
  rolldownOptions: {},
72
72
  emptyOutDir: true,
73
- css: {},
73
+ css: {
74
+ inject: true,
75
+ emit: true
76
+ },
74
77
  reportCompressedSize: true,
75
78
  chunkSizeWarningLimit: 500,
76
79
  cssCodeSplit: true,
@@ -425,7 +428,11 @@ async function resolveConfig(inlineConfig = {}, command) {
425
428
  allowClearScreen: clearScreen2,
426
429
  customLogger: merged.customLogger
427
430
  });
428
- const mergedBuild = { ...defaults.build, ...merged.build };
431
+ const mergedBuild = {
432
+ ...defaults.build,
433
+ ...merged.build,
434
+ css: { ...defaults.build.css, ...merged.build?.css }
435
+ };
429
436
  if (merged.build?.cssMinify === void 0) {
430
437
  mergedBuild.cssMinify = !!mergedBuild.minify;
431
438
  }
@@ -456,11 +463,17 @@ async function resolveConfig(inlineConfig = {}, command) {
456
463
  bundledDev: merged.experimental?.bundledDev ?? defaults.experimental.bundledDev
457
464
  }
458
465
  };
459
- const userEnvironments = {
466
+ const rawUserEnvironments = {
460
467
  client: {},
461
468
  ssr: {},
462
469
  ...merged.environments ?? {}
463
470
  };
471
+ const userEnvironments = Object.fromEntries(
472
+ Object.entries(rawUserEnvironments).map(([name, options]) => [
473
+ name,
474
+ deepMerge({}, options)
475
+ ])
476
+ );
464
477
  for (const [name, envOptions] of Object.entries(userEnvironments)) {
465
478
  for (const plugin of rawPlugins) {
466
479
  if (plugin.configEnvironment) {
@@ -471,6 +484,7 @@ async function resolveConfig(inlineConfig = {}, command) {
471
484
  }
472
485
  for (const [name, envOptions] of Object.entries(userEnvironments)) {
473
486
  const consumer = envOptions.consumer ?? (name === "client" ? "client" : "server");
487
+ const vueOptions = deepMerge({}, envOptions.vue ?? {});
474
488
  if (name === "client") {
475
489
  if (envOptions.resolve) {
476
490
  Object.assign(resolved.resolve, {
@@ -478,9 +492,14 @@ async function resolveConfig(inlineConfig = {}, command) {
478
492
  alias: { ...resolved.resolve.alias, ...envOptions.resolve.alias }
479
493
  });
480
494
  }
481
- if (envOptions.build) Object.assign(resolved.build, envOptions.build);
495
+ if (envOptions.build) {
496
+ const { css, ...environmentBuild } = envOptions.build;
497
+ Object.assign(resolved.build, environmentBuild);
498
+ if (css) resolved.build.css = { ...resolved.build.css, ...css };
499
+ }
482
500
  resolved.environments.client = {
483
501
  consumer,
502
+ buildEnabled: envOptions.buildEnabled ?? true,
484
503
  entry: normalizeEnvironmentEntries(envOptions.entry, root),
485
504
  html: path.resolve(
486
505
  root,
@@ -489,15 +508,18 @@ async function resolveConfig(inlineConfig = {}, command) {
489
508
  driver: envOptions.driver,
490
509
  // 同引用 —— 精确镜像(assertClientEnvironmentMirror 校验)
491
510
  resolve: resolved.resolve,
492
- build: resolved.build
511
+ build: resolved.build,
512
+ vue: vueOptions
493
513
  };
494
514
  continue;
495
515
  }
496
516
  resolved.environments[name] = {
497
517
  consumer,
518
+ buildEnabled: envOptions.buildEnabled ?? true,
498
519
  entry: normalizeEnvironmentEntries(envOptions.entry, root),
499
520
  html: envOptions.consumer === "client" && envOptions.html ? path.resolve(root, envOptions.html) : void 0,
500
521
  driver: envOptions.driver,
522
+ vue: vueOptions,
501
523
  resolve: {
502
524
  alias: { ...resolved.resolve.alias, ...envOptions.resolve?.alias },
503
525
  extensions: envOptions.resolve?.extensions ?? [...resolved.resolve.extensions],
@@ -508,6 +530,7 @@ async function resolveConfig(inlineConfig = {}, command) {
508
530
  build: {
509
531
  ...resolved.build,
510
532
  ...envOptions.build,
533
+ css: { ...resolved.build.css, ...envOptions.build?.css },
511
534
  // 非 client 环境默认产出到 <outDir>/<envName>(如 dist/ssr),可显式覆盖
512
535
  outDir: envOptions.build?.outDir ?? path.join(resolved.build.outDir, name),
513
536
  // server 产物默认不压缩(可调试性优先,与 Vite SSR 默认一致),可显式覆盖
@@ -721,6 +744,7 @@ function resolvePlugin(config) {
721
744
  }
722
745
  if (!source.startsWith("/") && !source.startsWith(".")) {
723
746
  if (vueRuntimeEntry && source === "vue") return vueRuntimeEntry;
747
+ if (config.command === "build") return null;
724
748
  try {
725
749
  const resolved = require2.resolve(source, {
726
750
  paths: [importer ? path2.dirname(importer) : config.root]
@@ -739,7 +763,7 @@ function resolvePlugin(config) {
739
763
  const content = fs2.readFileSync(id, "utf-8");
740
764
  return `export default ${content}`;
741
765
  }
742
- return fs2.readFileSync(id, "utf-8");
766
+ return null;
743
767
  }
744
768
  };
745
769
  }
@@ -852,27 +876,27 @@ var require_process = __commonJS({
852
876
  var require_filesystem = __commonJS({
853
877
  "node_modules/detect-libc/lib/filesystem.js"(exports, module) {
854
878
  "use strict";
855
- var fs13 = __require("fs");
879
+ var fs14 = __require("fs");
856
880
  var LDD_PATH = "/usr/bin/ldd";
857
881
  var SELF_PATH = "/proc/self/exe";
858
882
  var MAX_LENGTH = 2048;
859
- var readFileSync = (path18) => {
860
- const fd = fs13.openSync(path18, "r");
883
+ var readFileSync = (path19) => {
884
+ const fd = fs14.openSync(path19, "r");
861
885
  const buffer = Buffer.alloc(MAX_LENGTH);
862
- const bytesRead = fs13.readSync(fd, buffer, 0, MAX_LENGTH, 0);
863
- fs13.close(fd, () => {
886
+ const bytesRead = fs14.readSync(fd, buffer, 0, MAX_LENGTH, 0);
887
+ fs14.close(fd, () => {
864
888
  });
865
889
  return buffer.subarray(0, bytesRead);
866
890
  };
867
- var readFile = (path18) => new Promise((resolve, reject) => {
868
- fs13.open(path18, "r", (err, fd) => {
891
+ var readFile = (path19) => new Promise((resolve, reject) => {
892
+ fs14.open(path19, "r", (err, fd) => {
869
893
  if (err) {
870
894
  reject(err);
871
895
  } else {
872
896
  const buffer = Buffer.alloc(MAX_LENGTH);
873
- fs13.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
897
+ fs14.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
874
898
  resolve(buffer.subarray(0, bytesRead));
875
- fs13.close(fd, () => {
899
+ fs14.close(fd, () => {
876
900
  });
877
901
  });
878
902
  }
@@ -984,11 +1008,11 @@ var require_detect_libc = __commonJS({
984
1008
  }
985
1009
  return null;
986
1010
  };
987
- var familyFromInterpreterPath = (path18) => {
988
- if (path18) {
989
- if (path18.includes("/ld-musl-")) {
1011
+ var familyFromInterpreterPath = (path19) => {
1012
+ if (path19) {
1013
+ if (path19.includes("/ld-musl-")) {
990
1014
  return MUSL;
991
- } else if (path18.includes("/ld-linux-")) {
1015
+ } else if (path19.includes("/ld-linux-")) {
992
1016
  return GLIBC;
993
1017
  }
994
1018
  }
@@ -1035,8 +1059,8 @@ var require_detect_libc = __commonJS({
1035
1059
  cachedFamilyInterpreter = null;
1036
1060
  try {
1037
1061
  const selfContent = await readFile(SELF_PATH);
1038
- const path18 = interpreterPath(selfContent);
1039
- cachedFamilyInterpreter = familyFromInterpreterPath(path18);
1062
+ const path19 = interpreterPath(selfContent);
1063
+ cachedFamilyInterpreter = familyFromInterpreterPath(path19);
1040
1064
  } catch (e) {
1041
1065
  }
1042
1066
  return cachedFamilyInterpreter;
@@ -1048,8 +1072,8 @@ var require_detect_libc = __commonJS({
1048
1072
  cachedFamilyInterpreter = null;
1049
1073
  try {
1050
1074
  const selfContent = readFileSync(SELF_PATH);
1051
- const path18 = interpreterPath(selfContent);
1052
- cachedFamilyInterpreter = familyFromInterpreterPath(path18);
1075
+ const path19 = interpreterPath(selfContent);
1076
+ cachedFamilyInterpreter = familyFromInterpreterPath(path19);
1053
1077
  } catch (e) {
1054
1078
  }
1055
1079
  return cachedFamilyInterpreter;
@@ -1696,12 +1720,31 @@ var init_node = __esm({
1696
1720
  function createCssEngine() {
1697
1721
  return {
1698
1722
  styles: /* @__PURE__ */ new Map(),
1723
+ modules: /* @__PURE__ */ new Map(),
1724
+ chunks: /* @__PURE__ */ new Map(),
1699
1725
  entryCss: /* @__PURE__ */ new Map(),
1700
1726
  allCss: [],
1701
1727
  pendingSingle: [],
1702
1728
  singleFileName: null
1703
1729
  };
1704
1730
  }
1731
+ function getCssMetadata(engine) {
1732
+ return {
1733
+ modules: Object.fromEntries(
1734
+ [...engine.modules].map(([id, module]) => [id, { ...module }])
1735
+ ),
1736
+ chunks: Object.fromEntries(
1737
+ [...engine.chunks].map(([fileName, chunk]) => [
1738
+ fileName,
1739
+ {
1740
+ fileName,
1741
+ moduleIds: [...chunk.moduleIds],
1742
+ cssFileNames: [...chunk.cssFileNames]
1743
+ }
1744
+ ])
1745
+ )
1746
+ };
1747
+ }
1705
1748
  function normalizeCssModuleId(id) {
1706
1749
  return id.startsWith("\0") ? id.slice(1) : id;
1707
1750
  }
@@ -1803,6 +1846,7 @@ var init_tailwind = __esm({
1803
1846
 
1804
1847
  // src/plugins/css.ts
1805
1848
  import path4 from "path";
1849
+ import { SourceMapGenerator } from "source-map-js";
1806
1850
  function cssPlugin(config, engine, consumer = "client") {
1807
1851
  return {
1808
1852
  name: "nasti:css",
@@ -1822,15 +1866,29 @@ function cssPlugin(config, engine, consumer = "client") {
1822
1866
  }
1823
1867
  const rewritten = rewriteCssUrls(cssSource, file, config.root);
1824
1868
  const escaped = JSON.stringify(rewritten);
1869
+ const normalizedId = normalizeCssModuleId(id);
1870
+ const cssModule = { id: normalizedId, source: code, code: rewritten };
1871
+ const map = config.build.sourcemap ? createIdentitySourceMap(code, id) : void 0;
1872
+ engine?.modules.set(normalizedId, cssModule);
1873
+ this.environment?.setCssModule?.(cssModule);
1825
1874
  if (query === "inline") {
1826
1875
  return { code: `export default ${escaped};
1827
- `, moduleType: "js" };
1876
+ `, map, moduleType: "js" };
1828
1877
  }
1829
1878
  if (consumer === "server") {
1830
1879
  return { code: `export default ${escaped};
1831
- `, moduleType: "js" };
1880
+ `, map, moduleType: "js" };
1832
1881
  }
1833
1882
  if (config.command === "serve") {
1883
+ if (config.build.css.inject === false) {
1884
+ return {
1885
+ code: `export default ${escaped};
1886
+ `,
1887
+ map,
1888
+ moduleType: "js",
1889
+ moduleSideEffects: "no-treeshake"
1890
+ };
1891
+ }
1834
1892
  return {
1835
1893
  code: `
1836
1894
  const css = ${escaped};
@@ -1854,16 +1912,18 @@ if (import.meta.hot) {
1854
1912
 
1855
1913
  export default css;
1856
1914
  `,
1915
+ map,
1857
1916
  // bundled dev(DevEngine)下该模块会进 Rolldown:不标 js 会按 .css
1858
1917
  // 扩展名走 CSS 管线触发 #4271 报错;unbundled 中间件忽略此字段
1859
1918
  moduleType: "js"
1860
1919
  };
1861
1920
  }
1862
1921
  if (engine) {
1863
- engine.styles.set(normalizeCssModuleId(id), rewritten);
1922
+ engine.styles.set(normalizedId, rewritten);
1864
1923
  return {
1865
1924
  code: `export default '';
1866
1925
  `,
1926
+ map,
1867
1927
  moduleType: "js",
1868
1928
  // 防止空 stub 被 tree-shake 出 chunk.moduleIds(css-post 靠它定位)
1869
1929
  moduleSideEffects: "no-treeshake"
@@ -1883,11 +1943,27 @@ document.head.appendChild(style);
1883
1943
 
1884
1944
  export default css;
1885
1945
  `,
1946
+ map,
1886
1947
  moduleType: "js"
1887
1948
  };
1888
1949
  }
1889
1950
  };
1890
1951
  }
1952
+ function createIdentitySourceMap(code, id) {
1953
+ const map = new SourceMapGenerator({ file: id });
1954
+ const lines = code.split("\n");
1955
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
1956
+ for (let column = 0; column <= lines[lineIndex].length; column++) {
1957
+ map.addMapping({
1958
+ generated: { line: lineIndex + 1, column },
1959
+ original: { line: lineIndex + 1, column },
1960
+ source: id
1961
+ });
1962
+ }
1963
+ }
1964
+ map.setSourceContent(id, code);
1965
+ return map.toJSON();
1966
+ }
1891
1967
  function rewriteCssUrls(css, from, root) {
1892
1968
  return css.replace(/url\(\s*['"]?([^'")\s]+)['"]?\s*\)/g, (match, url) => {
1893
1969
  if (url.startsWith("/") || url.startsWith("data:") || url.startsWith("http")) {
@@ -1910,19 +1986,27 @@ var init_css = __esm({
1910
1986
  function collectChunkCss(chunk, engine) {
1911
1987
  const ids = chunk.moduleIds ?? Object.keys(chunk.modules);
1912
1988
  let css = "";
1989
+ const moduleIds = [];
1913
1990
  for (const id of ids) {
1914
- const styles = engine.styles.get(normalizeCssModuleId(id));
1915
- if (styles) css += styles + "\n";
1991
+ const normalizedId = normalizeCssModuleId(id);
1992
+ const styles = engine.styles.get(normalizedId);
1993
+ if (styles) {
1994
+ css += styles + "\n";
1995
+ moduleIds.push(normalizedId);
1996
+ }
1916
1997
  }
1917
- return css;
1998
+ return { css, moduleIds };
1918
1999
  }
1919
2000
  function cssPostPlugin(config, engine) {
1920
2001
  return {
1921
2002
  name: "nasti:css-post",
1922
2003
  enforce: "post",
1923
2004
  async renderChunk(code, chunk) {
1924
- const css = collectChunkCss(chunk, engine);
2005
+ const { css, moduleIds } = collectChunkCss(chunk, engine);
1925
2006
  if (!css) return null;
2007
+ const ownership = { moduleIds, cssFileNames: [] };
2008
+ engine.chunks.set(chunk.fileName, ownership);
2009
+ if (config.build.css.emit === false) return null;
1926
2010
  if (!config.build.cssCodeSplit) {
1927
2011
  engine.pendingSingle.push(css);
1928
2012
  return null;
@@ -1935,6 +2019,7 @@ function cssPostPlugin(config, engine) {
1935
2019
  });
1936
2020
  const fileName = this.getFileName(ref);
1937
2021
  engine.allCss.push(fileName);
2022
+ ownership.cssFileNames.push(fileName);
1938
2023
  if (chunk.isEntry) {
1939
2024
  const key = chunk.facadeModuleId ?? chunk.name;
1940
2025
  const existing = engine.entryCss.get(key) ?? [];
@@ -1942,13 +2027,14 @@ function cssPostPlugin(config, engine) {
1942
2027
  engine.entryCss.set(key, existing);
1943
2028
  return null;
1944
2029
  }
2030
+ if (config.build.css.inject === false) return null;
1945
2031
  const href = JSON.stringify(config.base + fileName);
1946
2032
  const snippet = `
1947
2033
  ;(function(){try{var d=document,h=${href};if(!d.querySelector('link[data-nasti-css="'+h+'"]')){var l=d.createElement('link');l.rel='stylesheet';l.href=h;l.setAttribute('data-nasti-css',h);d.head.appendChild(l);}}catch(e){}})();`;
1948
2034
  return { code: code + snippet, map: null };
1949
2035
  },
1950
2036
  augmentChunkHash(chunk) {
1951
- const css = collectChunkCss(chunk, engine);
2037
+ const { css } = collectChunkCss(chunk, engine);
1952
2038
  return css || void 0;
1953
2039
  },
1954
2040
  async generateBundle() {
@@ -1959,6 +2045,9 @@ function cssPostPlugin(config, engine) {
1959
2045
  const fileName = this.getFileName(ref);
1960
2046
  engine.singleFileName = fileName;
1961
2047
  engine.allCss.push(fileName);
2048
+ for (const ownership of engine.chunks.values()) {
2049
+ if (ownership.moduleIds.length > 0) ownership.cssFileNames.push(fileName);
2050
+ }
1962
2051
  }
1963
2052
  };
1964
2053
  }
@@ -1974,6 +2063,7 @@ import path5 from "path";
1974
2063
  import fs3 from "fs";
1975
2064
  import crypto from "crypto";
1976
2065
  function assetsPlugin(config) {
2066
+ const emittedAssets = /* @__PURE__ */ new Set();
1977
2067
  return {
1978
2068
  name: "nasti:assets",
1979
2069
  resolveId(source) {
@@ -2002,12 +2092,29 @@ function assetsPlugin(config) {
2002
2092
  const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 8);
2003
2093
  const basename = path5.basename(file, ext);
2004
2094
  const hashedName = `${config.build.assetsDir}/${basename}.${hash}${ext}`;
2095
+ const environment = this.environment;
2096
+ if (!environment) {
2097
+ throw new Error("[nasti:assets] build environment is not initialized");
2098
+ }
2099
+ if (!emittedAssets.has(hashedName)) {
2100
+ this.emitFile({
2101
+ type: "asset",
2102
+ fileName: hashedName,
2103
+ source: content
2104
+ });
2105
+ emittedAssets.add(hashedName);
2106
+ }
2107
+ environment.setAssetModule(file, hashedName);
2005
2108
  return `export default ${JSON.stringify(config.base + hashedName)}`;
2006
2109
  }
2007
2110
  return null;
2008
2111
  }
2009
2112
  };
2010
2113
  }
2114
+ function isAssetFile(id) {
2115
+ const ext = path5.extname(id.replace(/\?.*$/, ""));
2116
+ return ASSET_EXTENSIONS.has(ext);
2117
+ }
2011
2118
  var ASSET_EXTENSIONS;
2012
2119
  var init_assets = __esm({
2013
2120
  "src/plugins/assets.ts"() {
@@ -2079,6 +2186,11 @@ var init_transformer = __esm({
2079
2186
 
2080
2187
  // src/plugins/vue.ts
2081
2188
  import crypto2 from "crypto";
2189
+ import {
2190
+ SourceMapConsumer,
2191
+ SourceMapGenerator as SourceMapGenerator2,
2192
+ SourceNode
2193
+ } from "source-map-js";
2082
2194
  async function loadVueCompiler() {
2083
2195
  if (compiler) return compiler;
2084
2196
  try {
@@ -2088,9 +2200,10 @@ async function loadVueCompiler() {
2088
2200
  return null;
2089
2201
  }
2090
2202
  }
2091
- function vuePlugin(config) {
2203
+ function vuePlugin(config, environmentName = "client") {
2092
2204
  const isDev = config.command === "serve";
2093
2205
  const descriptorCache = /* @__PURE__ */ new Map();
2206
+ const vueOptions = config.environments[environmentName]?.vue ?? {};
2094
2207
  return {
2095
2208
  name: "nasti:vue",
2096
2209
  enforce: "pre",
@@ -2110,32 +2223,63 @@ function vuePlugin(config) {
2110
2223
  const sfc = await loadVueCompiler();
2111
2224
  if (!sfc) return null;
2112
2225
  const [, filePath, indexStr] = match;
2113
- let descriptor = descriptorCache.get(filePath);
2114
- if (!descriptor) {
2226
+ let cached2 = descriptorCache.get(filePath);
2227
+ if (!cached2) {
2115
2228
  try {
2116
- const fs13 = await import("fs");
2117
- const source = fs13.readFileSync(filePath, "utf-8");
2118
- const parsed = sfc.parse(source, { filename: filePath });
2229
+ const fs14 = await import("fs");
2230
+ const rawSource = fs14.readFileSync(filePath, "utf-8");
2231
+ const transformedSfc = await applySourceTransform(
2232
+ vueOptions.transformSfc,
2233
+ rawSource,
2234
+ { filename: filePath, environmentName, type: "sfc" }
2235
+ );
2236
+ const parsed = sfc.parse(transformedSfc.code, {
2237
+ ...vueOptions.parse,
2238
+ filename: filePath,
2239
+ sourceMap: true
2240
+ });
2119
2241
  if (parsed.errors.length) return null;
2120
- descriptor = parsed.descriptor;
2121
- descriptorCache.set(filePath, descriptor);
2242
+ cached2 = {
2243
+ descriptor: parsed.descriptor,
2244
+ sourceMap: transformedSfc.map
2245
+ };
2246
+ descriptorCache.set(filePath, cached2);
2122
2247
  } catch {
2123
2248
  return null;
2124
2249
  }
2125
2250
  }
2251
+ const { descriptor, sourceMap: sfcSourceMap } = cached2;
2126
2252
  const index2 = parseInt(indexStr ?? "0", 10);
2127
2253
  const style = descriptor.styles[index2];
2128
2254
  if (!style) return null;
2129
2255
  const scopeId = hashId(filePath);
2256
+ const transformedStyle = await applySourceTransform(
2257
+ vueOptions.transformStyle,
2258
+ style.content,
2259
+ { filename: filePath, environmentName, type: "style", index: index2 }
2260
+ );
2261
+ const wantsStyleSourceMap = !!config.build.sourcemap || transformedStyle.map != null || sfcSourceMap != null;
2262
+ const styleInputMap = wantsStyleSourceMap ? composeSourceMapChain(
2263
+ [transformedStyle.map, style.map, sfcSourceMap],
2264
+ { filename: filePath, environmentName, type: "style", index: index2 }
2265
+ ) : void 0;
2130
2266
  const result = await sfc.compileStyleAsync({
2131
- source: style.content,
2267
+ ...vueOptions.style,
2268
+ source: transformedStyle.code,
2132
2269
  filename: filePath,
2133
2270
  id: `data-v-${scopeId}`,
2134
2271
  scoped: style.scoped ?? false,
2272
+ inMap: styleInputMap,
2135
2273
  // <style lang="scss|less|stylus"> 需经对应预处理器(缺省 undefined = 纯 CSS)
2136
2274
  preprocessLang: style.lang
2137
2275
  });
2138
- return result.code;
2276
+ if (transformedStyle.map != null && result.map == null) {
2277
+ warnUnchainableMap(
2278
+ { filename: filePath, environmentName, type: "style", index: index2 },
2279
+ "compiler-sfc did not return a style map"
2280
+ );
2281
+ }
2282
+ return wantsStyleSourceMap ? { code: result.code, map: result.map } : result.code;
2139
2283
  },
2140
2284
  async transform(code, id) {
2141
2285
  if (!VUE_FILE_RE.test(id) && !VUE_QUERY_RE.test(id)) return null;
@@ -2147,57 +2291,144 @@ function vuePlugin(config) {
2147
2291
  if (VUE_QUERY_RE.test(id)) {
2148
2292
  return null;
2149
2293
  }
2150
- const { descriptor, errors } = sfc.parse(code, { filename: id });
2294
+ const transformedSfc = await applySourceTransform(
2295
+ vueOptions.transformSfc,
2296
+ code,
2297
+ { filename: id, environmentName, type: "sfc" }
2298
+ );
2299
+ code = transformedSfc.code;
2300
+ const { descriptor, errors } = sfc.parse(code, {
2301
+ ...vueOptions.parse,
2302
+ filename: id,
2303
+ sourceMap: true
2304
+ });
2151
2305
  if (errors.length) {
2152
- console.error(`[nasti:vue] Parse error in ${id}:`, errors[0].message);
2306
+ const firstError = errors[0];
2307
+ console.error(
2308
+ `[nasti:vue] Parse error in ${id}:`,
2309
+ typeof firstError === "string" ? firstError : firstError.message
2310
+ );
2153
2311
  return null;
2154
2312
  }
2155
- descriptorCache.set(id, descriptor);
2313
+ descriptorCache.set(id, {
2314
+ descriptor,
2315
+ sourceMap: transformedSfc.map
2316
+ });
2156
2317
  const scopeId = hashId(id);
2318
+ const wantsSourceMap = !!config.build.sourcemap || transformedSfc.map != null;
2157
2319
  let scriptCode = "";
2320
+ let scriptMap;
2158
2321
  if (descriptor.script || descriptor.scriptSetup) {
2322
+ const inlineTemplate = vueOptions.script?.inlineTemplate !== false;
2159
2323
  const compiled = sfc.compileScript(descriptor, {
2324
+ ...vueOptions.script,
2160
2325
  id: scopeId,
2161
2326
  isProd: !isDev,
2162
- inlineTemplate: true,
2327
+ inlineTemplate,
2328
+ sourceMap: wantsSourceMap,
2163
2329
  // 让 compileScript 产出 `const __sfc__ = ...`(而非默认的 `export default {...}`)。
2164
2330
  // 否则下方追加的 `__sfc__.render` / `__sfc__.__scopeId` / HMR 记录会引用一个
2165
2331
  // 不存在的 `__sfc__`,并与 compileScript 自带的 `export default` 形成双重默认导出。
2166
2332
  genDefaultAs: "__sfc__"
2167
2333
  });
2168
2334
  scriptCode = compiled.content;
2335
+ scriptMap = composeSourceMapChain(
2336
+ [compiled.map, transformedSfc.map],
2337
+ { filename: id, environmentName, type: "sfc" }
2338
+ );
2339
+ if (transformedSfc.map != null && scriptMap == null) {
2340
+ warnUnchainableMap(
2341
+ { filename: id, environmentName, type: "sfc" },
2342
+ "compiler-sfc did not return a script map"
2343
+ );
2344
+ }
2169
2345
  }
2170
2346
  let templateCode = "";
2171
- if (descriptor.template && !descriptor.scriptSetup) {
2347
+ let templateMap;
2348
+ const scriptSetupIsInline = !!descriptor.scriptSetup && vueOptions.script?.inlineTemplate !== false;
2349
+ if (descriptor.template && !scriptSetupIsInline) {
2350
+ const transformedTemplate = await applySourceTransform(
2351
+ vueOptions.transformTemplate,
2352
+ descriptor.template.content,
2353
+ { filename: id, environmentName, type: "template" }
2354
+ );
2355
+ const templateInputMap = composeSourceMapChain(
2356
+ [
2357
+ transformedTemplate.map,
2358
+ descriptor.template.map,
2359
+ transformedSfc.map
2360
+ ],
2361
+ { filename: id, environmentName, type: "template" }
2362
+ );
2363
+ const customCompilerOptions = vueOptions.template?.compilerOptions ?? {};
2172
2364
  const compiled = sfc.compileTemplate({
2173
- source: descriptor.template.content,
2365
+ ...vueOptions.template,
2366
+ source: transformedTemplate.code,
2174
2367
  filename: id,
2175
2368
  id: scopeId,
2176
- compilerOptions: { scopeId: `data-v-${scopeId}` }
2369
+ inMap: templateInputMap,
2370
+ compilerOptions: {
2371
+ ...customCompilerOptions,
2372
+ scopeId: `data-v-${scopeId}`
2373
+ }
2177
2374
  });
2178
2375
  templateCode = compiled.code;
2376
+ if (wantsSourceMap || transformedTemplate.map != null) {
2377
+ templateMap = compiled.map;
2378
+ }
2379
+ if (transformedTemplate.map != null && templateMap == null) {
2380
+ warnUnchainableMap(
2381
+ { filename: id, environmentName, type: "template" },
2382
+ "compiler-sfc did not return a template map"
2383
+ );
2384
+ }
2179
2385
  }
2180
- let output = scriptCode || "const __sfc__ = {}";
2386
+ const outputNode = new SourceNode();
2387
+ let hasMappedOutput = false;
2388
+ const append = (fragment, map) => {
2389
+ const normalizedMap = normalizeSourceMap(
2390
+ map,
2391
+ { filename: id, environmentName, type: "sfc" }
2392
+ );
2393
+ if (!normalizedMap) {
2394
+ outputNode.add(fragment);
2395
+ return;
2396
+ }
2397
+ try {
2398
+ outputNode.add(
2399
+ SourceNode.fromStringWithSourceMap(
2400
+ fragment,
2401
+ new SourceMapConsumer(normalizedMap)
2402
+ )
2403
+ );
2404
+ hasMappedOutput = true;
2405
+ } catch (error) {
2406
+ warnUnchainableMap(
2407
+ { filename: id, environmentName, type: "sfc" },
2408
+ `source-map assembly failed: ${error instanceof Error ? error.message : String(error)}`
2409
+ );
2410
+ outputNode.add(fragment);
2411
+ }
2412
+ };
2413
+ append(scriptCode || "const __sfc__ = {}", scriptMap);
2181
2414
  if (templateCode) {
2182
- output += `
2183
- ${templateCode}
2184
- `;
2185
- output += `
2186
- __sfc__.render = render
2187
- `;
2415
+ append("\n");
2416
+ append(templateCode, templateMap);
2417
+ append("\n");
2418
+ append("\n__sfc__.render = render\n");
2188
2419
  }
2189
2420
  if (descriptor.styles.length > 0) {
2190
2421
  for (let i = 0; i < descriptor.styles.length; i++) {
2191
- output += `
2422
+ append(`
2192
2423
  import "${id}?vue&type=style&index=${i}&lang.css"
2193
- `;
2424
+ `);
2194
2425
  }
2195
2426
  }
2196
- output += `
2427
+ append(`
2197
2428
  __sfc__.__scopeId = "data-v-${scopeId}"
2198
- `;
2429
+ `);
2199
2430
  if (isDev) {
2200
- output += `
2431
+ append(`
2201
2432
  __sfc__.__hmrId = ${JSON.stringify(scopeId)}
2202
2433
  if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') {
2203
2434
  __VUE_HMR_RUNTIME__.createRecord(__sfc__.__hmrId, __sfc__)
@@ -2211,17 +2442,34 @@ if (import.meta.hot) {
2211
2442
  }
2212
2443
  })
2213
2444
  }
2214
- `;
2445
+ `);
2446
+ }
2447
+ append("\nexport default __sfc__\n");
2448
+ const renderedOutput = outputNode.toStringWithSourceMap({ file: id });
2449
+ const output = renderedOutput.code;
2450
+ const outputMap = hasMappedOutput ? renderedOutput.map.toJSON() : void 0;
2451
+ if (transformedSfc.map != null && outputMap == null) {
2452
+ warnUnchainableMap(
2453
+ { filename: id, environmentName, type: "sfc" },
2454
+ "the compiled SFC output contained no chainable mappings"
2455
+ );
2215
2456
  }
2216
- output += `
2217
- export default __sfc__
2218
- `;
2219
2457
  const lang = descriptor.scriptSetup?.lang ?? descriptor.script?.lang;
2220
2458
  if (lang === "ts") {
2221
- const transpiled = transformCode(`${id}.ts`, output, { sourcemap: false });
2222
- return { code: transpiled.code };
2459
+ const transpiled = transformCode(`${id}.ts`, output, {
2460
+ sourcemap: wantsSourceMap,
2461
+ target: config.build.target
2462
+ });
2463
+ const transpiledMap = transpiled.map ? JSON.parse(transpiled.map) : void 0;
2464
+ return {
2465
+ code: transpiled.code,
2466
+ map: composeSourceMapChain(
2467
+ [transpiledMap, outputMap],
2468
+ { filename: id, environmentName, type: "sfc" }
2469
+ )
2470
+ };
2223
2471
  }
2224
- return { code: output };
2472
+ return { code: output, map: outputMap };
2225
2473
  },
2226
2474
  handleHotUpdate(ctx) {
2227
2475
  const { file, modules } = ctx;
@@ -2235,16 +2483,75 @@ export default __sfc__
2235
2483
  }
2236
2484
  };
2237
2485
  }
2486
+ async function applySourceTransform(transform2, source, context) {
2487
+ if (!transform2) return { code: source };
2488
+ const result = await transform2(source, context);
2489
+ return typeof result === "string" ? { code: result } : result;
2490
+ }
2491
+ function normalizeSourceMap(map, context) {
2492
+ if (map == null) return void 0;
2493
+ try {
2494
+ const value = typeof map === "string" ? JSON.parse(map) : map;
2495
+ if (value && typeof value === "object" && Array.isArray(value.sources) && Array.isArray(value.names) && typeof value.mappings === "string") {
2496
+ return value;
2497
+ }
2498
+ } catch {
2499
+ }
2500
+ warnUnchainableMap(context, "the provided map is not a valid source map");
2501
+ return void 0;
2502
+ }
2503
+ function composeSourceMapChain(maps, context) {
2504
+ const pending = maps.filter((map) => map != null);
2505
+ if (pending.length === 0) return void 0;
2506
+ let composed = normalizeSourceMap(pending.shift(), context);
2507
+ for (const map of pending) {
2508
+ const input = normalizeSourceMap(map, context);
2509
+ if (!input) continue;
2510
+ if (!composed) {
2511
+ composed = input;
2512
+ continue;
2513
+ }
2514
+ try {
2515
+ const consumer = new SourceMapConsumer(composed);
2516
+ if (consumer.sources.length !== 1) {
2517
+ warnUnchainableMap(
2518
+ context,
2519
+ "a generated map has multiple sources and cannot be chained safely"
2520
+ );
2521
+ continue;
2522
+ }
2523
+ const generator = SourceMapGenerator2.fromSourceMap(consumer);
2524
+ generator.applySourceMap(
2525
+ new SourceMapConsumer(input),
2526
+ consumer.sources[0]
2527
+ );
2528
+ composed = generator.toJSON();
2529
+ } catch (error) {
2530
+ warnUnchainableMap(
2531
+ context,
2532
+ `source-map composition failed: ${error instanceof Error ? error.message : String(error)}`
2533
+ );
2534
+ }
2535
+ }
2536
+ return composed;
2537
+ }
2538
+ function warnUnchainableMap(context, reason) {
2539
+ debug2?.(
2540
+ `source map warning for ${context.filename} (${context.type}, ${context.environmentName}): ${reason}`
2541
+ );
2542
+ }
2238
2543
  function hashId(filename) {
2239
2544
  return crypto2.createHash("sha256").update(filename).digest("hex").slice(0, 8);
2240
2545
  }
2241
- var VUE_FILE_RE, VUE_QUERY_RE, compiler;
2546
+ var VUE_FILE_RE, VUE_QUERY_RE, debug2, compiler;
2242
2547
  var init_vue = __esm({
2243
2548
  "src/plugins/vue.ts"() {
2244
2549
  "use strict";
2245
2550
  init_transformer();
2551
+ init_debug();
2246
2552
  VUE_FILE_RE = /\.vue$/;
2247
2553
  VUE_QUERY_RE = /\.vue\?vue&type=(script|template|style)(&index=\d+)?(&lang[.=]\w+)?/;
2554
+ debug2 = createDebugger("nasti:vue");
2248
2555
  compiler = null;
2249
2556
  }
2250
2557
  });
@@ -2335,16 +2642,27 @@ window.__vite_plugin_react_preamble_installed__ = true;
2335
2642
  // src/plugins/builtins.ts
2336
2643
  function resolvePluginList(config, userPlugins, opts = {}) {
2337
2644
  const isServe = config.command === "serve";
2645
+ let environmentOptions;
2646
+ if (opts.environmentName) {
2647
+ environmentOptions = config.environments[opts.environmentName];
2648
+ if (!environmentOptions) {
2649
+ throw new Error(
2650
+ `[nasti] unknown environment "${opts.environmentName}" \u2014 declare it in config.environments`
2651
+ );
2652
+ }
2653
+ }
2654
+ const pluginConfig = environmentOptions ? { ...config, resolve: environmentOptions.resolve, build: environmentOptions.build } : config;
2655
+ const consumer = opts.consumer ?? environmentOptions?.consumer;
2338
2656
  return [
2339
2657
  // vuePlugin 排最前(enforce: 'pre' 语义):.vue 先编译成 JS 再走后续管道
2340
- ...config.framework === "vue" ? [vuePlugin(config)] : [],
2341
- resolvePlugin(config),
2342
- cssPlugin(config, opts.cssEngine, opts.consumer),
2343
- assetsPlugin(config),
2344
- ...isServe ? [htmlPlugin(config)] : [],
2658
+ ...config.framework === "vue" ? [vuePlugin(pluginConfig, opts.environmentName ?? "client")] : [],
2659
+ resolvePlugin(pluginConfig),
2660
+ cssPlugin(pluginConfig, opts.cssEngine, consumer),
2661
+ assetsPlugin(pluginConfig),
2662
+ ...isServe ? [htmlPlugin(pluginConfig)] : [],
2345
2663
  ...userPlugins,
2346
2664
  // cssPostPlugin 最后(enforce: 'post' 语义):renderChunk 聚合抽取
2347
- ...!isServe && opts.cssEngine ? [cssPostPlugin(config, opts.cssEngine)] : []
2665
+ ...!isServe && opts.cssEngine ? [cssPostPlugin(pluginConfig, opts.cssEngine)] : []
2348
2666
  ];
2349
2667
  }
2350
2668
  var init_builtins = __esm({
@@ -2441,17 +2759,23 @@ var init_plugin_container = __esm({
2441
2759
  }
2442
2760
  async transform(code, id) {
2443
2761
  let currentCode = code;
2762
+ let lastResult;
2444
2763
  for (const plugin of this.plugins) {
2445
2764
  if (!plugin.transform) continue;
2446
2765
  const result = await plugin.transform.call(this.ctx, currentCode, id);
2447
2766
  if (result == null) continue;
2448
2767
  if (typeof result === "string") {
2449
2768
  currentCode = result;
2769
+ lastResult = void 0;
2450
2770
  } else {
2451
2771
  currentCode = result.code;
2772
+ lastResult = result;
2452
2773
  }
2453
2774
  }
2454
- return currentCode === code ? null : { code: currentCode };
2775
+ return currentCode === code ? null : {
2776
+ ...lastResult,
2777
+ code: currentCode
2778
+ };
2455
2779
  }
2456
2780
  /** 完整的模块处理管道: resolveId → load → transform */
2457
2781
  async processModule(source, importer) {
@@ -2474,17 +2798,39 @@ var init_plugin_container = __esm({
2474
2798
  }
2475
2799
  });
2476
2800
 
2801
+ // src/core/url.ts
2802
+ function removeTimestampQuery(url) {
2803
+ const hashIndex = url.indexOf("#");
2804
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
2805
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
2806
+ const queryIndex = withoutHash.indexOf("?");
2807
+ if (queryIndex < 0) return url;
2808
+ const pathname = withoutHash.slice(0, queryIndex);
2809
+ const query = withoutHash.slice(queryIndex + 1).split("&").filter((part) => !/^t=\d+$/.test(part)).join("&");
2810
+ return pathname + (query ? `?${query}` : "") + hash;
2811
+ }
2812
+ var init_url = __esm({
2813
+ "src/core/url.ts"() {
2814
+ "use strict";
2815
+ }
2816
+ });
2817
+
2477
2818
  // src/core/module-graph.ts
2478
2819
  var ModuleGraph;
2479
2820
  var init_module_graph = __esm({
2480
2821
  "src/core/module-graph.ts"() {
2481
2822
  "use strict";
2823
+ init_url();
2482
2824
  ModuleGraph = class {
2825
+ environmentName;
2483
2826
  urlToModuleMap = /* @__PURE__ */ new Map();
2484
2827
  idToModuleMap = /* @__PURE__ */ new Map();
2485
2828
  fileToModulesMap = /* @__PURE__ */ new Map();
2829
+ constructor(environmentName = "client") {
2830
+ this.environmentName = environmentName;
2831
+ }
2486
2832
  getModuleByUrl(url) {
2487
- return this.urlToModuleMap.get(url);
2833
+ return this.urlToModuleMap.get(removeTimestampQuery(url));
2488
2834
  }
2489
2835
  getModuleById(id) {
2490
2836
  return this.idToModuleMap.get(id);
@@ -2493,10 +2839,11 @@ var init_module_graph = __esm({
2493
2839
  return this.fileToModulesMap.get(file);
2494
2840
  }
2495
2841
  async ensureEntryFromUrl(url) {
2496
- let mod = this.urlToModuleMap.get(url);
2842
+ const normalizedUrl = removeTimestampQuery(url);
2843
+ let mod = this.urlToModuleMap.get(normalizedUrl);
2497
2844
  if (mod) return mod;
2498
- mod = this.createModule(url);
2499
- this.urlToModuleMap.set(url, mod);
2845
+ mod = this.createModule(normalizedUrl);
2846
+ this.urlToModuleMap.set(normalizedUrl, mod);
2500
2847
  return mod;
2501
2848
  }
2502
2849
  createModule(url, id) {
@@ -2510,7 +2857,9 @@ var init_module_graph = __esm({
2510
2857
  acceptedHmrDeps: /* @__PURE__ */ new Set(),
2511
2858
  transformResult: null,
2512
2859
  lastHMRTimestamp: 0,
2513
- isSelfAccepting: false
2860
+ invalidationVersion: 0,
2861
+ isSelfAccepting: false,
2862
+ environment: this.environmentName
2514
2863
  };
2515
2864
  this.idToModuleMap.set(mod.id, mod);
2516
2865
  return mod;
@@ -2552,10 +2901,64 @@ var init_module_graph = __esm({
2552
2901
  }
2553
2902
  }
2554
2903
  }
2904
+ /**
2905
+ * 用一次转换得到的信息原子更新 import 与 HMR accept 关系。
2906
+ * 依赖节点会在真正被浏览器请求前预先创建,这样入口模块先转换时也能建立完整图。
2907
+ */
2908
+ async updateModuleInfo(mod, importedUrls, acceptedUrls, isSelfAccepting, expectedInvalidationVersion) {
2909
+ const importedModules = await Promise.all(
2910
+ [...importedUrls].map((url) => this.ensureEntryFromUrl(url))
2911
+ );
2912
+ const acceptedModules = await Promise.all(
2913
+ [...acceptedUrls].map((url) => this.ensureEntryFromUrl(url))
2914
+ );
2915
+ if (expectedInvalidationVersion !== void 0 && mod.invalidationVersion !== expectedInvalidationVersion) {
2916
+ return null;
2917
+ }
2918
+ const previousImports = new Set(mod.importedModules);
2919
+ for (const imported of previousImports) {
2920
+ imported.importers.delete(mod);
2921
+ }
2922
+ mod.importedModules.clear();
2923
+ mod.acceptedHmrDeps.clear();
2924
+ for (const imported of importedModules) {
2925
+ mod.importedModules.add(imported);
2926
+ imported.importers.add(mod);
2927
+ }
2928
+ for (const accepted of acceptedModules) {
2929
+ mod.acceptedHmrDeps.add(accepted);
2930
+ }
2931
+ mod.isSelfAccepting = isSelfAccepting;
2932
+ const pruned = /* @__PURE__ */ new Set();
2933
+ for (const imported of previousImports) {
2934
+ if (!mod.importedModules.has(imported) && imported.importers.size === 0) {
2935
+ pruned.add(imported);
2936
+ }
2937
+ }
2938
+ return pruned;
2939
+ }
2555
2940
  /** 使模块的转换缓存失效 */
2556
- invalidateModule(mod) {
2941
+ invalidateModule(mod, timestamp = Date.now()) {
2557
2942
  mod.transformResult = null;
2558
- mod.lastHMRTimestamp = Date.now();
2943
+ mod.lastHMRTimestamp = timestamp;
2944
+ mod.invalidationVersion++;
2945
+ }
2946
+ /**
2947
+ * 仅失效到 HMR 边界:显式接受依赖的模块本身不会重执行;自接受模块需要失效,
2948
+ * 但不再继续影响其 importer。这样既能传播依赖时间戳,也不会隐式重复副作用。
2949
+ */
2950
+ invalidateModuleAndImporters(mod, timestamp = Date.now(), seen = /* @__PURE__ */ new Set()) {
2951
+ if (seen.has(mod)) return;
2952
+ seen.add(mod);
2953
+ this.invalidateModule(mod, timestamp);
2954
+ for (const importer of mod.importers) {
2955
+ if (importer.acceptedHmrDeps.has(mod)) continue;
2956
+ if (importer.isSelfAccepting) {
2957
+ this.invalidateModule(importer, timestamp);
2958
+ continue;
2959
+ }
2960
+ this.invalidateModuleAndImporters(importer, timestamp, seen);
2961
+ }
2559
2962
  }
2560
2963
  /** 使所有模块缓存失效 */
2561
2964
  invalidateAll() {
@@ -2566,34 +2969,32 @@ var init_module_graph = __esm({
2566
2969
  /** 获取 HMR 传播边界 - 从变更模块向上遍历找到接受更新的边界 */
2567
2970
  getHmrBoundaries(mod) {
2568
2971
  const boundaries = [];
2569
- const visited = /* @__PURE__ */ new Set();
2570
- const propagate = (node, via) => {
2571
- if (visited.has(node)) return true;
2572
- visited.add(node);
2573
- if (node.isSelfAccepting) {
2574
- boundaries.push({ boundary: node, acceptedVia: via });
2575
- return true;
2972
+ const traversed = /* @__PURE__ */ new Set();
2973
+ const addBoundary = (boundary, acceptedVia) => {
2974
+ if (!boundaries.some(
2975
+ (item) => item.boundary === boundary && item.acceptedVia === acceptedVia
2976
+ )) {
2977
+ boundaries.push({ boundary, acceptedVia });
2576
2978
  }
2577
- if (node.acceptedHmrDeps.has(via)) {
2578
- boundaries.push({ boundary: node, acceptedVia: via });
2979
+ };
2980
+ const propagate = (node) => {
2981
+ if (traversed.has(node)) return true;
2982
+ traversed.add(node);
2983
+ if (node.isSelfAccepting) {
2984
+ addBoundary(node, node);
2579
2985
  return true;
2580
2986
  }
2581
2987
  if (node.importers.size === 0) return false;
2582
2988
  for (const importer of node.importers) {
2583
- if (!propagate(importer, node)) return false;
2989
+ if (importer.acceptedHmrDeps.has(node)) {
2990
+ addBoundary(importer, node);
2991
+ continue;
2992
+ }
2993
+ if (!propagate(importer)) return false;
2584
2994
  }
2585
2995
  return true;
2586
2996
  };
2587
- if (mod.isSelfAccepting) {
2588
- boundaries.push({ boundary: mod, acceptedVia: mod });
2589
- return boundaries;
2590
- }
2591
- for (const importer of mod.importers) {
2592
- if (!propagate(importer, mod)) {
2593
- return [];
2594
- }
2595
- }
2596
- return boundaries;
2997
+ return propagate(mod) ? boundaries : [];
2597
2998
  }
2598
2999
  };
2599
3000
  }
@@ -2616,12 +3017,12 @@ function createNoopHotChannel() {
2616
3017
  }
2617
3018
  };
2618
3019
  }
2619
- function createWsHotChannel(ws) {
3020
+ function createWsHotChannel(ws, environmentName = "client") {
2620
3021
  const listeners = /* @__PURE__ */ new Map();
2621
3022
  let invokeHandlers;
2622
3023
  return {
2623
3024
  send(payload) {
2624
- ws.send(payload);
3025
+ ws.send({ ...payload, environment: payload.environment ?? environmentName });
2625
3026
  },
2626
3027
  on(event, listener) {
2627
3028
  let set = listeners.get(event);
@@ -2633,8 +3034,8 @@ function createWsHotChannel(ws) {
2633
3034
  },
2634
3035
  listen() {
2635
3036
  },
3037
+ // 多个 environment 共享底层 WebSocket server;它由 DevServer.close() 统一关闭。
2636
3038
  close() {
2637
- ws.close();
2638
3039
  },
2639
3040
  setInvokeHandler(handlers) {
2640
3041
  invokeHandlers = handlers;
@@ -2663,7 +3064,7 @@ function resolveEnvironmentPlugins(environment, plugins) {
2663
3064
  }
2664
3065
  });
2665
3066
  }
2666
- var debug2, NastiEnvironment;
3067
+ var debug3, NastiEnvironment;
2667
3068
  var init_environment = __esm({
2668
3069
  "src/core/environment.ts"() {
2669
3070
  "use strict";
@@ -2672,7 +3073,7 @@ var init_environment = __esm({
2672
3073
  init_hot_channel();
2673
3074
  init_debug();
2674
3075
  init_plugin_api();
2675
- debug2 = createDebugger("nasti:environment");
3076
+ debug3 = createDebugger("nasti:environment");
2676
3077
  NastiEnvironment = class {
2677
3078
  name;
2678
3079
  consumer;
@@ -2689,6 +3090,10 @@ var init_environment = __esm({
2689
3090
  moduleGraph;
2690
3091
  candidatePlugins;
2691
3092
  pluginApi;
3093
+ buildMetadata = {};
3094
+ cssModules = /* @__PURE__ */ new Map();
3095
+ assetModules = /* @__PURE__ */ new Map();
3096
+ transformRequestHandler;
2692
3097
  initialized = false;
2693
3098
  constructor(name, config, init = {}) {
2694
3099
  const options = config.environments[name];
@@ -2703,7 +3108,7 @@ var init_environment = __esm({
2703
3108
  this.config = config;
2704
3109
  this.options = options;
2705
3110
  this.hot = init.hot ?? createNoopHotChannel();
2706
- this.moduleGraph = new ModuleGraph();
3111
+ this.moduleGraph = new ModuleGraph(name);
2707
3112
  this.candidatePlugins = init.plugins ?? config.plugins;
2708
3113
  this.pluginApi = init.pluginApi ?? getPluginApi(config);
2709
3114
  }
@@ -2733,9 +3138,9 @@ var init_environment = __esm({
2733
3138
  );
2734
3139
  }
2735
3140
  this.driver = claimed[0].driver;
2736
- debug2?.(`env "${this.name}" uses driver "${this.driver.name}"`);
3141
+ debug3?.(`env "${this.name}" uses driver "${this.driver.name}"`);
2737
3142
  }
2738
- debug2?.(`env "${this.name}" initialized (${this.plugins.length} plugins)`);
3143
+ debug3?.(`env "${this.name}" initialized (${this.plugins.length} plugins)`);
2739
3144
  }
2740
3145
  getDriverContext() {
2741
3146
  return {
@@ -2745,6 +3150,53 @@ var init_environment = __esm({
2745
3150
  logger: this.config.logger
2746
3151
  };
2747
3152
  }
3153
+ configureDevPipeline(transformRequest2) {
3154
+ this.transformRequestHandler = transformRequest2;
3155
+ }
3156
+ async transformRequest(url) {
3157
+ if (!this.transformRequestHandler) {
3158
+ throw new Error(
3159
+ `[nasti] environment "${this.name}" does not have an initialized dev transform pipeline`
3160
+ );
3161
+ }
3162
+ return this.transformRequestHandler(url);
3163
+ }
3164
+ setCssModule(module) {
3165
+ this.cssModules.set(module.id, { ...module });
3166
+ }
3167
+ getCssModule(id) {
3168
+ const module = this.cssModules.get(id);
3169
+ return module ? { ...module } : void 0;
3170
+ }
3171
+ getCssModules() {
3172
+ return Object.freeze(
3173
+ Object.fromEntries(
3174
+ [...this.cssModules].map(([id, module]) => [id, { ...module }])
3175
+ )
3176
+ );
3177
+ }
3178
+ setAssetModule(id, fileName) {
3179
+ this.assetModules.set(id, fileName);
3180
+ }
3181
+ getAssetModules() {
3182
+ return Object.freeze(Object.fromEntries(this.assetModules));
3183
+ }
3184
+ setBuildMetadata(metadata) {
3185
+ const { entries: currentEntries, ...currentMetadata } = this.buildMetadata;
3186
+ const { entries, ...nextMetadata } = metadata;
3187
+ this.buildMetadata = {
3188
+ ...currentMetadata,
3189
+ ...nextMetadata,
3190
+ ...currentEntries || entries ? { entries: { ...currentEntries, ...entries } } : {}
3191
+ };
3192
+ }
3193
+ getBuildMetadata() {
3194
+ const { entries, ...metadata } = this.buildMetadata;
3195
+ return {
3196
+ ...metadata,
3197
+ ...entries ? { entries: { ...entries } } : {}
3198
+ };
3199
+ }
2748
3200
  async close() {
2749
3201
  try {
2750
3202
  await this.driver?.close?.(this.getDriverContext());
@@ -2841,7 +3293,7 @@ async function tryNativeReporterPlugin(config, logger) {
2841
3293
  logInfo: (msg) => logger.info(msg)
2842
3294
  });
2843
3295
  } catch (err) {
2844
- debug3?.(`native viteReporterPlugin unavailable, falling back to JS table: ${err}`);
3296
+ debug4?.(`native viteReporterPlugin unavailable, falling back to JS table: ${err}`);
2845
3297
  return null;
2846
3298
  }
2847
3299
  }
@@ -2896,12 +3348,12 @@ function warnLargeChunks(output, config, logger) {
2896
3348
  )
2897
3349
  );
2898
3350
  }
2899
- var debug3, numberFormatter;
3351
+ var debug4, numberFormatter;
2900
3352
  var init_reporter = __esm({
2901
3353
  "src/build/reporter.ts"() {
2902
3354
  "use strict";
2903
3355
  init_debug();
2904
- debug3 = createDebugger("nasti:reporter");
3356
+ debug4 = createDebugger("nasti:reporter");
2905
3357
  numberFormatter = new Intl.NumberFormat("en", {
2906
3358
  maximumFractionDigits: 2,
2907
3359
  minimumFractionDigits: 2
@@ -2909,6 +3361,155 @@ var init_reporter = __esm({
2909
3361
  }
2910
3362
  });
2911
3363
 
3364
+ // src/core/build-app-context.ts
3365
+ import fs6 from "fs";
3366
+ import path9 from "path";
3367
+ function createBuildAppContext(config, results) {
3368
+ const output = [];
3369
+ const emitted = /* @__PURE__ */ new Set();
3370
+ const outDir = path9.resolve(config.root, config.build.outDir);
3371
+ let environmentArtifacts;
3372
+ return {
3373
+ config,
3374
+ results,
3375
+ get output() {
3376
+ return Object.freeze([...output]);
3377
+ },
3378
+ getResult(environmentName) {
3379
+ return results[environmentName];
3380
+ },
3381
+ getArtifact(environmentName, fileName) {
3382
+ const normalized = normalizeEnvironmentFileName(fileName);
3383
+ return results[environmentName]?.output.find(
3384
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalized
3385
+ );
3386
+ },
3387
+ getEntry(environmentName, entryName) {
3388
+ const result = results[environmentName];
3389
+ const fileName = result?.entries?.[entryName];
3390
+ if (!fileName) return void 0;
3391
+ return result.output.find(
3392
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalizeEnvironmentFileName(fileName)
3393
+ );
3394
+ },
3395
+ getManifest(environmentName) {
3396
+ return results[environmentName]?.manifest;
3397
+ },
3398
+ getChunk(environmentName, fileName) {
3399
+ const normalized = normalizeEnvironmentFileName(fileName);
3400
+ return results[environmentName]?.chunks?.[normalized];
3401
+ },
3402
+ getCss(environmentName) {
3403
+ return results[environmentName]?.css;
3404
+ },
3405
+ getSourceMap(environmentName, fileName) {
3406
+ const normalized = normalizeEnvironmentFileName(fileName);
3407
+ return results[environmentName]?.sourceMaps?.[normalized];
3408
+ },
3409
+ resolvePublicPath(environmentName, fileName) {
3410
+ const result = results[environmentName];
3411
+ if (!result) return void 0;
3412
+ const normalized = normalizeEnvironmentFileName(fileName);
3413
+ const base = result.publicPath ?? config.base;
3414
+ return joinPublicPath(base, normalized);
3415
+ },
3416
+ emitFile(file) {
3417
+ const fileName = normalizeAppFileName(file.fileName);
3418
+ const collisionKey = artifactCollisionKey(fileName);
3419
+ if (emitted.has(collisionKey)) {
3420
+ throw new Error(`[nasti] app artifact already emitted: ${fileName}`);
3421
+ }
3422
+ environmentArtifacts ??= collectEnvironmentArtifacts(config, results, outDir);
3423
+ if (environmentArtifacts.has(collisionKey)) {
3424
+ throw new Error(`[nasti] app artifact conflicts with environment output: ${fileName}`);
3425
+ }
3426
+ const target = path9.resolve(outDir, ...fileName.split("/"));
3427
+ const relative = path9.relative(outDir, target);
3428
+ if (relative.startsWith("..") || path9.isAbsolute(relative)) {
3429
+ throw new Error(`[nasti] app artifact must stay inside build.outDir: ${file.fileName}`);
3430
+ }
3431
+ assertNoSymlinkComponents(outDir, fileName);
3432
+ fs6.mkdirSync(path9.dirname(target), { recursive: true });
3433
+ fs6.writeFileSync(target, file.source);
3434
+ const artifact = {
3435
+ ...file,
3436
+ fileName,
3437
+ type: "asset"
3438
+ };
3439
+ emitted.add(collisionKey);
3440
+ output.push(artifact);
3441
+ return fileName;
3442
+ }
3443
+ };
3444
+ }
3445
+ function joinPublicPath(base, fileName) {
3446
+ return `${base.endsWith("/") ? base : `${base}/`}${fileName.replace(/^\//, "")}`;
3447
+ }
3448
+ function normalizeEnvironmentFileName(fileName) {
3449
+ return path9.posix.normalize(fileName.replace(/\\/g, "/").replace(/^\.\//, ""));
3450
+ }
3451
+ function isInvalidEnvironmentFileName(fileName) {
3452
+ return !fileName || fileName === "." || fileName === ".." || fileName.startsWith("../") || path9.posix.isAbsolute(fileName) || /^[A-Za-z]:\//.test(fileName);
3453
+ }
3454
+ function normalizeAppFileName(fileName) {
3455
+ const normalized = normalizeEnvironmentFileName(fileName);
3456
+ if (isInvalidEnvironmentFileName(normalized)) {
3457
+ throw new Error(`[nasti] invalid app artifact fileName: ${fileName}`);
3458
+ }
3459
+ return normalized;
3460
+ }
3461
+ function artifactCollisionKey(fileName) {
3462
+ return normalizeEnvironmentFileName(fileName).toLowerCase();
3463
+ }
3464
+ function collectEnvironmentArtifacts(config, results, appOutDir) {
3465
+ const occupied = /* @__PURE__ */ new Set();
3466
+ for (const [environmentName, result] of Object.entries(results)) {
3467
+ const environment = config.environments[environmentName];
3468
+ if (!environment) continue;
3469
+ const environmentOutDir = path9.resolve(config.root, environment.build.outDir);
3470
+ for (const artifact of result.output) {
3471
+ const artifactPath = path9.resolve(
3472
+ environmentOutDir,
3473
+ ...normalizeEnvironmentFileName(artifact.fileName).split("/")
3474
+ );
3475
+ const relative = path9.relative(appOutDir, artifactPath);
3476
+ if (!relative.startsWith("..") && !path9.isAbsolute(relative)) {
3477
+ occupied.add(artifactCollisionKey(relative));
3478
+ }
3479
+ }
3480
+ }
3481
+ return occupied;
3482
+ }
3483
+ function assertNoSymlinkComponents(outDir, fileName) {
3484
+ let current = outDir;
3485
+ for (const segment of fileName.split("/")) {
3486
+ current = path9.join(current, segment);
3487
+ let stats;
3488
+ try {
3489
+ stats = fs6.lstatSync(current);
3490
+ } catch (error) {
3491
+ if (error.code === "ENOENT") continue;
3492
+ throw error;
3493
+ }
3494
+ if (stats.isSymbolicLink()) {
3495
+ throw new Error(`[nasti] app artifact path cannot traverse a symlink: ${fileName}`);
3496
+ }
3497
+ }
3498
+ }
3499
+ function inferEnvironmentEntries(output) {
3500
+ const entries = {};
3501
+ for (const artifact of output) {
3502
+ if (artifact.type !== "chunk" || !artifact.isEntry || !artifact.name) continue;
3503
+ entries[artifact.name] = normalizeEnvironmentFileName(artifact.fileName);
3504
+ }
3505
+ return Object.keys(entries).length > 0 ? entries : void 0;
3506
+ }
3507
+ var init_build_app_context = __esm({
3508
+ "src/core/build-app-context.ts"() {
3509
+ "use strict";
3510
+ }
3511
+ });
3512
+
2912
3513
  // src/build/index.ts
2913
3514
  var build_exports = {};
2914
3515
  __export(build_exports, {
@@ -2918,8 +3519,8 @@ __export(build_exports, {
2918
3519
  resolveClientEntries: () => resolveClientEntries,
2919
3520
  toRolldownPlugins: () => toRolldownPlugins
2920
3521
  });
2921
- import path9 from "path";
2922
- import fs6 from "fs";
3522
+ import path10 from "path";
3523
+ import fs7 from "fs";
2923
3524
  import { builtinModules } from "module";
2924
3525
  import { rolldown } from "rolldown";
2925
3526
  import pc4 from "picocolors";
@@ -2927,9 +3528,14 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
2927
3528
  const config = environment.config;
2928
3529
  const envOptions = environment.options;
2929
3530
  const isServer = environment.consumer === "server";
2930
- const outDir = path9.resolve(config.root, envOptions.build.outDir);
3531
+ const outDir = path10.resolve(config.root, envOptions.build.outDir);
2931
3532
  const assetsDir = envOptions.build.assetsDir;
2932
- const { output: userOutput, transform: userTransform, ...restInputOptions } = envOptions.build.rolldownOptions;
3533
+ const {
3534
+ output: userOutput,
3535
+ transform: userTransform,
3536
+ resolve: userResolve,
3537
+ ...restInputOptions
3538
+ } = envOptions.build.rolldownOptions;
2933
3539
  const vueDefine = config.framework === "vue" ? {
2934
3540
  __VUE_OPTIONS_API__: "true",
2935
3541
  __VUE_PROD_DEVTOOLS__: "false",
@@ -2941,27 +3547,34 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
2941
3547
  const inputOptions = {
2942
3548
  ...restInputOptions,
2943
3549
  input: entryPoints,
2944
- transform: { ...userTransform, define: mergedDefine },
3550
+ transform: {
3551
+ ...userTransform,
3552
+ target: userTransform?.target ?? envOptions.build.target,
3553
+ define: mergedDefine
3554
+ },
2945
3555
  plugins: rolldownPlugins,
3556
+ // client/server consumer 都必须使用当前环境的 conditions/mainFields;Lynx
3557
+ // BG/MT 通常同为 client consumer,但仍可能声明不同的运行时条件。
3558
+ resolve: {
3559
+ ...userResolve ?? {},
3560
+ // Environment API 是条件解析的唯一高层入口,优先于继承来的底层选项。
3561
+ conditionNames: envOptions.resolve.conditions,
3562
+ mainFields: envOptions.resolve.mainFields
3563
+ },
2946
3564
  ...isServer ? {
2947
3565
  platform: restInputOptions.platform ?? "node",
2948
- resolve: {
2949
- conditionNames: envOptions.resolve.conditions,
2950
- mainFields: envOptions.resolve.mainFields,
2951
- ...restInputOptions.resolve
2952
- },
2953
3566
  // server 产物:node 内建恒外部化;bare specifier 默认外部化
2954
3567
  //(同 Vite ssr.external 默认 —— 依赖由 node_modules 运行时解析),
2955
3568
  // 相对/绝对/虚拟模块照常打包。需要内联依赖时经 rolldownOptions.external 覆盖。
2956
3569
  external: restInputOptions.external ?? ((id) => {
2957
3570
  if (NODE_BUILTINS.has(id)) return true;
2958
- return !id.startsWith(".") && !path9.isAbsolute(id) && !id.startsWith("\0");
3571
+ return !id.startsWith(".") && !path10.isAbsolute(id) && !id.startsWith("\0");
2959
3572
  })
2960
3573
  } : {}
2961
3574
  };
2962
3575
  const outputOptions = isServer ? {
2963
3576
  format: "esm",
2964
- sourcemap: !!envOptions.build.sourcemap,
3577
+ sourcemap: envOptions.build.sourcemap,
2965
3578
  minify: !!envOptions.build.minify,
2966
3579
  entryFileNames: "[name].js",
2967
3580
  chunkFileNames: "chunks/[name]-[hash].js",
@@ -2970,7 +3583,7 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
2970
3583
  dir: outDir
2971
3584
  } : {
2972
3585
  format: "esm",
2973
- sourcemap: !!envOptions.build.sourcemap,
3586
+ sourcemap: envOptions.build.sourcemap,
2974
3587
  minify: !!envOptions.build.minify,
2975
3588
  entryFileNames: `${assetsDir}/[name].[hash].js`,
2976
3589
  chunkFileNames: `${assetsDir}/[name].[hash].js`,
@@ -2982,27 +3595,177 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
2982
3595
  };
2983
3596
  return { inputOptions, outputOptions, outDir };
2984
3597
  }
2985
- function toRolldownPlugins(plugins) {
3598
+ function toRolldownPlugins(plugins, environment) {
3599
+ const wrap = (hook) => {
3600
+ if (!hook) return hook;
3601
+ return function(...args) {
3602
+ return hook.apply(attachEnvironment(this, environment), args);
3603
+ };
3604
+ };
2986
3605
  return plugins.map((p) => ({
2987
3606
  name: p.name,
2988
- resolveId: p.resolveId,
2989
- load: p.load,
2990
- transform: p.transform,
2991
- buildStart: p.buildStart,
2992
- buildEnd: p.buildEnd,
3607
+ resolveId: wrap(p.resolveId),
3608
+ load: wrap(p.load),
3609
+ transform: wrap(p.transform),
3610
+ buildStart: wrap(p.buildStart),
3611
+ buildEnd: wrap(p.buildEnd),
2993
3612
  // closeBundle 在 bundle.close() 时触发 —— PWA manifest/SW 等终态产物依赖
2994
- closeBundle: p.closeBundle,
2995
- renderChunk: p.renderChunk,
2996
- augmentChunkHash: p.augmentChunkHash,
2997
- generateBundle: p.generateBundle
3613
+ closeBundle: wrap(p.closeBundle),
3614
+ renderChunk: wrap(p.renderChunk),
3615
+ augmentChunkHash: wrap(p.augmentChunkHash),
3616
+ generateBundle: wrap(p.generateBundle)
2998
3617
  }));
2999
3618
  }
3619
+ function attachEnvironment(context, environment) {
3620
+ if (context?.environment === environment) return context;
3621
+ try {
3622
+ Object.defineProperty(context, "environment", {
3623
+ configurable: true,
3624
+ enumerable: false,
3625
+ writable: false,
3626
+ value: environment
3627
+ });
3628
+ return context;
3629
+ } catch {
3630
+ return new Proxy(context, {
3631
+ get(target, property) {
3632
+ if (property === "environment") return environment;
3633
+ const value = Reflect.get(target, property, target);
3634
+ return typeof value === "function" ? value.bind(target) : value;
3635
+ },
3636
+ set(target, property, value) {
3637
+ return Reflect.set(target, property, value, target);
3638
+ }
3639
+ });
3640
+ }
3641
+ }
3642
+ function finalizeEnvironmentResult(environment, result) {
3643
+ const metadata = environment.getBuildMetadata();
3644
+ const inferredEntries = inferEnvironmentEntries(result.output);
3645
+ const entries = {
3646
+ ...inferredEntries,
3647
+ ...metadata.entries,
3648
+ ...result.entries
3649
+ };
3650
+ const normalizedEntries = Object.fromEntries(
3651
+ Object.entries(entries).map(([name, fileName]) => {
3652
+ const normalized = normalizeEnvironmentFileName(fileName);
3653
+ if (isInvalidEnvironmentFileName(normalized)) {
3654
+ throw new Error(
3655
+ `[nasti] environment "${environment.name}" returned invalid entry "${name}": ${fileName}`
3656
+ );
3657
+ }
3658
+ return [name, normalized];
3659
+ })
3660
+ );
3661
+ const inferredMetadata = inferOutputMetadata(environment, result.output);
3662
+ return {
3663
+ publicPath: environment.config.base,
3664
+ ...inferredMetadata,
3665
+ ...metadata,
3666
+ ...result,
3667
+ output: result.output,
3668
+ chunks: {
3669
+ ...inferredMetadata.chunks,
3670
+ ...metadata.chunks,
3671
+ ...result.chunks
3672
+ },
3673
+ assets: {
3674
+ ...inferredMetadata.assets,
3675
+ ...metadata.assets,
3676
+ ...result.assets
3677
+ },
3678
+ sourceMaps: {
3679
+ ...inferredMetadata.sourceMaps,
3680
+ ...metadata.sourceMaps,
3681
+ ...result.sourceMaps
3682
+ },
3683
+ ...Object.keys(normalizedEntries).length > 0 ? { entries: normalizedEntries } : {}
3684
+ };
3685
+ }
3686
+ function inferOutputMetadata(environment, output) {
3687
+ const chunks = {};
3688
+ const assets = {};
3689
+ const sourceMaps = {};
3690
+ const cssChunks = environment.getBuildMetadata().css?.chunks ?? {};
3691
+ const assetModules = environment.getAssetModules();
3692
+ const publicPath = environment.config.base;
3693
+ for (const artifact of output) {
3694
+ const fileName = normalizeEnvironmentFileName(artifact.fileName);
3695
+ if (artifact.map != null) sourceMaps[fileName] = artifact.map;
3696
+ if (artifact.type === "chunk") {
3697
+ const moduleIds = [...artifact.moduleIds ?? []];
3698
+ chunks[fileName] = {
3699
+ fileName,
3700
+ name: artifact.name ?? fileName,
3701
+ isEntry: !!artifact.isEntry,
3702
+ isDynamicEntry: !!artifact.isDynamicEntry,
3703
+ imports: [...artifact.imports ?? []],
3704
+ dynamicImports: [...artifact.dynamicImports ?? []],
3705
+ moduleIds,
3706
+ css: [...cssChunks[fileName]?.cssFileNames ?? []],
3707
+ assets: [
3708
+ ...new Set(
3709
+ moduleIds.map((id) => assetModules[id]).filter((asset) => !!asset)
3710
+ )
3711
+ ]
3712
+ };
3713
+ } else if (artifact.type === "asset") {
3714
+ assets[fileName] = {
3715
+ fileName,
3716
+ names: [...artifact.names ?? (artifact.name ? [artifact.name] : [])],
3717
+ publicPath: joinPublicPath(publicPath, fileName)
3718
+ };
3719
+ }
3720
+ }
3721
+ return { chunks, assets, sourceMaps };
3722
+ }
3723
+ function prepareBuildOutputDirectories(config, buildableNames) {
3724
+ const directories = /* @__PURE__ */ new Set();
3725
+ const protectedPaths = /* @__PURE__ */ new Set();
3726
+ const clientIsBuilt = buildableNames.includes("client");
3727
+ if (!clientIsBuilt && config.build.emptyOutDir) {
3728
+ directories.add(path10.resolve(config.root, config.build.outDir));
3729
+ }
3730
+ for (const name of buildableNames) {
3731
+ const environment = config.environments[name];
3732
+ const outDir = path10.resolve(config.root, environment.build.outDir);
3733
+ if (!environment.build.emptyOutDir) {
3734
+ protectedPaths.add(outDir);
3735
+ continue;
3736
+ }
3737
+ if (!environment.driver) directories.add(outDir);
3738
+ }
3739
+ const containsPath = (parent, child) => {
3740
+ const relative = path10.relative(parent, child);
3741
+ return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
3742
+ };
3743
+ const roots = [...directories].filter(
3744
+ (directory) => ![...protectedPaths].some((protectedPath) => containsPath(directory, protectedPath))
3745
+ ).sort((a, b) => a.length - b.length).filter(
3746
+ (directory, index2, all) => !all.slice(0, index2).some((parent) => containsPath(parent, directory))
3747
+ );
3748
+ for (const directory of roots) {
3749
+ if (fs7.existsSync(directory)) fs7.rmSync(directory, { recursive: true, force: true });
3750
+ }
3751
+ }
3752
+ function assertDriverBuildResult(environment, result) {
3753
+ const output = result != null && typeof result === "object" ? result.output : void 0;
3754
+ const hasValidOutput = Array.isArray(output) && output.every(
3755
+ (artifact) => artifact != null && typeof artifact === "object" && typeof artifact.fileName === "string" && typeof artifact.type === "string"
3756
+ );
3757
+ if (!hasValidOutput) {
3758
+ throw new Error(
3759
+ `[nasti] environment "${environment.name}" driver "${environment.driver?.name}" returned an invalid build result; expected { output: EnvironmentBuildOutput[] }`
3760
+ );
3761
+ }
3762
+ }
3000
3763
  function resolveClientEntries(config, html) {
3001
3764
  const configuredEntries = config.environments.client?.entry ?? [];
3002
3765
  if (configuredEntries.length > 0) return configuredEntries;
3003
3766
  const entryPoints = [];
3004
3767
  const htmlFile = config.environments.client?.html;
3005
- const htmlDir = htmlFile ? path9.dirname(htmlFile) : config.root;
3768
+ const htmlDir = htmlFile ? path10.dirname(htmlFile) : config.root;
3006
3769
  if (html) {
3007
3770
  const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
3008
3771
  for (const match of scriptMatches) {
@@ -3010,7 +3773,7 @@ function resolveClientEntries(config, html) {
3010
3773
  if (src && !src.startsWith("http")) {
3011
3774
  const cleanSrc = src.split(/[?#]/, 1)[0];
3012
3775
  entryPoints.push(
3013
- cleanSrc.startsWith("/") ? path9.resolve(config.root, cleanSrc.replace(/^\//, "")) : path9.resolve(htmlDir, cleanSrc)
3776
+ cleanSrc.startsWith("/") ? path10.resolve(config.root, cleanSrc.replace(/^\//, "")) : path10.resolve(htmlDir, cleanSrc)
3014
3777
  );
3015
3778
  }
3016
3779
  }
@@ -3018,8 +3781,8 @@ function resolveClientEntries(config, html) {
3018
3781
  if (entryPoints.length === 0) {
3019
3782
  const fallbackEntries = ["src/main.ts", "src/main.tsx", "src/main.js", "src/index.ts", "src/index.tsx", "src/index.js"];
3020
3783
  for (const entry of fallbackEntries) {
3021
- const fullPath = path9.resolve(config.root, entry);
3022
- if (fs6.existsSync(fullPath)) {
3784
+ const fullPath = path10.resolve(config.root, entry);
3785
+ if (fs7.existsSync(fullPath)) {
3023
3786
  entryPoints.push(fullPath);
3024
3787
  break;
3025
3788
  }
@@ -3034,6 +3797,7 @@ function createOxcTransformPlugin(config, environment) {
3034
3797
  if (!shouldTransform(id)) return null;
3035
3798
  const result = transformCode(id, code, {
3036
3799
  sourcemap: !!environment.options.build.sourcemap,
3800
+ target: environment.options.build.target,
3037
3801
  jsxRuntime: "automatic",
3038
3802
  jsxImportSource: config.framework === "vue" ? "vue" : "react"
3039
3803
  });
@@ -3047,16 +3811,20 @@ async function build(inlineConfig = {}) {
3047
3811
  const startTime = performance.now();
3048
3812
  logger.info(
3049
3813
  pc4.cyan(`
3050
- nasti v${"2.3.1"} `) + pc4.green(`building for ${config.mode}...`)
3051
- );
3052
- debug4?.(`root: ${config.root}`);
3053
- const buildableNames = Object.keys(config.environments).filter(
3054
- (name) => name === "client" || config.environments[name].entry.length > 0 || !!config.environments[name].driver
3814
+ nasti v${"2.4.1"} `) + pc4.green(`building for ${config.mode}...`)
3055
3815
  );
3816
+ debug5?.(`root: ${config.root}`);
3817
+ const buildableNames = Object.keys(config.environments).filter((name) => {
3818
+ const environment = config.environments[name];
3819
+ if (!environment.buildEnabled) return false;
3820
+ return name === "client" || environment.entry.length > 0 || !!environment.driver;
3821
+ });
3056
3822
  buildableNames.sort((a, b) => a === "client" ? -1 : b === "client" ? 1 : 0);
3823
+ prepareBuildOutputDirectories(config, buildableNames);
3057
3824
  const environments = {};
3058
3825
  const environmentResults = {};
3059
3826
  const initializedEnvironments = [];
3827
+ const buildAppContext = createBuildAppContext(config, environmentResults);
3060
3828
  let clientOutput = [];
3061
3829
  let buildFailed = false;
3062
3830
  try {
@@ -3067,12 +3835,12 @@ nasti v${"2.3.1"} `) + pc4.green(`building for ${config.mode}...`)
3067
3835
  environmentResults[name] = built.result;
3068
3836
  if (name === "client") clientOutput = built.result.output;
3069
3837
  if (buildableNames.length > 1) {
3070
- debug4?.(`environment "${name}" built (${built.result.output.length} files)`);
3838
+ debug5?.(`environment "${name}" built (${built.result.output.length} files)`);
3071
3839
  }
3072
3840
  }
3073
3841
  const pluginApi = getPluginApi(config);
3074
3842
  for (const plugin of config.plugins) {
3075
- await plugin.afterBuildApp?.(environmentResults, pluginApi);
3843
+ await plugin.afterBuildApp?.(environmentResults, pluginApi, buildAppContext);
3076
3844
  }
3077
3845
  } catch (error) {
3078
3846
  buildFailed = true;
@@ -3099,22 +3867,31 @@ nasti v${"2.3.1"} `) + pc4.green(`building for ${config.mode}...`)
3099
3867
  }
3100
3868
  }
3101
3869
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
3102
- const totalSize = Object.values(environments).flat().reduce((sum, chunk) => {
3870
+ const allOutput = [...Object.values(environments).flat(), ...buildAppContext.output];
3871
+ const totalSize = allOutput.reduce((sum, chunk) => {
3103
3872
  const content = chunk.type === "chunk" ? chunk.code : chunk.source;
3104
3873
  if (content == null) return sum;
3105
3874
  return sum + (typeof content === "string" ? Buffer.byteLength(content) : content.byteLength);
3106
3875
  }, 0);
3107
- const fileCount = Object.values(environments).flat().length;
3876
+ const fileCount = allOutput.length;
3108
3877
  const envSuffix = buildableNames.length > 1 ? ` (${buildableNames.join(" + ")})` : "";
3109
3878
  logger.info(pc4.green(`\u2713 built in ${elapsed}s`) + pc4.dim(envSuffix));
3110
3879
  logger.info(pc4.dim(` ${fileCount} files, ${displaySize(totalSize)} total \u2192 ${config.build.outDir}/`));
3111
- return { output: clientOutput, environments, environmentResults };
3880
+ return {
3881
+ output: clientOutput,
3882
+ environments,
3883
+ environmentResults,
3884
+ appOutput: [...buildAppContext.output]
3885
+ };
3112
3886
  }
3113
3887
  async function buildClientEnvironment(config) {
3114
3888
  const logger = config.logger;
3115
- const outDir = path9.resolve(config.root, config.build.outDir);
3889
+ const outDir = path10.resolve(config.root, config.build.outDir);
3116
3890
  const cssEngine = createCssEngine();
3117
- const pluginList = resolvePluginList(config, config.plugins, { cssEngine });
3891
+ const pluginList = resolvePluginList(config, config.plugins, {
3892
+ cssEngine,
3893
+ environmentName: "client"
3894
+ });
3118
3895
  const clientEnv = new NastiEnvironment("client", config, {
3119
3896
  mode: "build",
3120
3897
  plugins: pluginList,
@@ -3129,13 +3906,11 @@ async function buildClientEnvironment(config) {
3129
3906
  );
3130
3907
  }
3131
3908
  const result = await clientEnv.driver.build(clientEnv.getDriverContext());
3132
- return { environment: clientEnv, result };
3909
+ assertDriverBuildResult(clientEnv, result);
3910
+ return { environment: clientEnv, result: finalizeEnvironmentResult(clientEnv, result) };
3133
3911
  }
3134
- if (config.build.emptyOutDir && fs6.existsSync(outDir)) {
3135
- fs6.rmSync(outDir, { recursive: true, force: true });
3136
- }
3137
- fs6.mkdirSync(outDir, { recursive: true });
3138
- const htmlFile = config.environments.client.html ?? path9.resolve(config.root, "index.html");
3912
+ fs7.mkdirSync(outDir, { recursive: true });
3913
+ const htmlFile = config.environments.client.html ?? path10.resolve(config.root, "index.html");
3139
3914
  const html = await readHtmlFile(config.root, htmlFile);
3140
3915
  const entryPoints = resolveClientEntries(config, html);
3141
3916
  if (entryPoints.length === 0) {
@@ -3145,7 +3920,7 @@ async function buildClientEnvironment(config) {
3145
3920
  const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
3146
3921
  const rolldownPlugins = [
3147
3922
  createOxcTransformPlugin(config, clientEnv),
3148
- ...toRolldownPlugins(allPlugins),
3923
+ ...toRolldownPlugins(allPlugins, clientEnv),
3149
3924
  ...nativeReporter ? [nativeReporter] : []
3150
3925
  ];
3151
3926
  const { inputOptions, outputOptions } = getRolldownOptions(
@@ -3156,6 +3931,7 @@ async function buildClientEnvironment(config) {
3156
3931
  const bundle2 = await rolldown(inputOptions);
3157
3932
  const { output } = await bundle2.write(outputOptions);
3158
3933
  await bundle2.close();
3934
+ clientEnv.setBuildMetadata({ css: getCssMetadata(cssEngine) });
3159
3935
  if (html) {
3160
3936
  let processedHtml = html;
3161
3937
  const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
@@ -3169,7 +3945,9 @@ async function buildClientEnvironment(config) {
3169
3945
  processedHtml = processHtml(processedHtml, result);
3170
3946
  }
3171
3947
  }
3172
- processedHtml = injectCssLinks(processedHtml, cssEngine, config);
3948
+ if (clientEnv.options.build.css.inject !== false) {
3949
+ processedHtml = injectCssLinks(processedHtml, cssEngine, config);
3950
+ }
3173
3951
  for (const chunk of output) {
3174
3952
  if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
3175
3953
  processedHtml = replaceEntryScript(
@@ -3182,13 +3960,16 @@ async function buildClientEnvironment(config) {
3182
3960
  );
3183
3961
  }
3184
3962
  }
3185
- fs6.writeFileSync(path9.resolve(outDir, "index.html"), processedHtml);
3963
+ fs7.writeFileSync(path10.resolve(outDir, "index.html"), processedHtml);
3186
3964
  }
3187
3965
  if (!nativeReporter && config.logLevel !== "silent") {
3188
3966
  reportBuildOutput(output, config, logger);
3189
3967
  }
3190
3968
  warnLargeChunks(output, config, logger);
3191
- return { environment: clientEnv, result: { output } };
3969
+ return {
3970
+ environment: clientEnv,
3971
+ result: finalizeEnvironmentResult(clientEnv, { output })
3972
+ };
3192
3973
  } catch (error) {
3193
3974
  try {
3194
3975
  await clientEnv.close();
@@ -3204,7 +3985,12 @@ async function buildClientEnvironment(config) {
3204
3985
  async function buildServerEnvironment(config, name) {
3205
3986
  const envOptions = config.environments[name];
3206
3987
  const logger = config.logger;
3207
- const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
3988
+ const cssEngine = envOptions.consumer === "client" ? createCssEngine() : void 0;
3989
+ const pluginList = resolvePluginList(config, config.plugins, {
3990
+ consumer: envOptions.consumer,
3991
+ environmentName: name,
3992
+ cssEngine
3993
+ });
3208
3994
  const environment = new NastiEnvironment(name, config, {
3209
3995
  mode: "build",
3210
3996
  plugins: pluginList,
@@ -3220,38 +4006,40 @@ async function buildServerEnvironment(config, name) {
3220
4006
  }
3221
4007
  try {
3222
4008
  const result = await environment.driver.build(environment.getDriverContext());
3223
- return { environment, result };
4009
+ assertDriverBuildResult(environment, result);
4010
+ return { environment, result: finalizeEnvironmentResult(environment, result) };
3224
4011
  } catch (error) {
3225
4012
  await environment.close();
3226
4013
  throw error;
3227
4014
  }
3228
4015
  }
3229
4016
  for (const entry of envOptions.entry) {
3230
- if (!fs6.existsSync(entry)) {
4017
+ if (!fs7.existsSync(entry)) {
3231
4018
  await environment.close();
3232
4019
  throw new Error(`[nasti] environment "${name}" entry not found: ${entry}`);
3233
4020
  }
3234
4021
  }
3235
4022
  const rolldownPlugins = [
3236
4023
  createOxcTransformPlugin(config, environment),
3237
- ...toRolldownPlugins(environment.plugins)
4024
+ ...toRolldownPlugins(environment.plugins, environment)
3238
4025
  ];
3239
4026
  const { inputOptions, outputOptions, outDir } = getRolldownOptions(
3240
4027
  environment,
3241
4028
  envOptions.entry,
3242
4029
  rolldownPlugins
3243
4030
  );
3244
- if (envOptions.build.emptyOutDir && fs6.existsSync(outDir)) {
3245
- fs6.rmSync(outDir, { recursive: true, force: true });
3246
- }
3247
- fs6.mkdirSync(outDir, { recursive: true });
4031
+ fs7.mkdirSync(outDir, { recursive: true });
3248
4032
  const bundle2 = await rolldown(inputOptions);
3249
4033
  const { output } = await bundle2.write(outputOptions);
3250
4034
  await bundle2.close();
4035
+ if (cssEngine) environment.setBuildMetadata({ css: getCssMetadata(cssEngine) });
3251
4036
  logger.info(
3252
- pc4.dim(` [${name}] `) + output.map((o) => path9.join(envOptions.build.outDir, o.fileName)).join(pc4.dim(", "))
4037
+ pc4.dim(` [${name}] `) + output.map((o) => path10.join(envOptions.build.outDir, o.fileName)).join(pc4.dim(", "))
3253
4038
  );
3254
- return { environment, result: { output } };
4039
+ return {
4040
+ environment,
4041
+ result: finalizeEnvironmentResult(environment, { output })
4042
+ };
3255
4043
  }
3256
4044
  function injectCssLinks(html, cssEngine, config) {
3257
4045
  const cssLinkTags = [];
@@ -3278,9 +4066,9 @@ function escapeRegExp(string) {
3278
4066
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3279
4067
  }
3280
4068
  function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, urlPrefix) {
3281
- const rootRelative = path9.relative(config.root, facadeModuleId).split(path9.sep).join("/");
3282
- const resolvedHtmlFile = path9.resolve(config.root, htmlFile);
3283
- const htmlRelative = path9.relative(path9.dirname(resolvedHtmlFile), facadeModuleId).split(path9.sep).join("/");
4069
+ const rootRelative = path10.relative(config.root, facadeModuleId).split(path10.sep).join("/");
4070
+ const resolvedHtmlFile = path10.resolve(config.root, htmlFile);
4071
+ const htmlRelative = path10.relative(path10.dirname(resolvedHtmlFile), facadeModuleId).split(path10.sep).join("/");
3284
4072
  const candidates = /* @__PURE__ */ new Set([
3285
4073
  rootRelative,
3286
4074
  `/${rootRelative}`,
@@ -3296,7 +4084,7 @@ function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, ur
3296
4084
  }
3297
4085
  return processed;
3298
4086
  }
3299
- var debug4, NODE_BUILTINS;
4087
+ var debug5, NODE_BUILTINS;
3300
4088
  var init_build = __esm({
3301
4089
  "src/build/index.ts"() {
3302
4090
  "use strict";
@@ -3310,7 +4098,8 @@ var init_build = __esm({
3310
4098
  init_reporter();
3311
4099
  init_debug();
3312
4100
  init_plugin_api();
3313
- debug4 = createDebugger("nasti:build");
4101
+ init_build_app_context();
4102
+ debug5 = createDebugger("nasti:build");
3314
4103
  NODE_BUILTINS = /* @__PURE__ */ new Set([...builtinModules, ...builtinModules.map((m) => `node:${m}`)]);
3315
4104
  }
3316
4105
  });
@@ -3360,27 +4149,22 @@ var init_ws = __esm({
3360
4149
  });
3361
4150
 
3362
4151
  // src/server/middleware.ts
3363
- var middleware_exports = {};
3364
- __export(middleware_exports, {
3365
- REACT_REFRESH_GLOBAL_PREAMBLE: () => REACT_REFRESH_GLOBAL_PREAMBLE,
3366
- getReactRefreshRuntimeEsm: () => getReactRefreshRuntimeEsm,
3367
- transformMiddleware: () => transformMiddleware,
3368
- transformRequest: () => transformRequest
3369
- });
3370
- import path11 from "path";
3371
- import fs8 from "fs";
4152
+ import path12 from "path";
4153
+ import fs9 from "fs";
3372
4154
  import { createRequire as createRequire3 } from "module";
3373
4155
  import { fileURLToPath, pathToFileURL as pathToFileURL3 } from "url";
3374
4156
  import pc6 from "picocolors";
3375
- function getReactRefreshRuntimeEsm() {
3376
- if (__refreshRuntimeCache) return __refreshRuntimeCache;
4157
+ function getReactRefreshRuntimeEsm(includeBoundaryHelpers = false) {
4158
+ if (__refreshRuntimeCache) {
4159
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
4160
+ }
3377
4161
  let cjsPath;
3378
4162
  try {
3379
4163
  const pkgPath = __require2.resolve("react-refresh/package.json");
3380
- cjsPath = path11.join(path11.dirname(pkgPath), "cjs", "react-refresh-runtime.development.js");
4164
+ cjsPath = path12.join(path12.dirname(pkgPath), "cjs", "react-refresh-runtime.development.js");
3381
4165
  } catch (err) {
3382
- cjsPath = path11.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
3383
- if (!fs8.existsSync(cjsPath)) {
4166
+ cjsPath = path12.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
4167
+ if (!fs9.existsSync(cjsPath)) {
3384
4168
  const origMsg = err instanceof Error ? err.message : String(err);
3385
4169
  throw new Error(
3386
4170
  `[nasti] Missing dependency "react-refresh". Install it with: npm install react-refresh
@@ -3388,7 +4172,7 @@ Original resolve error: ${origMsg}`
3388
4172
  );
3389
4173
  }
3390
4174
  }
3391
- const cjsSource = fs8.readFileSync(cjsPath, "utf-8");
4175
+ const cjsSource = fs9.readFileSync(cjsPath, "utf-8");
3392
4176
  __refreshRuntimeCache = `// Wrapped react-refresh runtime -> ESM
3393
4177
  const exports = {};
3394
4178
  const module = { exports };
@@ -3408,7 +4192,7 @@ export const findAffectedHostInstances = __rt.findAffectedHostInstances;
3408
4192
  export const collectCustomHooksForSignature = __rt.collectCustomHooksForSignature;
3409
4193
  export default __rt;
3410
4194
  `;
3411
- return __refreshRuntimeCache;
4195
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
3412
4196
  }
3413
4197
  function buildReactRefreshWrapper(moduleUrl, transformedCode) {
3414
4198
  const urlLit = JSON.stringify(moduleUrl);
@@ -3434,27 +4218,46 @@ window.$RefreshReg$ = prevRefreshReg;
3434
4218
  window.$RefreshSig$ = prevRefreshSig;
3435
4219
 
3436
4220
  if (__nasti_hot__) {
3437
- __nasti_hot__.accept(() => {
3438
- clearTimeout(window.__nasti_refresh_timer__);
3439
- window.__nasti_refresh_timer__ = setTimeout(() => {
3440
- RefreshRuntime.performReactRefresh();
3441
- }, 30);
4221
+ let __nasti_current_exports__;
4222
+ __nasti_hot__.accept((nextExports) => {
4223
+ if (!nextExports) return;
4224
+ if (!__nasti_current_exports__) {
4225
+ __nasti_hot__.invalidate('Could not Fast Refresh (previous exports unavailable)');
4226
+ return;
4227
+ }
4228
+ const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate(
4229
+ ${urlLit},
4230
+ __nasti_current_exports__,
4231
+ nextExports,
4232
+ );
4233
+ if (invalidateMessage) __nasti_hot__.invalidate(invalidateMessage);
4234
+ });
4235
+ RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
4236
+ __nasti_current_exports__ = currentExports;
4237
+ RefreshRuntime.registerExportsForReactRefresh(${urlLit}, currentExports);
3442
4238
  });
3443
4239
  }
3444
4240
  `;
3445
4241
  }
3446
4242
  function injectImportMetaHot(code, moduleUrl) {
3447
- if (!/\bimport\.meta\.hot\b/.test(code)) return code;
4243
+ const hotRE = /\bimport\.meta\.hot\b/g;
4244
+ const matches = [...maskStringsAndComments(code).matchAll(hotRE)];
4245
+ if (matches.length === 0) return code;
4246
+ for (const match of matches.reverse()) {
4247
+ const start = match.index;
4248
+ code = code.slice(0, start) + "__nasti_hot__" + code.slice(start + match[0].length);
4249
+ }
3448
4250
  const urlLit = JSON.stringify(moduleUrl);
3449
4251
  const header = `import { createHotContext as __nasti_createHotContext__ } from "/@nasti/client";
3450
4252
  const __nasti_hot__ = __nasti_createHotContext__(${urlLit});
3451
4253
  `;
3452
- return header + code.replace(/\bimport\.meta\.hot\b/g, "__nasti_hot__");
4254
+ return header + code;
3453
4255
  }
3454
4256
  function transformMiddleware(ctx) {
3455
4257
  ctx.envDefine = buildEnvDefine(
3456
4258
  loadEnv(ctx.config.mode, ctx.config.root, ctx.config.envPrefix),
3457
- ctx.config.mode
4259
+ ctx.config.mode,
4260
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
3458
4261
  );
3459
4262
  return async (req, res, next) => {
3460
4263
  const url = req.url ?? "/";
@@ -3499,7 +4302,7 @@ function transformMiddleware(ctx) {
3499
4302
  return;
3500
4303
  }
3501
4304
  }
3502
- if (isModuleRequest(url)) {
4305
+ if (isModuleRequest(url, req.headers["sec-fetch-dest"])) {
3503
4306
  try {
3504
4307
  const result = await transformRequest(url, ctx);
3505
4308
  if (result) {
@@ -3525,13 +4328,14 @@ function transformMiddleware(ctx) {
3525
4328
  }
3526
4329
  async function transformRequest(url, ctx) {
3527
4330
  const { config, pluginContainer, moduleGraph } = ctx;
4331
+ url = removeTimestampQuery(url);
3528
4332
  const cleanReqUrl = url.split("?")[0];
3529
4333
  const cached2 = moduleGraph.getModuleByUrl(url);
3530
4334
  if (cached2?.transformResult) {
3531
4335
  return cached2.transformResult;
3532
4336
  }
3533
4337
  if (cleanReqUrl === "/@react-refresh") {
3534
- return { code: getReactRefreshRuntimeEsm() };
4338
+ return { code: getReactRefreshRuntimeEsm(true) };
3535
4339
  }
3536
4340
  if (cleanReqUrl.startsWith("/@modules/") && url.includes("?")) {
3537
4341
  const idParam = new URLSearchParams(url.slice(url.indexOf("?") + 1)).get("id");
@@ -3539,8 +4343,8 @@ async function transformRequest(url, ctx) {
3539
4343
  let realIdValid = false;
3540
4344
  try {
3541
4345
  if (idParam) {
3542
- realId = fs8.realpathSync(idParam);
3543
- realIdValid = fs8.statSync(realId).isFile() && (realId.includes(`${path11.sep}node_modules${path11.sep}`) || isUnderRoot(realId, config.root));
4346
+ realId = fs9.realpathSync(idParam);
4347
+ realIdValid = fs9.statSync(realId).isFile() && (realId.includes(`${path12.sep}node_modules${path12.sep}`) || isUnderRoot(realId, config.root));
3544
4348
  }
3545
4349
  } catch {
3546
4350
  realId = null;
@@ -3567,40 +4371,63 @@ async function transformRequest(url, ctx) {
3567
4371
  }
3568
4372
  const rawQuery = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
3569
4373
  if (rawQuery && !/^t=\d+$/.test(rawQuery)) {
3570
- const loaded = await pluginContainer.load(url);
3571
- if (loaded != null) {
3572
- let code2 = typeof loaded === "string" ? loaded : loaded.code;
4374
+ const mod2 = await moduleGraph.ensureEntryFromUrl(url);
4375
+ const transformVersion2 = mod2.invalidationVersion;
4376
+ const loaded2 = await pluginContainer.load(url);
4377
+ if (loaded2 != null) {
4378
+ let code2 = typeof loaded2 === "string" ? loaded2 : loaded2.code;
4379
+ let map2 = typeof loaded2 === "string" ? void 0 : loaded2.map;
3573
4380
  const transformed = await pluginContainer.transform(code2, url);
3574
4381
  if (transformed != null) {
3575
4382
  code2 = typeof transformed === "string" ? transformed : transformed.code;
4383
+ if (typeof transformed !== "string" && transformed.map != null) {
4384
+ map2 = transformed.map;
4385
+ }
3576
4386
  }
3577
- const mod2 = await moduleGraph.ensureEntryFromUrl(url);
3578
- moduleGraph.registerModule(mod2, cleanReqUrl);
3579
- code2 = injectImportMetaHot(code2, url);
4387
+ const parentFile = resolveUrlToFile(cleanReqUrl, config.root) ?? cleanReqUrl;
4388
+ moduleGraph.registerModule(mod2, parentFile);
4389
+ const hotInfo2 = rewriteHotAcceptDeps(code2, config, parentFile);
4390
+ code2 = injectImportMetaHot(hotInfo2.code, url);
3580
4391
  code2 = replaceEnvInCode(code2, ctx.envDefine ?? buildEnvDefine(
3581
4392
  loadEnv(config.mode, config.root, config.envPrefix),
3582
- config.mode
4393
+ config.mode,
4394
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
3583
4395
  ));
3584
- code2 = rewriteImports(code2, config, cleanReqUrl);
3585
- const transformResult2 = { code: code2 };
3586
- mod2.transformResult = transformResult2;
4396
+ const importedUrls2 = /* @__PURE__ */ new Set();
4397
+ code2 = rewriteImports(code2, config, parentFile, importedUrls2, moduleGraph);
4398
+ const pruned2 = await moduleGraph.updateModuleInfo(
4399
+ mod2,
4400
+ importedUrls2,
4401
+ hotInfo2.acceptedUrls,
4402
+ hotInfo2.isSelfAccepting,
4403
+ transformVersion2
4404
+ );
4405
+ const transformResult2 = { code: code2, map: map2 };
4406
+ if (pruned2) {
4407
+ if (pruned2.size > 0) ctx.onPrune?.([...pruned2].map((item) => item.url));
4408
+ mod2.transformResult = transformResult2;
4409
+ }
3587
4410
  return transformResult2;
3588
4411
  }
3589
4412
  }
3590
4413
  const filePath = resolveUrlToFile(url, config.root);
3591
- if (!filePath || !fs8.existsSync(filePath)) return null;
4414
+ if (!filePath || !fs9.existsSync(filePath)) return null;
3592
4415
  const mod = await moduleGraph.ensureEntryFromUrl(url);
3593
4416
  moduleGraph.registerModule(mod, filePath);
4417
+ const transformVersion = mod.invalidationVersion;
3594
4418
  if (cleanReqUrl.startsWith("/@modules/")) {
3595
4419
  const code2 = await bundlePackageAsEsm(filePath, config.root);
3596
4420
  const transformResult2 = { code: code2 };
3597
4421
  mod.transformResult = transformResult2;
3598
4422
  return transformResult2;
3599
4423
  }
3600
- let code = fs8.readFileSync(filePath, "utf-8");
4424
+ const loaded = await pluginContainer.load(filePath);
4425
+ let code = loaded == null ? fs9.readFileSync(filePath, "utf-8") : typeof loaded === "string" ? loaded : loaded.code;
4426
+ let map = loaded && typeof loaded !== "string" ? loaded.map : void 0;
3601
4427
  const pluginResult = await pluginContainer.transform(code, filePath);
3602
4428
  if (pluginResult) {
3603
4429
  code = typeof pluginResult === "string" ? pluginResult : pluginResult.code;
4430
+ if (typeof pluginResult !== "string") map = pluginResult.map;
3604
4431
  }
3605
4432
  const stableUrl = cleanReqUrl;
3606
4433
  let wrappedWithRefresh = false;
@@ -3611,26 +4438,41 @@ async function transformRequest(url, ctx) {
3611
4438
  sourcemap: true,
3612
4439
  jsxRuntime: "automatic",
3613
4440
  jsxImportSource: config.framework === "vue" ? "vue" : "react",
3614
- reactRefresh: useRefresh
4441
+ reactRefresh: useRefresh,
4442
+ target: ctx.environment?.options.build.target ?? config.build.target
3615
4443
  });
3616
4444
  code = result.code;
4445
+ if (result.map) map = JSON.parse(result.map);
3617
4446
  if (useRefresh) {
3618
4447
  code = buildReactRefreshWrapper(stableUrl, code);
3619
4448
  wrappedWithRefresh = true;
3620
- mod.isSelfAccepting = true;
3621
4449
  }
3622
4450
  }
4451
+ const hotInfo = rewriteHotAcceptDeps(code, config, filePath);
4452
+ code = hotInfo.code;
3623
4453
  if (!wrappedWithRefresh) {
3624
4454
  code = injectImportMetaHot(code, stableUrl);
3625
4455
  }
3626
4456
  const envDefine = ctx.envDefine ?? buildEnvDefine(
3627
4457
  loadEnv(config.mode, config.root, config.envPrefix),
3628
- config.mode
4458
+ config.mode,
4459
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
3629
4460
  );
3630
4461
  code = replaceEnvInCode(code, envDefine);
3631
- code = rewriteImports(code, config, filePath);
3632
- const transformResult = { code };
3633
- mod.transformResult = transformResult;
4462
+ const importedUrls = /* @__PURE__ */ new Set();
4463
+ code = rewriteImports(code, config, filePath, importedUrls, moduleGraph);
4464
+ const pruned = await moduleGraph.updateModuleInfo(
4465
+ mod,
4466
+ importedUrls,
4467
+ hotInfo.acceptedUrls,
4468
+ wrappedWithRefresh || hotInfo.isSelfAccepting,
4469
+ transformVersion
4470
+ );
4471
+ const transformResult = { code, map };
4472
+ if (pruned) {
4473
+ if (pruned.size > 0) ctx.onPrune?.([...pruned].map((item) => item.url));
4474
+ mod.transformResult = transformResult;
4475
+ }
3634
4476
  return transformResult;
3635
4477
  }
3636
4478
  async function loadVirtualModule(spec, ctx) {
@@ -3638,7 +4480,7 @@ async function loadVirtualModule(spec, ctx) {
3638
4480
  const resolved = await pluginContainer.resolveId(spec);
3639
4481
  if (resolved == null) return null;
3640
4482
  const resolvedId = typeof resolved === "string" ? resolved : resolved.id;
3641
- const looksVirtual = resolvedId.startsWith("\0") || !fs8.existsSync(resolvedId);
4483
+ const looksVirtual = resolvedId.startsWith("\0") || !fs9.existsSync(resolvedId);
3642
4484
  if (!looksVirtual) return null;
3643
4485
  const loadResult = await pluginContainer.load(resolvedId);
3644
4486
  if (loadResult == null) return null;
@@ -3649,9 +4491,10 @@ async function loadVirtualModule(spec, ctx) {
3649
4491
  }
3650
4492
  code = replaceEnvInCode(code, ctx.envDefine ?? buildEnvDefine(
3651
4493
  loadEnv(config.mode, config.root, config.envPrefix),
3652
- config.mode
4494
+ config.mode,
4495
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
3653
4496
  ));
3654
- const anchor = path11.join(config.root, "__nasti_virtual__.ts");
4497
+ const anchor = path12.join(config.root, "__nasti_virtual__.ts");
3655
4498
  code = rewriteImports(code, config, anchor);
3656
4499
  return { id: resolvedId, result: { code } };
3657
4500
  }
@@ -3677,7 +4520,7 @@ async function doBundlePackage(entryFile, root) {
3677
4520
  await bundle2.close();
3678
4521
  let code = result.output[0].code;
3679
4522
  code = code.replace(/process\.env\.NODE_ENV/g, '"development"');
3680
- const externalBaseDir = path11.dirname(entryFile);
4523
+ const externalBaseDir = path12.dirname(entryFile);
3681
4524
  code = code.replace(
3682
4525
  /^(import\b[^;'"]*?\bfrom\s+)(['"])([^'"./][^'"]*)(\2)/gm,
3683
4526
  (_, prefix, q, spec) => `${prefix}${q}${externalSpecToModuleUrl(spec, externalBaseDir, root)}${q}`
@@ -3695,16 +4538,16 @@ async function doBundlePackage(entryFile, root) {
3695
4538
  return code;
3696
4539
  }
3697
4540
  async function tryGenerateSubpathShim(entryFile, root) {
3698
- const NM = `${path11.sep}node_modules${path11.sep}`;
4541
+ const NM = `${path12.sep}node_modules${path12.sep}`;
3699
4542
  if (!entryFile.includes(NM)) return null;
3700
4543
  let pkgDir = null;
3701
4544
  let pkgName = null;
3702
- let dir = path11.dirname(entryFile);
4545
+ let dir = path12.dirname(entryFile);
3703
4546
  while (true) {
3704
- const pkgJsonPath = path11.join(dir, "package.json");
3705
- if (fs8.existsSync(pkgJsonPath)) {
4547
+ const pkgJsonPath = path12.join(dir, "package.json");
4548
+ if (fs9.existsSync(pkgJsonPath)) {
3706
4549
  try {
3707
- const pkg = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
4550
+ const pkg = JSON.parse(fs9.readFileSync(pkgJsonPath, "utf-8"));
3708
4551
  if (typeof pkg?.name === "string" && pkg.name) {
3709
4552
  pkgDir = dir;
3710
4553
  pkgName = pkg.name;
@@ -3713,16 +4556,16 @@ async function tryGenerateSubpathShim(entryFile, root) {
3713
4556
  } catch {
3714
4557
  }
3715
4558
  }
3716
- const parent = path11.dirname(dir);
4559
+ const parent = path12.dirname(dir);
3717
4560
  if (parent === dir) return null;
3718
4561
  dir = parent;
3719
4562
  if (!dir.includes(NM)) return null;
3720
4563
  }
3721
4564
  if (!pkgDir || !pkgName) return null;
3722
- const entryExt = path11.extname(entryFile);
4565
+ const entryExt = path12.extname(entryFile);
3723
4566
  const mainEntry = pickMainEntryByExtension(pkgDir, entryExt);
3724
4567
  if (!mainEntry) return null;
3725
- if (path11.resolve(mainEntry) === path11.resolve(entryFile)) return null;
4568
+ if (path12.resolve(mainEntry) === path12.resolve(entryFile)) return null;
3726
4569
  let mainNs;
3727
4570
  let subNs;
3728
4571
  try {
@@ -3746,7 +4589,7 @@ async function tryGenerateSubpathShim(entryFile, root) {
3746
4589
  if (mainNs["default"] !== subNs["default"]) return null;
3747
4590
  }
3748
4591
  const rootMain = resolveNodeModule(root, pkgName);
3749
- const mainEntryUrl = rootMain && rootMain.startsWith(pkgDir + path11.sep) ? `/@modules/${pkgName}` : `/@modules/${pkgName}?id=${encodeURIComponent(mainEntry)}`;
4592
+ const mainEntryUrl = rootMain && rootMain.startsWith(pkgDir + path12.sep) ? `/@modules/${pkgName}` : `/@modules/${pkgName}?id=${encodeURIComponent(mainEntry)}`;
3750
4593
  const lines = [
3751
4594
  `// Nasti subpath shim \u2192 ${pkgName} (avoid duplicate bundling)`,
3752
4595
  `import * as __pkg from "${mainEntryUrl}";`
@@ -3760,10 +4603,10 @@ async function tryGenerateSubpathShim(entryFile, root) {
3760
4603
  return lines.join("\n") + "\n";
3761
4604
  }
3762
4605
  function pickMainEntryByExtension(pkgDir, preferredExt) {
3763
- const pkgJsonPath = path11.join(pkgDir, "package.json");
4606
+ const pkgJsonPath = path12.join(pkgDir, "package.json");
3764
4607
  let pkg;
3765
4608
  try {
3766
- pkg = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
4609
+ pkg = JSON.parse(fs9.readFileSync(pkgJsonPath, "utf-8"));
3767
4610
  } catch {
3768
4611
  return null;
3769
4612
  }
@@ -3782,14 +4625,14 @@ function pickMainEntryByExtension(pkgDir, preferredExt) {
3782
4625
  if (typeof pkg.module === "string") candidates.push(pkg.module);
3783
4626
  if (typeof pkg.main === "string") candidates.push(pkg.main);
3784
4627
  for (const cand of candidates) {
3785
- if (path11.extname(cand) === preferredExt) {
3786
- const full = path11.resolve(pkgDir, cand);
3787
- if (fs8.existsSync(full)) return full;
4628
+ if (path12.extname(cand) === preferredExt) {
4629
+ const full = path12.resolve(pkgDir, cand);
4630
+ if (fs9.existsSync(full)) return full;
3788
4631
  }
3789
4632
  }
3790
4633
  for (const cand of candidates) {
3791
- const full = path11.resolve(pkgDir, cand);
3792
- if (fs8.existsSync(full)) return full;
4634
+ const full = path12.resolve(pkgDir, cand);
4635
+ if (fs9.existsSync(full)) return full;
3793
4636
  }
3794
4637
  return null;
3795
4638
  }
@@ -3835,72 +4678,231 @@ async function injectCjsNamedExports(code, entryFile) {
3835
4678
  return code;
3836
4679
  }
3837
4680
  }
3838
- function rewriteImports(code, config, filePath) {
4681
+ function rewriteImports(code, config, filePath, importedUrls, moduleGraph) {
4682
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
4683
+ const transformSpec = (spec) => {
4684
+ const resolved = removeTimestampQuery(resolveSpec(spec));
4685
+ importedUrls?.add(resolved);
4686
+ const timestamp = moduleGraph?.getModuleByUrl(resolved)?.lastHMRTimestamp ?? 0;
4687
+ return timestamp > 0 ? appendTimestampQuery(resolved, timestamp) : resolved;
4688
+ };
4689
+ return code.replace(
4690
+ /\bfrom\s+(['"])([^'"]+)\1/g,
4691
+ (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
4692
+ ).replace(
4693
+ /\bimport\s+(['"])([^'"]+)\1/g,
4694
+ (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
4695
+ ).replace(
4696
+ /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
4697
+ (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
4698
+ );
4699
+ }
4700
+ function createModuleSpecifierResolver(config, filePath) {
3839
4701
  const root = config.root;
3840
- const fileDir = path11.dirname(filePath);
4702
+ const fileDir = path12.dirname(filePath);
3841
4703
  const aliasEntries = Object.entries(config.resolve.alias).sort(
3842
4704
  ([a], [b]) => b.length - a.length
3843
4705
  );
3844
- const toRootUrl = (abs) => "/" + path11.relative(root, abs).replace(/\\/g, "/");
3845
- const transformSpec = (spec) => {
3846
- const suffixMatch = spec.match(/[?#].*$/);
4706
+ const toRootUrl = (abs) => "/" + path12.relative(root, abs).replace(/\\/g, "/");
4707
+ return (specifier) => {
4708
+ const suffixMatch = specifier.match(/[?#].*$/);
3847
4709
  const suffix = suffixMatch ? suffixMatch[0] : "";
3848
- const baseSpec = suffix ? spec.slice(0, -suffix.length) : spec;
4710
+ const baseSpec = suffix ? specifier.slice(0, -suffix.length) : specifier;
3849
4711
  for (const [key, value] of aliasEntries) {
3850
4712
  if (baseSpec === key || baseSpec.startsWith(key + "/")) {
3851
4713
  const aliasBase = resolveAliasTarget2(value, root);
3852
4714
  const sub = baseSpec.slice(key.length).replace(/^\//, "");
3853
- const target = sub ? path11.join(aliasBase, sub) : aliasBase;
4715
+ const target = sub ? path12.join(aliasBase, sub) : aliasBase;
3854
4716
  const resolved = tryResolveDiskPath(target);
3855
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
4717
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
3856
4718
  }
3857
4719
  }
3858
4720
  if (baseSpec.startsWith("./") || baseSpec.startsWith("../")) {
3859
- const target = path11.resolve(fileDir, baseSpec);
3860
- const resolved = tryResolveDiskPath(target);
3861
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
4721
+ const resolved = tryResolveDiskPath(path12.resolve(fileDir, baseSpec));
4722
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
3862
4723
  }
3863
4724
  if (baseSpec.startsWith("/") && !baseSpec.startsWith("/@")) {
3864
- const target = path11.join(root, baseSpec.replace(/^\//, ""));
3865
- const resolved = tryResolveDiskPath(target);
3866
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
4725
+ const resolved = tryResolveDiskPath(path12.join(root, baseSpec.replace(/^\//, "")));
4726
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
3867
4727
  }
3868
- if (baseSpec.startsWith("/")) return spec;
3869
- return `/@modules/${spec}`;
4728
+ if (baseSpec.startsWith("/")) return specifier;
4729
+ return `/@modules/${specifier}`;
3870
4730
  };
3871
- return code.replace(
3872
- /\bfrom\s+(['"])([^'"]+)\1/g,
3873
- (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
3874
- ).replace(
3875
- /\bimport\s+(['"])([^'"]+)\1/g,
3876
- (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
3877
- ).replace(
3878
- /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
3879
- (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
3880
- );
4731
+ }
4732
+ function rewriteHotAcceptDeps(code, config, filePath) {
4733
+ const acceptedUrls = /* @__PURE__ */ new Set();
4734
+ const edits = [];
4735
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
4736
+ const acceptRE = /(?:\bimport\.meta\.hot|\b__nasti_hot__)(?:(?:\?\.)|\.)accept\s*\(/g;
4737
+ const searchableCode = maskStringsAndComments(code);
4738
+ let isSelfAccepting = false;
4739
+ let match;
4740
+ while (match = acceptRE.exec(searchableCode)) {
4741
+ let cursor = match.index + match[0].length;
4742
+ const skipTrivia = () => {
4743
+ while (cursor < code.length) {
4744
+ if (/\s/.test(code[cursor])) {
4745
+ cursor++;
4746
+ continue;
4747
+ }
4748
+ if (code[cursor] === "/" && code[cursor + 1] === "/") {
4749
+ cursor += 2;
4750
+ while (cursor < code.length && code[cursor] !== "\n") cursor++;
4751
+ continue;
4752
+ }
4753
+ if (code[cursor] === "/" && code[cursor + 1] === "*") {
4754
+ cursor += 2;
4755
+ while (cursor < code.length && !(code[cursor] === "*" && code[cursor + 1] === "/")) cursor++;
4756
+ cursor += 2;
4757
+ continue;
4758
+ }
4759
+ break;
4760
+ }
4761
+ };
4762
+ skipTrivia();
4763
+ const first = code[cursor];
4764
+ if (!first || first === ")" || first !== "[" && first !== "'" && first !== '"' && first !== "`") {
4765
+ isSelfAccepting = true;
4766
+ continue;
4767
+ }
4768
+ const readLiteral = () => {
4769
+ const quote = code[cursor];
4770
+ if (quote !== "'" && quote !== '"' && quote !== "`") return;
4771
+ const start = cursor;
4772
+ cursor++;
4773
+ let raw = "";
4774
+ while (cursor < code.length) {
4775
+ const char = code[cursor];
4776
+ if (char === "\\") {
4777
+ raw += code[cursor + 1] ?? "";
4778
+ cursor += 2;
4779
+ continue;
4780
+ }
4781
+ if (char === quote) {
4782
+ cursor++;
4783
+ const resolved = removeTimestampQuery(resolveSpec(raw));
4784
+ acceptedUrls.add(resolved);
4785
+ edits.push({ start, end: cursor, value: JSON.stringify(resolved) });
4786
+ return;
4787
+ }
4788
+ if (quote === "`" && char === "$" && code[cursor + 1] === "{") return;
4789
+ raw += char;
4790
+ cursor++;
4791
+ }
4792
+ };
4793
+ if (first === "[") {
4794
+ cursor++;
4795
+ while (cursor < code.length) {
4796
+ skipTrivia();
4797
+ if (code[cursor] === ",") {
4798
+ cursor++;
4799
+ skipTrivia();
4800
+ }
4801
+ if (code[cursor] === "]") break;
4802
+ const before = cursor;
4803
+ readLiteral();
4804
+ if (cursor === before) break;
4805
+ }
4806
+ } else {
4807
+ readLiteral();
4808
+ }
4809
+ }
4810
+ for (const edit of edits.sort((a, b) => b.start - a.start)) {
4811
+ code = code.slice(0, edit.start) + edit.value + code.slice(edit.end);
4812
+ }
4813
+ return { code, acceptedUrls, isSelfAccepting };
4814
+ }
4815
+ function maskStringsAndComments(code) {
4816
+ const masked = code.split("");
4817
+ let state = "code";
4818
+ const isRegexStart = (index2) => {
4819
+ let previous = index2 - 1;
4820
+ while (previous >= 0 && /\s/.test(code[previous])) previous--;
4821
+ return previous < 0 || "=(:,!&|?{};[]+-*%^~<>".includes(code[previous]);
4822
+ };
4823
+ for (let i = 0; i < code.length; i++) {
4824
+ const char = code[i];
4825
+ const next = code[i + 1];
4826
+ if (state === "code") {
4827
+ if (char === "'") state = "single";
4828
+ else if (char === '"') state = "double";
4829
+ else if (char === "`") state = "template";
4830
+ else if (char === "/" && next === "/") state = "line-comment";
4831
+ else if (char === "/" && next === "*") state = "block-comment";
4832
+ else if (char === "/" && isRegexStart(i)) state = "regex";
4833
+ else continue;
4834
+ masked[i] = " ";
4835
+ continue;
4836
+ }
4837
+ if (state === "line-comment") {
4838
+ if (char === "\n") {
4839
+ state = "code";
4840
+ } else {
4841
+ masked[i] = " ";
4842
+ }
4843
+ continue;
4844
+ }
4845
+ if (state === "block-comment") {
4846
+ masked[i] = char === "\n" ? "\n" : " ";
4847
+ if (char === "*" && next === "/") {
4848
+ masked[i + 1] = " ";
4849
+ i++;
4850
+ state = "code";
4851
+ }
4852
+ continue;
4853
+ }
4854
+ if (state === "regex" || state === "regex-class") {
4855
+ masked[i] = char === "\n" ? "\n" : " ";
4856
+ if (char === "\\") {
4857
+ if (i + 1 < code.length) masked[++i] = " ";
4858
+ } else if (state === "regex" && char === "[") {
4859
+ state = "regex-class";
4860
+ } else if (state === "regex-class" && char === "]") {
4861
+ state = "regex";
4862
+ } else if (state === "regex" && char === "/") {
4863
+ state = "code";
4864
+ }
4865
+ continue;
4866
+ }
4867
+ masked[i] = char === "\n" ? "\n" : " ";
4868
+ if (char === "\\") {
4869
+ if (i + 1 < code.length) masked[++i] = " ";
4870
+ continue;
4871
+ }
4872
+ if (state === "single" && char === "'" || state === "double" && char === '"' || state === "template" && char === "`") {
4873
+ state = "code";
4874
+ }
4875
+ }
4876
+ return masked.join("");
3881
4877
  }
3882
4878
  function resolveAliasTarget2(value, root) {
3883
- if (path11.isAbsolute(value) && fs8.existsSync(value)) return value;
3884
- if (value.startsWith("/")) return path11.join(root, value.slice(1));
3885
- return path11.resolve(root, value);
4879
+ if (path12.isAbsolute(value) && fs9.existsSync(value)) return value;
4880
+ if (value.startsWith("/")) return path12.join(root, value.slice(1));
4881
+ return path12.resolve(root, value);
3886
4882
  }
3887
4883
  function tryResolveDiskPath(target) {
3888
- if (fs8.existsSync(target) && fs8.statSync(target).isFile()) return target;
4884
+ if (fs9.existsSync(target) && fs9.statSync(target).isFile()) return target;
3889
4885
  for (const ext of RESOLVE_EXTENSIONS) {
3890
4886
  const withExt = target + ext;
3891
- if (fs8.existsSync(withExt) && fs8.statSync(withExt).isFile()) return withExt;
4887
+ if (fs9.existsSync(withExt) && fs9.statSync(withExt).isFile()) return withExt;
3892
4888
  }
3893
- if (fs8.existsSync(target) && fs8.statSync(target).isDirectory()) {
4889
+ if (fs9.existsSync(target) && fs9.statSync(target).isDirectory()) {
3894
4890
  for (const ext of RESOLVE_EXTENSIONS) {
3895
- const idx = path11.join(target, "index" + ext);
3896
- if (fs8.existsSync(idx) && fs8.statSync(idx).isFile()) return idx;
4891
+ const idx = path12.join(target, "index" + ext);
4892
+ if (fs9.existsSync(idx) && fs9.statSync(idx).isFile()) return idx;
3897
4893
  }
3898
4894
  }
3899
4895
  return null;
3900
4896
  }
3901
4897
  function isUnderRoot(abs, root) {
3902
- const rel = path11.relative(root, abs);
3903
- return !!rel && !rel.startsWith("..") && !path11.isAbsolute(rel);
4898
+ const rel = path12.relative(root, abs);
4899
+ return !!rel && !rel.startsWith("..") && !path12.isAbsolute(rel);
4900
+ }
4901
+ function appendTimestampQuery(url, timestamp) {
4902
+ const hashIndex = url.indexOf("#");
4903
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
4904
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
4905
+ return `${withoutHash}${withoutHash.includes("?") ? "&" : "?"}t=${timestamp}${hash}`;
3904
4906
  }
3905
4907
  function externalSpecToModuleUrl(spec, baseDir, root) {
3906
4908
  const resolved = resolveNodeModule(baseDir, spec);
@@ -3913,7 +4915,7 @@ function resolveNodeModule(baseDir, moduleName) {
3913
4915
  const resolved = resolveNodeModuleEntry(baseDir, moduleName);
3914
4916
  if (!resolved) return null;
3915
4917
  try {
3916
- return fs8.realpathSync(resolved);
4918
+ return fs9.realpathSync(resolved);
3917
4919
  } catch {
3918
4920
  return resolved;
3919
4921
  }
@@ -3933,21 +4935,21 @@ function resolveNodeModuleEntry(root, moduleName) {
3933
4935
  let pkgDir = null;
3934
4936
  let dir = root;
3935
4937
  for (; ; ) {
3936
- const candidate = path11.join(dir, "node_modules", pkgName);
3937
- if (fs8.existsSync(candidate)) {
4938
+ const candidate = path12.join(dir, "node_modules", pkgName);
4939
+ if (fs9.existsSync(candidate)) {
3938
4940
  pkgDir = candidate;
3939
4941
  break;
3940
4942
  }
3941
- const parent = path11.dirname(dir);
4943
+ const parent = path12.dirname(dir);
3942
4944
  if (parent === dir) break;
3943
4945
  dir = parent;
3944
4946
  }
3945
4947
  if (!pkgDir) return null;
3946
- const pkgJsonPath = path11.join(pkgDir, "package.json");
3947
- if (!fs8.existsSync(pkgJsonPath)) return null;
4948
+ const pkgJsonPath = path12.join(pkgDir, "package.json");
4949
+ if (!fs9.existsSync(pkgJsonPath)) return null;
3948
4950
  let pkg;
3949
4951
  try {
3950
- pkg = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
4952
+ pkg = JSON.parse(fs9.readFileSync(pkgJsonPath, "utf-8"));
3951
4953
  } catch {
3952
4954
  return null;
3953
4955
  }
@@ -3960,32 +4962,32 @@ function resolveNodeModuleEntry(root, moduleName) {
3960
4962
  const subDirs = [""];
3961
4963
  for (const field of ["module", "main"]) {
3962
4964
  if (typeof pkg[field] === "string") {
3963
- const dir2 = path11.dirname(pkg[field]);
4965
+ const dir2 = path12.dirname(pkg[field]);
3964
4966
  if (dir2 && dir2 !== "." && !subDirs.includes(dir2)) subDirs.push(dir2);
3965
4967
  }
3966
4968
  }
3967
4969
  for (const dir2 of subDirs) {
3968
- const direct = path11.join(pkgDir, dir2, subpath);
3969
- if (fs8.existsSync(direct) && fs8.statSync(direct).isFile()) return direct;
4970
+ const direct = path12.join(pkgDir, dir2, subpath);
4971
+ if (fs9.existsSync(direct) && fs9.statSync(direct).isFile()) return direct;
3970
4972
  for (const ext of RESOLVE_EXTENSIONS) {
3971
- if (fs8.existsSync(direct + ext)) return direct + ext;
4973
+ if (fs9.existsSync(direct + ext)) return direct + ext;
3972
4974
  }
3973
4975
  }
3974
4976
  return null;
3975
4977
  }
3976
4978
  for (const field of ["module", "jsnext:main", "jsnext", "main"]) {
3977
4979
  if (typeof pkg[field] === "string") {
3978
- const entry = path11.join(pkgDir, pkg[field]);
3979
- if (fs8.existsSync(entry)) return entry;
4980
+ const entry = path12.join(pkgDir, pkg[field]);
4981
+ if (fs9.existsSync(entry)) return entry;
3980
4982
  }
3981
4983
  }
3982
- const indexFallback = path11.join(pkgDir, "index.js");
3983
- if (fs8.existsSync(indexFallback)) return indexFallback;
4984
+ const indexFallback = path12.join(pkgDir, "index.js");
4985
+ if (fs9.existsSync(indexFallback)) return indexFallback;
3984
4986
  return null;
3985
4987
  }
3986
4988
  function resolvePackageExports(exports, key, pkgDir) {
3987
4989
  if (typeof exports === "string") {
3988
- return key === "." ? path11.join(pkgDir, exports) : null;
4990
+ return key === "." ? path12.join(pkgDir, exports) : null;
3989
4991
  }
3990
4992
  const entry = exports[key];
3991
4993
  if (entry === void 0) {
@@ -3997,7 +4999,7 @@ function resolvePackageExports(exports, key, pkgDir) {
3997
4999
  return resolveExportValue(entry, pkgDir);
3998
5000
  }
3999
5001
  function resolveExportValue(value, pkgDir) {
4000
- if (typeof value === "string") return path11.join(pkgDir, value);
5002
+ if (typeof value === "string") return path12.join(pkgDir, value);
4001
5003
  if (Array.isArray(value)) {
4002
5004
  for (const item of value) {
4003
5005
  const r = resolveExportValue(item, pkgDir);
@@ -4021,54 +5023,62 @@ function resolveUrlToFile(url, root) {
4021
5023
  const moduleName = cleanUrl.slice("/@modules/".length);
4022
5024
  return resolveNodeModule(root, moduleName);
4023
5025
  }
4024
- const filePath = path11.resolve(root, cleanUrl.replace(/^\//, ""));
4025
- if (fs8.existsSync(filePath) && fs8.statSync(filePath).isFile()) {
5026
+ const filePath = path12.resolve(root, cleanUrl.replace(/^\//, ""));
5027
+ if (fs9.existsSync(filePath) && fs9.statSync(filePath).isFile()) {
4026
5028
  return filePath;
4027
5029
  }
4028
5030
  for (const ext of RESOLVE_EXTENSIONS) {
4029
5031
  const withExt = filePath + ext;
4030
- if (fs8.existsSync(withExt)) return withExt;
5032
+ if (fs9.existsSync(withExt)) return withExt;
4031
5033
  }
4032
5034
  for (const ext of RESOLVE_EXTENSIONS) {
4033
- const indexFile = path11.join(filePath, "index" + ext);
4034
- if (fs8.existsSync(indexFile)) return indexFile;
5035
+ const indexFile = path12.join(filePath, "index" + ext);
5036
+ if (fs9.existsSync(indexFile)) return indexFile;
4035
5037
  }
4036
5038
  return null;
4037
5039
  }
4038
- function isModuleRequest(url) {
5040
+ function isModuleRequest(url, destination) {
4039
5041
  const cleanUrl = url.split("?")[0];
4040
5042
  if (/\.(ts|tsx|jsx|js|mjs|vue|css|json)$/.test(cleanUrl)) return true;
4041
5043
  if (cleanUrl.startsWith("/@modules/")) return true;
4042
- if (!path11.extname(cleanUrl)) return true;
5044
+ if (isAssetFile(cleanUrl)) {
5045
+ const query = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
5046
+ const isExplicitAssetModule = /(?:^|&)(?:url|raw)(?:&|$)/.test(query);
5047
+ return isExplicitAssetModule || destination === "script";
5048
+ }
5049
+ if (!path12.extname(cleanUrl)) return true;
4043
5050
  return false;
4044
5051
  }
4045
5052
  function getHmrClientCode() {
4046
5053
  return `
4047
5054
  // Nasti HMR Client
4048
- const socket = new WebSocket(\`ws://\${location.host}\`, 'nasti-hmr');
5055
+ const socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
5056
+ const socket = new WebSocket(socketProtocol + '://' + location.host, 'nasti-hmr');
4049
5057
  const hotModulesMap = new Map();
4050
5058
  const disposeMap = new Map();
4051
5059
  const pruneMap = new Map();
5060
+ const dataMap = new Map();
5061
+ const customListenersMap = new Map();
5062
+ let updateQueue = [];
5063
+ let pendingUpdateQueue = false;
4052
5064
 
4053
5065
  socket.addEventListener('message', async ({ data }) => {
4054
5066
  const payload = JSON.parse(data);
5067
+ // \u9ED8\u8BA4\u6D4F\u89C8\u5668 client \u53EA\u6D88\u8D39\u81EA\u5DF1\u7684 HMR \u6D88\u606F\uFF1Bnative/worker \u73AF\u5883\u901A\u8FC7\u5404\u81EA\u7684
5068
+ // HotChannel \u6216 app-level HMR \u534F\u8C03\u5668\u5904\u7406\u540C\u4E00 transport \u4E0A\u7684\u547D\u540D\u6D88\u606F\u3002
5069
+ if (payload.environment && payload.environment !== 'client') return;
4055
5070
  switch (payload.type) {
4056
5071
  case 'connected':
4057
- console.log('[nasti] connected.');
5072
+ console.debug('[nasti] connected.');
4058
5073
  clearErrorOverlay();
4059
5074
  break;
4060
5075
  case 'update':
4061
5076
  try {
4062
- await Promise.all(payload.updates.map((update) => {
4063
- if (update.type === 'js-update') {
4064
- return fetchUpdate(update);
4065
- } else if (update.type === 'css-update') {
4066
- return updateCss(update.path);
4067
- }
4068
- }));
5077
+ // CSS \u5728 unbundled \u6A21\u5F0F\u4E0B\u4E5F\u662F\u4F1A\u6CE8\u5165 <style> \u7684 JS \u6A21\u5757\uFF0C\u548C\u666E\u901A JS
5078
+ // \u4E00\u6837\u91CD\u65B0 import \u624D\u80FD\u6267\u884C dispose/accept \u5E76\u4FDD\u6301\u9875\u9762\u72B6\u6001\u3002
5079
+ await Promise.all(payload.updates.map(queueUpdate));
4069
5080
  clearErrorOverlay();
4070
- console.log('[nasti] HMR update complete, reloading page');
4071
- location.reload();
5081
+ console.debug('[nasti] HMR update complete.');
4072
5082
  } catch (err) {
4073
5083
  console.error('[nasti] HMR update failed:', err);
4074
5084
  showErrorOverlay(err);
@@ -4079,11 +5089,34 @@ socket.addEventListener('message', async ({ data }) => {
4079
5089
  location.reload();
4080
5090
  break;
4081
5091
  case 'prune':
4082
- payload.paths.forEach((p) => {
4083
- const cb = pruneMap.get(p);
4084
- if (cb) cb();
4085
- });
5092
+ await Promise.all(payload.paths.map(async (path) => {
5093
+ const data = dataMap.get(path);
5094
+ const dispose = disposeMap.get(path);
5095
+ const prune = pruneMap.get(path);
5096
+ if (dispose) await dispose(data);
5097
+ if (prune) await prune(data);
5098
+ hotModulesMap.delete(path);
5099
+ disposeMap.delete(path);
5100
+ pruneMap.delete(path);
5101
+ dataMap.delete(path);
5102
+ clearCustomListeners(path);
5103
+ }));
5104
+ break;
5105
+ case 'custom': {
5106
+ const listenersByOwner = customListenersMap.get(payload.event);
5107
+ if (!listenersByOwner) break;
5108
+ const results = await Promise.allSettled(
5109
+ [...listenersByOwner.values()]
5110
+ .flatMap((listeners) => [...listeners])
5111
+ .map((listener) => Promise.resolve().then(() => listener(payload.data)))
5112
+ );
5113
+ for (const result of results) {
5114
+ if (result.status === 'rejected') {
5115
+ console.error('[nasti] custom HMR event listener failed:', result.reason);
5116
+ }
5117
+ }
4086
5118
  break;
5119
+ }
4087
5120
  case 'error':
4088
5121
  console.error('[nasti] error:', payload.err.message);
4089
5122
  showErrorOverlay(payload.err);
@@ -4091,33 +5124,64 @@ socket.addEventListener('message', async ({ data }) => {
4091
5124
  }
4092
5125
  });
4093
5126
 
4094
- // \u81EA\u52A8\u91CD\u8FDE\uFF08\u65AD\u7EBF\u65F6\u6307\u6570\u9000\u907F\uFF09
5127
+ // \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
4095
5128
  let reconnectTimer = 0;
4096
5129
  socket.addEventListener('close', () => {
4097
5130
  clearTimeout(reconnectTimer);
4098
5131
  reconnectTimer = setTimeout(() => location.reload(), 1000);
4099
5132
  });
4100
5133
 
5134
+ /**
5135
+ * \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
5136
+ * \u6539\u53D8\u6A21\u5757\u5E94\u7528\u987A\u5E8F\u3002\u8FD9\u4E0E Vite HMRClient \u7684 fetch/apply \u4E24\u9636\u6BB5\u4E00\u81F4\u3002
5137
+ */
5138
+ async function queueUpdate(update) {
5139
+ updateQueue.push(fetchUpdate(update));
5140
+ if (pendingUpdateQueue) return;
5141
+
5142
+ pendingUpdateQueue = true;
5143
+ await Promise.resolve();
5144
+ pendingUpdateQueue = false;
5145
+ const loading = updateQueue;
5146
+ updateQueue = [];
5147
+ const applyUpdates = await Promise.all(loading);
5148
+ for (const apply of applyUpdates) {
5149
+ if (apply) apply();
5150
+ }
5151
+ }
5152
+
4101
5153
  async function fetchUpdate(update) {
4102
5154
  const mod = hotModulesMap.get(update.path);
4103
- // \u5148\u8DD1 dispose\uFF08\u7ED9\u6A21\u5757\u673A\u4F1A\u6E05\u7406\u526F\u4F5C\u7528\uFF09
4104
- const dispose = disposeMap.get(update.path);
4105
- if (dispose) dispose();
5155
+ // \u5C1A\u672A\u5728\u5F53\u524D\u9875\u9762\u52A0\u8F7D\u7684\u52A8\u6001\u6A21\u5757\u4E0D\u9700\u8981\u66F4\u65B0\u3002
5156
+ if (!mod) return;
4106
5157
 
4107
- const newMod = await import(update.acceptedPath + '?t=' + update.timestamp);
4108
- if (mod) {
4109
- // \u590D\u5236\u56DE\u8C03\u6570\u7EC4\u907F\u514D\u56DE\u8C03\u5185\u90E8\u53C8\u4FEE\u6539 hotModulesMap \u9020\u6210\u8FED\u4EE3\u5F02\u5E38
4110
- [...mod.callbacks].forEach((cb) => cb(newMod));
4111
- }
5158
+ // \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
5159
+ const qualifiedCallbacks = mod.callbacks.filter(({ deps }) =>
5160
+ deps.includes(update.acceptedPath)
5161
+ );
5162
+ const isSelfUpdate = update.path === update.acceptedPath;
5163
+ if (!isSelfUpdate && qualifiedCallbacks.length === 0) return;
5164
+
5165
+ const dispose = disposeMap.get(update.acceptedPath);
5166
+ if (dispose) await dispose(dataMap.get(update.acceptedPath));
5167
+ const newMod = await import(appendTimestampQuery(update.acceptedPath, update.timestamp));
5168
+
5169
+ return () => {
5170
+ for (const { deps, fn } of qualifiedCallbacks) {
5171
+ fn(deps.map((dep) => dep === update.acceptedPath ? newMod : undefined));
5172
+ }
5173
+ const detail = isSelfUpdate
5174
+ ? update.path
5175
+ : update.acceptedPath + ' via ' + update.path;
5176
+ console.debug('[nasti] hot updated:', detail);
5177
+ };
4112
5178
  }
4113
5179
 
4114
- function updateCss(path) {
4115
- const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
4116
- if (el) {
4117
- return fetch(path + '?t=' + Date.now())
4118
- .then(r => r.text())
4119
- .then(css => { el.textContent = css; });
4120
- }
5180
+ function appendTimestampQuery(url, timestamp) {
5181
+ const hashIndex = url.indexOf('#');
5182
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : '';
5183
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
5184
+ return withoutHash + (withoutHash.includes('?') ? '&' : '?') + 't=' + timestamp + hash;
4121
5185
  }
4122
5186
 
4123
5187
  function clearErrorOverlay() {
@@ -4145,23 +5209,31 @@ function showErrorOverlay(err) {
4145
5209
  document.body.appendChild(overlay);
4146
5210
  }
4147
5211
 
4148
- /**
4149
- * \u751F\u6210 import.meta.hot \u7684 hot context\u3002
4150
- * \u5173\u952E\u7EA6\u675F\uFF1A\u540C\u4E00 ownerPath \u7684 accept \u56DE\u8C03\u5FC5\u987B\u66FF\u6362\uFF08\u4E0D\u662F append\uFF09\u3002
4151
- * \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
4152
- * \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
4153
- */
4154
5212
  export function createHotContext(ownerPath) {
5213
+ if (!dataMap.has(ownerPath)) dataMap.set(ownerPath, {});
5214
+
5215
+ // \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
5216
+ const existing = hotModulesMap.get(ownerPath);
5217
+ if (existing) existing.callbacks = [];
5218
+ clearCustomListeners(ownerPath);
5219
+
5220
+ const acceptDeps = (deps, callback = () => {}) => {
5221
+ const mod = hotModulesMap.get(ownerPath) || { id: ownerPath, callbacks: [] };
5222
+ mod.callbacks.push({ deps, fn: callback });
5223
+ hotModulesMap.set(ownerPath, mod);
5224
+ };
5225
+
4155
5226
  return {
4156
5227
  accept(deps, callback) {
4157
- // \u81EA\u63A5\u53D7: hot.accept() \u6216 hot.accept(callback)
4158
5228
  if (typeof deps === 'function' || deps === undefined) {
4159
- hotModulesMap.set(ownerPath, { callbacks: [deps || (() => {})] });
4160
- return;
5229
+ acceptDeps([ownerPath], ([mod]) => deps?.(mod));
5230
+ } else if (typeof deps === 'string') {
5231
+ acceptDeps([deps], ([mod]) => callback?.(mod));
5232
+ } else if (Array.isArray(deps)) {
5233
+ acceptDeps(deps, callback);
5234
+ } else {
5235
+ throw new Error('invalid hot.accept() usage');
4161
5236
  }
4162
- // \u4F9D\u8D56\u63A5\u53D7: hot.accept(deps, callback)\uFF0C\u591A\u6B21\u8C03\u7528\u8FFD\u52A0
4163
- const existing = hotModulesMap.get(ownerPath)?.callbacks ?? [];
4164
- hotModulesMap.set(ownerPath, { callbacks: [...existing, callback] });
4165
5237
  },
4166
5238
  prune(callback) {
4167
5239
  pruneMap.set(ownerPath, callback);
@@ -4169,24 +5241,116 @@ export function createHotContext(ownerPath) {
4169
5241
  dispose(callback) {
4170
5242
  disposeMap.set(ownerPath, callback);
4171
5243
  },
5244
+ on(event, callback) {
5245
+ let listenersByOwner = customListenersMap.get(event);
5246
+ if (!listenersByOwner) {
5247
+ listenersByOwner = new Map();
5248
+ customListenersMap.set(event, listenersByOwner);
5249
+ }
5250
+ let listeners = listenersByOwner.get(ownerPath);
5251
+ if (!listeners) {
5252
+ listeners = new Set();
5253
+ listenersByOwner.set(ownerPath, listeners);
5254
+ }
5255
+ listeners.add(callback);
5256
+ },
5257
+ off(event, callback) {
5258
+ const listenersByOwner = customListenersMap.get(event);
5259
+ const listeners = listenersByOwner?.get(ownerPath);
5260
+ listeners?.delete(callback);
5261
+ if (listeners?.size === 0) listenersByOwner.delete(ownerPath);
5262
+ if (listenersByOwner?.size === 0) customListenersMap.delete(event);
5263
+ },
4172
5264
  invalidate() {
4173
5265
  location.reload();
4174
5266
  },
4175
- data: {},
5267
+ data: dataMap.get(ownerPath),
4176
5268
  };
4177
5269
  }
5270
+
5271
+ function clearCustomListeners(ownerPath) {
5272
+ for (const [event, listenersByOwner] of customListenersMap) {
5273
+ listenersByOwner.delete(ownerPath);
5274
+ if (listenersByOwner.size === 0) customListenersMap.delete(event);
5275
+ }
5276
+ }
4178
5277
  `;
4179
5278
  }
4180
- var __dirname_esm, __require2, __refreshRuntimeCache, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
5279
+ var __dirname_esm, __require2, __refreshRuntimeCache, REACT_REFRESH_BOUNDARY_HELPERS, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
4181
5280
  var init_middleware = __esm({
4182
5281
  "src/server/middleware.ts"() {
4183
5282
  "use strict";
4184
5283
  init_transformer();
4185
5284
  init_html();
4186
5285
  init_env();
4187
- __dirname_esm = path11.dirname(fileURLToPath(import.meta.url));
5286
+ init_url();
5287
+ init_assets();
5288
+ __dirname_esm = path12.dirname(fileURLToPath(import.meta.url));
4188
5289
  __require2 = createRequire3(import.meta.url);
4189
5290
  __refreshRuntimeCache = null;
5291
+ REACT_REFRESH_BOUNDARY_HELPERS = `
5292
+ function __nastiIsPlainObject(obj) {
5293
+ return Object.prototype.toString.call(obj) === '[object Object]' &&
5294
+ (obj.constructor === Object || obj.constructor === undefined);
5295
+ }
5296
+ function __nastiIsCompoundComponent(type) {
5297
+ if (!__nastiIsPlainObject(type)) return false;
5298
+ for (const key in type) {
5299
+ if (!isLikelyComponentType(type[key])) return false;
5300
+ }
5301
+ return true;
5302
+ }
5303
+ export function registerExportsForReactRefresh(filename, moduleExports) {
5304
+ for (const key in moduleExports) {
5305
+ if (key === '__esModule') continue;
5306
+ const value = moduleExports[key];
5307
+ if (isLikelyComponentType(value)) {
5308
+ register(value, filename + ' export ' + key);
5309
+ } else if (__nastiIsCompoundComponent(value)) {
5310
+ for (const subKey in value) {
5311
+ register(value[subKey], filename + ' export ' + key + '-' + subKey);
5312
+ }
5313
+ }
5314
+ }
5315
+ }
5316
+ let __nastiRefreshTimer;
5317
+ function __nastiEnqueueRefresh() {
5318
+ clearTimeout(__nastiRefreshTimer);
5319
+ __nastiRefreshTimer = setTimeout(() => performReactRefresh(), 16);
5320
+ }
5321
+ function __nastiCheckExports(ignored, exports, predicate) {
5322
+ for (const key in exports) {
5323
+ if (ignored.includes(key)) continue;
5324
+ if (!predicate(key, exports[key])) return key;
5325
+ }
5326
+ return true;
5327
+ }
5328
+ export function validateRefreshBoundaryAndEnqueueUpdate(id, prevExports, nextExports) {
5329
+ const ignored = window.__getReactRefreshIgnoredExports?.({ id }) ?? [];
5330
+ if (__nastiCheckExports(ignored, prevExports, (key) => key in nextExports) !== true) {
5331
+ return 'Could not Fast Refresh (export removed)';
5332
+ }
5333
+ if (__nastiCheckExports(ignored, nextExports, (key) => key in prevExports) !== true) {
5334
+ return 'Could not Fast Refresh (new export)';
5335
+ }
5336
+ let hasExports = false;
5337
+ const compatible = __nastiCheckExports(ignored, nextExports, (key, value) => {
5338
+ hasExports = true;
5339
+ return isLikelyComponentType(value) ||
5340
+ __nastiIsCompoundComponent(value) ||
5341
+ prevExports[key] === value;
5342
+ });
5343
+ if (!hasExports) {
5344
+ return 'Could not Fast Refresh (no exports)';
5345
+ }
5346
+ if (compatible === true) {
5347
+ __nastiEnqueueRefresh();
5348
+ return;
5349
+ }
5350
+ return 'Could not Fast Refresh ("' + compatible + '" export is incompatible)';
5351
+ }
5352
+ export const __hmr_import = (module) => import(module);
5353
+ `;
4190
5354
  REACT_REFRESH_GLOBAL_PREAMBLE = `
4191
5355
  import RefreshRuntime from "/@react-refresh";
4192
5356
  RefreshRuntime.injectIntoGlobalHook(window);
@@ -4202,31 +5366,40 @@ window.__vite_plugin_react_preamble_installed__ = true;
4202
5366
  });
4203
5367
 
4204
5368
  // src/server/hmr.ts
4205
- import path12 from "path";
4206
- import fs9 from "fs";
5369
+ import path13 from "path";
5370
+ import fs10 from "fs";
4207
5371
  import pc7 from "picocolors";
4208
- async function handleFileChange(file, server) {
4209
- const { moduleGraph, ws, config } = server;
5372
+ async function handleFileChange(file, server, environmentName = "client", timestamp = Date.now()) {
5373
+ const { config } = server;
5374
+ const environment = server.environments[environmentName];
5375
+ if (!environment) {
5376
+ throw new Error(`[nasti] unknown dev environment "${environmentName}"`);
5377
+ }
5378
+ const moduleGraph = environment.moduleGraph;
4210
5379
  const logger = config.logger;
4211
- const relativePath = "/" + path12.relative(config.root, file);
4212
- const shortFile = path12.relative(config.root, file);
5380
+ const relativePath = "/" + path13.relative(config.root, file);
5381
+ const shortFile = path13.relative(config.root, file);
4213
5382
  const mods = moduleGraph.getModulesByFile(file);
4214
5383
  if (!mods || mods.size === 0) {
4215
- return;
5384
+ return null;
4216
5385
  }
4217
5386
  const updates = [];
4218
- const timestamp = Date.now();
5387
+ const graph = moduleGraph;
5388
+ const invalidatedModules = /* @__PURE__ */ new Set();
5389
+ const affectedSet = /* @__PURE__ */ new Set();
5390
+ let fullReload = false;
4219
5391
  for (const mod of mods) {
4220
- moduleGraph.invalidateModule(mod);
5392
+ graph.invalidateModuleAndImporters(mod, timestamp, invalidatedModules);
4221
5393
  const ctx = {
4222
5394
  file,
4223
5395
  timestamp,
4224
5396
  modules: [mod],
4225
- read: () => fs9.readFileSync(file, "utf-8"),
4226
- server
5397
+ read: () => fs10.readFileSync(file, "utf-8"),
5398
+ server,
5399
+ environment
4227
5400
  };
4228
5401
  let affectedModules = [mod];
4229
- for (const plugin of config.plugins) {
5402
+ for (const plugin of environment.plugins) {
4230
5403
  if (plugin.handleHotUpdate) {
4231
5404
  const result = await plugin.handleHotUpdate(ctx);
4232
5405
  if (result) {
@@ -4235,29 +5408,52 @@ async function handleFileChange(file, server) {
4235
5408
  }
4236
5409
  }
4237
5410
  for (const affected of affectedModules) {
4238
- const boundaries = moduleGraph.getHmrBoundaries(affected);
5411
+ affectedSet.add(affected);
5412
+ graph.invalidateModuleAndImporters(affected, timestamp, invalidatedModules);
5413
+ const boundaries = graph.getHmrBoundaries(affected);
4239
5414
  if (boundaries.length === 0) {
4240
- logger.info(pc7.green("page reload ") + pc7.dim(shortFile), { timestamp: true });
4241
- ws.send({ type: "full-reload", path: relativePath });
4242
- return;
5415
+ fullReload = true;
5416
+ continue;
4243
5417
  }
4244
- for (const { boundary } of boundaries) {
4245
- updates.push({
5418
+ for (const { boundary, acceptedVia } of boundaries) {
5419
+ const update = {
4246
5420
  type: boundary.type === "css" ? "css-update" : "js-update",
4247
5421
  path: boundary.url,
4248
- acceptedPath: affected.url,
5422
+ acceptedPath: acceptedVia.url,
4249
5423
  timestamp
4250
- });
5424
+ };
5425
+ if (!updates.some(
5426
+ (existing) => existing.type === update.type && existing.path === update.path && existing.acceptedPath === update.acceptedPath
5427
+ )) {
5428
+ updates.push(update);
5429
+ }
4251
5430
  }
4252
5431
  }
4253
5432
  }
4254
- if (updates.length > 0) {
5433
+ const transformed = await Promise.all(
5434
+ [...affectedSet].map(async (module) => ({
5435
+ module,
5436
+ result: await environment.transformRequest(module.url)
5437
+ }))
5438
+ );
5439
+ const logPrefix = environmentName === "client" ? "" : `[${environmentName}] `;
5440
+ if (fullReload) {
5441
+ logger.info(pc7.green(`${logPrefix}reload `) + pc7.dim(shortFile), { timestamp: true });
5442
+ environment.hot.send({ type: "full-reload", path: relativePath });
5443
+ } else if (updates.length > 0) {
4255
5444
  logger.info(
4256
- updates.map((u) => pc7.green("hmr update ") + pc7.dim(u.path)).join("\n"),
5445
+ updates.map((u) => pc7.green(`${logPrefix}hmr update `) + pc7.dim(u.path)).join("\n"),
4257
5446
  { timestamp: true }
4258
5447
  );
4259
- ws.send({ type: "update", updates });
5448
+ environment.hot.send({ type: "update", updates });
4260
5449
  }
5450
+ return {
5451
+ environment,
5452
+ modules: [...affectedSet],
5453
+ updates,
5454
+ transformed,
5455
+ fullReload
5456
+ };
4261
5457
  }
4262
5458
  var init_hmr = __esm({
4263
5459
  "src/server/hmr.ts"() {
@@ -4271,8 +5467,8 @@ __export(runnable_environment_exports, {
4271
5467
  NastiModuleRunner: () => NastiModuleRunner,
4272
5468
  createModuleRunner: () => createModuleRunner
4273
5469
  });
4274
- import path13 from "path";
4275
- import fs10 from "fs";
5470
+ import path14 from "path";
5471
+ import fs11 from "fs";
4276
5472
  import { builtinModules as builtinModules3, createRequire as createRequire4 } from "module";
4277
5473
  import { pathToFileURL as pathToFileURL4 } from "url";
4278
5474
  function createModuleRunner(environment) {
@@ -4283,14 +5479,14 @@ function createModuleRunner(environment) {
4283
5479
  }
4284
5480
  return new NastiModuleRunner(environment);
4285
5481
  }
4286
- var debug5, NODE_BUILTINS3, NastiModuleRunner, AsyncFunction;
5482
+ var debug6, NODE_BUILTINS3, NastiModuleRunner, AsyncFunction;
4287
5483
  var init_runnable_environment = __esm({
4288
5484
  "src/server/runnable-environment.ts"() {
4289
5485
  "use strict";
4290
5486
  init_transformer();
4291
5487
  init_env();
4292
5488
  init_debug();
4293
- debug5 = createDebugger("nasti:ssr");
5489
+ debug6 = createDebugger("nasti:ssr");
4294
5490
  NODE_BUILTINS3 = /* @__PURE__ */ new Set([...builtinModules3, ...builtinModules3.map((m) => `node:${m}`)]);
4295
5491
  NastiModuleRunner = class {
4296
5492
  environment;
@@ -4306,7 +5502,7 @@ var init_runnable_environment = __esm({
4306
5502
  this.config.mode,
4307
5503
  ssrDefineOverrides(environment.consumer)
4308
5504
  );
4309
- this.require = createRequire4(path13.join(this.config.root, "package.json"));
5505
+ this.require = createRequire4(path14.join(this.config.root, "package.json"));
4310
5506
  const handlers = {
4311
5507
  fetchModule: async (id, importer) => this.fetchModule(id, importer),
4312
5508
  getBuiltins: () => [/^node:/, ...builtinModules3]
@@ -4330,9 +5526,9 @@ var init_runnable_environment = __esm({
4330
5526
  this.cache.clear();
4331
5527
  }
4332
5528
  resolveToId(rawUrl) {
4333
- if (path13.isAbsolute(rawUrl) && fs10.existsSync(rawUrl.split("?")[0])) return rawUrl;
5529
+ if (path14.isAbsolute(rawUrl) && fs11.existsSync(rawUrl.split("?")[0])) return rawUrl;
4334
5530
  const clean = rawUrl.replace(/^\//, "");
4335
- return path13.resolve(this.config.root, clean);
5531
+ return path14.resolve(this.config.root, clean);
4336
5532
  }
4337
5533
  /**
4338
5534
  * fetchModule(invoke 契约方法):环境管线产出 runner 可执行代码。
@@ -4341,14 +5537,14 @@ var init_runnable_environment = __esm({
4341
5537
  */
4342
5538
  async fetchModule(id, importer) {
4343
5539
  if (NODE_BUILTINS3.has(id)) return { externalize: id };
4344
- if (!id.startsWith(".") && !path13.isAbsolute(id) && !id.startsWith("\0")) {
5540
+ if (!id.startsWith(".") && !path14.isAbsolute(id) && !id.startsWith("\0")) {
4345
5541
  return { externalize: id };
4346
5542
  }
4347
5543
  const container = this.environment.pluginContainer;
4348
5544
  let resolvedId = id;
4349
5545
  if (id.startsWith(".") && importer) {
4350
5546
  const resolved = await container.resolveId(id, importer);
4351
- resolvedId = resolved ? typeof resolved === "string" ? resolved : resolved.id : path13.resolve(path13.dirname(importer.split("?")[0]), id);
5547
+ resolvedId = resolved ? typeof resolved === "string" ? resolved : resolved.id : path14.resolve(path14.dirname(importer.split("?")[0]), id);
4352
5548
  }
4353
5549
  resolvedId = this.completeExtension(resolvedId);
4354
5550
  const cleanId = resolvedId.split("?")[0];
@@ -4356,8 +5552,8 @@ var init_runnable_environment = __esm({
4356
5552
  const loaded = await container.load(resolvedId);
4357
5553
  if (loaded != null) {
4358
5554
  code = typeof loaded === "string" ? loaded : loaded.code;
4359
- } else if (fs10.existsSync(cleanId)) {
4360
- code = fs10.readFileSync(cleanId, "utf-8");
5555
+ } else if (fs11.existsSync(cleanId)) {
5556
+ code = fs11.readFileSync(cleanId, "utf-8");
4361
5557
  } else {
4362
5558
  throw new Error(`[nasti:ssr] cannot load module: ${resolvedId}`);
4363
5559
  }
@@ -4368,6 +5564,7 @@ var init_runnable_environment = __esm({
4368
5564
  if (shouldTransform(cleanId)) {
4369
5565
  const result = transformCode(cleanId, code, {
4370
5566
  sourcemap: false,
5567
+ target: this.environment.options.build.target,
4371
5568
  jsxRuntime: "automatic",
4372
5569
  jsxImportSource: this.config.framework === "vue" ? "vue" : "react"
4373
5570
  });
@@ -4384,25 +5581,25 @@ var init_runnable_environment = __esm({
4384
5581
  );
4385
5582
  }
4386
5583
  const runnerResult = await moduleRunnerTransform(resolvedId, code);
4387
- debug5?.(`fetchModule ${resolvedId} (${runnerResult.deps?.length ?? 0} deps)`);
5584
+ debug6?.(`fetchModule ${resolvedId} (${runnerResult.deps?.length ?? 0} deps)`);
4388
5585
  return { id: resolvedId, code: runnerResult.code };
4389
5586
  }
4390
5587
  completeExtension(id) {
4391
5588
  const clean = id.split("?")[0];
4392
5589
  const query = id.includes("?") ? id.slice(id.indexOf("?")) : "";
4393
- if (fs10.existsSync(clean) && fs10.statSync(clean).isFile()) return id;
5590
+ if (fs11.existsSync(clean) && fs11.statSync(clean).isFile()) return id;
4394
5591
  const jsMatch = clean.match(/^(.*)\.([mc]?)jsx?$/);
4395
5592
  if (jsMatch) {
4396
5593
  for (const tsExt of [`.${jsMatch[2]}ts`, `.${jsMatch[2]}tsx`]) {
4397
- if (fs10.existsSync(jsMatch[1] + tsExt)) return jsMatch[1] + tsExt + query;
5594
+ if (fs11.existsSync(jsMatch[1] + tsExt)) return jsMatch[1] + tsExt + query;
4398
5595
  }
4399
5596
  }
4400
5597
  for (const ext of this.config.resolve.extensions) {
4401
- if (fs10.existsSync(clean + ext)) return clean + ext + query;
5598
+ if (fs11.existsSync(clean + ext)) return clean + ext + query;
4402
5599
  }
4403
5600
  for (const ext of this.config.resolve.extensions) {
4404
- const indexPath = path13.join(clean, `index${ext}`);
4405
- if (fs10.existsSync(indexPath)) return indexPath;
5601
+ const indexPath = path14.join(clean, `index${ext}`);
5602
+ if (fs11.existsSync(indexPath)) return indexPath;
4406
5603
  }
4407
5604
  return id;
4408
5605
  }
@@ -4429,10 +5626,10 @@ var init_runnable_environment = __esm({
4429
5626
  return;
4430
5627
  }
4431
5628
  const ssrImport = async (dep) => {
4432
- if (NODE_BUILTINS3.has(dep) || !dep.startsWith(".") && !path13.isAbsolute(dep) && !dep.startsWith("\0")) {
5629
+ if (NODE_BUILTINS3.has(dep) || !dep.startsWith(".") && !path14.isAbsolute(dep) && !dep.startsWith("\0")) {
4433
5630
  return this.importExternal(dep);
4434
5631
  }
4435
- const depId = dep.startsWith(".") ? this.completeExtension(path13.resolve(path13.dirname(fetched.id.split("?")[0]), dep)) : this.completeExtension(dep);
5632
+ const depId = dep.startsWith(".") ? this.completeExtension(path14.resolve(path14.dirname(fetched.id.split("?")[0]), dep)) : this.completeExtension(dep);
4436
5633
  return this.instantiate(depId);
4437
5634
  };
4438
5635
  const ssrExportAll = (sourceModule) => {
@@ -4464,7 +5661,7 @@ var init_runnable_environment = __esm({
4464
5661
  }
4465
5662
  async importExternal(spec) {
4466
5663
  try {
4467
- return await (spec.startsWith("node:") || !path13.isAbsolute(spec) ? import(this.resolveExternalSpecifier(spec)) : import(pathToFileURL4(spec).href));
5664
+ return await (spec.startsWith("node:") || !path14.isAbsolute(spec) ? import(this.resolveExternalSpecifier(spec)) : import(pathToFileURL4(spec).href));
4468
5665
  } catch (err) {
4469
5666
  throw new Error(`[nasti:ssr] failed to import external "${spec}": ${err.message}`);
4470
5667
  }
@@ -4490,7 +5687,7 @@ var dev_engine_exports = {};
4490
5687
  __export(dev_engine_exports, {
4491
5688
  createBundledDevServer: () => createBundledDevServer
4492
5689
  });
4493
- import path14 from "path";
5690
+ import path15 from "path";
4494
5691
  import crypto3 from "crypto";
4495
5692
  import { WebSocketServer as WsServer2 } from "ws";
4496
5693
  import pc8 from "picocolors";
@@ -4508,7 +5705,7 @@ async function createBundledDevServer(opts) {
4508
5705
  }
4509
5706
  } catch (err) {
4510
5707
  throw new Error(
4511
- `[nasti] experimental.bundledDev requires rolldown's experimental dev() API (locked to the installed rc; got: ${err.message}). Remove --bundle / experimental.bundledDev to use the default unbundled dev server.`
5708
+ `[nasti] experimental.bundledDev requires rolldown's experimental dev() API (locked version is incompatible; got: ${err.message}). Remove --bundle / experimental.bundledDev to use the default unbundled dev server.`
4512
5709
  );
4513
5710
  }
4514
5711
  const html = await readHtmlFile(config.root, config.environments.client?.html);
@@ -4526,7 +5723,7 @@ async function createBundledDevServer(opts) {
4526
5723
  createReactRefreshRuntimePlugin(entryPoints),
4527
5724
  createBundledOxcRefreshPlugin()
4528
5725
  ] : [],
4529
- ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins)),
5726
+ ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins, clientEnv)),
4530
5727
  ...useReactRefresh ? [
4531
5728
  refreshWrapperFn({
4532
5729
  cwd: config.root,
@@ -4561,7 +5758,7 @@ async function createBundledDevServer(opts) {
4561
5758
  for (const { clientId, update } of updates) {
4562
5759
  if (update.type === "Noop") continue;
4563
5760
  if (update.type === "FullReload") {
4564
- debug6?.(`full reload for ${clientId}: ${update.reason ?? ""}`);
5761
+ debug7?.(`full reload for ${clientId}: ${update.reason ?? ""}`);
4565
5762
  needsLatestOutput = true;
4566
5763
  continue;
4567
5764
  }
@@ -4575,7 +5772,7 @@ async function createBundledDevServer(opts) {
4575
5772
  }
4576
5773
  const url = `/${patchPath}`;
4577
5774
  logger.info(
4578
- pc8.green("hmr update ") + pc8.dim(changedFiles.map((f) => path14.relative(config.root, f)).join(", ")),
5775
+ pc8.green("hmr update ") + pc8.dim(changedFiles.map((f) => path15.relative(config.root, f)).join(", ")),
4579
5776
  { timestamp: true }
4580
5777
  );
4581
5778
  sendTo(clientId, { type: "hmr:update", path: url, url });
@@ -4611,7 +5808,7 @@ async function createBundledDevServer(opts) {
4611
5808
  },
4612
5809
  {
4613
5810
  watch: { skipWrite: true },
4614
- rebuildStrategy: "auto",
5811
+ rebuildStrategy: "never",
4615
5812
  onOutput(result) {
4616
5813
  if (result instanceof Error) {
4617
5814
  logger.error(pc8.red(`[bundled] build error: ${result.message}`), { error: result });
@@ -4627,7 +5824,13 @@ async function createBundledDevServer(opts) {
4627
5824
  memoryFiles.set(`${file.fileName}.map`, JSON.stringify(file.map));
4628
5825
  }
4629
5826
  }
4630
- debug6?.(`bundle output refreshed (${result.output.length} files)`);
5827
+ debug7?.(`bundle output refreshed (${result.output.length} files)`);
5828
+ },
5829
+ onAdditionalAssets(result) {
5830
+ for (const file of result.output) {
5831
+ const content = file.type === "chunk" ? file.code : file.source;
5832
+ if (content != null) memoryFiles.set(file.fileName, content);
5833
+ }
4631
5834
  },
4632
5835
  async onHmrUpdates(result) {
4633
5836
  if (result instanceof Error) {
@@ -4636,7 +5839,7 @@ async function createBundledDevServer(opts) {
4636
5839
  return;
4637
5840
  }
4638
5841
  const { updates, changedFiles } = result;
4639
- debug6?.(
5842
+ debug7?.(
4640
5843
  `onHmrUpdates(engine watcher): ${changedFiles.length} changed, ${updates.length} updates`
4641
5844
  );
4642
5845
  if (changedFiles.length === 0) return;
@@ -4655,24 +5858,29 @@ async function createBundledDevServer(opts) {
4655
5858
  if (!clientId) return;
4656
5859
  wss.handleUpgrade(req, socket, head, (ws) => {
4657
5860
  bundledClients.set(clientId, ws);
4658
- debug6?.(`bundled client connected: ${clientId}`);
4659
- ws.send(JSON.stringify({ type: "connected" }));
5861
+ debug7?.(`bundled client connected: ${clientId}`);
5862
+ void engine.registerClient(clientId).then(async () => {
5863
+ for (const fileName of entryFileNames.values()) {
5864
+ await engine.notifyPayloadDelivered(fileName);
5865
+ }
5866
+ ws.send(JSON.stringify({ type: "connected" }));
5867
+ }).catch((err) => {
5868
+ debug7?.(`registerClient failed for ${clientId}: ${err?.message ?? err}`);
5869
+ ws.close();
5870
+ });
4660
5871
  ws.on("message", async (raw) => {
4661
5872
  try {
4662
5873
  const msg = JSON.parse(String(raw));
4663
- if (msg.type === "hmr:module-registered" && Array.isArray(msg.modules)) {
4664
- await engine.registerModules(clientId, msg.modules);
4665
- debug6?.(`registered ${msg.modules.length} modules for ${clientId}`);
4666
- } else if (msg.type === "hmr:invalidate") {
5874
+ if (msg.type === "hmr:invalidate") {
4667
5875
  scheduleFullReload();
4668
5876
  }
4669
5877
  } catch (err) {
4670
- debug6?.(`bundled ws message error: ${err.message}`);
5878
+ debug7?.(`bundled ws message error: ${err.message}`);
4671
5879
  }
4672
5880
  });
4673
5881
  ws.on("close", () => {
4674
5882
  bundledClients.delete(clientId);
4675
- engine.removeClient(clientId).catch((err) => debug6?.(`removeClient failed for ${clientId}: ${err?.message ?? err}`));
5883
+ engine.removeClient(clientId).catch((err) => debug7?.(`removeClient failed for ${clientId}: ${err?.message ?? err}`));
4676
5884
  });
4677
5885
  });
4678
5886
  });
@@ -4689,10 +5897,18 @@ async function createBundledDevServer(opts) {
4689
5897
  res.end("// [nasti] lazy endpoint requires id & clientId");
4690
5898
  return;
4691
5899
  }
4692
- const code = await engine.compileEntry(id, clientId);
5900
+ const output = await engine.compileEntry(id, clientId);
5901
+ if (output.sourcemap && output.sourcemapFilename) {
5902
+ memoryFiles.set(output.sourcemapFilename, output.sourcemap);
5903
+ }
5904
+ res.once("finish", () => {
5905
+ void engine.notifyPayloadDelivered(output.filename).catch(
5906
+ (err) => debug7?.(`notifyPayloadDelivered failed: ${err?.message ?? err}`)
5907
+ );
5908
+ });
4693
5909
  res.setHeader("Content-Type", "application/javascript");
4694
5910
  res.setHeader("Cache-Control", "no-store");
4695
- res.end(code + "\n;export {}");
5911
+ res.end(output.code + "\n;export {}");
4696
5912
  return;
4697
5913
  }
4698
5914
  const patchHit = patches.get(pathname.replace(/^\//, ""));
@@ -4711,8 +5927,13 @@ async function createBundledDevServer(opts) {
4711
5927
  return;
4712
5928
  }
4713
5929
  res.setHeader("ETag", hit.etag);
4714
- res.setHeader("Content-Type", MIME_TYPES[path14.extname(fileName)] ?? "application/octet-stream");
5930
+ res.setHeader("Content-Type", MIME_TYPES[path15.extname(fileName)] ?? "application/octet-stream");
4715
5931
  res.setHeader("Cache-Control", "no-cache");
5932
+ res.once("finish", () => {
5933
+ void engine.notifyPayloadDelivered(fileName).catch(
5934
+ (err) => debug7?.(`notifyPayloadDelivered failed: ${err?.message ?? err}`)
5935
+ );
5936
+ });
4716
5937
  res.end(hit.content);
4717
5938
  return;
4718
5939
  }
@@ -4747,7 +5968,7 @@ function stripCatchAllLoad(plugins) {
4747
5968
  );
4748
5969
  }
4749
5970
  function createReactRefreshRuntimePlugin(entryPoints) {
4750
- const entryIds = new Set(entryPoints.map((p) => path14.resolve(p)));
5971
+ const entryIds = new Set(entryPoints.map((p) => path15.resolve(p)));
4751
5972
  return {
4752
5973
  name: "nasti:bundled-react-refresh",
4753
5974
  resolveId(source) {
@@ -4765,7 +5986,7 @@ function createReactRefreshRuntimePlugin(entryPoints) {
4765
5986
  return null;
4766
5987
  },
4767
5988
  transform(code, id) {
4768
- if (!entryIds.has(path14.resolve(id.split("?")[0]))) return null;
5989
+ if (!entryIds.has(path15.resolve(id.split("?")[0]))) return null;
4769
5990
  return { code: `import ${JSON.stringify(PREAMBLE_SPEC)};
4770
5991
  ${code}`, map: null };
4771
5992
  }
@@ -4812,7 +6033,7 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
4812
6033
  }
4813
6034
  return processed;
4814
6035
  }
4815
- var debug6, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
6036
+ var debug7, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
4816
6037
  var init_dev_engine = __esm({
4817
6038
  "src/server/bundled/dev-engine.ts"() {
4818
6039
  "use strict";
@@ -4821,7 +6042,7 @@ var init_dev_engine = __esm({
4821
6042
  init_transformer();
4822
6043
  init_middleware();
4823
6044
  init_debug();
4824
- debug6 = createDebugger("nasti:bundled");
6045
+ debug7 = createDebugger("nasti:bundled");
4825
6046
  MIME_TYPES = {
4826
6047
  ".js": "application/javascript",
4827
6048
  ".mjs": "application/javascript",
@@ -4936,7 +6157,7 @@ __export(server_exports, {
4936
6157
  createServer: () => createServer
4937
6158
  });
4938
6159
  import http from "http";
4939
- import path15 from "path";
6160
+ import path16 from "path";
4940
6161
  import os from "os";
4941
6162
  import connect from "connect";
4942
6163
  import sirv from "sirv";
@@ -4946,14 +6167,16 @@ async function createServer(inlineConfig = {}) {
4946
6167
  const startTime = performance.now();
4947
6168
  const config = await resolveConfig(inlineConfig, "serve");
4948
6169
  const logger = config.logger;
4949
- const allPlugins = resolvePluginList(config, config.plugins);
6170
+ const allPlugins = resolvePluginList(config, config.plugins, {
6171
+ environmentName: "client"
6172
+ });
4950
6173
  const configWithPlugins = { ...config, plugins: allPlugins };
4951
6174
  const app = connect();
4952
6175
  const httpServer = http.createServer(app);
4953
6176
  const ws = createWebSocketServer(httpServer);
4954
6177
  const pluginApi = getPluginApi(config);
4955
6178
  const clientEnv = new NastiEnvironment("client", config, {
4956
- hot: createWsHotChannel(ws),
6179
+ hot: createWsHotChannel(ws, "client"),
4957
6180
  mode: "dev",
4958
6181
  plugins: allPlugins,
4959
6182
  pluginApi
@@ -4963,15 +6186,45 @@ async function createServer(inlineConfig = {}) {
4963
6186
  for (const name of Object.keys(config.environments)) {
4964
6187
  if (name === "client") continue;
4965
6188
  const consumer = config.environments[name].consumer;
4966
- const envPlugins = resolvePluginList(config, config.plugins, { consumer });
6189
+ const envPlugins = resolvePluginList(config, config.plugins, {
6190
+ consumer,
6191
+ environmentName: name
6192
+ });
4967
6193
  environments[name] = new NastiEnvironment(name, config, {
6194
+ hot: consumer === "client" ? createWsHotChannel(ws, name) : void 0,
4968
6195
  mode: "dev",
4969
6196
  plugins: envPlugins,
4970
6197
  pluginApi
4971
6198
  });
4972
6199
  }
4973
6200
  for (const [name, environment] of Object.entries(environments)) {
4974
- if (name !== "client" && environment.options.driver) await environment.init();
6201
+ if (name === "client" || environment.consumer === "client" || environment.options.driver) {
6202
+ await environment.init();
6203
+ }
6204
+ }
6205
+ const transformContexts = /* @__PURE__ */ new Map();
6206
+ for (const environment of Object.values(environments)) {
6207
+ if (environment.consumer !== "client" || environment.driver) continue;
6208
+ const environmentConfig = {
6209
+ ...configWithPlugins,
6210
+ resolve: environment.options.resolve,
6211
+ build: environment.options.build,
6212
+ plugins: environment.plugins
6213
+ };
6214
+ const context = {
6215
+ config: environmentConfig,
6216
+ pluginContainer: environment.pluginContainer,
6217
+ moduleGraph: environment.moduleGraph,
6218
+ environment,
6219
+ envDefine: buildEnvDefine(
6220
+ loadEnv(environmentConfig.mode, environmentConfig.root, environmentConfig.envPrefix),
6221
+ environmentConfig.mode,
6222
+ ssrDefineOverrides(environment.consumer)
6223
+ ),
6224
+ onPrune: (paths) => environment.hot.send({ type: "prune", paths })
6225
+ };
6226
+ transformContexts.set(environment.name, context);
6227
+ environment.configureDevPipeline((url) => transformRequest(url, context));
4975
6228
  }
4976
6229
  let ssrRunner = null;
4977
6230
  async function getSsrRunner() {
@@ -4986,7 +6239,6 @@ async function createServer(inlineConfig = {}) {
4986
6239
  return ssrRunner;
4987
6240
  }
4988
6241
  const moduleGraph = clientEnv.moduleGraph;
4989
- const pluginContainer = clientEnv.pluginContainer;
4990
6242
  let bundledServer = null;
4991
6243
  if (config.experimental.bundledDev) {
4992
6244
  const { createBundledDevServer: createBundledDevServer2 } = await Promise.resolve().then(() => (init_dev_engine(), dev_engine_exports));
@@ -4998,14 +6250,14 @@ async function createServer(inlineConfig = {}) {
4998
6250
  app.use(bundledServer.middleware);
4999
6251
  }
5000
6252
  const ignoredSegments = /* @__PURE__ */ new Set(["node_modules", ".git", ".nasti"]);
5001
- const outDirAbs = path15.resolve(config.root, config.build.outDir);
6253
+ const outDirAbs = path16.resolve(config.root, config.build.outDir);
5002
6254
  const watcher = watch(config.root, {
5003
6255
  ignored: (filePath) => {
5004
6256
  if (filePath === config.root) return false;
5005
- if (filePath === outDirAbs || filePath.startsWith(outDirAbs + path15.sep)) return true;
5006
- const rel = path15.relative(config.root, filePath);
5007
- if (!rel || rel.startsWith("..") || path15.isAbsolute(rel)) return false;
5008
- for (const seg of rel.split(path15.sep)) {
6257
+ if (filePath === outDirAbs || filePath.startsWith(outDirAbs + path16.sep)) return true;
6258
+ const rel = path16.relative(config.root, filePath);
6259
+ if (!rel || rel.startsWith("..") || path16.isAbsolute(rel)) return false;
6260
+ for (const seg of rel.split(path16.sep)) {
5009
6261
  if (ignoredSegments.has(seg)) return true;
5010
6262
  }
5011
6263
  return false;
@@ -5015,6 +6267,15 @@ async function createServer(inlineConfig = {}) {
5015
6267
  let server;
5016
6268
  const environmentServices = {};
5017
6269
  let environmentDriversStarted = false;
6270
+ let devPipelinesStarted = false;
6271
+ const startDevPipelines = async () => {
6272
+ if (devPipelinesStarted) return;
6273
+ devPipelinesStarted = true;
6274
+ for (const environment of Object.values(environments)) {
6275
+ if (!transformContexts.has(environment.name)) continue;
6276
+ await environment.pluginContainer.buildStart();
6277
+ }
6278
+ };
5018
6279
  const logCloseError = (target, error) => {
5019
6280
  const normalized = error instanceof Error ? error : new Error(String(error));
5020
6281
  logger.error(`[nasti] failed to close ${target}`, { error: normalized });
@@ -5066,14 +6327,61 @@ async function createServer(inlineConfig = {}) {
5066
6327
  });
5067
6328
  }
5068
6329
  };
6330
+ const updateClientEnvironments = async (file) => {
6331
+ const timestamp = Date.now();
6332
+ const results = {};
6333
+ for (const environment of Object.values(environments)) {
6334
+ if (environment.consumer !== "client" || environment.driver) continue;
6335
+ try {
6336
+ const result = await handleFileChange(file, server, environment.name, timestamp);
6337
+ if (result) results[environment.name] = result;
6338
+ } catch (error) {
6339
+ const normalized = error instanceof Error ? error : new Error(String(error));
6340
+ logger.error(
6341
+ `[nasti] HMR failed for environment "${environment.name}": ${normalized.message}`,
6342
+ { error: normalized }
6343
+ );
6344
+ try {
6345
+ environment.hot.send({
6346
+ type: "error",
6347
+ err: { message: normalized.message, stack: normalized.stack }
6348
+ });
6349
+ } catch (channelError) {
6350
+ const channelFailure = channelError instanceof Error ? channelError : new Error(String(channelError));
6351
+ logger.error(
6352
+ `[nasti] failed to deliver HMR error to environment "${environment.name}"`,
6353
+ { error: channelFailure }
6354
+ );
6355
+ }
6356
+ }
6357
+ }
6358
+ if (Object.keys(results).length === 0) return;
6359
+ const context = {
6360
+ file,
6361
+ timestamp,
6362
+ environments: Object.freeze({ ...results }),
6363
+ server
6364
+ };
6365
+ for (const plugin of config.plugins) {
6366
+ await plugin.handleHotUpdateApp?.(context);
6367
+ }
6368
+ };
6369
+ const queueClientEnvironmentUpdate = (file) => {
6370
+ void updateClientEnvironments(file).catch((error) => {
6371
+ const normalized = error instanceof Error ? error : new Error(String(error));
6372
+ logger.error(`[nasti] multi-environment HMR failed: ${normalized.message}`, {
6373
+ error: normalized
6374
+ });
6375
+ });
6376
+ };
5069
6377
  watcher.on("change", (file) => {
5070
6378
  ssrRunner?.invalidateFile(file);
5071
- handleFileChange(file, server);
6379
+ queueClientEnvironmentUpdate(file);
5072
6380
  notifyEnvironmentDrivers(file, "change");
5073
6381
  });
5074
6382
  watcher.on("add", (file) => {
5075
6383
  ssrRunner?.invalidateFile(file);
5076
- handleFileChange(file, server);
6384
+ queueClientEnvironmentUpdate(file);
5077
6385
  notifyEnvironmentDrivers(file, "add");
5078
6386
  });
5079
6387
  watcher.on("unlink", (file) => {
@@ -5091,7 +6399,7 @@ async function createServer(inlineConfig = {}) {
5091
6399
  async listen(port) {
5092
6400
  const finalPort = port ?? config.server.port;
5093
6401
  const host = config.server.host === true ? "0.0.0.0" : config.server.host;
5094
- await pluginContainer.buildStart();
6402
+ await startDevPipelines();
5095
6403
  await startEnvironmentDrivers();
5096
6404
  return new Promise((resolve, reject) => {
5097
6405
  let currentPort = finalPort;
@@ -5106,7 +6414,7 @@ async function createServer(inlineConfig = {}) {
5106
6414
  const readyIn = Math.ceil(performance.now() - startTime);
5107
6415
  logger.info(
5108
6416
  `
5109
- ${pc9.cyan(pc9.bold("NASTI"))} ${pc9.cyan(`v${"2.3.1"}`)} ${pc9.dim("ready in")} ${pc9.bold(readyIn)} ${pc9.dim("ms")}
6417
+ ${pc9.cyan(pc9.bold("NASTI"))} ${pc9.cyan(`v${"2.4.1"}`)} ${pc9.dim("ready in")} ${pc9.bold(readyIn)} ${pc9.dim("ms")}
5110
6418
  `
5111
6419
  );
5112
6420
  printServerUrls(
@@ -5133,15 +6441,26 @@ async function createServer(inlineConfig = {}) {
5133
6441
  });
5134
6442
  },
5135
6443
  async transformRequest(url) {
5136
- const { transformRequest: transformRequest2 } = await Promise.resolve().then(() => (init_middleware(), middleware_exports));
5137
- return transformRequest2(url, { config: configWithPlugins, pluginContainer, moduleGraph });
6444
+ return clientEnv.transformRequest(url);
6445
+ },
6446
+ async transformEnvironmentRequest(environmentName, url) {
6447
+ const environment = environments[environmentName];
6448
+ if (!environment) {
6449
+ throw new Error(`[nasti] unknown dev environment "${environmentName}"`);
6450
+ }
6451
+ return environment.transformRequest(url);
5138
6452
  },
5139
6453
  async ssrLoadModule(url) {
5140
6454
  const runner = await getSsrRunner();
5141
6455
  return runner.import(url);
5142
6456
  },
5143
6457
  async close() {
5144
- await pluginContainer.buildEnd();
6458
+ if (devPipelinesStarted) {
6459
+ for (const environment of Object.values(environments).reverse()) {
6460
+ if (!transformContexts.has(environment.name)) continue;
6461
+ await environment.pluginContainer.buildEnd();
6462
+ }
6463
+ }
5145
6464
  await bundledServer?.close();
5146
6465
  let environmentCloseFailed = false;
5147
6466
  let firstEnvironmentCloseError;
@@ -5191,12 +6510,8 @@ async function createServer(inlineConfig = {}) {
5191
6510
  }
5192
6511
  throw error;
5193
6512
  }
5194
- app.use(transformMiddleware({
5195
- config: configWithPlugins,
5196
- pluginContainer,
5197
- moduleGraph
5198
- }));
5199
- const publicDir = path15.resolve(config.root, "public");
6513
+ app.use(transformMiddleware(transformContexts.get("client")));
6514
+ const publicDir = path16.resolve(config.root, "public");
5200
6515
  app.use(sirv(publicDir, { dev: true, etag: true }));
5201
6516
  app.use(sirv(config.root, { dev: true, etag: true }));
5202
6517
  const postMiddlewares = [];
@@ -5234,6 +6549,7 @@ var init_server = __esm({
5234
6549
  init_hmr();
5235
6550
  init_builtins();
5236
6551
  init_plugin_api();
6552
+ init_env();
5237
6553
  }
5238
6554
  });
5239
6555
 
@@ -5244,8 +6560,8 @@ init_build();
5244
6560
  // src/build/electron.ts
5245
6561
  init_config();
5246
6562
  init_resolve();
5247
- import path10 from "path";
5248
- import fs7 from "fs";
6563
+ import path11 from "path";
6564
+ import fs8 from "fs";
5249
6565
  import { rolldown as rolldown2 } from "rolldown";
5250
6566
  import pc5 from "picocolors";
5251
6567
 
@@ -5289,16 +6605,16 @@ async function buildElectron(inlineConfig = {}) {
5289
6605
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
5290
6606
  const startTime = performance.now();
5291
6607
  assertElectronVersion(config);
5292
- console.log(pc5.cyan("\n\u26A1 nasti build (electron)") + pc5.dim(` v${"2.3.1"}`));
6608
+ console.log(pc5.cyan("\n\u26A1 nasti build (electron)") + pc5.dim(` v${"2.4.1"}`));
5293
6609
  console.log(pc5.dim(` root: ${config.root}`));
5294
6610
  console.log(pc5.dim(` mode: ${config.mode}`));
5295
6611
  console.log(pc5.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
5296
- const outDir = path10.resolve(config.root, config.build.outDir);
5297
- if (config.build.emptyOutDir && fs7.existsSync(outDir)) {
5298
- fs7.rmSync(outDir, { recursive: true, force: true });
6612
+ const outDir = path11.resolve(config.root, config.build.outDir);
6613
+ if (config.build.emptyOutDir && fs8.existsSync(outDir)) {
6614
+ fs8.rmSync(outDir, { recursive: true, force: true });
5299
6615
  }
5300
- fs7.mkdirSync(outDir, { recursive: true });
5301
- const rendererOutDir = path10.join(outDir, "renderer");
6616
+ fs8.mkdirSync(outDir, { recursive: true });
6617
+ const rendererOutDir = path11.join(outDir, "renderer");
5302
6618
  const { build: build2 } = await Promise.resolve().then(() => (init_build(), build_exports));
5303
6619
  await build2(createElectronRendererConfig(config, inlineConfig, {
5304
6620
  build: {
@@ -5307,8 +6623,8 @@ async function buildElectron(inlineConfig = {}) {
5307
6623
  emptyOutDir: false
5308
6624
  }
5309
6625
  }));
5310
- const mainEntry = path10.resolve(config.root, config.electron.main);
5311
- if (!fs7.existsSync(mainEntry)) {
6626
+ const mainEntry = path11.resolve(config.root, config.electron.main);
6627
+ if (!fs8.existsSync(mainEntry)) {
5312
6628
  throw new Error(
5313
6629
  `Electron main entry not found: ${config.electron.main}
5314
6630
  \u5728 nasti.config.ts \u7684 electron.main \u6307\u5B9A\u4E3B\u8FDB\u7A0B\u5165\u53E3\u6587\u4EF6\u3002`
@@ -5322,11 +6638,11 @@ async function buildElectron(inlineConfig = {}) {
5322
6638
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
5323
6639
  const preloadFiles = [];
5324
6640
  for (const entry of preloadEntries) {
5325
- if (!fs7.existsSync(entry)) {
6641
+ if (!fs8.existsSync(entry)) {
5326
6642
  console.warn(pc5.yellow(` \u26A0 preload entry not found, skipped: ${entry}`));
5327
6643
  continue;
5328
6644
  }
5329
- const base = path10.basename(entry).replace(/\.[^.]+$/, "");
6645
+ const base = path11.basename(entry).replace(/\.[^.]+$/, "");
5330
6646
  const out = outFileName(outDir, base, config.electron.preloadFormat);
5331
6647
  await bundleNode(config, entry, {
5332
6648
  outFile: out,
@@ -5338,10 +6654,10 @@ async function buildElectron(inlineConfig = {}) {
5338
6654
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
5339
6655
  console.log(pc5.green(`
5340
6656
  \u2713 Electron build complete in ${elapsed}s`));
5341
- console.log(pc5.dim(` renderer: ${path10.relative(config.root, rendererOutDir)}/`));
5342
- console.log(pc5.dim(` main: ${path10.relative(config.root, mainFile)}`));
6657
+ console.log(pc5.dim(` renderer: ${path11.relative(config.root, rendererOutDir)}/`));
6658
+ console.log(pc5.dim(` main: ${path11.relative(config.root, mainFile)}`));
5343
6659
  for (const pf of preloadFiles) {
5344
- console.log(pc5.dim(` preload: ${path10.relative(config.root, pf)}`));
6660
+ console.log(pc5.dim(` preload: ${path11.relative(config.root, pf)}`));
5345
6661
  }
5346
6662
  console.log();
5347
6663
  return { rendererOutDir, mainFile, preloadFiles };
@@ -5379,7 +6695,7 @@ async function bundleNode(config, entry, opts) {
5379
6695
  },
5380
6696
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5381
6697
  });
5382
- fs7.mkdirSync(path10.dirname(opts.outFile), { recursive: true });
6698
+ fs8.mkdirSync(path11.dirname(opts.outFile), { recursive: true });
5383
6699
  await bundle2.write({
5384
6700
  sourcemap: !!config.build.sourcemap,
5385
6701
  minify: !!config.build.minify,
@@ -5390,7 +6706,7 @@ async function bundleNode(config, entry, opts) {
5390
6706
  codeSplitting: false
5391
6707
  });
5392
6708
  await bundle2.close();
5393
- console.log(pc5.dim(` \u2713 ${opts.label} \u2192 ${path10.relative(config.root, opts.outFile)}`));
6709
+ console.log(pc5.dim(` \u2713 ${opts.label} \u2192 ${path11.relative(config.root, opts.outFile)}`));
5394
6710
  return opts.outFile;
5395
6711
  }
5396
6712
  function createElectronRendererConfig(config, inlineConfig = {}, overrides = {}) {
@@ -5414,11 +6730,11 @@ function createElectronRendererConfig(config, inlineConfig = {}, overrides = {})
5414
6730
  }
5415
6731
  function outFileName(outDir, base, format) {
5416
6732
  const ext = format === "cjs" ? ".cjs" : ".mjs";
5417
- return path10.join(outDir, base + ext);
6733
+ return path11.join(outDir, base + ext);
5418
6734
  }
5419
6735
  function normalizePreload(preload, root) {
5420
6736
  const list = Array.isArray(preload) ? preload : preload ? [preload] : [];
5421
- return list.map((p) => path10.resolve(root, p));
6737
+ return list.map((p) => path11.resolve(root, p));
5422
6738
  }
5423
6739
  function assertElectronVersion(config) {
5424
6740
  const min = config.electron.minVersion;
@@ -5433,9 +6749,9 @@ function assertElectronVersion(config) {
5433
6749
  }
5434
6750
  function detectInstalledElectron(root) {
5435
6751
  try {
5436
- const pkgPath = path10.resolve(root, "node_modules/electron/package.json");
5437
- if (!fs7.existsSync(pkgPath)) return null;
5438
- const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
6752
+ const pkgPath = path11.resolve(root, "node_modules/electron/package.json");
6753
+ if (!fs8.existsSync(pkgPath)) return null;
6754
+ const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
5439
6755
  const major = parseInt(String(pkg.version).split(".")[0], 10);
5440
6756
  return Number.isFinite(major) ? major : null;
5441
6757
  } catch {
@@ -5448,8 +6764,8 @@ init_server();
5448
6764
 
5449
6765
  // src/server/electron-dev.ts
5450
6766
  init_config();
5451
- import path16 from "path";
5452
- import fs11 from "fs";
6767
+ import path17 from "path";
6768
+ import fs12 from "fs";
5453
6769
  import { createRequire as createRequire5 } from "module";
5454
6770
  import { spawn } from "child_process";
5455
6771
  import chokidar from "chokidar";
@@ -5462,7 +6778,7 @@ async function startElectronDev(inlineConfig = {}) {
5462
6778
  const { noSpawn, ...rest } = inlineConfig;
5463
6779
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
5464
6780
  warnElectronVersion(config);
5465
- console.log(pc10.cyan("\n\u26A1 nasti electron dev") + pc10.dim(` v${"2.3.1"}`));
6781
+ console.log(pc10.cyan("\n\u26A1 nasti electron dev") + pc10.dim(` v${"2.4.1"}`));
5466
6782
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5467
6783
  const server = await createServer2({
5468
6784
  ...rest,
@@ -5472,11 +6788,11 @@ async function startElectronDev(inlineConfig = {}) {
5472
6788
  await server.listen();
5473
6789
  const devUrl = `http://localhost:${server.config.server.port}` + electronRendererDevPath(config.electron.renderer);
5474
6790
  console.log(pc10.dim(` renderer: ${devUrl}`));
5475
- const stageDir = path16.resolve(config.root, ".nasti");
5476
- fs11.mkdirSync(stageDir, { recursive: true });
5477
- const mainEntry = path16.resolve(config.root, config.electron.main);
6791
+ const stageDir = path17.resolve(config.root, ".nasti");
6792
+ fs12.mkdirSync(stageDir, { recursive: true });
6793
+ const mainEntry = path17.resolve(config.root, config.electron.main);
5478
6794
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
5479
- const builtMainFile = path16.join(stageDir, "main" + extFor(config.electron.mainFormat));
6795
+ const builtMainFile = path17.join(stageDir, "main" + extFor(config.electron.mainFormat));
5480
6796
  const builtPreloadFiles = [];
5481
6797
  const compileAll = async () => {
5482
6798
  await compileNode(config, mainEntry, {
@@ -5486,9 +6802,9 @@ async function startElectronDev(inlineConfig = {}) {
5486
6802
  });
5487
6803
  builtPreloadFiles.length = 0;
5488
6804
  for (const entry of preloadEntries) {
5489
- if (!fs11.existsSync(entry)) continue;
5490
- const base = path16.basename(entry).replace(/\.[^.]+$/, "");
5491
- const out = path16.join(stageDir, base + extFor(config.electron.preloadFormat));
6805
+ if (!fs12.existsSync(entry)) continue;
6806
+ const base = path17.basename(entry).replace(/\.[^.]+$/, "");
6807
+ const out = path17.join(stageDir, base + extFor(config.electron.preloadFormat));
5492
6808
  await compileNode(config, entry, {
5493
6809
  outFile: out,
5494
6810
  format: config.electron.preloadFormat,
@@ -5527,7 +6843,7 @@ async function startElectronDev(inlineConfig = {}) {
5527
6843
  };
5528
6844
  spawnElectron();
5529
6845
  if (config.electron.autoRestart) {
5530
- const watchTargets = [mainEntry, ...preloadEntries].filter(fs11.existsSync);
6846
+ const watchTargets = [mainEntry, ...preloadEntries].filter(fs12.existsSync);
5531
6847
  const watcher = chokidar.watch(watchTargets, { ignoreInitial: true });
5532
6848
  let restarting = null;
5533
6849
  let pending = false;
@@ -5607,7 +6923,7 @@ async function compileNode(config, entry, opts) {
5607
6923
  platform: "node",
5608
6924
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5609
6925
  });
5610
- fs11.mkdirSync(path16.dirname(opts.outFile), { recursive: true });
6926
+ fs12.mkdirSync(path17.dirname(opts.outFile), { recursive: true });
5611
6927
  await bundle2.write({
5612
6928
  file: opts.outFile,
5613
6929
  format: opts.format === "cjs" ? "cjs" : "esm",
@@ -5620,18 +6936,18 @@ async function compileNode(config, entry, opts) {
5620
6936
  await bundle2.close();
5621
6937
  }
5622
6938
  function electronRendererDevPath(renderer) {
5623
- const normalized = renderer.split(path16.sep).join("/").replace(/^\.?\//, "");
6939
+ const normalized = renderer.split(path17.sep).join("/").replace(/^\.?\//, "");
5624
6940
  return normalized === "index.html" ? "/" : `/${normalized}`;
5625
6941
  }
5626
6942
  function resolveElectronBinary(config) {
5627
- if (config.electron.electronPath && fs11.existsSync(config.electron.electronPath)) {
6943
+ if (config.electron.electronPath && fs12.existsSync(config.electron.electronPath)) {
5628
6944
  return config.electron.electronPath;
5629
6945
  }
5630
6946
  try {
5631
- const require2 = createRequire5(path16.resolve(config.root, "package.json"));
6947
+ const require2 = createRequire5(path17.resolve(config.root, "package.json"));
5632
6948
  const pathFile = require2.resolve("electron");
5633
6949
  const electronModule = require2(pathFile);
5634
- if (typeof electronModule === "string" && fs11.existsSync(electronModule)) {
6950
+ if (typeof electronModule === "string" && fs12.existsSync(electronModule)) {
5635
6951
  return electronModule;
5636
6952
  }
5637
6953
  } catch {
@@ -5658,8 +6974,8 @@ function warnElectronVersion(config) {
5658
6974
  }
5659
6975
 
5660
6976
  // src/plugins/monaco-editor.ts
5661
- import path17 from "path";
5662
- import fs12 from "fs";
6977
+ import path18 from "path";
6978
+ import fs13 from "fs";
5663
6979
  import crypto4 from "crypto";
5664
6980
  import { createRequire as createRequire6 } from "module";
5665
6981
  var DEFAULT_WORKERS = {
@@ -5680,9 +6996,9 @@ function normalizePublicPath(p) {
5680
6996
  }
5681
6997
  function readMonacoVersion(root) {
5682
6998
  try {
5683
- const require2 = createRequire6(path17.resolve(root, "package.json"));
6999
+ const require2 = createRequire6(path18.resolve(root, "package.json"));
5684
7000
  const pkgJsonPath = require2.resolve("monaco-editor/package.json", { paths: [root] });
5685
- const pkg = JSON.parse(fs12.readFileSync(pkgJsonPath, "utf-8"));
7001
+ const pkg = JSON.parse(fs13.readFileSync(pkgJsonPath, "utf-8"));
5686
7002
  return typeof pkg.version === "string" ? pkg.version : "unknown";
5687
7003
  } catch {
5688
7004
  return "unknown";
@@ -5702,20 +7018,20 @@ function monacoEditorPlugin(options = {}) {
5702
7018
  let cacheDir = "";
5703
7019
  const building = /* @__PURE__ */ new Map();
5704
7020
  async function buildWorker(worker) {
5705
- const cacheFile = path17.join(cacheDir, `${worker.label}.worker.js`);
5706
- if (fs12.existsSync(cacheFile)) return cacheFile;
7021
+ const cacheFile = path18.join(cacheDir, `${worker.label}.worker.js`);
7022
+ if (fs13.existsSync(cacheFile)) return cacheFile;
5707
7023
  const existing = building.get(worker.label);
5708
7024
  if (existing) return existing;
5709
7025
  const task = (async () => {
5710
7026
  const { rolldown: rolldown4 } = await import("rolldown");
5711
- const require2 = createRequire6(path17.resolve(resolvedConfig.root, "package.json"));
7027
+ const require2 = createRequire6(path18.resolve(resolvedConfig.root, "package.json"));
5712
7028
  let entry;
5713
7029
  try {
5714
7030
  entry = require2.resolve(worker.entry, { paths: [resolvedConfig.root] });
5715
7031
  } catch {
5716
7032
  entry = require2.resolve(worker.entry + ".js", { paths: [resolvedConfig.root] });
5717
7033
  }
5718
- fs12.mkdirSync(cacheDir, { recursive: true });
7034
+ fs13.mkdirSync(cacheDir, { recursive: true });
5719
7035
  const bundle2 = await rolldown4({
5720
7036
  input: entry,
5721
7037
  platform: "browser"
@@ -5775,12 +7091,12 @@ function monacoEditorPlugin(options = {}) {
5775
7091
  resolvedConfig = config;
5776
7092
  const version = readMonacoVersion(config.root);
5777
7093
  const key = crypto4.createHash("sha1").update(version + "|" + publicPath).digest("hex").slice(0, 8);
5778
- cacheDir = path17.resolve(config.root, "node_modules/.nasti/monaco", key);
7094
+ cacheDir = path18.resolve(config.root, "node_modules/.nasti/monaco", key);
5779
7095
  },
5780
7096
  async configureServer(server) {
5781
7097
  const shouldBuild = !isCDN(publicPath) || forceBuildCDN;
5782
7098
  const watcher = server.watcher;
5783
- const monacoDir = path17.resolve(resolvedConfig.root, "node_modules/monaco-editor");
7099
+ const monacoDir = path18.resolve(resolvedConfig.root, "node_modules/monaco-editor");
5784
7100
  try {
5785
7101
  watcher?.unwatch?.(monacoDir);
5786
7102
  } catch {
@@ -5810,7 +7126,7 @@ function monacoEditorPlugin(options = {}) {
5810
7126
  const file = await buildWorker(worker);
5811
7127
  res.setHeader("Content-Type", "application/javascript; charset=utf-8");
5812
7128
  res.setHeader("Cache-Control", "public, max-age=604800, immutable");
5813
- fs12.createReadStream(file).pipe(res);
7129
+ fs13.createReadStream(file).pipe(res);
5814
7130
  } catch (e) {
5815
7131
  res.statusCode = 500;
5816
7132
  res.end(`Monaco worker build failed: ${e.message}`);
@@ -5845,16 +7161,16 @@ self.monaco = monaco;`,
5845
7161
  resolvedConfig.root,
5846
7162
  resolvedConfig.build.outDir,
5847
7163
  resolvedConfig.base
5848
- ) : isCDN(publicPath) ? path17.resolve(resolvedConfig.root, resolvedConfig.build.outDir, "monaco") : path17.resolve(
7164
+ ) : isCDN(publicPath) ? path18.resolve(resolvedConfig.root, resolvedConfig.build.outDir, "monaco") : path18.resolve(
5849
7165
  resolvedConfig.root,
5850
7166
  resolvedConfig.build.outDir,
5851
7167
  publicPath.replace(/^\//, "")
5852
7168
  );
5853
- fs12.mkdirSync(outDir, { recursive: true });
7169
+ fs13.mkdirSync(outDir, { recursive: true });
5854
7170
  for (const worker of workers) {
5855
7171
  try {
5856
7172
  const cacheFile = await buildWorker(worker);
5857
- fs12.copyFileSync(cacheFile, path17.join(outDir, `${worker.label}.worker.js`));
7173
+ fs13.copyFileSync(cacheFile, path18.join(outDir, `${worker.label}.worker.js`));
5858
7174
  } catch (e) {
5859
7175
  throw new Error(
5860
7176
  `[nasti:monaco-editor] worker build failed for "${worker.label}": ${e.message}