@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.cjs CHANGED
@@ -5,10 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __glob = (map) => (path18) => {
9
- var fn = map[path18];
8
+ var __glob = (map) => (path19) => {
9
+ var fn = map[path19];
10
10
  if (fn) return fn();
11
- throw new Error("Module not found in bundle: " + path18);
11
+ throw new Error("Module not found in bundle: " + path19);
12
12
  };
13
13
  var __esm = (fn, res) => function __init() {
14
14
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -66,7 +66,10 @@ var init_defaults = __esm({
66
66
  target: "es2022",
67
67
  rolldownOptions: {},
68
68
  emptyOutDir: true,
69
- css: {},
69
+ css: {
70
+ inject: true,
71
+ emit: true
72
+ },
70
73
  reportCompressedSize: true,
71
74
  chunkSizeWarningLimit: 500,
72
75
  cssCodeSplit: true,
@@ -418,7 +421,11 @@ async function resolveConfig(inlineConfig = {}, command) {
418
421
  allowClearScreen: clearScreen2,
419
422
  customLogger: merged.customLogger
420
423
  });
421
- const mergedBuild = { ...defaults.build, ...merged.build };
424
+ const mergedBuild = {
425
+ ...defaults.build,
426
+ ...merged.build,
427
+ css: { ...defaults.build.css, ...merged.build?.css }
428
+ };
422
429
  if (merged.build?.cssMinify === void 0) {
423
430
  mergedBuild.cssMinify = !!mergedBuild.minify;
424
431
  }
@@ -449,11 +456,17 @@ async function resolveConfig(inlineConfig = {}, command) {
449
456
  bundledDev: merged.experimental?.bundledDev ?? defaults.experimental.bundledDev
450
457
  }
451
458
  };
452
- const userEnvironments = {
459
+ const rawUserEnvironments = {
453
460
  client: {},
454
461
  ssr: {},
455
462
  ...merged.environments ?? {}
456
463
  };
464
+ const userEnvironments = Object.fromEntries(
465
+ Object.entries(rawUserEnvironments).map(([name, options]) => [
466
+ name,
467
+ deepMerge({}, options)
468
+ ])
469
+ );
457
470
  for (const [name, envOptions] of Object.entries(userEnvironments)) {
458
471
  for (const plugin of rawPlugins) {
459
472
  if (plugin.configEnvironment) {
@@ -464,6 +477,7 @@ async function resolveConfig(inlineConfig = {}, command) {
464
477
  }
465
478
  for (const [name, envOptions] of Object.entries(userEnvironments)) {
466
479
  const consumer = envOptions.consumer ?? (name === "client" ? "client" : "server");
480
+ const vueOptions = deepMerge({}, envOptions.vue ?? {});
467
481
  if (name === "client") {
468
482
  if (envOptions.resolve) {
469
483
  Object.assign(resolved.resolve, {
@@ -471,9 +485,14 @@ async function resolveConfig(inlineConfig = {}, command) {
471
485
  alias: { ...resolved.resolve.alias, ...envOptions.resolve.alias }
472
486
  });
473
487
  }
474
- if (envOptions.build) Object.assign(resolved.build, envOptions.build);
488
+ if (envOptions.build) {
489
+ const { css, ...environmentBuild } = envOptions.build;
490
+ Object.assign(resolved.build, environmentBuild);
491
+ if (css) resolved.build.css = { ...resolved.build.css, ...css };
492
+ }
475
493
  resolved.environments.client = {
476
494
  consumer,
495
+ buildEnabled: envOptions.buildEnabled ?? true,
477
496
  entry: normalizeEnvironmentEntries(envOptions.entry, root),
478
497
  html: import_node_path.default.resolve(
479
498
  root,
@@ -482,15 +501,18 @@ async function resolveConfig(inlineConfig = {}, command) {
482
501
  driver: envOptions.driver,
483
502
  // 同引用 —— 精确镜像(assertClientEnvironmentMirror 校验)
484
503
  resolve: resolved.resolve,
485
- build: resolved.build
504
+ build: resolved.build,
505
+ vue: vueOptions
486
506
  };
487
507
  continue;
488
508
  }
489
509
  resolved.environments[name] = {
490
510
  consumer,
511
+ buildEnabled: envOptions.buildEnabled ?? true,
491
512
  entry: normalizeEnvironmentEntries(envOptions.entry, root),
492
513
  html: envOptions.consumer === "client" && envOptions.html ? import_node_path.default.resolve(root, envOptions.html) : void 0,
493
514
  driver: envOptions.driver,
515
+ vue: vueOptions,
494
516
  resolve: {
495
517
  alias: { ...resolved.resolve.alias, ...envOptions.resolve?.alias },
496
518
  extensions: envOptions.resolve?.extensions ?? [...resolved.resolve.extensions],
@@ -501,6 +523,7 @@ async function resolveConfig(inlineConfig = {}, command) {
501
523
  build: {
502
524
  ...resolved.build,
503
525
  ...envOptions.build,
526
+ css: { ...resolved.build.css, ...envOptions.build?.css },
504
527
  // 非 client 环境默认产出到 <outDir>/<envName>(如 dist/ssr),可显式覆盖
505
528
  outDir: envOptions.build?.outDir ?? import_node_path.default.join(resolved.build.outDir, name),
506
529
  // server 产物默认不压缩(可调试性优先,与 Vite SSR 默认一致),可显式覆盖
@@ -714,6 +737,7 @@ function resolvePlugin(config) {
714
737
  }
715
738
  if (!source.startsWith("/") && !source.startsWith(".")) {
716
739
  if (vueRuntimeEntry && source === "vue") return vueRuntimeEntry;
740
+ if (config.command === "build") return null;
717
741
  try {
718
742
  const resolved = require2.resolve(source, {
719
743
  paths: [importer ? import_node_path2.default.dirname(importer) : config.root]
@@ -732,7 +756,7 @@ function resolvePlugin(config) {
732
756
  const content = import_node_fs2.default.readFileSync(id, "utf-8");
733
757
  return `export default ${content}`;
734
758
  }
735
- return import_node_fs2.default.readFileSync(id, "utf-8");
759
+ return null;
736
760
  }
737
761
  };
738
762
  }
@@ -849,27 +873,27 @@ var require_process = __commonJS({
849
873
  var require_filesystem = __commonJS({
850
874
  "node_modules/detect-libc/lib/filesystem.js"(exports2, module2) {
851
875
  "use strict";
852
- var fs13 = require("fs");
876
+ var fs14 = require("fs");
853
877
  var LDD_PATH = "/usr/bin/ldd";
854
878
  var SELF_PATH = "/proc/self/exe";
855
879
  var MAX_LENGTH = 2048;
856
- var readFileSync = (path18) => {
857
- const fd = fs13.openSync(path18, "r");
880
+ var readFileSync = (path19) => {
881
+ const fd = fs14.openSync(path19, "r");
858
882
  const buffer = Buffer.alloc(MAX_LENGTH);
859
- const bytesRead = fs13.readSync(fd, buffer, 0, MAX_LENGTH, 0);
860
- fs13.close(fd, () => {
883
+ const bytesRead = fs14.readSync(fd, buffer, 0, MAX_LENGTH, 0);
884
+ fs14.close(fd, () => {
861
885
  });
862
886
  return buffer.subarray(0, bytesRead);
863
887
  };
864
- var readFile = (path18) => new Promise((resolve, reject) => {
865
- fs13.open(path18, "r", (err, fd) => {
888
+ var readFile = (path19) => new Promise((resolve, reject) => {
889
+ fs14.open(path19, "r", (err, fd) => {
866
890
  if (err) {
867
891
  reject(err);
868
892
  } else {
869
893
  const buffer = Buffer.alloc(MAX_LENGTH);
870
- fs13.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
894
+ fs14.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
871
895
  resolve(buffer.subarray(0, bytesRead));
872
- fs13.close(fd, () => {
896
+ fs14.close(fd, () => {
873
897
  });
874
898
  });
875
899
  }
@@ -981,11 +1005,11 @@ var require_detect_libc = __commonJS({
981
1005
  }
982
1006
  return null;
983
1007
  };
984
- var familyFromInterpreterPath = (path18) => {
985
- if (path18) {
986
- if (path18.includes("/ld-musl-")) {
1008
+ var familyFromInterpreterPath = (path19) => {
1009
+ if (path19) {
1010
+ if (path19.includes("/ld-musl-")) {
987
1011
  return MUSL;
988
- } else if (path18.includes("/ld-linux-")) {
1012
+ } else if (path19.includes("/ld-linux-")) {
989
1013
  return GLIBC;
990
1014
  }
991
1015
  }
@@ -1032,8 +1056,8 @@ var require_detect_libc = __commonJS({
1032
1056
  cachedFamilyInterpreter = null;
1033
1057
  try {
1034
1058
  const selfContent = await readFile(SELF_PATH);
1035
- const path18 = interpreterPath(selfContent);
1036
- cachedFamilyInterpreter = familyFromInterpreterPath(path18);
1059
+ const path19 = interpreterPath(selfContent);
1060
+ cachedFamilyInterpreter = familyFromInterpreterPath(path19);
1037
1061
  } catch (e) {
1038
1062
  }
1039
1063
  return cachedFamilyInterpreter;
@@ -1045,8 +1069,8 @@ var require_detect_libc = __commonJS({
1045
1069
  cachedFamilyInterpreter = null;
1046
1070
  try {
1047
1071
  const selfContent = readFileSync(SELF_PATH);
1048
- const path18 = interpreterPath(selfContent);
1049
- cachedFamilyInterpreter = familyFromInterpreterPath(path18);
1072
+ const path19 = interpreterPath(selfContent);
1073
+ cachedFamilyInterpreter = familyFromInterpreterPath(path19);
1050
1074
  } catch (e) {
1051
1075
  }
1052
1076
  return cachedFamilyInterpreter;
@@ -1693,12 +1717,31 @@ var init_node = __esm({
1693
1717
  function createCssEngine() {
1694
1718
  return {
1695
1719
  styles: /* @__PURE__ */ new Map(),
1720
+ modules: /* @__PURE__ */ new Map(),
1721
+ chunks: /* @__PURE__ */ new Map(),
1696
1722
  entryCss: /* @__PURE__ */ new Map(),
1697
1723
  allCss: [],
1698
1724
  pendingSingle: [],
1699
1725
  singleFileName: null
1700
1726
  };
1701
1727
  }
1728
+ function getCssMetadata(engine) {
1729
+ return {
1730
+ modules: Object.fromEntries(
1731
+ [...engine.modules].map(([id, module2]) => [id, { ...module2 }])
1732
+ ),
1733
+ chunks: Object.fromEntries(
1734
+ [...engine.chunks].map(([fileName, chunk]) => [
1735
+ fileName,
1736
+ {
1737
+ fileName,
1738
+ moduleIds: [...chunk.moduleIds],
1739
+ cssFileNames: [...chunk.cssFileNames]
1740
+ }
1741
+ ])
1742
+ )
1743
+ };
1744
+ }
1702
1745
  function normalizeCssModuleId(id) {
1703
1746
  return id.startsWith("\0") ? id.slice(1) : id;
1704
1747
  }
@@ -1818,15 +1861,29 @@ function cssPlugin(config, engine, consumer = "client") {
1818
1861
  }
1819
1862
  const rewritten = rewriteCssUrls(cssSource, file, config.root);
1820
1863
  const escaped = JSON.stringify(rewritten);
1864
+ const normalizedId = normalizeCssModuleId(id);
1865
+ const cssModule = { id: normalizedId, source: code, code: rewritten };
1866
+ const map = config.build.sourcemap ? createIdentitySourceMap(code, id) : void 0;
1867
+ engine?.modules.set(normalizedId, cssModule);
1868
+ this.environment?.setCssModule?.(cssModule);
1821
1869
  if (query === "inline") {
1822
1870
  return { code: `export default ${escaped};
1823
- `, moduleType: "js" };
1871
+ `, map, moduleType: "js" };
1824
1872
  }
1825
1873
  if (consumer === "server") {
1826
1874
  return { code: `export default ${escaped};
1827
- `, moduleType: "js" };
1875
+ `, map, moduleType: "js" };
1828
1876
  }
1829
1877
  if (config.command === "serve") {
1878
+ if (config.build.css.inject === false) {
1879
+ return {
1880
+ code: `export default ${escaped};
1881
+ `,
1882
+ map,
1883
+ moduleType: "js",
1884
+ moduleSideEffects: "no-treeshake"
1885
+ };
1886
+ }
1830
1887
  return {
1831
1888
  code: `
1832
1889
  const css = ${escaped};
@@ -1850,16 +1907,18 @@ if (import.meta.hot) {
1850
1907
 
1851
1908
  export default css;
1852
1909
  `,
1910
+ map,
1853
1911
  // bundled dev(DevEngine)下该模块会进 Rolldown:不标 js 会按 .css
1854
1912
  // 扩展名走 CSS 管线触发 #4271 报错;unbundled 中间件忽略此字段
1855
1913
  moduleType: "js"
1856
1914
  };
1857
1915
  }
1858
1916
  if (engine) {
1859
- engine.styles.set(normalizeCssModuleId(id), rewritten);
1917
+ engine.styles.set(normalizedId, rewritten);
1860
1918
  return {
1861
1919
  code: `export default '';
1862
1920
  `,
1921
+ map,
1863
1922
  moduleType: "js",
1864
1923
  // 防止空 stub 被 tree-shake 出 chunk.moduleIds(css-post 靠它定位)
1865
1924
  moduleSideEffects: "no-treeshake"
@@ -1879,11 +1938,27 @@ document.head.appendChild(style);
1879
1938
 
1880
1939
  export default css;
1881
1940
  `,
1941
+ map,
1882
1942
  moduleType: "js"
1883
1943
  };
1884
1944
  }
1885
1945
  };
1886
1946
  }
1947
+ function createIdentitySourceMap(code, id) {
1948
+ const map = new import_source_map_js.SourceMapGenerator({ file: id });
1949
+ const lines = code.split("\n");
1950
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
1951
+ for (let column = 0; column <= lines[lineIndex].length; column++) {
1952
+ map.addMapping({
1953
+ generated: { line: lineIndex + 1, column },
1954
+ original: { line: lineIndex + 1, column },
1955
+ source: id
1956
+ });
1957
+ }
1958
+ }
1959
+ map.setSourceContent(id, code);
1960
+ return map.toJSON();
1961
+ }
1887
1962
  function rewriteCssUrls(css, from, root) {
1888
1963
  return css.replace(/url\(\s*['"]?([^'")\s]+)['"]?\s*\)/g, (match, url) => {
1889
1964
  if (url.startsWith("/") || url.startsWith("data:") || url.startsWith("http")) {
@@ -1894,11 +1969,12 @@ function rewriteCssUrls(css, from, root) {
1894
1969
  return `url(${relative})`;
1895
1970
  });
1896
1971
  }
1897
- var import_node_path4;
1972
+ var import_node_path4, import_source_map_js;
1898
1973
  var init_css = __esm({
1899
1974
  "src/plugins/css.ts"() {
1900
1975
  "use strict";
1901
1976
  import_node_path4 = __toESM(require("path"), 1);
1977
+ import_source_map_js = require("source-map-js");
1902
1978
  init_css_engine();
1903
1979
  init_tailwind();
1904
1980
  }
@@ -1908,19 +1984,27 @@ var init_css = __esm({
1908
1984
  function collectChunkCss(chunk, engine) {
1909
1985
  const ids = chunk.moduleIds ?? Object.keys(chunk.modules);
1910
1986
  let css = "";
1987
+ const moduleIds = [];
1911
1988
  for (const id of ids) {
1912
- const styles = engine.styles.get(normalizeCssModuleId(id));
1913
- if (styles) css += styles + "\n";
1989
+ const normalizedId = normalizeCssModuleId(id);
1990
+ const styles = engine.styles.get(normalizedId);
1991
+ if (styles) {
1992
+ css += styles + "\n";
1993
+ moduleIds.push(normalizedId);
1994
+ }
1914
1995
  }
1915
- return css;
1996
+ return { css, moduleIds };
1916
1997
  }
1917
1998
  function cssPostPlugin(config, engine) {
1918
1999
  return {
1919
2000
  name: "nasti:css-post",
1920
2001
  enforce: "post",
1921
2002
  async renderChunk(code, chunk) {
1922
- const css = collectChunkCss(chunk, engine);
2003
+ const { css, moduleIds } = collectChunkCss(chunk, engine);
1923
2004
  if (!css) return null;
2005
+ const ownership = { moduleIds, cssFileNames: [] };
2006
+ engine.chunks.set(chunk.fileName, ownership);
2007
+ if (config.build.css.emit === false) return null;
1924
2008
  if (!config.build.cssCodeSplit) {
1925
2009
  engine.pendingSingle.push(css);
1926
2010
  return null;
@@ -1933,6 +2017,7 @@ function cssPostPlugin(config, engine) {
1933
2017
  });
1934
2018
  const fileName = this.getFileName(ref);
1935
2019
  engine.allCss.push(fileName);
2020
+ ownership.cssFileNames.push(fileName);
1936
2021
  if (chunk.isEntry) {
1937
2022
  const key = chunk.facadeModuleId ?? chunk.name;
1938
2023
  const existing = engine.entryCss.get(key) ?? [];
@@ -1940,13 +2025,14 @@ function cssPostPlugin(config, engine) {
1940
2025
  engine.entryCss.set(key, existing);
1941
2026
  return null;
1942
2027
  }
2028
+ if (config.build.css.inject === false) return null;
1943
2029
  const href = JSON.stringify(config.base + fileName);
1944
2030
  const snippet = `
1945
2031
  ;(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){}})();`;
1946
2032
  return { code: code + snippet, map: null };
1947
2033
  },
1948
2034
  augmentChunkHash(chunk) {
1949
- const css = collectChunkCss(chunk, engine);
2035
+ const { css } = collectChunkCss(chunk, engine);
1950
2036
  return css || void 0;
1951
2037
  },
1952
2038
  async generateBundle() {
@@ -1957,6 +2043,9 @@ function cssPostPlugin(config, engine) {
1957
2043
  const fileName = this.getFileName(ref);
1958
2044
  engine.singleFileName = fileName;
1959
2045
  engine.allCss.push(fileName);
2046
+ for (const ownership of engine.chunks.values()) {
2047
+ if (ownership.moduleIds.length > 0) ownership.cssFileNames.push(fileName);
2048
+ }
1960
2049
  }
1961
2050
  };
1962
2051
  }
@@ -1969,6 +2058,7 @@ var init_css_post = __esm({
1969
2058
 
1970
2059
  // src/plugins/assets.ts
1971
2060
  function assetsPlugin(config) {
2061
+ const emittedAssets = /* @__PURE__ */ new Set();
1972
2062
  return {
1973
2063
  name: "nasti:assets",
1974
2064
  resolveId(source) {
@@ -1997,12 +2087,29 @@ function assetsPlugin(config) {
1997
2087
  const hash = import_node_crypto.default.createHash("sha256").update(content).digest("hex").slice(0, 8);
1998
2088
  const basename = import_node_path5.default.basename(file, ext);
1999
2089
  const hashedName = `${config.build.assetsDir}/${basename}.${hash}${ext}`;
2090
+ const environment = this.environment;
2091
+ if (!environment) {
2092
+ throw new Error("[nasti:assets] build environment is not initialized");
2093
+ }
2094
+ if (!emittedAssets.has(hashedName)) {
2095
+ this.emitFile({
2096
+ type: "asset",
2097
+ fileName: hashedName,
2098
+ source: content
2099
+ });
2100
+ emittedAssets.add(hashedName);
2101
+ }
2102
+ environment.setAssetModule(file, hashedName);
2000
2103
  return `export default ${JSON.stringify(config.base + hashedName)}`;
2001
2104
  }
2002
2105
  return null;
2003
2106
  }
2004
2107
  };
2005
2108
  }
2109
+ function isAssetFile(id) {
2110
+ const ext = import_node_path5.default.extname(id.replace(/\?.*$/, ""));
2111
+ return ASSET_EXTENSIONS.has(ext);
2112
+ }
2006
2113
  var import_node_path5, import_node_fs3, import_node_crypto, ASSET_EXTENSIONS;
2007
2114
  var init_assets = __esm({
2008
2115
  "src/plugins/assets.ts"() {
@@ -2085,9 +2192,10 @@ async function loadVueCompiler() {
2085
2192
  return null;
2086
2193
  }
2087
2194
  }
2088
- function vuePlugin(config) {
2195
+ function vuePlugin(config, environmentName = "client") {
2089
2196
  const isDev = config.command === "serve";
2090
2197
  const descriptorCache = /* @__PURE__ */ new Map();
2198
+ const vueOptions = config.environments[environmentName]?.vue ?? {};
2091
2199
  return {
2092
2200
  name: "nasti:vue",
2093
2201
  enforce: "pre",
@@ -2107,32 +2215,63 @@ function vuePlugin(config) {
2107
2215
  const sfc = await loadVueCompiler();
2108
2216
  if (!sfc) return null;
2109
2217
  const [, filePath, indexStr] = match;
2110
- let descriptor = descriptorCache.get(filePath);
2111
- if (!descriptor) {
2218
+ let cached2 = descriptorCache.get(filePath);
2219
+ if (!cached2) {
2112
2220
  try {
2113
- const fs13 = await import("fs");
2114
- const source = fs13.readFileSync(filePath, "utf-8");
2115
- const parsed = sfc.parse(source, { filename: filePath });
2221
+ const fs14 = await import("fs");
2222
+ const rawSource = fs14.readFileSync(filePath, "utf-8");
2223
+ const transformedSfc = await applySourceTransform(
2224
+ vueOptions.transformSfc,
2225
+ rawSource,
2226
+ { filename: filePath, environmentName, type: "sfc" }
2227
+ );
2228
+ const parsed = sfc.parse(transformedSfc.code, {
2229
+ ...vueOptions.parse,
2230
+ filename: filePath,
2231
+ sourceMap: true
2232
+ });
2116
2233
  if (parsed.errors.length) return null;
2117
- descriptor = parsed.descriptor;
2118
- descriptorCache.set(filePath, descriptor);
2234
+ cached2 = {
2235
+ descriptor: parsed.descriptor,
2236
+ sourceMap: transformedSfc.map
2237
+ };
2238
+ descriptorCache.set(filePath, cached2);
2119
2239
  } catch {
2120
2240
  return null;
2121
2241
  }
2122
2242
  }
2243
+ const { descriptor, sourceMap: sfcSourceMap } = cached2;
2123
2244
  const index2 = parseInt(indexStr ?? "0", 10);
2124
2245
  const style = descriptor.styles[index2];
2125
2246
  if (!style) return null;
2126
2247
  const scopeId = hashId(filePath);
2248
+ const transformedStyle = await applySourceTransform(
2249
+ vueOptions.transformStyle,
2250
+ style.content,
2251
+ { filename: filePath, environmentName, type: "style", index: index2 }
2252
+ );
2253
+ const wantsStyleSourceMap = !!config.build.sourcemap || transformedStyle.map != null || sfcSourceMap != null;
2254
+ const styleInputMap = wantsStyleSourceMap ? composeSourceMapChain(
2255
+ [transformedStyle.map, style.map, sfcSourceMap],
2256
+ { filename: filePath, environmentName, type: "style", index: index2 }
2257
+ ) : void 0;
2127
2258
  const result = await sfc.compileStyleAsync({
2128
- source: style.content,
2259
+ ...vueOptions.style,
2260
+ source: transformedStyle.code,
2129
2261
  filename: filePath,
2130
2262
  id: `data-v-${scopeId}`,
2131
2263
  scoped: style.scoped ?? false,
2264
+ inMap: styleInputMap,
2132
2265
  // <style lang="scss|less|stylus"> 需经对应预处理器(缺省 undefined = 纯 CSS)
2133
2266
  preprocessLang: style.lang
2134
2267
  });
2135
- return result.code;
2268
+ if (transformedStyle.map != null && result.map == null) {
2269
+ warnUnchainableMap(
2270
+ { filename: filePath, environmentName, type: "style", index: index2 },
2271
+ "compiler-sfc did not return a style map"
2272
+ );
2273
+ }
2274
+ return wantsStyleSourceMap ? { code: result.code, map: result.map } : result.code;
2136
2275
  },
2137
2276
  async transform(code, id) {
2138
2277
  if (!VUE_FILE_RE.test(id) && !VUE_QUERY_RE.test(id)) return null;
@@ -2144,57 +2283,144 @@ function vuePlugin(config) {
2144
2283
  if (VUE_QUERY_RE.test(id)) {
2145
2284
  return null;
2146
2285
  }
2147
- const { descriptor, errors } = sfc.parse(code, { filename: id });
2286
+ const transformedSfc = await applySourceTransform(
2287
+ vueOptions.transformSfc,
2288
+ code,
2289
+ { filename: id, environmentName, type: "sfc" }
2290
+ );
2291
+ code = transformedSfc.code;
2292
+ const { descriptor, errors } = sfc.parse(code, {
2293
+ ...vueOptions.parse,
2294
+ filename: id,
2295
+ sourceMap: true
2296
+ });
2148
2297
  if (errors.length) {
2149
- console.error(`[nasti:vue] Parse error in ${id}:`, errors[0].message);
2298
+ const firstError = errors[0];
2299
+ console.error(
2300
+ `[nasti:vue] Parse error in ${id}:`,
2301
+ typeof firstError === "string" ? firstError : firstError.message
2302
+ );
2150
2303
  return null;
2151
2304
  }
2152
- descriptorCache.set(id, descriptor);
2305
+ descriptorCache.set(id, {
2306
+ descriptor,
2307
+ sourceMap: transformedSfc.map
2308
+ });
2153
2309
  const scopeId = hashId(id);
2310
+ const wantsSourceMap = !!config.build.sourcemap || transformedSfc.map != null;
2154
2311
  let scriptCode = "";
2312
+ let scriptMap;
2155
2313
  if (descriptor.script || descriptor.scriptSetup) {
2314
+ const inlineTemplate = vueOptions.script?.inlineTemplate !== false;
2156
2315
  const compiled = sfc.compileScript(descriptor, {
2316
+ ...vueOptions.script,
2157
2317
  id: scopeId,
2158
2318
  isProd: !isDev,
2159
- inlineTemplate: true,
2319
+ inlineTemplate,
2320
+ sourceMap: wantsSourceMap,
2160
2321
  // 让 compileScript 产出 `const __sfc__ = ...`(而非默认的 `export default {...}`)。
2161
2322
  // 否则下方追加的 `__sfc__.render` / `__sfc__.__scopeId` / HMR 记录会引用一个
2162
2323
  // 不存在的 `__sfc__`,并与 compileScript 自带的 `export default` 形成双重默认导出。
2163
2324
  genDefaultAs: "__sfc__"
2164
2325
  });
2165
2326
  scriptCode = compiled.content;
2327
+ scriptMap = composeSourceMapChain(
2328
+ [compiled.map, transformedSfc.map],
2329
+ { filename: id, environmentName, type: "sfc" }
2330
+ );
2331
+ if (transformedSfc.map != null && scriptMap == null) {
2332
+ warnUnchainableMap(
2333
+ { filename: id, environmentName, type: "sfc" },
2334
+ "compiler-sfc did not return a script map"
2335
+ );
2336
+ }
2166
2337
  }
2167
2338
  let templateCode = "";
2168
- if (descriptor.template && !descriptor.scriptSetup) {
2339
+ let templateMap;
2340
+ const scriptSetupIsInline = !!descriptor.scriptSetup && vueOptions.script?.inlineTemplate !== false;
2341
+ if (descriptor.template && !scriptSetupIsInline) {
2342
+ const transformedTemplate = await applySourceTransform(
2343
+ vueOptions.transformTemplate,
2344
+ descriptor.template.content,
2345
+ { filename: id, environmentName, type: "template" }
2346
+ );
2347
+ const templateInputMap = composeSourceMapChain(
2348
+ [
2349
+ transformedTemplate.map,
2350
+ descriptor.template.map,
2351
+ transformedSfc.map
2352
+ ],
2353
+ { filename: id, environmentName, type: "template" }
2354
+ );
2355
+ const customCompilerOptions = vueOptions.template?.compilerOptions ?? {};
2169
2356
  const compiled = sfc.compileTemplate({
2170
- source: descriptor.template.content,
2357
+ ...vueOptions.template,
2358
+ source: transformedTemplate.code,
2171
2359
  filename: id,
2172
2360
  id: scopeId,
2173
- compilerOptions: { scopeId: `data-v-${scopeId}` }
2361
+ inMap: templateInputMap,
2362
+ compilerOptions: {
2363
+ ...customCompilerOptions,
2364
+ scopeId: `data-v-${scopeId}`
2365
+ }
2174
2366
  });
2175
2367
  templateCode = compiled.code;
2368
+ if (wantsSourceMap || transformedTemplate.map != null) {
2369
+ templateMap = compiled.map;
2370
+ }
2371
+ if (transformedTemplate.map != null && templateMap == null) {
2372
+ warnUnchainableMap(
2373
+ { filename: id, environmentName, type: "template" },
2374
+ "compiler-sfc did not return a template map"
2375
+ );
2376
+ }
2176
2377
  }
2177
- let output = scriptCode || "const __sfc__ = {}";
2378
+ const outputNode = new import_source_map_js2.SourceNode();
2379
+ let hasMappedOutput = false;
2380
+ const append = (fragment, map) => {
2381
+ const normalizedMap = normalizeSourceMap(
2382
+ map,
2383
+ { filename: id, environmentName, type: "sfc" }
2384
+ );
2385
+ if (!normalizedMap) {
2386
+ outputNode.add(fragment);
2387
+ return;
2388
+ }
2389
+ try {
2390
+ outputNode.add(
2391
+ import_source_map_js2.SourceNode.fromStringWithSourceMap(
2392
+ fragment,
2393
+ new import_source_map_js2.SourceMapConsumer(normalizedMap)
2394
+ )
2395
+ );
2396
+ hasMappedOutput = true;
2397
+ } catch (error) {
2398
+ warnUnchainableMap(
2399
+ { filename: id, environmentName, type: "sfc" },
2400
+ `source-map assembly failed: ${error instanceof Error ? error.message : String(error)}`
2401
+ );
2402
+ outputNode.add(fragment);
2403
+ }
2404
+ };
2405
+ append(scriptCode || "const __sfc__ = {}", scriptMap);
2178
2406
  if (templateCode) {
2179
- output += `
2180
- ${templateCode}
2181
- `;
2182
- output += `
2183
- __sfc__.render = render
2184
- `;
2407
+ append("\n");
2408
+ append(templateCode, templateMap);
2409
+ append("\n");
2410
+ append("\n__sfc__.render = render\n");
2185
2411
  }
2186
2412
  if (descriptor.styles.length > 0) {
2187
2413
  for (let i = 0; i < descriptor.styles.length; i++) {
2188
- output += `
2414
+ append(`
2189
2415
  import "${id}?vue&type=style&index=${i}&lang.css"
2190
- `;
2416
+ `);
2191
2417
  }
2192
2418
  }
2193
- output += `
2419
+ append(`
2194
2420
  __sfc__.__scopeId = "data-v-${scopeId}"
2195
- `;
2421
+ `);
2196
2422
  if (isDev) {
2197
- output += `
2423
+ append(`
2198
2424
  __sfc__.__hmrId = ${JSON.stringify(scopeId)}
2199
2425
  if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') {
2200
2426
  __VUE_HMR_RUNTIME__.createRecord(__sfc__.__hmrId, __sfc__)
@@ -2208,17 +2434,34 @@ if (import.meta.hot) {
2208
2434
  }
2209
2435
  })
2210
2436
  }
2211
- `;
2437
+ `);
2438
+ }
2439
+ append("\nexport default __sfc__\n");
2440
+ const renderedOutput = outputNode.toStringWithSourceMap({ file: id });
2441
+ const output = renderedOutput.code;
2442
+ const outputMap = hasMappedOutput ? renderedOutput.map.toJSON() : void 0;
2443
+ if (transformedSfc.map != null && outputMap == null) {
2444
+ warnUnchainableMap(
2445
+ { filename: id, environmentName, type: "sfc" },
2446
+ "the compiled SFC output contained no chainable mappings"
2447
+ );
2212
2448
  }
2213
- output += `
2214
- export default __sfc__
2215
- `;
2216
2449
  const lang = descriptor.scriptSetup?.lang ?? descriptor.script?.lang;
2217
2450
  if (lang === "ts") {
2218
- const transpiled = transformCode(`${id}.ts`, output, { sourcemap: false });
2219
- return { code: transpiled.code };
2451
+ const transpiled = transformCode(`${id}.ts`, output, {
2452
+ sourcemap: wantsSourceMap,
2453
+ target: config.build.target
2454
+ });
2455
+ const transpiledMap = transpiled.map ? JSON.parse(transpiled.map) : void 0;
2456
+ return {
2457
+ code: transpiled.code,
2458
+ map: composeSourceMapChain(
2459
+ [transpiledMap, outputMap],
2460
+ { filename: id, environmentName, type: "sfc" }
2461
+ )
2462
+ };
2220
2463
  }
2221
- return { code: output };
2464
+ return { code: output, map: outputMap };
2222
2465
  },
2223
2466
  handleHotUpdate(ctx) {
2224
2467
  const { file, modules } = ctx;
@@ -2232,17 +2475,77 @@ export default __sfc__
2232
2475
  }
2233
2476
  };
2234
2477
  }
2478
+ async function applySourceTransform(transform2, source, context) {
2479
+ if (!transform2) return { code: source };
2480
+ const result = await transform2(source, context);
2481
+ return typeof result === "string" ? { code: result } : result;
2482
+ }
2483
+ function normalizeSourceMap(map, context) {
2484
+ if (map == null) return void 0;
2485
+ try {
2486
+ const value = typeof map === "string" ? JSON.parse(map) : map;
2487
+ if (value && typeof value === "object" && Array.isArray(value.sources) && Array.isArray(value.names) && typeof value.mappings === "string") {
2488
+ return value;
2489
+ }
2490
+ } catch {
2491
+ }
2492
+ warnUnchainableMap(context, "the provided map is not a valid source map");
2493
+ return void 0;
2494
+ }
2495
+ function composeSourceMapChain(maps, context) {
2496
+ const pending = maps.filter((map) => map != null);
2497
+ if (pending.length === 0) return void 0;
2498
+ let composed = normalizeSourceMap(pending.shift(), context);
2499
+ for (const map of pending) {
2500
+ const input = normalizeSourceMap(map, context);
2501
+ if (!input) continue;
2502
+ if (!composed) {
2503
+ composed = input;
2504
+ continue;
2505
+ }
2506
+ try {
2507
+ const consumer = new import_source_map_js2.SourceMapConsumer(composed);
2508
+ if (consumer.sources.length !== 1) {
2509
+ warnUnchainableMap(
2510
+ context,
2511
+ "a generated map has multiple sources and cannot be chained safely"
2512
+ );
2513
+ continue;
2514
+ }
2515
+ const generator = import_source_map_js2.SourceMapGenerator.fromSourceMap(consumer);
2516
+ generator.applySourceMap(
2517
+ new import_source_map_js2.SourceMapConsumer(input),
2518
+ consumer.sources[0]
2519
+ );
2520
+ composed = generator.toJSON();
2521
+ } catch (error) {
2522
+ warnUnchainableMap(
2523
+ context,
2524
+ `source-map composition failed: ${error instanceof Error ? error.message : String(error)}`
2525
+ );
2526
+ }
2527
+ }
2528
+ return composed;
2529
+ }
2530
+ function warnUnchainableMap(context, reason) {
2531
+ debug2?.(
2532
+ `source map warning for ${context.filename} (${context.type}, ${context.environmentName}): ${reason}`
2533
+ );
2534
+ }
2235
2535
  function hashId(filename) {
2236
2536
  return import_node_crypto2.default.createHash("sha256").update(filename).digest("hex").slice(0, 8);
2237
2537
  }
2238
- var import_node_crypto2, VUE_FILE_RE, VUE_QUERY_RE, compiler;
2538
+ var import_node_crypto2, import_source_map_js2, VUE_FILE_RE, VUE_QUERY_RE, debug2, compiler;
2239
2539
  var init_vue = __esm({
2240
2540
  "src/plugins/vue.ts"() {
2241
2541
  "use strict";
2242
2542
  import_node_crypto2 = __toESM(require("crypto"), 1);
2543
+ import_source_map_js2 = require("source-map-js");
2243
2544
  init_transformer();
2545
+ init_debug();
2244
2546
  VUE_FILE_RE = /\.vue$/;
2245
2547
  VUE_QUERY_RE = /\.vue\?vue&type=(script|template|style)(&index=\d+)?(&lang[.=]\w+)?/;
2548
+ debug2 = createDebugger("nasti:vue");
2246
2549
  compiler = null;
2247
2550
  }
2248
2551
  });
@@ -2333,16 +2636,27 @@ window.__vite_plugin_react_preamble_installed__ = true;
2333
2636
  // src/plugins/builtins.ts
2334
2637
  function resolvePluginList(config, userPlugins, opts = {}) {
2335
2638
  const isServe = config.command === "serve";
2639
+ let environmentOptions;
2640
+ if (opts.environmentName) {
2641
+ environmentOptions = config.environments[opts.environmentName];
2642
+ if (!environmentOptions) {
2643
+ throw new Error(
2644
+ `[nasti] unknown environment "${opts.environmentName}" \u2014 declare it in config.environments`
2645
+ );
2646
+ }
2647
+ }
2648
+ const pluginConfig = environmentOptions ? { ...config, resolve: environmentOptions.resolve, build: environmentOptions.build } : config;
2649
+ const consumer = opts.consumer ?? environmentOptions?.consumer;
2336
2650
  return [
2337
2651
  // vuePlugin 排最前(enforce: 'pre' 语义):.vue 先编译成 JS 再走后续管道
2338
- ...config.framework === "vue" ? [vuePlugin(config)] : [],
2339
- resolvePlugin(config),
2340
- cssPlugin(config, opts.cssEngine, opts.consumer),
2341
- assetsPlugin(config),
2342
- ...isServe ? [htmlPlugin(config)] : [],
2652
+ ...config.framework === "vue" ? [vuePlugin(pluginConfig, opts.environmentName ?? "client")] : [],
2653
+ resolvePlugin(pluginConfig),
2654
+ cssPlugin(pluginConfig, opts.cssEngine, consumer),
2655
+ assetsPlugin(pluginConfig),
2656
+ ...isServe ? [htmlPlugin(pluginConfig)] : [],
2343
2657
  ...userPlugins,
2344
2658
  // cssPostPlugin 最后(enforce: 'post' 语义):renderChunk 聚合抽取
2345
- ...!isServe && opts.cssEngine ? [cssPostPlugin(config, opts.cssEngine)] : []
2659
+ ...!isServe && opts.cssEngine ? [cssPostPlugin(pluginConfig, opts.cssEngine)] : []
2346
2660
  ];
2347
2661
  }
2348
2662
  var init_builtins = __esm({
@@ -2439,17 +2753,23 @@ var init_plugin_container = __esm({
2439
2753
  }
2440
2754
  async transform(code, id) {
2441
2755
  let currentCode = code;
2756
+ let lastResult;
2442
2757
  for (const plugin of this.plugins) {
2443
2758
  if (!plugin.transform) continue;
2444
2759
  const result = await plugin.transform.call(this.ctx, currentCode, id);
2445
2760
  if (result == null) continue;
2446
2761
  if (typeof result === "string") {
2447
2762
  currentCode = result;
2763
+ lastResult = void 0;
2448
2764
  } else {
2449
2765
  currentCode = result.code;
2766
+ lastResult = result;
2450
2767
  }
2451
2768
  }
2452
- return currentCode === code ? null : { code: currentCode };
2769
+ return currentCode === code ? null : {
2770
+ ...lastResult,
2771
+ code: currentCode
2772
+ };
2453
2773
  }
2454
2774
  /** 完整的模块处理管道: resolveId → load → transform */
2455
2775
  async processModule(source, importer) {
@@ -2472,17 +2792,39 @@ var init_plugin_container = __esm({
2472
2792
  }
2473
2793
  });
2474
2794
 
2795
+ // src/core/url.ts
2796
+ function removeTimestampQuery(url) {
2797
+ const hashIndex = url.indexOf("#");
2798
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
2799
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
2800
+ const queryIndex = withoutHash.indexOf("?");
2801
+ if (queryIndex < 0) return url;
2802
+ const pathname = withoutHash.slice(0, queryIndex);
2803
+ const query = withoutHash.slice(queryIndex + 1).split("&").filter((part) => !/^t=\d+$/.test(part)).join("&");
2804
+ return pathname + (query ? `?${query}` : "") + hash;
2805
+ }
2806
+ var init_url = __esm({
2807
+ "src/core/url.ts"() {
2808
+ "use strict";
2809
+ }
2810
+ });
2811
+
2475
2812
  // src/core/module-graph.ts
2476
2813
  var ModuleGraph;
2477
2814
  var init_module_graph = __esm({
2478
2815
  "src/core/module-graph.ts"() {
2479
2816
  "use strict";
2817
+ init_url();
2480
2818
  ModuleGraph = class {
2819
+ environmentName;
2481
2820
  urlToModuleMap = /* @__PURE__ */ new Map();
2482
2821
  idToModuleMap = /* @__PURE__ */ new Map();
2483
2822
  fileToModulesMap = /* @__PURE__ */ new Map();
2823
+ constructor(environmentName = "client") {
2824
+ this.environmentName = environmentName;
2825
+ }
2484
2826
  getModuleByUrl(url) {
2485
- return this.urlToModuleMap.get(url);
2827
+ return this.urlToModuleMap.get(removeTimestampQuery(url));
2486
2828
  }
2487
2829
  getModuleById(id) {
2488
2830
  return this.idToModuleMap.get(id);
@@ -2491,10 +2833,11 @@ var init_module_graph = __esm({
2491
2833
  return this.fileToModulesMap.get(file);
2492
2834
  }
2493
2835
  async ensureEntryFromUrl(url) {
2494
- let mod = this.urlToModuleMap.get(url);
2836
+ const normalizedUrl = removeTimestampQuery(url);
2837
+ let mod = this.urlToModuleMap.get(normalizedUrl);
2495
2838
  if (mod) return mod;
2496
- mod = this.createModule(url);
2497
- this.urlToModuleMap.set(url, mod);
2839
+ mod = this.createModule(normalizedUrl);
2840
+ this.urlToModuleMap.set(normalizedUrl, mod);
2498
2841
  return mod;
2499
2842
  }
2500
2843
  createModule(url, id) {
@@ -2508,7 +2851,9 @@ var init_module_graph = __esm({
2508
2851
  acceptedHmrDeps: /* @__PURE__ */ new Set(),
2509
2852
  transformResult: null,
2510
2853
  lastHMRTimestamp: 0,
2511
- isSelfAccepting: false
2854
+ invalidationVersion: 0,
2855
+ isSelfAccepting: false,
2856
+ environment: this.environmentName
2512
2857
  };
2513
2858
  this.idToModuleMap.set(mod.id, mod);
2514
2859
  return mod;
@@ -2550,10 +2895,64 @@ var init_module_graph = __esm({
2550
2895
  }
2551
2896
  }
2552
2897
  }
2898
+ /**
2899
+ * 用一次转换得到的信息原子更新 import 与 HMR accept 关系。
2900
+ * 依赖节点会在真正被浏览器请求前预先创建,这样入口模块先转换时也能建立完整图。
2901
+ */
2902
+ async updateModuleInfo(mod, importedUrls, acceptedUrls, isSelfAccepting, expectedInvalidationVersion) {
2903
+ const importedModules = await Promise.all(
2904
+ [...importedUrls].map((url) => this.ensureEntryFromUrl(url))
2905
+ );
2906
+ const acceptedModules = await Promise.all(
2907
+ [...acceptedUrls].map((url) => this.ensureEntryFromUrl(url))
2908
+ );
2909
+ if (expectedInvalidationVersion !== void 0 && mod.invalidationVersion !== expectedInvalidationVersion) {
2910
+ return null;
2911
+ }
2912
+ const previousImports = new Set(mod.importedModules);
2913
+ for (const imported of previousImports) {
2914
+ imported.importers.delete(mod);
2915
+ }
2916
+ mod.importedModules.clear();
2917
+ mod.acceptedHmrDeps.clear();
2918
+ for (const imported of importedModules) {
2919
+ mod.importedModules.add(imported);
2920
+ imported.importers.add(mod);
2921
+ }
2922
+ for (const accepted of acceptedModules) {
2923
+ mod.acceptedHmrDeps.add(accepted);
2924
+ }
2925
+ mod.isSelfAccepting = isSelfAccepting;
2926
+ const pruned = /* @__PURE__ */ new Set();
2927
+ for (const imported of previousImports) {
2928
+ if (!mod.importedModules.has(imported) && imported.importers.size === 0) {
2929
+ pruned.add(imported);
2930
+ }
2931
+ }
2932
+ return pruned;
2933
+ }
2553
2934
  /** 使模块的转换缓存失效 */
2554
- invalidateModule(mod) {
2935
+ invalidateModule(mod, timestamp = Date.now()) {
2555
2936
  mod.transformResult = null;
2556
- mod.lastHMRTimestamp = Date.now();
2937
+ mod.lastHMRTimestamp = timestamp;
2938
+ mod.invalidationVersion++;
2939
+ }
2940
+ /**
2941
+ * 仅失效到 HMR 边界:显式接受依赖的模块本身不会重执行;自接受模块需要失效,
2942
+ * 但不再继续影响其 importer。这样既能传播依赖时间戳,也不会隐式重复副作用。
2943
+ */
2944
+ invalidateModuleAndImporters(mod, timestamp = Date.now(), seen = /* @__PURE__ */ new Set()) {
2945
+ if (seen.has(mod)) return;
2946
+ seen.add(mod);
2947
+ this.invalidateModule(mod, timestamp);
2948
+ for (const importer of mod.importers) {
2949
+ if (importer.acceptedHmrDeps.has(mod)) continue;
2950
+ if (importer.isSelfAccepting) {
2951
+ this.invalidateModule(importer, timestamp);
2952
+ continue;
2953
+ }
2954
+ this.invalidateModuleAndImporters(importer, timestamp, seen);
2955
+ }
2557
2956
  }
2558
2957
  /** 使所有模块缓存失效 */
2559
2958
  invalidateAll() {
@@ -2564,34 +2963,32 @@ var init_module_graph = __esm({
2564
2963
  /** 获取 HMR 传播边界 - 从变更模块向上遍历找到接受更新的边界 */
2565
2964
  getHmrBoundaries(mod) {
2566
2965
  const boundaries = [];
2567
- const visited = /* @__PURE__ */ new Set();
2568
- const propagate = (node, via) => {
2569
- if (visited.has(node)) return true;
2570
- visited.add(node);
2571
- if (node.isSelfAccepting) {
2572
- boundaries.push({ boundary: node, acceptedVia: via });
2573
- return true;
2966
+ const traversed = /* @__PURE__ */ new Set();
2967
+ const addBoundary = (boundary, acceptedVia) => {
2968
+ if (!boundaries.some(
2969
+ (item) => item.boundary === boundary && item.acceptedVia === acceptedVia
2970
+ )) {
2971
+ boundaries.push({ boundary, acceptedVia });
2574
2972
  }
2575
- if (node.acceptedHmrDeps.has(via)) {
2576
- boundaries.push({ boundary: node, acceptedVia: via });
2973
+ };
2974
+ const propagate = (node) => {
2975
+ if (traversed.has(node)) return true;
2976
+ traversed.add(node);
2977
+ if (node.isSelfAccepting) {
2978
+ addBoundary(node, node);
2577
2979
  return true;
2578
2980
  }
2579
2981
  if (node.importers.size === 0) return false;
2580
2982
  for (const importer of node.importers) {
2581
- if (!propagate(importer, node)) return false;
2983
+ if (importer.acceptedHmrDeps.has(node)) {
2984
+ addBoundary(importer, node);
2985
+ continue;
2986
+ }
2987
+ if (!propagate(importer)) return false;
2582
2988
  }
2583
2989
  return true;
2584
2990
  };
2585
- if (mod.isSelfAccepting) {
2586
- boundaries.push({ boundary: mod, acceptedVia: mod });
2587
- return boundaries;
2588
- }
2589
- for (const importer of mod.importers) {
2590
- if (!propagate(importer, mod)) {
2591
- return [];
2592
- }
2593
- }
2594
- return boundaries;
2991
+ return propagate(mod) ? boundaries : [];
2595
2992
  }
2596
2993
  };
2597
2994
  }
@@ -2614,12 +3011,12 @@ function createNoopHotChannel() {
2614
3011
  }
2615
3012
  };
2616
3013
  }
2617
- function createWsHotChannel(ws) {
3014
+ function createWsHotChannel(ws, environmentName = "client") {
2618
3015
  const listeners = /* @__PURE__ */ new Map();
2619
3016
  let invokeHandlers;
2620
3017
  return {
2621
3018
  send(payload) {
2622
- ws.send(payload);
3019
+ ws.send({ ...payload, environment: payload.environment ?? environmentName });
2623
3020
  },
2624
3021
  on(event, listener) {
2625
3022
  let set = listeners.get(event);
@@ -2631,8 +3028,8 @@ function createWsHotChannel(ws) {
2631
3028
  },
2632
3029
  listen() {
2633
3030
  },
3031
+ // 多个 environment 共享底层 WebSocket server;它由 DevServer.close() 统一关闭。
2634
3032
  close() {
2635
- ws.close();
2636
3033
  },
2637
3034
  setInvokeHandler(handlers) {
2638
3035
  invokeHandlers = handlers;
@@ -2661,7 +3058,7 @@ function resolveEnvironmentPlugins(environment, plugins) {
2661
3058
  }
2662
3059
  });
2663
3060
  }
2664
- var debug2, NastiEnvironment;
3061
+ var debug3, NastiEnvironment;
2665
3062
  var init_environment = __esm({
2666
3063
  "src/core/environment.ts"() {
2667
3064
  "use strict";
@@ -2670,7 +3067,7 @@ var init_environment = __esm({
2670
3067
  init_hot_channel();
2671
3068
  init_debug();
2672
3069
  init_plugin_api();
2673
- debug2 = createDebugger("nasti:environment");
3070
+ debug3 = createDebugger("nasti:environment");
2674
3071
  NastiEnvironment = class {
2675
3072
  name;
2676
3073
  consumer;
@@ -2687,6 +3084,10 @@ var init_environment = __esm({
2687
3084
  moduleGraph;
2688
3085
  candidatePlugins;
2689
3086
  pluginApi;
3087
+ buildMetadata = {};
3088
+ cssModules = /* @__PURE__ */ new Map();
3089
+ assetModules = /* @__PURE__ */ new Map();
3090
+ transformRequestHandler;
2690
3091
  initialized = false;
2691
3092
  constructor(name, config, init = {}) {
2692
3093
  const options = config.environments[name];
@@ -2701,7 +3102,7 @@ var init_environment = __esm({
2701
3102
  this.config = config;
2702
3103
  this.options = options;
2703
3104
  this.hot = init.hot ?? createNoopHotChannel();
2704
- this.moduleGraph = new ModuleGraph();
3105
+ this.moduleGraph = new ModuleGraph(name);
2705
3106
  this.candidatePlugins = init.plugins ?? config.plugins;
2706
3107
  this.pluginApi = init.pluginApi ?? getPluginApi(config);
2707
3108
  }
@@ -2731,9 +3132,9 @@ var init_environment = __esm({
2731
3132
  );
2732
3133
  }
2733
3134
  this.driver = claimed[0].driver;
2734
- debug2?.(`env "${this.name}" uses driver "${this.driver.name}"`);
3135
+ debug3?.(`env "${this.name}" uses driver "${this.driver.name}"`);
2735
3136
  }
2736
- debug2?.(`env "${this.name}" initialized (${this.plugins.length} plugins)`);
3137
+ debug3?.(`env "${this.name}" initialized (${this.plugins.length} plugins)`);
2737
3138
  }
2738
3139
  getDriverContext() {
2739
3140
  return {
@@ -2743,6 +3144,53 @@ var init_environment = __esm({
2743
3144
  logger: this.config.logger
2744
3145
  };
2745
3146
  }
3147
+ configureDevPipeline(transformRequest2) {
3148
+ this.transformRequestHandler = transformRequest2;
3149
+ }
3150
+ async transformRequest(url) {
3151
+ if (!this.transformRequestHandler) {
3152
+ throw new Error(
3153
+ `[nasti] environment "${this.name}" does not have an initialized dev transform pipeline`
3154
+ );
3155
+ }
3156
+ return this.transformRequestHandler(url);
3157
+ }
3158
+ setCssModule(module2) {
3159
+ this.cssModules.set(module2.id, { ...module2 });
3160
+ }
3161
+ getCssModule(id) {
3162
+ const module2 = this.cssModules.get(id);
3163
+ return module2 ? { ...module2 } : void 0;
3164
+ }
3165
+ getCssModules() {
3166
+ return Object.freeze(
3167
+ Object.fromEntries(
3168
+ [...this.cssModules].map(([id, module2]) => [id, { ...module2 }])
3169
+ )
3170
+ );
3171
+ }
3172
+ setAssetModule(id, fileName) {
3173
+ this.assetModules.set(id, fileName);
3174
+ }
3175
+ getAssetModules() {
3176
+ return Object.freeze(Object.fromEntries(this.assetModules));
3177
+ }
3178
+ setBuildMetadata(metadata) {
3179
+ const { entries: currentEntries, ...currentMetadata } = this.buildMetadata;
3180
+ const { entries, ...nextMetadata } = metadata;
3181
+ this.buildMetadata = {
3182
+ ...currentMetadata,
3183
+ ...nextMetadata,
3184
+ ...currentEntries || entries ? { entries: { ...currentEntries, ...entries } } : {}
3185
+ };
3186
+ }
3187
+ getBuildMetadata() {
3188
+ const { entries, ...metadata } = this.buildMetadata;
3189
+ return {
3190
+ ...metadata,
3191
+ ...entries ? { entries: { ...entries } } : {}
3192
+ };
3193
+ }
2746
3194
  async close() {
2747
3195
  try {
2748
3196
  await this.driver?.close?.(this.getDriverContext());
@@ -2837,7 +3285,7 @@ async function tryNativeReporterPlugin(config, logger) {
2837
3285
  logInfo: (msg) => logger.info(msg)
2838
3286
  });
2839
3287
  } catch (err) {
2840
- debug3?.(`native viteReporterPlugin unavailable, falling back to JS table: ${err}`);
3288
+ debug4?.(`native viteReporterPlugin unavailable, falling back to JS table: ${err}`);
2841
3289
  return null;
2842
3290
  }
2843
3291
  }
@@ -2892,7 +3340,7 @@ function warnLargeChunks(output, config, logger) {
2892
3340
  )
2893
3341
  );
2894
3342
  }
2895
- var import_node_path8, import_node_zlib, import_picocolors3, debug3, numberFormatter;
3343
+ var import_node_path8, import_node_zlib, import_picocolors3, debug4, numberFormatter;
2896
3344
  var init_reporter = __esm({
2897
3345
  "src/build/reporter.ts"() {
2898
3346
  "use strict";
@@ -2900,7 +3348,7 @@ var init_reporter = __esm({
2900
3348
  import_node_zlib = require("zlib");
2901
3349
  import_picocolors3 = __toESM(require("picocolors"), 1);
2902
3350
  init_debug();
2903
- debug3 = createDebugger("nasti:reporter");
3351
+ debug4 = createDebugger("nasti:reporter");
2904
3352
  numberFormatter = new Intl.NumberFormat("en", {
2905
3353
  maximumFractionDigits: 2,
2906
3354
  minimumFractionDigits: 2
@@ -2908,6 +3356,156 @@ var init_reporter = __esm({
2908
3356
  }
2909
3357
  });
2910
3358
 
3359
+ // src/core/build-app-context.ts
3360
+ function createBuildAppContext(config, results) {
3361
+ const output = [];
3362
+ const emitted = /* @__PURE__ */ new Set();
3363
+ const outDir = import_node_path9.default.resolve(config.root, config.build.outDir);
3364
+ let environmentArtifacts;
3365
+ return {
3366
+ config,
3367
+ results,
3368
+ get output() {
3369
+ return Object.freeze([...output]);
3370
+ },
3371
+ getResult(environmentName) {
3372
+ return results[environmentName];
3373
+ },
3374
+ getArtifact(environmentName, fileName) {
3375
+ const normalized = normalizeEnvironmentFileName(fileName);
3376
+ return results[environmentName]?.output.find(
3377
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalized
3378
+ );
3379
+ },
3380
+ getEntry(environmentName, entryName) {
3381
+ const result = results[environmentName];
3382
+ const fileName = result?.entries?.[entryName];
3383
+ if (!fileName) return void 0;
3384
+ return result.output.find(
3385
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalizeEnvironmentFileName(fileName)
3386
+ );
3387
+ },
3388
+ getManifest(environmentName) {
3389
+ return results[environmentName]?.manifest;
3390
+ },
3391
+ getChunk(environmentName, fileName) {
3392
+ const normalized = normalizeEnvironmentFileName(fileName);
3393
+ return results[environmentName]?.chunks?.[normalized];
3394
+ },
3395
+ getCss(environmentName) {
3396
+ return results[environmentName]?.css;
3397
+ },
3398
+ getSourceMap(environmentName, fileName) {
3399
+ const normalized = normalizeEnvironmentFileName(fileName);
3400
+ return results[environmentName]?.sourceMaps?.[normalized];
3401
+ },
3402
+ resolvePublicPath(environmentName, fileName) {
3403
+ const result = results[environmentName];
3404
+ if (!result) return void 0;
3405
+ const normalized = normalizeEnvironmentFileName(fileName);
3406
+ const base = result.publicPath ?? config.base;
3407
+ return joinPublicPath(base, normalized);
3408
+ },
3409
+ emitFile(file) {
3410
+ const fileName = normalizeAppFileName(file.fileName);
3411
+ const collisionKey = artifactCollisionKey(fileName);
3412
+ if (emitted.has(collisionKey)) {
3413
+ throw new Error(`[nasti] app artifact already emitted: ${fileName}`);
3414
+ }
3415
+ environmentArtifacts ??= collectEnvironmentArtifacts(config, results, outDir);
3416
+ if (environmentArtifacts.has(collisionKey)) {
3417
+ throw new Error(`[nasti] app artifact conflicts with environment output: ${fileName}`);
3418
+ }
3419
+ const target = import_node_path9.default.resolve(outDir, ...fileName.split("/"));
3420
+ const relative = import_node_path9.default.relative(outDir, target);
3421
+ if (relative.startsWith("..") || import_node_path9.default.isAbsolute(relative)) {
3422
+ throw new Error(`[nasti] app artifact must stay inside build.outDir: ${file.fileName}`);
3423
+ }
3424
+ assertNoSymlinkComponents(outDir, fileName);
3425
+ import_node_fs6.default.mkdirSync(import_node_path9.default.dirname(target), { recursive: true });
3426
+ import_node_fs6.default.writeFileSync(target, file.source);
3427
+ const artifact = {
3428
+ ...file,
3429
+ fileName,
3430
+ type: "asset"
3431
+ };
3432
+ emitted.add(collisionKey);
3433
+ output.push(artifact);
3434
+ return fileName;
3435
+ }
3436
+ };
3437
+ }
3438
+ function joinPublicPath(base, fileName) {
3439
+ return `${base.endsWith("/") ? base : `${base}/`}${fileName.replace(/^\//, "")}`;
3440
+ }
3441
+ function normalizeEnvironmentFileName(fileName) {
3442
+ return import_node_path9.default.posix.normalize(fileName.replace(/\\/g, "/").replace(/^\.\//, ""));
3443
+ }
3444
+ function isInvalidEnvironmentFileName(fileName) {
3445
+ return !fileName || fileName === "." || fileName === ".." || fileName.startsWith("../") || import_node_path9.default.posix.isAbsolute(fileName) || /^[A-Za-z]:\//.test(fileName);
3446
+ }
3447
+ function normalizeAppFileName(fileName) {
3448
+ const normalized = normalizeEnvironmentFileName(fileName);
3449
+ if (isInvalidEnvironmentFileName(normalized)) {
3450
+ throw new Error(`[nasti] invalid app artifact fileName: ${fileName}`);
3451
+ }
3452
+ return normalized;
3453
+ }
3454
+ function artifactCollisionKey(fileName) {
3455
+ return normalizeEnvironmentFileName(fileName).toLowerCase();
3456
+ }
3457
+ function collectEnvironmentArtifacts(config, results, appOutDir) {
3458
+ const occupied = /* @__PURE__ */ new Set();
3459
+ for (const [environmentName, result] of Object.entries(results)) {
3460
+ const environment = config.environments[environmentName];
3461
+ if (!environment) continue;
3462
+ const environmentOutDir = import_node_path9.default.resolve(config.root, environment.build.outDir);
3463
+ for (const artifact of result.output) {
3464
+ const artifactPath = import_node_path9.default.resolve(
3465
+ environmentOutDir,
3466
+ ...normalizeEnvironmentFileName(artifact.fileName).split("/")
3467
+ );
3468
+ const relative = import_node_path9.default.relative(appOutDir, artifactPath);
3469
+ if (!relative.startsWith("..") && !import_node_path9.default.isAbsolute(relative)) {
3470
+ occupied.add(artifactCollisionKey(relative));
3471
+ }
3472
+ }
3473
+ }
3474
+ return occupied;
3475
+ }
3476
+ function assertNoSymlinkComponents(outDir, fileName) {
3477
+ let current = outDir;
3478
+ for (const segment of fileName.split("/")) {
3479
+ current = import_node_path9.default.join(current, segment);
3480
+ let stats;
3481
+ try {
3482
+ stats = import_node_fs6.default.lstatSync(current);
3483
+ } catch (error) {
3484
+ if (error.code === "ENOENT") continue;
3485
+ throw error;
3486
+ }
3487
+ if (stats.isSymbolicLink()) {
3488
+ throw new Error(`[nasti] app artifact path cannot traverse a symlink: ${fileName}`);
3489
+ }
3490
+ }
3491
+ }
3492
+ function inferEnvironmentEntries(output) {
3493
+ const entries = {};
3494
+ for (const artifact of output) {
3495
+ if (artifact.type !== "chunk" || !artifact.isEntry || !artifact.name) continue;
3496
+ entries[artifact.name] = normalizeEnvironmentFileName(artifact.fileName);
3497
+ }
3498
+ return Object.keys(entries).length > 0 ? entries : void 0;
3499
+ }
3500
+ var import_node_fs6, import_node_path9;
3501
+ var init_build_app_context = __esm({
3502
+ "src/core/build-app-context.ts"() {
3503
+ "use strict";
3504
+ import_node_fs6 = __toESM(require("fs"), 1);
3505
+ import_node_path9 = __toESM(require("path"), 1);
3506
+ }
3507
+ });
3508
+
2911
3509
  // src/build/index.ts
2912
3510
  var build_exports = {};
2913
3511
  __export(build_exports, {
@@ -2921,9 +3519,14 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
2921
3519
  const config = environment.config;
2922
3520
  const envOptions = environment.options;
2923
3521
  const isServer = environment.consumer === "server";
2924
- const outDir = import_node_path9.default.resolve(config.root, envOptions.build.outDir);
3522
+ const outDir = import_node_path10.default.resolve(config.root, envOptions.build.outDir);
2925
3523
  const assetsDir = envOptions.build.assetsDir;
2926
- const { output: userOutput, transform: userTransform, ...restInputOptions } = envOptions.build.rolldownOptions;
3524
+ const {
3525
+ output: userOutput,
3526
+ transform: userTransform,
3527
+ resolve: userResolve,
3528
+ ...restInputOptions
3529
+ } = envOptions.build.rolldownOptions;
2927
3530
  const vueDefine = config.framework === "vue" ? {
2928
3531
  __VUE_OPTIONS_API__: "true",
2929
3532
  __VUE_PROD_DEVTOOLS__: "false",
@@ -2935,27 +3538,34 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
2935
3538
  const inputOptions = {
2936
3539
  ...restInputOptions,
2937
3540
  input: entryPoints,
2938
- transform: { ...userTransform, define: mergedDefine },
3541
+ transform: {
3542
+ ...userTransform,
3543
+ target: userTransform?.target ?? envOptions.build.target,
3544
+ define: mergedDefine
3545
+ },
2939
3546
  plugins: rolldownPlugins,
3547
+ // client/server consumer 都必须使用当前环境的 conditions/mainFields;Lynx
3548
+ // BG/MT 通常同为 client consumer,但仍可能声明不同的运行时条件。
3549
+ resolve: {
3550
+ ...userResolve ?? {},
3551
+ // Environment API 是条件解析的唯一高层入口,优先于继承来的底层选项。
3552
+ conditionNames: envOptions.resolve.conditions,
3553
+ mainFields: envOptions.resolve.mainFields
3554
+ },
2940
3555
  ...isServer ? {
2941
3556
  platform: restInputOptions.platform ?? "node",
2942
- resolve: {
2943
- conditionNames: envOptions.resolve.conditions,
2944
- mainFields: envOptions.resolve.mainFields,
2945
- ...restInputOptions.resolve
2946
- },
2947
3557
  // server 产物:node 内建恒外部化;bare specifier 默认外部化
2948
3558
  //(同 Vite ssr.external 默认 —— 依赖由 node_modules 运行时解析),
2949
3559
  // 相对/绝对/虚拟模块照常打包。需要内联依赖时经 rolldownOptions.external 覆盖。
2950
3560
  external: restInputOptions.external ?? ((id) => {
2951
3561
  if (NODE_BUILTINS.has(id)) return true;
2952
- return !id.startsWith(".") && !import_node_path9.default.isAbsolute(id) && !id.startsWith("\0");
3562
+ return !id.startsWith(".") && !import_node_path10.default.isAbsolute(id) && !id.startsWith("\0");
2953
3563
  })
2954
3564
  } : {}
2955
3565
  };
2956
3566
  const outputOptions = isServer ? {
2957
3567
  format: "esm",
2958
- sourcemap: !!envOptions.build.sourcemap,
3568
+ sourcemap: envOptions.build.sourcemap,
2959
3569
  minify: !!envOptions.build.minify,
2960
3570
  entryFileNames: "[name].js",
2961
3571
  chunkFileNames: "chunks/[name]-[hash].js",
@@ -2964,7 +3574,7 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
2964
3574
  dir: outDir
2965
3575
  } : {
2966
3576
  format: "esm",
2967
- sourcemap: !!envOptions.build.sourcemap,
3577
+ sourcemap: envOptions.build.sourcemap,
2968
3578
  minify: !!envOptions.build.minify,
2969
3579
  entryFileNames: `${assetsDir}/[name].[hash].js`,
2970
3580
  chunkFileNames: `${assetsDir}/[name].[hash].js`,
@@ -2976,27 +3586,177 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
2976
3586
  };
2977
3587
  return { inputOptions, outputOptions, outDir };
2978
3588
  }
2979
- function toRolldownPlugins(plugins) {
3589
+ function toRolldownPlugins(plugins, environment) {
3590
+ const wrap = (hook) => {
3591
+ if (!hook) return hook;
3592
+ return function(...args) {
3593
+ return hook.apply(attachEnvironment(this, environment), args);
3594
+ };
3595
+ };
2980
3596
  return plugins.map((p) => ({
2981
3597
  name: p.name,
2982
- resolveId: p.resolveId,
2983
- load: p.load,
2984
- transform: p.transform,
2985
- buildStart: p.buildStart,
2986
- buildEnd: p.buildEnd,
3598
+ resolveId: wrap(p.resolveId),
3599
+ load: wrap(p.load),
3600
+ transform: wrap(p.transform),
3601
+ buildStart: wrap(p.buildStart),
3602
+ buildEnd: wrap(p.buildEnd),
2987
3603
  // closeBundle 在 bundle.close() 时触发 —— PWA manifest/SW 等终态产物依赖
2988
- closeBundle: p.closeBundle,
2989
- renderChunk: p.renderChunk,
2990
- augmentChunkHash: p.augmentChunkHash,
2991
- generateBundle: p.generateBundle
3604
+ closeBundle: wrap(p.closeBundle),
3605
+ renderChunk: wrap(p.renderChunk),
3606
+ augmentChunkHash: wrap(p.augmentChunkHash),
3607
+ generateBundle: wrap(p.generateBundle)
2992
3608
  }));
2993
3609
  }
3610
+ function attachEnvironment(context, environment) {
3611
+ if (context?.environment === environment) return context;
3612
+ try {
3613
+ Object.defineProperty(context, "environment", {
3614
+ configurable: true,
3615
+ enumerable: false,
3616
+ writable: false,
3617
+ value: environment
3618
+ });
3619
+ return context;
3620
+ } catch {
3621
+ return new Proxy(context, {
3622
+ get(target, property) {
3623
+ if (property === "environment") return environment;
3624
+ const value = Reflect.get(target, property, target);
3625
+ return typeof value === "function" ? value.bind(target) : value;
3626
+ },
3627
+ set(target, property, value) {
3628
+ return Reflect.set(target, property, value, target);
3629
+ }
3630
+ });
3631
+ }
3632
+ }
3633
+ function finalizeEnvironmentResult(environment, result) {
3634
+ const metadata = environment.getBuildMetadata();
3635
+ const inferredEntries = inferEnvironmentEntries(result.output);
3636
+ const entries = {
3637
+ ...inferredEntries,
3638
+ ...metadata.entries,
3639
+ ...result.entries
3640
+ };
3641
+ const normalizedEntries = Object.fromEntries(
3642
+ Object.entries(entries).map(([name, fileName]) => {
3643
+ const normalized = normalizeEnvironmentFileName(fileName);
3644
+ if (isInvalidEnvironmentFileName(normalized)) {
3645
+ throw new Error(
3646
+ `[nasti] environment "${environment.name}" returned invalid entry "${name}": ${fileName}`
3647
+ );
3648
+ }
3649
+ return [name, normalized];
3650
+ })
3651
+ );
3652
+ const inferredMetadata = inferOutputMetadata(environment, result.output);
3653
+ return {
3654
+ publicPath: environment.config.base,
3655
+ ...inferredMetadata,
3656
+ ...metadata,
3657
+ ...result,
3658
+ output: result.output,
3659
+ chunks: {
3660
+ ...inferredMetadata.chunks,
3661
+ ...metadata.chunks,
3662
+ ...result.chunks
3663
+ },
3664
+ assets: {
3665
+ ...inferredMetadata.assets,
3666
+ ...metadata.assets,
3667
+ ...result.assets
3668
+ },
3669
+ sourceMaps: {
3670
+ ...inferredMetadata.sourceMaps,
3671
+ ...metadata.sourceMaps,
3672
+ ...result.sourceMaps
3673
+ },
3674
+ ...Object.keys(normalizedEntries).length > 0 ? { entries: normalizedEntries } : {}
3675
+ };
3676
+ }
3677
+ function inferOutputMetadata(environment, output) {
3678
+ const chunks = {};
3679
+ const assets = {};
3680
+ const sourceMaps = {};
3681
+ const cssChunks = environment.getBuildMetadata().css?.chunks ?? {};
3682
+ const assetModules = environment.getAssetModules();
3683
+ const publicPath = environment.config.base;
3684
+ for (const artifact of output) {
3685
+ const fileName = normalizeEnvironmentFileName(artifact.fileName);
3686
+ if (artifact.map != null) sourceMaps[fileName] = artifact.map;
3687
+ if (artifact.type === "chunk") {
3688
+ const moduleIds = [...artifact.moduleIds ?? []];
3689
+ chunks[fileName] = {
3690
+ fileName,
3691
+ name: artifact.name ?? fileName,
3692
+ isEntry: !!artifact.isEntry,
3693
+ isDynamicEntry: !!artifact.isDynamicEntry,
3694
+ imports: [...artifact.imports ?? []],
3695
+ dynamicImports: [...artifact.dynamicImports ?? []],
3696
+ moduleIds,
3697
+ css: [...cssChunks[fileName]?.cssFileNames ?? []],
3698
+ assets: [
3699
+ ...new Set(
3700
+ moduleIds.map((id) => assetModules[id]).filter((asset) => !!asset)
3701
+ )
3702
+ ]
3703
+ };
3704
+ } else if (artifact.type === "asset") {
3705
+ assets[fileName] = {
3706
+ fileName,
3707
+ names: [...artifact.names ?? (artifact.name ? [artifact.name] : [])],
3708
+ publicPath: joinPublicPath(publicPath, fileName)
3709
+ };
3710
+ }
3711
+ }
3712
+ return { chunks, assets, sourceMaps };
3713
+ }
3714
+ function prepareBuildOutputDirectories(config, buildableNames) {
3715
+ const directories = /* @__PURE__ */ new Set();
3716
+ const protectedPaths = /* @__PURE__ */ new Set();
3717
+ const clientIsBuilt = buildableNames.includes("client");
3718
+ if (!clientIsBuilt && config.build.emptyOutDir) {
3719
+ directories.add(import_node_path10.default.resolve(config.root, config.build.outDir));
3720
+ }
3721
+ for (const name of buildableNames) {
3722
+ const environment = config.environments[name];
3723
+ const outDir = import_node_path10.default.resolve(config.root, environment.build.outDir);
3724
+ if (!environment.build.emptyOutDir) {
3725
+ protectedPaths.add(outDir);
3726
+ continue;
3727
+ }
3728
+ if (!environment.driver) directories.add(outDir);
3729
+ }
3730
+ const containsPath = (parent, child) => {
3731
+ const relative = import_node_path10.default.relative(parent, child);
3732
+ return relative === "" || !relative.startsWith("..") && !import_node_path10.default.isAbsolute(relative);
3733
+ };
3734
+ const roots = [...directories].filter(
3735
+ (directory) => ![...protectedPaths].some((protectedPath) => containsPath(directory, protectedPath))
3736
+ ).sort((a, b) => a.length - b.length).filter(
3737
+ (directory, index2, all) => !all.slice(0, index2).some((parent) => containsPath(parent, directory))
3738
+ );
3739
+ for (const directory of roots) {
3740
+ if (import_node_fs7.default.existsSync(directory)) import_node_fs7.default.rmSync(directory, { recursive: true, force: true });
3741
+ }
3742
+ }
3743
+ function assertDriverBuildResult(environment, result) {
3744
+ const output = result != null && typeof result === "object" ? result.output : void 0;
3745
+ const hasValidOutput = Array.isArray(output) && output.every(
3746
+ (artifact) => artifact != null && typeof artifact === "object" && typeof artifact.fileName === "string" && typeof artifact.type === "string"
3747
+ );
3748
+ if (!hasValidOutput) {
3749
+ throw new Error(
3750
+ `[nasti] environment "${environment.name}" driver "${environment.driver?.name}" returned an invalid build result; expected { output: EnvironmentBuildOutput[] }`
3751
+ );
3752
+ }
3753
+ }
2994
3754
  function resolveClientEntries(config, html) {
2995
3755
  const configuredEntries = config.environments.client?.entry ?? [];
2996
3756
  if (configuredEntries.length > 0) return configuredEntries;
2997
3757
  const entryPoints = [];
2998
3758
  const htmlFile = config.environments.client?.html;
2999
- const htmlDir = htmlFile ? import_node_path9.default.dirname(htmlFile) : config.root;
3759
+ const htmlDir = htmlFile ? import_node_path10.default.dirname(htmlFile) : config.root;
3000
3760
  if (html) {
3001
3761
  const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
3002
3762
  for (const match of scriptMatches) {
@@ -3004,7 +3764,7 @@ function resolveClientEntries(config, html) {
3004
3764
  if (src && !src.startsWith("http")) {
3005
3765
  const cleanSrc = src.split(/[?#]/, 1)[0];
3006
3766
  entryPoints.push(
3007
- cleanSrc.startsWith("/") ? import_node_path9.default.resolve(config.root, cleanSrc.replace(/^\//, "")) : import_node_path9.default.resolve(htmlDir, cleanSrc)
3767
+ cleanSrc.startsWith("/") ? import_node_path10.default.resolve(config.root, cleanSrc.replace(/^\//, "")) : import_node_path10.default.resolve(htmlDir, cleanSrc)
3008
3768
  );
3009
3769
  }
3010
3770
  }
@@ -3012,8 +3772,8 @@ function resolveClientEntries(config, html) {
3012
3772
  if (entryPoints.length === 0) {
3013
3773
  const fallbackEntries = ["src/main.ts", "src/main.tsx", "src/main.js", "src/index.ts", "src/index.tsx", "src/index.js"];
3014
3774
  for (const entry of fallbackEntries) {
3015
- const fullPath = import_node_path9.default.resolve(config.root, entry);
3016
- if (import_node_fs6.default.existsSync(fullPath)) {
3775
+ const fullPath = import_node_path10.default.resolve(config.root, entry);
3776
+ if (import_node_fs7.default.existsSync(fullPath)) {
3017
3777
  entryPoints.push(fullPath);
3018
3778
  break;
3019
3779
  }
@@ -3028,6 +3788,7 @@ function createOxcTransformPlugin(config, environment) {
3028
3788
  if (!shouldTransform(id)) return null;
3029
3789
  const result = transformCode(id, code, {
3030
3790
  sourcemap: !!environment.options.build.sourcemap,
3791
+ target: environment.options.build.target,
3031
3792
  jsxRuntime: "automatic",
3032
3793
  jsxImportSource: config.framework === "vue" ? "vue" : "react"
3033
3794
  });
@@ -3041,16 +3802,20 @@ async function build(inlineConfig = {}) {
3041
3802
  const startTime = performance.now();
3042
3803
  logger.info(
3043
3804
  import_picocolors4.default.cyan(`
3044
- nasti v${"2.3.1"} `) + import_picocolors4.default.green(`building for ${config.mode}...`)
3045
- );
3046
- debug4?.(`root: ${config.root}`);
3047
- const buildableNames = Object.keys(config.environments).filter(
3048
- (name) => name === "client" || config.environments[name].entry.length > 0 || !!config.environments[name].driver
3805
+ nasti v${"2.4.1"} `) + import_picocolors4.default.green(`building for ${config.mode}...`)
3049
3806
  );
3807
+ debug5?.(`root: ${config.root}`);
3808
+ const buildableNames = Object.keys(config.environments).filter((name) => {
3809
+ const environment = config.environments[name];
3810
+ if (!environment.buildEnabled) return false;
3811
+ return name === "client" || environment.entry.length > 0 || !!environment.driver;
3812
+ });
3050
3813
  buildableNames.sort((a, b) => a === "client" ? -1 : b === "client" ? 1 : 0);
3814
+ prepareBuildOutputDirectories(config, buildableNames);
3051
3815
  const environments = {};
3052
3816
  const environmentResults = {};
3053
3817
  const initializedEnvironments = [];
3818
+ const buildAppContext = createBuildAppContext(config, environmentResults);
3054
3819
  let clientOutput = [];
3055
3820
  let buildFailed = false;
3056
3821
  try {
@@ -3061,12 +3826,12 @@ nasti v${"2.3.1"} `) + import_picocolors4.default.green(`building for ${config.m
3061
3826
  environmentResults[name] = built.result;
3062
3827
  if (name === "client") clientOutput = built.result.output;
3063
3828
  if (buildableNames.length > 1) {
3064
- debug4?.(`environment "${name}" built (${built.result.output.length} files)`);
3829
+ debug5?.(`environment "${name}" built (${built.result.output.length} files)`);
3065
3830
  }
3066
3831
  }
3067
3832
  const pluginApi = getPluginApi(config);
3068
3833
  for (const plugin of config.plugins) {
3069
- await plugin.afterBuildApp?.(environmentResults, pluginApi);
3834
+ await plugin.afterBuildApp?.(environmentResults, pluginApi, buildAppContext);
3070
3835
  }
3071
3836
  } catch (error) {
3072
3837
  buildFailed = true;
@@ -3093,22 +3858,31 @@ nasti v${"2.3.1"} `) + import_picocolors4.default.green(`building for ${config.m
3093
3858
  }
3094
3859
  }
3095
3860
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
3096
- const totalSize = Object.values(environments).flat().reduce((sum, chunk) => {
3861
+ const allOutput = [...Object.values(environments).flat(), ...buildAppContext.output];
3862
+ const totalSize = allOutput.reduce((sum, chunk) => {
3097
3863
  const content = chunk.type === "chunk" ? chunk.code : chunk.source;
3098
3864
  if (content == null) return sum;
3099
3865
  return sum + (typeof content === "string" ? Buffer.byteLength(content) : content.byteLength);
3100
3866
  }, 0);
3101
- const fileCount = Object.values(environments).flat().length;
3867
+ const fileCount = allOutput.length;
3102
3868
  const envSuffix = buildableNames.length > 1 ? ` (${buildableNames.join(" + ")})` : "";
3103
3869
  logger.info(import_picocolors4.default.green(`\u2713 built in ${elapsed}s`) + import_picocolors4.default.dim(envSuffix));
3104
3870
  logger.info(import_picocolors4.default.dim(` ${fileCount} files, ${displaySize(totalSize)} total \u2192 ${config.build.outDir}/`));
3105
- return { output: clientOutput, environments, environmentResults };
3871
+ return {
3872
+ output: clientOutput,
3873
+ environments,
3874
+ environmentResults,
3875
+ appOutput: [...buildAppContext.output]
3876
+ };
3106
3877
  }
3107
3878
  async function buildClientEnvironment(config) {
3108
3879
  const logger = config.logger;
3109
- const outDir = import_node_path9.default.resolve(config.root, config.build.outDir);
3880
+ const outDir = import_node_path10.default.resolve(config.root, config.build.outDir);
3110
3881
  const cssEngine = createCssEngine();
3111
- const pluginList = resolvePluginList(config, config.plugins, { cssEngine });
3882
+ const pluginList = resolvePluginList(config, config.plugins, {
3883
+ cssEngine,
3884
+ environmentName: "client"
3885
+ });
3112
3886
  const clientEnv = new NastiEnvironment("client", config, {
3113
3887
  mode: "build",
3114
3888
  plugins: pluginList,
@@ -3123,13 +3897,11 @@ async function buildClientEnvironment(config) {
3123
3897
  );
3124
3898
  }
3125
3899
  const result = await clientEnv.driver.build(clientEnv.getDriverContext());
3126
- return { environment: clientEnv, result };
3900
+ assertDriverBuildResult(clientEnv, result);
3901
+ return { environment: clientEnv, result: finalizeEnvironmentResult(clientEnv, result) };
3127
3902
  }
3128
- if (config.build.emptyOutDir && import_node_fs6.default.existsSync(outDir)) {
3129
- import_node_fs6.default.rmSync(outDir, { recursive: true, force: true });
3130
- }
3131
- import_node_fs6.default.mkdirSync(outDir, { recursive: true });
3132
- const htmlFile = config.environments.client.html ?? import_node_path9.default.resolve(config.root, "index.html");
3903
+ import_node_fs7.default.mkdirSync(outDir, { recursive: true });
3904
+ const htmlFile = config.environments.client.html ?? import_node_path10.default.resolve(config.root, "index.html");
3133
3905
  const html = await readHtmlFile(config.root, htmlFile);
3134
3906
  const entryPoints = resolveClientEntries(config, html);
3135
3907
  if (entryPoints.length === 0) {
@@ -3139,7 +3911,7 @@ async function buildClientEnvironment(config) {
3139
3911
  const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
3140
3912
  const rolldownPlugins = [
3141
3913
  createOxcTransformPlugin(config, clientEnv),
3142
- ...toRolldownPlugins(allPlugins),
3914
+ ...toRolldownPlugins(allPlugins, clientEnv),
3143
3915
  ...nativeReporter ? [nativeReporter] : []
3144
3916
  ];
3145
3917
  const { inputOptions, outputOptions } = getRolldownOptions(
@@ -3150,6 +3922,7 @@ async function buildClientEnvironment(config) {
3150
3922
  const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
3151
3923
  const { output } = await bundle2.write(outputOptions);
3152
3924
  await bundle2.close();
3925
+ clientEnv.setBuildMetadata({ css: getCssMetadata(cssEngine) });
3153
3926
  if (html) {
3154
3927
  let processedHtml = html;
3155
3928
  const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
@@ -3163,7 +3936,9 @@ async function buildClientEnvironment(config) {
3163
3936
  processedHtml = processHtml(processedHtml, result);
3164
3937
  }
3165
3938
  }
3166
- processedHtml = injectCssLinks(processedHtml, cssEngine, config);
3939
+ if (clientEnv.options.build.css.inject !== false) {
3940
+ processedHtml = injectCssLinks(processedHtml, cssEngine, config);
3941
+ }
3167
3942
  for (const chunk of output) {
3168
3943
  if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
3169
3944
  processedHtml = replaceEntryScript(
@@ -3176,13 +3951,16 @@ async function buildClientEnvironment(config) {
3176
3951
  );
3177
3952
  }
3178
3953
  }
3179
- import_node_fs6.default.writeFileSync(import_node_path9.default.resolve(outDir, "index.html"), processedHtml);
3954
+ import_node_fs7.default.writeFileSync(import_node_path10.default.resolve(outDir, "index.html"), processedHtml);
3180
3955
  }
3181
3956
  if (!nativeReporter && config.logLevel !== "silent") {
3182
3957
  reportBuildOutput(output, config, logger);
3183
3958
  }
3184
3959
  warnLargeChunks(output, config, logger);
3185
- return { environment: clientEnv, result: { output } };
3960
+ return {
3961
+ environment: clientEnv,
3962
+ result: finalizeEnvironmentResult(clientEnv, { output })
3963
+ };
3186
3964
  } catch (error) {
3187
3965
  try {
3188
3966
  await clientEnv.close();
@@ -3198,7 +3976,12 @@ async function buildClientEnvironment(config) {
3198
3976
  async function buildServerEnvironment(config, name) {
3199
3977
  const envOptions = config.environments[name];
3200
3978
  const logger = config.logger;
3201
- const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
3979
+ const cssEngine = envOptions.consumer === "client" ? createCssEngine() : void 0;
3980
+ const pluginList = resolvePluginList(config, config.plugins, {
3981
+ consumer: envOptions.consumer,
3982
+ environmentName: name,
3983
+ cssEngine
3984
+ });
3202
3985
  const environment = new NastiEnvironment(name, config, {
3203
3986
  mode: "build",
3204
3987
  plugins: pluginList,
@@ -3214,38 +3997,40 @@ async function buildServerEnvironment(config, name) {
3214
3997
  }
3215
3998
  try {
3216
3999
  const result = await environment.driver.build(environment.getDriverContext());
3217
- return { environment, result };
4000
+ assertDriverBuildResult(environment, result);
4001
+ return { environment, result: finalizeEnvironmentResult(environment, result) };
3218
4002
  } catch (error) {
3219
4003
  await environment.close();
3220
4004
  throw error;
3221
4005
  }
3222
4006
  }
3223
4007
  for (const entry of envOptions.entry) {
3224
- if (!import_node_fs6.default.existsSync(entry)) {
4008
+ if (!import_node_fs7.default.existsSync(entry)) {
3225
4009
  await environment.close();
3226
4010
  throw new Error(`[nasti] environment "${name}" entry not found: ${entry}`);
3227
4011
  }
3228
4012
  }
3229
4013
  const rolldownPlugins = [
3230
4014
  createOxcTransformPlugin(config, environment),
3231
- ...toRolldownPlugins(environment.plugins)
4015
+ ...toRolldownPlugins(environment.plugins, environment)
3232
4016
  ];
3233
4017
  const { inputOptions, outputOptions, outDir } = getRolldownOptions(
3234
4018
  environment,
3235
4019
  envOptions.entry,
3236
4020
  rolldownPlugins
3237
4021
  );
3238
- if (envOptions.build.emptyOutDir && import_node_fs6.default.existsSync(outDir)) {
3239
- import_node_fs6.default.rmSync(outDir, { recursive: true, force: true });
3240
- }
3241
- import_node_fs6.default.mkdirSync(outDir, { recursive: true });
4022
+ import_node_fs7.default.mkdirSync(outDir, { recursive: true });
3242
4023
  const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
3243
4024
  const { output } = await bundle2.write(outputOptions);
3244
4025
  await bundle2.close();
4026
+ if (cssEngine) environment.setBuildMetadata({ css: getCssMetadata(cssEngine) });
3245
4027
  logger.info(
3246
- import_picocolors4.default.dim(` [${name}] `) + output.map((o) => import_node_path9.default.join(envOptions.build.outDir, o.fileName)).join(import_picocolors4.default.dim(", "))
4028
+ import_picocolors4.default.dim(` [${name}] `) + output.map((o) => import_node_path10.default.join(envOptions.build.outDir, o.fileName)).join(import_picocolors4.default.dim(", "))
3247
4029
  );
3248
- return { environment, result: { output } };
4030
+ return {
4031
+ environment,
4032
+ result: finalizeEnvironmentResult(environment, { output })
4033
+ };
3249
4034
  }
3250
4035
  function injectCssLinks(html, cssEngine, config) {
3251
4036
  const cssLinkTags = [];
@@ -3272,9 +4057,9 @@ function escapeRegExp(string) {
3272
4057
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3273
4058
  }
3274
4059
  function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, urlPrefix) {
3275
- const rootRelative = import_node_path9.default.relative(config.root, facadeModuleId).split(import_node_path9.default.sep).join("/");
3276
- const resolvedHtmlFile = import_node_path9.default.resolve(config.root, htmlFile);
3277
- const htmlRelative = import_node_path9.default.relative(import_node_path9.default.dirname(resolvedHtmlFile), facadeModuleId).split(import_node_path9.default.sep).join("/");
4060
+ const rootRelative = import_node_path10.default.relative(config.root, facadeModuleId).split(import_node_path10.default.sep).join("/");
4061
+ const resolvedHtmlFile = import_node_path10.default.resolve(config.root, htmlFile);
4062
+ const htmlRelative = import_node_path10.default.relative(import_node_path10.default.dirname(resolvedHtmlFile), facadeModuleId).split(import_node_path10.default.sep).join("/");
3278
4063
  const candidates = /* @__PURE__ */ new Set([
3279
4064
  rootRelative,
3280
4065
  `/${rootRelative}`,
@@ -3290,12 +4075,12 @@ function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, ur
3290
4075
  }
3291
4076
  return processed;
3292
4077
  }
3293
- var import_node_path9, import_node_fs6, import_node_module3, import_rolldown, import_picocolors4, debug4, NODE_BUILTINS;
4078
+ var import_node_path10, import_node_fs7, import_node_module3, import_rolldown, import_picocolors4, debug5, NODE_BUILTINS;
3294
4079
  var init_build = __esm({
3295
4080
  "src/build/index.ts"() {
3296
4081
  "use strict";
3297
- import_node_path9 = __toESM(require("path"), 1);
3298
- import_node_fs6 = __toESM(require("fs"), 1);
4082
+ import_node_path10 = __toESM(require("path"), 1);
4083
+ import_node_fs7 = __toESM(require("fs"), 1);
3299
4084
  import_node_module3 = require("module");
3300
4085
  import_rolldown = require("rolldown");
3301
4086
  init_config();
@@ -3308,8 +4093,9 @@ var init_build = __esm({
3308
4093
  init_reporter();
3309
4094
  init_debug();
3310
4095
  init_plugin_api();
4096
+ init_build_app_context();
3311
4097
  import_picocolors4 = __toESM(require("picocolors"), 1);
3312
- debug4 = createDebugger("nasti:build");
4098
+ debug5 = createDebugger("nasti:build");
3313
4099
  NODE_BUILTINS = /* @__PURE__ */ new Set([...import_node_module3.builtinModules, ...import_node_module3.builtinModules.map((m) => `node:${m}`)]);
3314
4100
  }
3315
4101
  });
@@ -3360,22 +4146,17 @@ var init_ws = __esm({
3360
4146
  });
3361
4147
 
3362
4148
  // 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
- function getReactRefreshRuntimeEsm() {
3371
- if (__refreshRuntimeCache) return __refreshRuntimeCache;
4149
+ function getReactRefreshRuntimeEsm(includeBoundaryHelpers = false) {
4150
+ if (__refreshRuntimeCache) {
4151
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
4152
+ }
3372
4153
  let cjsPath;
3373
4154
  try {
3374
4155
  const pkgPath = __require.resolve("react-refresh/package.json");
3375
- cjsPath = import_node_path11.default.join(import_node_path11.default.dirname(pkgPath), "cjs", "react-refresh-runtime.development.js");
4156
+ cjsPath = import_node_path12.default.join(import_node_path12.default.dirname(pkgPath), "cjs", "react-refresh-runtime.development.js");
3376
4157
  } catch (err) {
3377
- cjsPath = import_node_path11.default.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
3378
- if (!import_node_fs8.default.existsSync(cjsPath)) {
4158
+ cjsPath = import_node_path12.default.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
4159
+ if (!import_node_fs9.default.existsSync(cjsPath)) {
3379
4160
  const origMsg = err instanceof Error ? err.message : String(err);
3380
4161
  throw new Error(
3381
4162
  `[nasti] Missing dependency "react-refresh". Install it with: npm install react-refresh
@@ -3383,7 +4164,7 @@ Original resolve error: ${origMsg}`
3383
4164
  );
3384
4165
  }
3385
4166
  }
3386
- const cjsSource = import_node_fs8.default.readFileSync(cjsPath, "utf-8");
4167
+ const cjsSource = import_node_fs9.default.readFileSync(cjsPath, "utf-8");
3387
4168
  __refreshRuntimeCache = `// Wrapped react-refresh runtime -> ESM
3388
4169
  const exports = {};
3389
4170
  const module = { exports };
@@ -3403,7 +4184,7 @@ export const findAffectedHostInstances = __rt.findAffectedHostInstances;
3403
4184
  export const collectCustomHooksForSignature = __rt.collectCustomHooksForSignature;
3404
4185
  export default __rt;
3405
4186
  `;
3406
- return __refreshRuntimeCache;
4187
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
3407
4188
  }
3408
4189
  function buildReactRefreshWrapper(moduleUrl, transformedCode) {
3409
4190
  const urlLit = JSON.stringify(moduleUrl);
@@ -3429,27 +4210,46 @@ window.$RefreshReg$ = prevRefreshReg;
3429
4210
  window.$RefreshSig$ = prevRefreshSig;
3430
4211
 
3431
4212
  if (__nasti_hot__) {
3432
- __nasti_hot__.accept(() => {
3433
- clearTimeout(window.__nasti_refresh_timer__);
3434
- window.__nasti_refresh_timer__ = setTimeout(() => {
3435
- RefreshRuntime.performReactRefresh();
3436
- }, 30);
4213
+ let __nasti_current_exports__;
4214
+ __nasti_hot__.accept((nextExports) => {
4215
+ if (!nextExports) return;
4216
+ if (!__nasti_current_exports__) {
4217
+ __nasti_hot__.invalidate('Could not Fast Refresh (previous exports unavailable)');
4218
+ return;
4219
+ }
4220
+ const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate(
4221
+ ${urlLit},
4222
+ __nasti_current_exports__,
4223
+ nextExports,
4224
+ );
4225
+ if (invalidateMessage) __nasti_hot__.invalidate(invalidateMessage);
4226
+ });
4227
+ RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
4228
+ __nasti_current_exports__ = currentExports;
4229
+ RefreshRuntime.registerExportsForReactRefresh(${urlLit}, currentExports);
3437
4230
  });
3438
4231
  }
3439
4232
  `;
3440
4233
  }
3441
4234
  function injectImportMetaHot(code, moduleUrl) {
3442
- if (!/\bimport\.meta\.hot\b/.test(code)) return code;
4235
+ const hotRE = /\bimport\.meta\.hot\b/g;
4236
+ const matches = [...maskStringsAndComments(code).matchAll(hotRE)];
4237
+ if (matches.length === 0) return code;
4238
+ for (const match of matches.reverse()) {
4239
+ const start = match.index;
4240
+ code = code.slice(0, start) + "__nasti_hot__" + code.slice(start + match[0].length);
4241
+ }
3443
4242
  const urlLit = JSON.stringify(moduleUrl);
3444
4243
  const header = `import { createHotContext as __nasti_createHotContext__ } from "/@nasti/client";
3445
4244
  const __nasti_hot__ = __nasti_createHotContext__(${urlLit});
3446
4245
  `;
3447
- return header + code.replace(/\bimport\.meta\.hot\b/g, "__nasti_hot__");
4246
+ return header + code;
3448
4247
  }
3449
4248
  function transformMiddleware(ctx) {
3450
4249
  ctx.envDefine = buildEnvDefine(
3451
4250
  loadEnv(ctx.config.mode, ctx.config.root, ctx.config.envPrefix),
3452
- ctx.config.mode
4251
+ ctx.config.mode,
4252
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
3453
4253
  );
3454
4254
  return async (req, res, next) => {
3455
4255
  const url = req.url ?? "/";
@@ -3494,7 +4294,7 @@ function transformMiddleware(ctx) {
3494
4294
  return;
3495
4295
  }
3496
4296
  }
3497
- if (isModuleRequest(url)) {
4297
+ if (isModuleRequest(url, req.headers["sec-fetch-dest"])) {
3498
4298
  try {
3499
4299
  const result = await transformRequest(url, ctx);
3500
4300
  if (result) {
@@ -3520,13 +4320,14 @@ function transformMiddleware(ctx) {
3520
4320
  }
3521
4321
  async function transformRequest(url, ctx) {
3522
4322
  const { config, pluginContainer, moduleGraph } = ctx;
4323
+ url = removeTimestampQuery(url);
3523
4324
  const cleanReqUrl = url.split("?")[0];
3524
4325
  const cached2 = moduleGraph.getModuleByUrl(url);
3525
4326
  if (cached2?.transformResult) {
3526
4327
  return cached2.transformResult;
3527
4328
  }
3528
4329
  if (cleanReqUrl === "/@react-refresh") {
3529
- return { code: getReactRefreshRuntimeEsm() };
4330
+ return { code: getReactRefreshRuntimeEsm(true) };
3530
4331
  }
3531
4332
  if (cleanReqUrl.startsWith("/@modules/") && url.includes("?")) {
3532
4333
  const idParam = new URLSearchParams(url.slice(url.indexOf("?") + 1)).get("id");
@@ -3534,8 +4335,8 @@ async function transformRequest(url, ctx) {
3534
4335
  let realIdValid = false;
3535
4336
  try {
3536
4337
  if (idParam) {
3537
- realId = import_node_fs8.default.realpathSync(idParam);
3538
- realIdValid = import_node_fs8.default.statSync(realId).isFile() && (realId.includes(`${import_node_path11.default.sep}node_modules${import_node_path11.default.sep}`) || isUnderRoot(realId, config.root));
4338
+ realId = import_node_fs9.default.realpathSync(idParam);
4339
+ realIdValid = import_node_fs9.default.statSync(realId).isFile() && (realId.includes(`${import_node_path12.default.sep}node_modules${import_node_path12.default.sep}`) || isUnderRoot(realId, config.root));
3539
4340
  }
3540
4341
  } catch {
3541
4342
  realId = null;
@@ -3562,40 +4363,63 @@ async function transformRequest(url, ctx) {
3562
4363
  }
3563
4364
  const rawQuery = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
3564
4365
  if (rawQuery && !/^t=\d+$/.test(rawQuery)) {
3565
- const loaded = await pluginContainer.load(url);
3566
- if (loaded != null) {
3567
- let code2 = typeof loaded === "string" ? loaded : loaded.code;
4366
+ const mod2 = await moduleGraph.ensureEntryFromUrl(url);
4367
+ const transformVersion2 = mod2.invalidationVersion;
4368
+ const loaded2 = await pluginContainer.load(url);
4369
+ if (loaded2 != null) {
4370
+ let code2 = typeof loaded2 === "string" ? loaded2 : loaded2.code;
4371
+ let map2 = typeof loaded2 === "string" ? void 0 : loaded2.map;
3568
4372
  const transformed = await pluginContainer.transform(code2, url);
3569
4373
  if (transformed != null) {
3570
4374
  code2 = typeof transformed === "string" ? transformed : transformed.code;
4375
+ if (typeof transformed !== "string" && transformed.map != null) {
4376
+ map2 = transformed.map;
4377
+ }
3571
4378
  }
3572
- const mod2 = await moduleGraph.ensureEntryFromUrl(url);
3573
- moduleGraph.registerModule(mod2, cleanReqUrl);
3574
- code2 = injectImportMetaHot(code2, url);
4379
+ const parentFile = resolveUrlToFile(cleanReqUrl, config.root) ?? cleanReqUrl;
4380
+ moduleGraph.registerModule(mod2, parentFile);
4381
+ const hotInfo2 = rewriteHotAcceptDeps(code2, config, parentFile);
4382
+ code2 = injectImportMetaHot(hotInfo2.code, url);
3575
4383
  code2 = replaceEnvInCode(code2, ctx.envDefine ?? buildEnvDefine(
3576
4384
  loadEnv(config.mode, config.root, config.envPrefix),
3577
- config.mode
4385
+ config.mode,
4386
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
3578
4387
  ));
3579
- code2 = rewriteImports(code2, config, cleanReqUrl);
3580
- const transformResult2 = { code: code2 };
3581
- mod2.transformResult = transformResult2;
4388
+ const importedUrls2 = /* @__PURE__ */ new Set();
4389
+ code2 = rewriteImports(code2, config, parentFile, importedUrls2, moduleGraph);
4390
+ const pruned2 = await moduleGraph.updateModuleInfo(
4391
+ mod2,
4392
+ importedUrls2,
4393
+ hotInfo2.acceptedUrls,
4394
+ hotInfo2.isSelfAccepting,
4395
+ transformVersion2
4396
+ );
4397
+ const transformResult2 = { code: code2, map: map2 };
4398
+ if (pruned2) {
4399
+ if (pruned2.size > 0) ctx.onPrune?.([...pruned2].map((item) => item.url));
4400
+ mod2.transformResult = transformResult2;
4401
+ }
3582
4402
  return transformResult2;
3583
4403
  }
3584
4404
  }
3585
4405
  const filePath = resolveUrlToFile(url, config.root);
3586
- if (!filePath || !import_node_fs8.default.existsSync(filePath)) return null;
4406
+ if (!filePath || !import_node_fs9.default.existsSync(filePath)) return null;
3587
4407
  const mod = await moduleGraph.ensureEntryFromUrl(url);
3588
4408
  moduleGraph.registerModule(mod, filePath);
4409
+ const transformVersion = mod.invalidationVersion;
3589
4410
  if (cleanReqUrl.startsWith("/@modules/")) {
3590
4411
  const code2 = await bundlePackageAsEsm(filePath, config.root);
3591
4412
  const transformResult2 = { code: code2 };
3592
4413
  mod.transformResult = transformResult2;
3593
4414
  return transformResult2;
3594
4415
  }
3595
- let code = import_node_fs8.default.readFileSync(filePath, "utf-8");
4416
+ const loaded = await pluginContainer.load(filePath);
4417
+ let code = loaded == null ? import_node_fs9.default.readFileSync(filePath, "utf-8") : typeof loaded === "string" ? loaded : loaded.code;
4418
+ let map = loaded && typeof loaded !== "string" ? loaded.map : void 0;
3596
4419
  const pluginResult = await pluginContainer.transform(code, filePath);
3597
4420
  if (pluginResult) {
3598
4421
  code = typeof pluginResult === "string" ? pluginResult : pluginResult.code;
4422
+ if (typeof pluginResult !== "string") map = pluginResult.map;
3599
4423
  }
3600
4424
  const stableUrl = cleanReqUrl;
3601
4425
  let wrappedWithRefresh = false;
@@ -3606,26 +4430,41 @@ async function transformRequest(url, ctx) {
3606
4430
  sourcemap: true,
3607
4431
  jsxRuntime: "automatic",
3608
4432
  jsxImportSource: config.framework === "vue" ? "vue" : "react",
3609
- reactRefresh: useRefresh
4433
+ reactRefresh: useRefresh,
4434
+ target: ctx.environment?.options.build.target ?? config.build.target
3610
4435
  });
3611
4436
  code = result.code;
4437
+ if (result.map) map = JSON.parse(result.map);
3612
4438
  if (useRefresh) {
3613
4439
  code = buildReactRefreshWrapper(stableUrl, code);
3614
4440
  wrappedWithRefresh = true;
3615
- mod.isSelfAccepting = true;
3616
4441
  }
3617
4442
  }
4443
+ const hotInfo = rewriteHotAcceptDeps(code, config, filePath);
4444
+ code = hotInfo.code;
3618
4445
  if (!wrappedWithRefresh) {
3619
4446
  code = injectImportMetaHot(code, stableUrl);
3620
4447
  }
3621
4448
  const envDefine = ctx.envDefine ?? buildEnvDefine(
3622
4449
  loadEnv(config.mode, config.root, config.envPrefix),
3623
- config.mode
4450
+ config.mode,
4451
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
3624
4452
  );
3625
4453
  code = replaceEnvInCode(code, envDefine);
3626
- code = rewriteImports(code, config, filePath);
3627
- const transformResult = { code };
3628
- mod.transformResult = transformResult;
4454
+ const importedUrls = /* @__PURE__ */ new Set();
4455
+ code = rewriteImports(code, config, filePath, importedUrls, moduleGraph);
4456
+ const pruned = await moduleGraph.updateModuleInfo(
4457
+ mod,
4458
+ importedUrls,
4459
+ hotInfo.acceptedUrls,
4460
+ wrappedWithRefresh || hotInfo.isSelfAccepting,
4461
+ transformVersion
4462
+ );
4463
+ const transformResult = { code, map };
4464
+ if (pruned) {
4465
+ if (pruned.size > 0) ctx.onPrune?.([...pruned].map((item) => item.url));
4466
+ mod.transformResult = transformResult;
4467
+ }
3629
4468
  return transformResult;
3630
4469
  }
3631
4470
  async function loadVirtualModule(spec, ctx) {
@@ -3633,7 +4472,7 @@ async function loadVirtualModule(spec, ctx) {
3633
4472
  const resolved = await pluginContainer.resolveId(spec);
3634
4473
  if (resolved == null) return null;
3635
4474
  const resolvedId = typeof resolved === "string" ? resolved : resolved.id;
3636
- const looksVirtual = resolvedId.startsWith("\0") || !import_node_fs8.default.existsSync(resolvedId);
4475
+ const looksVirtual = resolvedId.startsWith("\0") || !import_node_fs9.default.existsSync(resolvedId);
3637
4476
  if (!looksVirtual) return null;
3638
4477
  const loadResult = await pluginContainer.load(resolvedId);
3639
4478
  if (loadResult == null) return null;
@@ -3644,9 +4483,10 @@ async function loadVirtualModule(spec, ctx) {
3644
4483
  }
3645
4484
  code = replaceEnvInCode(code, ctx.envDefine ?? buildEnvDefine(
3646
4485
  loadEnv(config.mode, config.root, config.envPrefix),
3647
- config.mode
4486
+ config.mode,
4487
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
3648
4488
  ));
3649
- const anchor = import_node_path11.default.join(config.root, "__nasti_virtual__.ts");
4489
+ const anchor = import_node_path12.default.join(config.root, "__nasti_virtual__.ts");
3650
4490
  code = rewriteImports(code, config, anchor);
3651
4491
  return { id: resolvedId, result: { code } };
3652
4492
  }
@@ -3672,7 +4512,7 @@ async function doBundlePackage(entryFile, root) {
3672
4512
  await bundle2.close();
3673
4513
  let code = result.output[0].code;
3674
4514
  code = code.replace(/process\.env\.NODE_ENV/g, '"development"');
3675
- const externalBaseDir = import_node_path11.default.dirname(entryFile);
4515
+ const externalBaseDir = import_node_path12.default.dirname(entryFile);
3676
4516
  code = code.replace(
3677
4517
  /^(import\b[^;'"]*?\bfrom\s+)(['"])([^'"./][^'"]*)(\2)/gm,
3678
4518
  (_, prefix, q, spec) => `${prefix}${q}${externalSpecToModuleUrl(spec, externalBaseDir, root)}${q}`
@@ -3690,16 +4530,16 @@ async function doBundlePackage(entryFile, root) {
3690
4530
  return code;
3691
4531
  }
3692
4532
  async function tryGenerateSubpathShim(entryFile, root) {
3693
- const NM = `${import_node_path11.default.sep}node_modules${import_node_path11.default.sep}`;
4533
+ const NM = `${import_node_path12.default.sep}node_modules${import_node_path12.default.sep}`;
3694
4534
  if (!entryFile.includes(NM)) return null;
3695
4535
  let pkgDir = null;
3696
4536
  let pkgName = null;
3697
- let dir = import_node_path11.default.dirname(entryFile);
4537
+ let dir = import_node_path12.default.dirname(entryFile);
3698
4538
  while (true) {
3699
- const pkgJsonPath = import_node_path11.default.join(dir, "package.json");
3700
- if (import_node_fs8.default.existsSync(pkgJsonPath)) {
4539
+ const pkgJsonPath = import_node_path12.default.join(dir, "package.json");
4540
+ if (import_node_fs9.default.existsSync(pkgJsonPath)) {
3701
4541
  try {
3702
- const pkg = JSON.parse(import_node_fs8.default.readFileSync(pkgJsonPath, "utf-8"));
4542
+ const pkg = JSON.parse(import_node_fs9.default.readFileSync(pkgJsonPath, "utf-8"));
3703
4543
  if (typeof pkg?.name === "string" && pkg.name) {
3704
4544
  pkgDir = dir;
3705
4545
  pkgName = pkg.name;
@@ -3708,16 +4548,16 @@ async function tryGenerateSubpathShim(entryFile, root) {
3708
4548
  } catch {
3709
4549
  }
3710
4550
  }
3711
- const parent = import_node_path11.default.dirname(dir);
4551
+ const parent = import_node_path12.default.dirname(dir);
3712
4552
  if (parent === dir) return null;
3713
4553
  dir = parent;
3714
4554
  if (!dir.includes(NM)) return null;
3715
4555
  }
3716
4556
  if (!pkgDir || !pkgName) return null;
3717
- const entryExt = import_node_path11.default.extname(entryFile);
4557
+ const entryExt = import_node_path12.default.extname(entryFile);
3718
4558
  const mainEntry = pickMainEntryByExtension(pkgDir, entryExt);
3719
4559
  if (!mainEntry) return null;
3720
- if (import_node_path11.default.resolve(mainEntry) === import_node_path11.default.resolve(entryFile)) return null;
4560
+ if (import_node_path12.default.resolve(mainEntry) === import_node_path12.default.resolve(entryFile)) return null;
3721
4561
  let mainNs;
3722
4562
  let subNs;
3723
4563
  try {
@@ -3741,7 +4581,7 @@ async function tryGenerateSubpathShim(entryFile, root) {
3741
4581
  if (mainNs["default"] !== subNs["default"]) return null;
3742
4582
  }
3743
4583
  const rootMain = resolveNodeModule(root, pkgName);
3744
- const mainEntryUrl = rootMain && rootMain.startsWith(pkgDir + import_node_path11.default.sep) ? `/@modules/${pkgName}` : `/@modules/${pkgName}?id=${encodeURIComponent(mainEntry)}`;
4584
+ const mainEntryUrl = rootMain && rootMain.startsWith(pkgDir + import_node_path12.default.sep) ? `/@modules/${pkgName}` : `/@modules/${pkgName}?id=${encodeURIComponent(mainEntry)}`;
3745
4585
  const lines = [
3746
4586
  `// Nasti subpath shim \u2192 ${pkgName} (avoid duplicate bundling)`,
3747
4587
  `import * as __pkg from "${mainEntryUrl}";`
@@ -3755,10 +4595,10 @@ async function tryGenerateSubpathShim(entryFile, root) {
3755
4595
  return lines.join("\n") + "\n";
3756
4596
  }
3757
4597
  function pickMainEntryByExtension(pkgDir, preferredExt) {
3758
- const pkgJsonPath = import_node_path11.default.join(pkgDir, "package.json");
4598
+ const pkgJsonPath = import_node_path12.default.join(pkgDir, "package.json");
3759
4599
  let pkg;
3760
4600
  try {
3761
- pkg = JSON.parse(import_node_fs8.default.readFileSync(pkgJsonPath, "utf-8"));
4601
+ pkg = JSON.parse(import_node_fs9.default.readFileSync(pkgJsonPath, "utf-8"));
3762
4602
  } catch {
3763
4603
  return null;
3764
4604
  }
@@ -3777,14 +4617,14 @@ function pickMainEntryByExtension(pkgDir, preferredExt) {
3777
4617
  if (typeof pkg.module === "string") candidates.push(pkg.module);
3778
4618
  if (typeof pkg.main === "string") candidates.push(pkg.main);
3779
4619
  for (const cand of candidates) {
3780
- if (import_node_path11.default.extname(cand) === preferredExt) {
3781
- const full = import_node_path11.default.resolve(pkgDir, cand);
3782
- if (import_node_fs8.default.existsSync(full)) return full;
4620
+ if (import_node_path12.default.extname(cand) === preferredExt) {
4621
+ const full = import_node_path12.default.resolve(pkgDir, cand);
4622
+ if (import_node_fs9.default.existsSync(full)) return full;
3783
4623
  }
3784
4624
  }
3785
4625
  for (const cand of candidates) {
3786
- const full = import_node_path11.default.resolve(pkgDir, cand);
3787
- if (import_node_fs8.default.existsSync(full)) return full;
4626
+ const full = import_node_path12.default.resolve(pkgDir, cand);
4627
+ if (import_node_fs9.default.existsSync(full)) return full;
3788
4628
  }
3789
4629
  return null;
3790
4630
  }
@@ -3830,72 +4670,231 @@ async function injectCjsNamedExports(code, entryFile) {
3830
4670
  return code;
3831
4671
  }
3832
4672
  }
3833
- function rewriteImports(code, config, filePath) {
4673
+ function rewriteImports(code, config, filePath, importedUrls, moduleGraph) {
4674
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
4675
+ const transformSpec = (spec) => {
4676
+ const resolved = removeTimestampQuery(resolveSpec(spec));
4677
+ importedUrls?.add(resolved);
4678
+ const timestamp = moduleGraph?.getModuleByUrl(resolved)?.lastHMRTimestamp ?? 0;
4679
+ return timestamp > 0 ? appendTimestampQuery(resolved, timestamp) : resolved;
4680
+ };
4681
+ return code.replace(
4682
+ /\bfrom\s+(['"])([^'"]+)\1/g,
4683
+ (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
4684
+ ).replace(
4685
+ /\bimport\s+(['"])([^'"]+)\1/g,
4686
+ (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
4687
+ ).replace(
4688
+ /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
4689
+ (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
4690
+ );
4691
+ }
4692
+ function createModuleSpecifierResolver(config, filePath) {
3834
4693
  const root = config.root;
3835
- const fileDir = import_node_path11.default.dirname(filePath);
4694
+ const fileDir = import_node_path12.default.dirname(filePath);
3836
4695
  const aliasEntries = Object.entries(config.resolve.alias).sort(
3837
4696
  ([a], [b]) => b.length - a.length
3838
4697
  );
3839
- const toRootUrl = (abs) => "/" + import_node_path11.default.relative(root, abs).replace(/\\/g, "/");
3840
- const transformSpec = (spec) => {
3841
- const suffixMatch = spec.match(/[?#].*$/);
4698
+ const toRootUrl = (abs) => "/" + import_node_path12.default.relative(root, abs).replace(/\\/g, "/");
4699
+ return (specifier) => {
4700
+ const suffixMatch = specifier.match(/[?#].*$/);
3842
4701
  const suffix = suffixMatch ? suffixMatch[0] : "";
3843
- const baseSpec = suffix ? spec.slice(0, -suffix.length) : spec;
4702
+ const baseSpec = suffix ? specifier.slice(0, -suffix.length) : specifier;
3844
4703
  for (const [key, value] of aliasEntries) {
3845
4704
  if (baseSpec === key || baseSpec.startsWith(key + "/")) {
3846
4705
  const aliasBase = resolveAliasTarget2(value, root);
3847
4706
  const sub = baseSpec.slice(key.length).replace(/^\//, "");
3848
- const target = sub ? import_node_path11.default.join(aliasBase, sub) : aliasBase;
4707
+ const target = sub ? import_node_path12.default.join(aliasBase, sub) : aliasBase;
3849
4708
  const resolved = tryResolveDiskPath(target);
3850
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
4709
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
3851
4710
  }
3852
4711
  }
3853
4712
  if (baseSpec.startsWith("./") || baseSpec.startsWith("../")) {
3854
- const target = import_node_path11.default.resolve(fileDir, baseSpec);
3855
- const resolved = tryResolveDiskPath(target);
3856
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
4713
+ const resolved = tryResolveDiskPath(import_node_path12.default.resolve(fileDir, baseSpec));
4714
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
3857
4715
  }
3858
4716
  if (baseSpec.startsWith("/") && !baseSpec.startsWith("/@")) {
3859
- const target = import_node_path11.default.join(root, baseSpec.replace(/^\//, ""));
3860
- const resolved = tryResolveDiskPath(target);
3861
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
4717
+ const resolved = tryResolveDiskPath(import_node_path12.default.join(root, baseSpec.replace(/^\//, "")));
4718
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
3862
4719
  }
3863
- if (baseSpec.startsWith("/")) return spec;
3864
- return `/@modules/${spec}`;
4720
+ if (baseSpec.startsWith("/")) return specifier;
4721
+ return `/@modules/${specifier}`;
3865
4722
  };
3866
- return code.replace(
3867
- /\bfrom\s+(['"])([^'"]+)\1/g,
3868
- (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
3869
- ).replace(
3870
- /\bimport\s+(['"])([^'"]+)\1/g,
3871
- (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
3872
- ).replace(
3873
- /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
3874
- (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
3875
- );
4723
+ }
4724
+ function rewriteHotAcceptDeps(code, config, filePath) {
4725
+ const acceptedUrls = /* @__PURE__ */ new Set();
4726
+ const edits = [];
4727
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
4728
+ const acceptRE = /(?:\bimport\.meta\.hot|\b__nasti_hot__)(?:(?:\?\.)|\.)accept\s*\(/g;
4729
+ const searchableCode = maskStringsAndComments(code);
4730
+ let isSelfAccepting = false;
4731
+ let match;
4732
+ while (match = acceptRE.exec(searchableCode)) {
4733
+ let cursor = match.index + match[0].length;
4734
+ const skipTrivia = () => {
4735
+ while (cursor < code.length) {
4736
+ if (/\s/.test(code[cursor])) {
4737
+ cursor++;
4738
+ continue;
4739
+ }
4740
+ if (code[cursor] === "/" && code[cursor + 1] === "/") {
4741
+ cursor += 2;
4742
+ while (cursor < code.length && code[cursor] !== "\n") cursor++;
4743
+ continue;
4744
+ }
4745
+ if (code[cursor] === "/" && code[cursor + 1] === "*") {
4746
+ cursor += 2;
4747
+ while (cursor < code.length && !(code[cursor] === "*" && code[cursor + 1] === "/")) cursor++;
4748
+ cursor += 2;
4749
+ continue;
4750
+ }
4751
+ break;
4752
+ }
4753
+ };
4754
+ skipTrivia();
4755
+ const first = code[cursor];
4756
+ if (!first || first === ")" || first !== "[" && first !== "'" && first !== '"' && first !== "`") {
4757
+ isSelfAccepting = true;
4758
+ continue;
4759
+ }
4760
+ const readLiteral = () => {
4761
+ const quote = code[cursor];
4762
+ if (quote !== "'" && quote !== '"' && quote !== "`") return;
4763
+ const start = cursor;
4764
+ cursor++;
4765
+ let raw = "";
4766
+ while (cursor < code.length) {
4767
+ const char = code[cursor];
4768
+ if (char === "\\") {
4769
+ raw += code[cursor + 1] ?? "";
4770
+ cursor += 2;
4771
+ continue;
4772
+ }
4773
+ if (char === quote) {
4774
+ cursor++;
4775
+ const resolved = removeTimestampQuery(resolveSpec(raw));
4776
+ acceptedUrls.add(resolved);
4777
+ edits.push({ start, end: cursor, value: JSON.stringify(resolved) });
4778
+ return;
4779
+ }
4780
+ if (quote === "`" && char === "$" && code[cursor + 1] === "{") return;
4781
+ raw += char;
4782
+ cursor++;
4783
+ }
4784
+ };
4785
+ if (first === "[") {
4786
+ cursor++;
4787
+ while (cursor < code.length) {
4788
+ skipTrivia();
4789
+ if (code[cursor] === ",") {
4790
+ cursor++;
4791
+ skipTrivia();
4792
+ }
4793
+ if (code[cursor] === "]") break;
4794
+ const before = cursor;
4795
+ readLiteral();
4796
+ if (cursor === before) break;
4797
+ }
4798
+ } else {
4799
+ readLiteral();
4800
+ }
4801
+ }
4802
+ for (const edit of edits.sort((a, b) => b.start - a.start)) {
4803
+ code = code.slice(0, edit.start) + edit.value + code.slice(edit.end);
4804
+ }
4805
+ return { code, acceptedUrls, isSelfAccepting };
4806
+ }
4807
+ function maskStringsAndComments(code) {
4808
+ const masked = code.split("");
4809
+ let state = "code";
4810
+ const isRegexStart = (index2) => {
4811
+ let previous = index2 - 1;
4812
+ while (previous >= 0 && /\s/.test(code[previous])) previous--;
4813
+ return previous < 0 || "=(:,!&|?{};[]+-*%^~<>".includes(code[previous]);
4814
+ };
4815
+ for (let i = 0; i < code.length; i++) {
4816
+ const char = code[i];
4817
+ const next = code[i + 1];
4818
+ if (state === "code") {
4819
+ if (char === "'") state = "single";
4820
+ else if (char === '"') state = "double";
4821
+ else if (char === "`") state = "template";
4822
+ else if (char === "/" && next === "/") state = "line-comment";
4823
+ else if (char === "/" && next === "*") state = "block-comment";
4824
+ else if (char === "/" && isRegexStart(i)) state = "regex";
4825
+ else continue;
4826
+ masked[i] = " ";
4827
+ continue;
4828
+ }
4829
+ if (state === "line-comment") {
4830
+ if (char === "\n") {
4831
+ state = "code";
4832
+ } else {
4833
+ masked[i] = " ";
4834
+ }
4835
+ continue;
4836
+ }
4837
+ if (state === "block-comment") {
4838
+ masked[i] = char === "\n" ? "\n" : " ";
4839
+ if (char === "*" && next === "/") {
4840
+ masked[i + 1] = " ";
4841
+ i++;
4842
+ state = "code";
4843
+ }
4844
+ continue;
4845
+ }
4846
+ if (state === "regex" || state === "regex-class") {
4847
+ masked[i] = char === "\n" ? "\n" : " ";
4848
+ if (char === "\\") {
4849
+ if (i + 1 < code.length) masked[++i] = " ";
4850
+ } else if (state === "regex" && char === "[") {
4851
+ state = "regex-class";
4852
+ } else if (state === "regex-class" && char === "]") {
4853
+ state = "regex";
4854
+ } else if (state === "regex" && char === "/") {
4855
+ state = "code";
4856
+ }
4857
+ continue;
4858
+ }
4859
+ masked[i] = char === "\n" ? "\n" : " ";
4860
+ if (char === "\\") {
4861
+ if (i + 1 < code.length) masked[++i] = " ";
4862
+ continue;
4863
+ }
4864
+ if (state === "single" && char === "'" || state === "double" && char === '"' || state === "template" && char === "`") {
4865
+ state = "code";
4866
+ }
4867
+ }
4868
+ return masked.join("");
3876
4869
  }
3877
4870
  function resolveAliasTarget2(value, root) {
3878
- if (import_node_path11.default.isAbsolute(value) && import_node_fs8.default.existsSync(value)) return value;
3879
- if (value.startsWith("/")) return import_node_path11.default.join(root, value.slice(1));
3880
- return import_node_path11.default.resolve(root, value);
4871
+ if (import_node_path12.default.isAbsolute(value) && import_node_fs9.default.existsSync(value)) return value;
4872
+ if (value.startsWith("/")) return import_node_path12.default.join(root, value.slice(1));
4873
+ return import_node_path12.default.resolve(root, value);
3881
4874
  }
3882
4875
  function tryResolveDiskPath(target) {
3883
- if (import_node_fs8.default.existsSync(target) && import_node_fs8.default.statSync(target).isFile()) return target;
4876
+ if (import_node_fs9.default.existsSync(target) && import_node_fs9.default.statSync(target).isFile()) return target;
3884
4877
  for (const ext of RESOLVE_EXTENSIONS) {
3885
4878
  const withExt = target + ext;
3886
- if (import_node_fs8.default.existsSync(withExt) && import_node_fs8.default.statSync(withExt).isFile()) return withExt;
4879
+ if (import_node_fs9.default.existsSync(withExt) && import_node_fs9.default.statSync(withExt).isFile()) return withExt;
3887
4880
  }
3888
- if (import_node_fs8.default.existsSync(target) && import_node_fs8.default.statSync(target).isDirectory()) {
4881
+ if (import_node_fs9.default.existsSync(target) && import_node_fs9.default.statSync(target).isDirectory()) {
3889
4882
  for (const ext of RESOLVE_EXTENSIONS) {
3890
- const idx = import_node_path11.default.join(target, "index" + ext);
3891
- if (import_node_fs8.default.existsSync(idx) && import_node_fs8.default.statSync(idx).isFile()) return idx;
4883
+ const idx = import_node_path12.default.join(target, "index" + ext);
4884
+ if (import_node_fs9.default.existsSync(idx) && import_node_fs9.default.statSync(idx).isFile()) return idx;
3892
4885
  }
3893
4886
  }
3894
4887
  return null;
3895
4888
  }
3896
4889
  function isUnderRoot(abs, root) {
3897
- const rel = import_node_path11.default.relative(root, abs);
3898
- return !!rel && !rel.startsWith("..") && !import_node_path11.default.isAbsolute(rel);
4890
+ const rel = import_node_path12.default.relative(root, abs);
4891
+ return !!rel && !rel.startsWith("..") && !import_node_path12.default.isAbsolute(rel);
4892
+ }
4893
+ function appendTimestampQuery(url, timestamp) {
4894
+ const hashIndex = url.indexOf("#");
4895
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
4896
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
4897
+ return `${withoutHash}${withoutHash.includes("?") ? "&" : "?"}t=${timestamp}${hash}`;
3899
4898
  }
3900
4899
  function externalSpecToModuleUrl(spec, baseDir, root) {
3901
4900
  const resolved = resolveNodeModule(baseDir, spec);
@@ -3908,7 +4907,7 @@ function resolveNodeModule(baseDir, moduleName) {
3908
4907
  const resolved = resolveNodeModuleEntry(baseDir, moduleName);
3909
4908
  if (!resolved) return null;
3910
4909
  try {
3911
- return import_node_fs8.default.realpathSync(resolved);
4910
+ return import_node_fs9.default.realpathSync(resolved);
3912
4911
  } catch {
3913
4912
  return resolved;
3914
4913
  }
@@ -3928,21 +4927,21 @@ function resolveNodeModuleEntry(root, moduleName) {
3928
4927
  let pkgDir = null;
3929
4928
  let dir = root;
3930
4929
  for (; ; ) {
3931
- const candidate = import_node_path11.default.join(dir, "node_modules", pkgName);
3932
- if (import_node_fs8.default.existsSync(candidate)) {
4930
+ const candidate = import_node_path12.default.join(dir, "node_modules", pkgName);
4931
+ if (import_node_fs9.default.existsSync(candidate)) {
3933
4932
  pkgDir = candidate;
3934
4933
  break;
3935
4934
  }
3936
- const parent = import_node_path11.default.dirname(dir);
4935
+ const parent = import_node_path12.default.dirname(dir);
3937
4936
  if (parent === dir) break;
3938
4937
  dir = parent;
3939
4938
  }
3940
4939
  if (!pkgDir) return null;
3941
- const pkgJsonPath = import_node_path11.default.join(pkgDir, "package.json");
3942
- if (!import_node_fs8.default.existsSync(pkgJsonPath)) return null;
4940
+ const pkgJsonPath = import_node_path12.default.join(pkgDir, "package.json");
4941
+ if (!import_node_fs9.default.existsSync(pkgJsonPath)) return null;
3943
4942
  let pkg;
3944
4943
  try {
3945
- pkg = JSON.parse(import_node_fs8.default.readFileSync(pkgJsonPath, "utf-8"));
4944
+ pkg = JSON.parse(import_node_fs9.default.readFileSync(pkgJsonPath, "utf-8"));
3946
4945
  } catch {
3947
4946
  return null;
3948
4947
  }
@@ -3955,32 +4954,32 @@ function resolveNodeModuleEntry(root, moduleName) {
3955
4954
  const subDirs = [""];
3956
4955
  for (const field of ["module", "main"]) {
3957
4956
  if (typeof pkg[field] === "string") {
3958
- const dir2 = import_node_path11.default.dirname(pkg[field]);
4957
+ const dir2 = import_node_path12.default.dirname(pkg[field]);
3959
4958
  if (dir2 && dir2 !== "." && !subDirs.includes(dir2)) subDirs.push(dir2);
3960
4959
  }
3961
4960
  }
3962
4961
  for (const dir2 of subDirs) {
3963
- const direct = import_node_path11.default.join(pkgDir, dir2, subpath);
3964
- if (import_node_fs8.default.existsSync(direct) && import_node_fs8.default.statSync(direct).isFile()) return direct;
4962
+ const direct = import_node_path12.default.join(pkgDir, dir2, subpath);
4963
+ if (import_node_fs9.default.existsSync(direct) && import_node_fs9.default.statSync(direct).isFile()) return direct;
3965
4964
  for (const ext of RESOLVE_EXTENSIONS) {
3966
- if (import_node_fs8.default.existsSync(direct + ext)) return direct + ext;
4965
+ if (import_node_fs9.default.existsSync(direct + ext)) return direct + ext;
3967
4966
  }
3968
4967
  }
3969
4968
  return null;
3970
4969
  }
3971
4970
  for (const field of ["module", "jsnext:main", "jsnext", "main"]) {
3972
4971
  if (typeof pkg[field] === "string") {
3973
- const entry = import_node_path11.default.join(pkgDir, pkg[field]);
3974
- if (import_node_fs8.default.existsSync(entry)) return entry;
4972
+ const entry = import_node_path12.default.join(pkgDir, pkg[field]);
4973
+ if (import_node_fs9.default.existsSync(entry)) return entry;
3975
4974
  }
3976
4975
  }
3977
- const indexFallback = import_node_path11.default.join(pkgDir, "index.js");
3978
- if (import_node_fs8.default.existsSync(indexFallback)) return indexFallback;
4976
+ const indexFallback = import_node_path12.default.join(pkgDir, "index.js");
4977
+ if (import_node_fs9.default.existsSync(indexFallback)) return indexFallback;
3979
4978
  return null;
3980
4979
  }
3981
4980
  function resolvePackageExports(exports2, key, pkgDir) {
3982
4981
  if (typeof exports2 === "string") {
3983
- return key === "." ? import_node_path11.default.join(pkgDir, exports2) : null;
4982
+ return key === "." ? import_node_path12.default.join(pkgDir, exports2) : null;
3984
4983
  }
3985
4984
  const entry = exports2[key];
3986
4985
  if (entry === void 0) {
@@ -3992,7 +4991,7 @@ function resolvePackageExports(exports2, key, pkgDir) {
3992
4991
  return resolveExportValue(entry, pkgDir);
3993
4992
  }
3994
4993
  function resolveExportValue(value, pkgDir) {
3995
- if (typeof value === "string") return import_node_path11.default.join(pkgDir, value);
4994
+ if (typeof value === "string") return import_node_path12.default.join(pkgDir, value);
3996
4995
  if (Array.isArray(value)) {
3997
4996
  for (const item of value) {
3998
4997
  const r = resolveExportValue(item, pkgDir);
@@ -4016,54 +5015,62 @@ function resolveUrlToFile(url, root) {
4016
5015
  const moduleName = cleanUrl.slice("/@modules/".length);
4017
5016
  return resolveNodeModule(root, moduleName);
4018
5017
  }
4019
- const filePath = import_node_path11.default.resolve(root, cleanUrl.replace(/^\//, ""));
4020
- if (import_node_fs8.default.existsSync(filePath) && import_node_fs8.default.statSync(filePath).isFile()) {
5018
+ const filePath = import_node_path12.default.resolve(root, cleanUrl.replace(/^\//, ""));
5019
+ if (import_node_fs9.default.existsSync(filePath) && import_node_fs9.default.statSync(filePath).isFile()) {
4021
5020
  return filePath;
4022
5021
  }
4023
5022
  for (const ext of RESOLVE_EXTENSIONS) {
4024
5023
  const withExt = filePath + ext;
4025
- if (import_node_fs8.default.existsSync(withExt)) return withExt;
5024
+ if (import_node_fs9.default.existsSync(withExt)) return withExt;
4026
5025
  }
4027
5026
  for (const ext of RESOLVE_EXTENSIONS) {
4028
- const indexFile = import_node_path11.default.join(filePath, "index" + ext);
4029
- if (import_node_fs8.default.existsSync(indexFile)) return indexFile;
5027
+ const indexFile = import_node_path12.default.join(filePath, "index" + ext);
5028
+ if (import_node_fs9.default.existsSync(indexFile)) return indexFile;
4030
5029
  }
4031
5030
  return null;
4032
5031
  }
4033
- function isModuleRequest(url) {
5032
+ function isModuleRequest(url, destination) {
4034
5033
  const cleanUrl = url.split("?")[0];
4035
5034
  if (/\.(ts|tsx|jsx|js|mjs|vue|css|json)$/.test(cleanUrl)) return true;
4036
5035
  if (cleanUrl.startsWith("/@modules/")) return true;
4037
- if (!import_node_path11.default.extname(cleanUrl)) return true;
5036
+ if (isAssetFile(cleanUrl)) {
5037
+ const query = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
5038
+ const isExplicitAssetModule = /(?:^|&)(?:url|raw)(?:&|$)/.test(query);
5039
+ return isExplicitAssetModule || destination === "script";
5040
+ }
5041
+ if (!import_node_path12.default.extname(cleanUrl)) return true;
4038
5042
  return false;
4039
5043
  }
4040
5044
  function getHmrClientCode() {
4041
5045
  return `
4042
5046
  // Nasti HMR Client
4043
- const socket = new WebSocket(\`ws://\${location.host}\`, 'nasti-hmr');
5047
+ const socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
5048
+ const socket = new WebSocket(socketProtocol + '://' + location.host, 'nasti-hmr');
4044
5049
  const hotModulesMap = new Map();
4045
5050
  const disposeMap = new Map();
4046
5051
  const pruneMap = new Map();
5052
+ const dataMap = new Map();
5053
+ const customListenersMap = new Map();
5054
+ let updateQueue = [];
5055
+ let pendingUpdateQueue = false;
4047
5056
 
4048
5057
  socket.addEventListener('message', async ({ data }) => {
4049
5058
  const payload = JSON.parse(data);
5059
+ // \u9ED8\u8BA4\u6D4F\u89C8\u5668 client \u53EA\u6D88\u8D39\u81EA\u5DF1\u7684 HMR \u6D88\u606F\uFF1Bnative/worker \u73AF\u5883\u901A\u8FC7\u5404\u81EA\u7684
5060
+ // HotChannel \u6216 app-level HMR \u534F\u8C03\u5668\u5904\u7406\u540C\u4E00 transport \u4E0A\u7684\u547D\u540D\u6D88\u606F\u3002
5061
+ if (payload.environment && payload.environment !== 'client') return;
4050
5062
  switch (payload.type) {
4051
5063
  case 'connected':
4052
- console.log('[nasti] connected.');
5064
+ console.debug('[nasti] connected.');
4053
5065
  clearErrorOverlay();
4054
5066
  break;
4055
5067
  case 'update':
4056
5068
  try {
4057
- await Promise.all(payload.updates.map((update) => {
4058
- if (update.type === 'js-update') {
4059
- return fetchUpdate(update);
4060
- } else if (update.type === 'css-update') {
4061
- return updateCss(update.path);
4062
- }
4063
- }));
5069
+ // CSS \u5728 unbundled \u6A21\u5F0F\u4E0B\u4E5F\u662F\u4F1A\u6CE8\u5165 <style> \u7684 JS \u6A21\u5757\uFF0C\u548C\u666E\u901A JS
5070
+ // \u4E00\u6837\u91CD\u65B0 import \u624D\u80FD\u6267\u884C dispose/accept \u5E76\u4FDD\u6301\u9875\u9762\u72B6\u6001\u3002
5071
+ await Promise.all(payload.updates.map(queueUpdate));
4064
5072
  clearErrorOverlay();
4065
- console.log('[nasti] HMR update complete, reloading page');
4066
- location.reload();
5073
+ console.debug('[nasti] HMR update complete.');
4067
5074
  } catch (err) {
4068
5075
  console.error('[nasti] HMR update failed:', err);
4069
5076
  showErrorOverlay(err);
@@ -4074,11 +5081,34 @@ socket.addEventListener('message', async ({ data }) => {
4074
5081
  location.reload();
4075
5082
  break;
4076
5083
  case 'prune':
4077
- payload.paths.forEach((p) => {
4078
- const cb = pruneMap.get(p);
4079
- if (cb) cb();
4080
- });
5084
+ await Promise.all(payload.paths.map(async (path) => {
5085
+ const data = dataMap.get(path);
5086
+ const dispose = disposeMap.get(path);
5087
+ const prune = pruneMap.get(path);
5088
+ if (dispose) await dispose(data);
5089
+ if (prune) await prune(data);
5090
+ hotModulesMap.delete(path);
5091
+ disposeMap.delete(path);
5092
+ pruneMap.delete(path);
5093
+ dataMap.delete(path);
5094
+ clearCustomListeners(path);
5095
+ }));
4081
5096
  break;
5097
+ case 'custom': {
5098
+ const listenersByOwner = customListenersMap.get(payload.event);
5099
+ if (!listenersByOwner) break;
5100
+ const results = await Promise.allSettled(
5101
+ [...listenersByOwner.values()]
5102
+ .flatMap((listeners) => [...listeners])
5103
+ .map((listener) => Promise.resolve().then(() => listener(payload.data)))
5104
+ );
5105
+ for (const result of results) {
5106
+ if (result.status === 'rejected') {
5107
+ console.error('[nasti] custom HMR event listener failed:', result.reason);
5108
+ }
5109
+ }
5110
+ break;
5111
+ }
4082
5112
  case 'error':
4083
5113
  console.error('[nasti] error:', payload.err.message);
4084
5114
  showErrorOverlay(payload.err);
@@ -4086,33 +5116,64 @@ socket.addEventListener('message', async ({ data }) => {
4086
5116
  }
4087
5117
  });
4088
5118
 
4089
- // \u81EA\u52A8\u91CD\u8FDE\uFF08\u65AD\u7EBF\u65F6\u6307\u6570\u9000\u907F\uFF09
5119
+ // \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
4090
5120
  let reconnectTimer = 0;
4091
5121
  socket.addEventListener('close', () => {
4092
5122
  clearTimeout(reconnectTimer);
4093
5123
  reconnectTimer = setTimeout(() => location.reload(), 1000);
4094
5124
  });
4095
5125
 
5126
+ /**
5127
+ * \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
5128
+ * \u6539\u53D8\u6A21\u5757\u5E94\u7528\u987A\u5E8F\u3002\u8FD9\u4E0E Vite HMRClient \u7684 fetch/apply \u4E24\u9636\u6BB5\u4E00\u81F4\u3002
5129
+ */
5130
+ async function queueUpdate(update) {
5131
+ updateQueue.push(fetchUpdate(update));
5132
+ if (pendingUpdateQueue) return;
5133
+
5134
+ pendingUpdateQueue = true;
5135
+ await Promise.resolve();
5136
+ pendingUpdateQueue = false;
5137
+ const loading = updateQueue;
5138
+ updateQueue = [];
5139
+ const applyUpdates = await Promise.all(loading);
5140
+ for (const apply of applyUpdates) {
5141
+ if (apply) apply();
5142
+ }
5143
+ }
5144
+
4096
5145
  async function fetchUpdate(update) {
4097
5146
  const mod = hotModulesMap.get(update.path);
4098
- // \u5148\u8DD1 dispose\uFF08\u7ED9\u6A21\u5757\u673A\u4F1A\u6E05\u7406\u526F\u4F5C\u7528\uFF09
4099
- const dispose = disposeMap.get(update.path);
4100
- if (dispose) dispose();
5147
+ // \u5C1A\u672A\u5728\u5F53\u524D\u9875\u9762\u52A0\u8F7D\u7684\u52A8\u6001\u6A21\u5757\u4E0D\u9700\u8981\u66F4\u65B0\u3002
5148
+ if (!mod) return;
4101
5149
 
4102
- const newMod = await import(update.acceptedPath + '?t=' + update.timestamp);
4103
- if (mod) {
4104
- // \u590D\u5236\u56DE\u8C03\u6570\u7EC4\u907F\u514D\u56DE\u8C03\u5185\u90E8\u53C8\u4FEE\u6539 hotModulesMap \u9020\u6210\u8FED\u4EE3\u5F02\u5E38
4105
- [...mod.callbacks].forEach((cb) => cb(newMod));
4106
- }
5150
+ // \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
5151
+ const qualifiedCallbacks = mod.callbacks.filter(({ deps }) =>
5152
+ deps.includes(update.acceptedPath)
5153
+ );
5154
+ const isSelfUpdate = update.path === update.acceptedPath;
5155
+ if (!isSelfUpdate && qualifiedCallbacks.length === 0) return;
5156
+
5157
+ const dispose = disposeMap.get(update.acceptedPath);
5158
+ if (dispose) await dispose(dataMap.get(update.acceptedPath));
5159
+ const newMod = await import(appendTimestampQuery(update.acceptedPath, update.timestamp));
5160
+
5161
+ return () => {
5162
+ for (const { deps, fn } of qualifiedCallbacks) {
5163
+ fn(deps.map((dep) => dep === update.acceptedPath ? newMod : undefined));
5164
+ }
5165
+ const detail = isSelfUpdate
5166
+ ? update.path
5167
+ : update.acceptedPath + ' via ' + update.path;
5168
+ console.debug('[nasti] hot updated:', detail);
5169
+ };
4107
5170
  }
4108
5171
 
4109
- function updateCss(path) {
4110
- const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
4111
- if (el) {
4112
- return fetch(path + '?t=' + Date.now())
4113
- .then(r => r.text())
4114
- .then(css => { el.textContent = css; });
4115
- }
5172
+ function appendTimestampQuery(url, timestamp) {
5173
+ const hashIndex = url.indexOf('#');
5174
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : '';
5175
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
5176
+ return withoutHash + (withoutHash.includes('?') ? '&' : '?') + 't=' + timestamp + hash;
4116
5177
  }
4117
5178
 
4118
5179
  function clearErrorOverlay() {
@@ -4140,23 +5201,31 @@ function showErrorOverlay(err) {
4140
5201
  document.body.appendChild(overlay);
4141
5202
  }
4142
5203
 
4143
- /**
4144
- * \u751F\u6210 import.meta.hot \u7684 hot context\u3002
4145
- * \u5173\u952E\u7EA6\u675F\uFF1A\u540C\u4E00 ownerPath \u7684 accept \u56DE\u8C03\u5FC5\u987B\u66FF\u6362\uFF08\u4E0D\u662F append\uFF09\u3002
4146
- * \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
4147
- * \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
4148
- */
4149
5204
  export function createHotContext(ownerPath) {
5205
+ if (!dataMap.has(ownerPath)) dataMap.set(ownerPath, {});
5206
+
5207
+ // \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
5208
+ const existing = hotModulesMap.get(ownerPath);
5209
+ if (existing) existing.callbacks = [];
5210
+ clearCustomListeners(ownerPath);
5211
+
5212
+ const acceptDeps = (deps, callback = () => {}) => {
5213
+ const mod = hotModulesMap.get(ownerPath) || { id: ownerPath, callbacks: [] };
5214
+ mod.callbacks.push({ deps, fn: callback });
5215
+ hotModulesMap.set(ownerPath, mod);
5216
+ };
5217
+
4150
5218
  return {
4151
5219
  accept(deps, callback) {
4152
- // \u81EA\u63A5\u53D7: hot.accept() \u6216 hot.accept(callback)
4153
5220
  if (typeof deps === 'function' || deps === undefined) {
4154
- hotModulesMap.set(ownerPath, { callbacks: [deps || (() => {})] });
4155
- return;
5221
+ acceptDeps([ownerPath], ([mod]) => deps?.(mod));
5222
+ } else if (typeof deps === 'string') {
5223
+ acceptDeps([deps], ([mod]) => callback?.(mod));
5224
+ } else if (Array.isArray(deps)) {
5225
+ acceptDeps(deps, callback);
5226
+ } else {
5227
+ throw new Error('invalid hot.accept() usage');
4156
5228
  }
4157
- // \u4F9D\u8D56\u63A5\u53D7: hot.accept(deps, callback)\uFF0C\u591A\u6B21\u8C03\u7528\u8FFD\u52A0
4158
- const existing = hotModulesMap.get(ownerPath)?.callbacks ?? [];
4159
- hotModulesMap.set(ownerPath, { callbacks: [...existing, callback] });
4160
5229
  },
4161
5230
  prune(callback) {
4162
5231
  pruneMap.set(ownerPath, callback);
@@ -4164,30 +5233,122 @@ export function createHotContext(ownerPath) {
4164
5233
  dispose(callback) {
4165
5234
  disposeMap.set(ownerPath, callback);
4166
5235
  },
5236
+ on(event, callback) {
5237
+ let listenersByOwner = customListenersMap.get(event);
5238
+ if (!listenersByOwner) {
5239
+ listenersByOwner = new Map();
5240
+ customListenersMap.set(event, listenersByOwner);
5241
+ }
5242
+ let listeners = listenersByOwner.get(ownerPath);
5243
+ if (!listeners) {
5244
+ listeners = new Set();
5245
+ listenersByOwner.set(ownerPath, listeners);
5246
+ }
5247
+ listeners.add(callback);
5248
+ },
5249
+ off(event, callback) {
5250
+ const listenersByOwner = customListenersMap.get(event);
5251
+ const listeners = listenersByOwner?.get(ownerPath);
5252
+ listeners?.delete(callback);
5253
+ if (listeners?.size === 0) listenersByOwner.delete(ownerPath);
5254
+ if (listenersByOwner?.size === 0) customListenersMap.delete(event);
5255
+ },
4167
5256
  invalidate() {
4168
5257
  location.reload();
4169
5258
  },
4170
- data: {},
5259
+ data: dataMap.get(ownerPath),
4171
5260
  };
4172
5261
  }
5262
+
5263
+ function clearCustomListeners(ownerPath) {
5264
+ for (const [event, listenersByOwner] of customListenersMap) {
5265
+ listenersByOwner.delete(ownerPath);
5266
+ if (listenersByOwner.size === 0) customListenersMap.delete(event);
5267
+ }
5268
+ }
4173
5269
  `;
4174
5270
  }
4175
- var import_node_path11, import_node_fs8, import_node_module5, import_node_url3, import_picocolors6, import_meta, __dirname_esm, __require, __refreshRuntimeCache, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
5271
+ var import_node_path12, import_node_fs9, import_node_module5, import_node_url3, import_picocolors6, import_meta, __dirname_esm, __require, __refreshRuntimeCache, REACT_REFRESH_BOUNDARY_HELPERS, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
4176
5272
  var init_middleware = __esm({
4177
5273
  "src/server/middleware.ts"() {
4178
5274
  "use strict";
4179
- import_node_path11 = __toESM(require("path"), 1);
4180
- import_node_fs8 = __toESM(require("fs"), 1);
5275
+ import_node_path12 = __toESM(require("path"), 1);
5276
+ import_node_fs9 = __toESM(require("fs"), 1);
4181
5277
  import_node_module5 = require("module");
4182
5278
  import_node_url3 = require("url");
4183
5279
  import_picocolors6 = __toESM(require("picocolors"), 1);
4184
5280
  init_transformer();
4185
5281
  init_html();
4186
5282
  init_env();
5283
+ init_url();
5284
+ init_assets();
4187
5285
  import_meta = {};
4188
- __dirname_esm = import_node_path11.default.dirname((0, import_node_url3.fileURLToPath)(import_meta.url));
5286
+ __dirname_esm = import_node_path12.default.dirname((0, import_node_url3.fileURLToPath)(import_meta.url));
4189
5287
  __require = (0, import_node_module5.createRequire)(import_meta.url);
4190
5288
  __refreshRuntimeCache = null;
5289
+ REACT_REFRESH_BOUNDARY_HELPERS = `
5290
+ function __nastiIsPlainObject(obj) {
5291
+ return Object.prototype.toString.call(obj) === '[object Object]' &&
5292
+ (obj.constructor === Object || obj.constructor === undefined);
5293
+ }
5294
+ function __nastiIsCompoundComponent(type) {
5295
+ if (!__nastiIsPlainObject(type)) return false;
5296
+ for (const key in type) {
5297
+ if (!isLikelyComponentType(type[key])) return false;
5298
+ }
5299
+ return true;
5300
+ }
5301
+ export function registerExportsForReactRefresh(filename, moduleExports) {
5302
+ for (const key in moduleExports) {
5303
+ if (key === '__esModule') continue;
5304
+ const value = moduleExports[key];
5305
+ if (isLikelyComponentType(value)) {
5306
+ register(value, filename + ' export ' + key);
5307
+ } else if (__nastiIsCompoundComponent(value)) {
5308
+ for (const subKey in value) {
5309
+ register(value[subKey], filename + ' export ' + key + '-' + subKey);
5310
+ }
5311
+ }
5312
+ }
5313
+ }
5314
+ let __nastiRefreshTimer;
5315
+ function __nastiEnqueueRefresh() {
5316
+ clearTimeout(__nastiRefreshTimer);
5317
+ __nastiRefreshTimer = setTimeout(() => performReactRefresh(), 16);
5318
+ }
5319
+ function __nastiCheckExports(ignored, exports, predicate) {
5320
+ for (const key in exports) {
5321
+ if (ignored.includes(key)) continue;
5322
+ if (!predicate(key, exports[key])) return key;
5323
+ }
5324
+ return true;
5325
+ }
5326
+ export function validateRefreshBoundaryAndEnqueueUpdate(id, prevExports, nextExports) {
5327
+ const ignored = window.__getReactRefreshIgnoredExports?.({ id }) ?? [];
5328
+ if (__nastiCheckExports(ignored, prevExports, (key) => key in nextExports) !== true) {
5329
+ return 'Could not Fast Refresh (export removed)';
5330
+ }
5331
+ if (__nastiCheckExports(ignored, nextExports, (key) => key in prevExports) !== true) {
5332
+ return 'Could not Fast Refresh (new export)';
5333
+ }
5334
+ let hasExports = false;
5335
+ const compatible = __nastiCheckExports(ignored, nextExports, (key, value) => {
5336
+ hasExports = true;
5337
+ return isLikelyComponentType(value) ||
5338
+ __nastiIsCompoundComponent(value) ||
5339
+ prevExports[key] === value;
5340
+ });
5341
+ if (!hasExports) {
5342
+ return 'Could not Fast Refresh (no exports)';
5343
+ }
5344
+ if (compatible === true) {
5345
+ __nastiEnqueueRefresh();
5346
+ return;
5347
+ }
5348
+ return 'Could not Fast Refresh ("' + compatible + '" export is incompatible)';
5349
+ }
5350
+ export const __hmr_import = (module) => import(module);
5351
+ `;
4191
5352
  REACT_REFRESH_GLOBAL_PREAMBLE = `
4192
5353
  import RefreshRuntime from "/@react-refresh";
4193
5354
  RefreshRuntime.injectIntoGlobalHook(window);
@@ -4203,28 +5364,37 @@ window.__vite_plugin_react_preamble_installed__ = true;
4203
5364
  });
4204
5365
 
4205
5366
  // src/server/hmr.ts
4206
- async function handleFileChange(file, server) {
4207
- const { moduleGraph, ws, config } = server;
5367
+ async function handleFileChange(file, server, environmentName = "client", timestamp = Date.now()) {
5368
+ const { config } = server;
5369
+ const environment = server.environments[environmentName];
5370
+ if (!environment) {
5371
+ throw new Error(`[nasti] unknown dev environment "${environmentName}"`);
5372
+ }
5373
+ const moduleGraph = environment.moduleGraph;
4208
5374
  const logger = config.logger;
4209
- const relativePath = "/" + import_node_path12.default.relative(config.root, file);
4210
- const shortFile = import_node_path12.default.relative(config.root, file);
5375
+ const relativePath = "/" + import_node_path13.default.relative(config.root, file);
5376
+ const shortFile = import_node_path13.default.relative(config.root, file);
4211
5377
  const mods = moduleGraph.getModulesByFile(file);
4212
5378
  if (!mods || mods.size === 0) {
4213
- return;
5379
+ return null;
4214
5380
  }
4215
5381
  const updates = [];
4216
- const timestamp = Date.now();
5382
+ const graph = moduleGraph;
5383
+ const invalidatedModules = /* @__PURE__ */ new Set();
5384
+ const affectedSet = /* @__PURE__ */ new Set();
5385
+ let fullReload = false;
4217
5386
  for (const mod of mods) {
4218
- moduleGraph.invalidateModule(mod);
5387
+ graph.invalidateModuleAndImporters(mod, timestamp, invalidatedModules);
4219
5388
  const ctx = {
4220
5389
  file,
4221
5390
  timestamp,
4222
5391
  modules: [mod],
4223
- read: () => import_node_fs9.default.readFileSync(file, "utf-8"),
4224
- server
5392
+ read: () => import_node_fs10.default.readFileSync(file, "utf-8"),
5393
+ server,
5394
+ environment
4225
5395
  };
4226
5396
  let affectedModules = [mod];
4227
- for (const plugin of config.plugins) {
5397
+ for (const plugin of environment.plugins) {
4228
5398
  if (plugin.handleHotUpdate) {
4229
5399
  const result = await plugin.handleHotUpdate(ctx);
4230
5400
  if (result) {
@@ -4233,36 +5403,59 @@ async function handleFileChange(file, server) {
4233
5403
  }
4234
5404
  }
4235
5405
  for (const affected of affectedModules) {
4236
- const boundaries = moduleGraph.getHmrBoundaries(affected);
5406
+ affectedSet.add(affected);
5407
+ graph.invalidateModuleAndImporters(affected, timestamp, invalidatedModules);
5408
+ const boundaries = graph.getHmrBoundaries(affected);
4237
5409
  if (boundaries.length === 0) {
4238
- logger.info(import_picocolors7.default.green("page reload ") + import_picocolors7.default.dim(shortFile), { timestamp: true });
4239
- ws.send({ type: "full-reload", path: relativePath });
4240
- return;
5410
+ fullReload = true;
5411
+ continue;
4241
5412
  }
4242
- for (const { boundary } of boundaries) {
4243
- updates.push({
5413
+ for (const { boundary, acceptedVia } of boundaries) {
5414
+ const update = {
4244
5415
  type: boundary.type === "css" ? "css-update" : "js-update",
4245
5416
  path: boundary.url,
4246
- acceptedPath: affected.url,
5417
+ acceptedPath: acceptedVia.url,
4247
5418
  timestamp
4248
- });
5419
+ };
5420
+ if (!updates.some(
5421
+ (existing) => existing.type === update.type && existing.path === update.path && existing.acceptedPath === update.acceptedPath
5422
+ )) {
5423
+ updates.push(update);
5424
+ }
4249
5425
  }
4250
5426
  }
4251
5427
  }
4252
- if (updates.length > 0) {
5428
+ const transformed = await Promise.all(
5429
+ [...affectedSet].map(async (module2) => ({
5430
+ module: module2,
5431
+ result: await environment.transformRequest(module2.url)
5432
+ }))
5433
+ );
5434
+ const logPrefix = environmentName === "client" ? "" : `[${environmentName}] `;
5435
+ if (fullReload) {
5436
+ logger.info(import_picocolors7.default.green(`${logPrefix}reload `) + import_picocolors7.default.dim(shortFile), { timestamp: true });
5437
+ environment.hot.send({ type: "full-reload", path: relativePath });
5438
+ } else if (updates.length > 0) {
4253
5439
  logger.info(
4254
- updates.map((u) => import_picocolors7.default.green("hmr update ") + import_picocolors7.default.dim(u.path)).join("\n"),
5440
+ updates.map((u) => import_picocolors7.default.green(`${logPrefix}hmr update `) + import_picocolors7.default.dim(u.path)).join("\n"),
4255
5441
  { timestamp: true }
4256
5442
  );
4257
- ws.send({ type: "update", updates });
5443
+ environment.hot.send({ type: "update", updates });
4258
5444
  }
5445
+ return {
5446
+ environment,
5447
+ modules: [...affectedSet],
5448
+ updates,
5449
+ transformed,
5450
+ fullReload
5451
+ };
4259
5452
  }
4260
- var import_node_path12, import_node_fs9, import_picocolors7;
5453
+ var import_node_path13, import_node_fs10, import_picocolors7;
4261
5454
  var init_hmr = __esm({
4262
5455
  "src/server/hmr.ts"() {
4263
5456
  "use strict";
4264
- import_node_path12 = __toESM(require("path"), 1);
4265
- import_node_fs9 = __toESM(require("fs"), 1);
5457
+ import_node_path13 = __toESM(require("path"), 1);
5458
+ import_node_fs10 = __toESM(require("fs"), 1);
4266
5459
  import_picocolors7 = __toESM(require("picocolors"), 1);
4267
5460
  }
4268
5461
  });
@@ -4281,18 +5474,18 @@ function createModuleRunner(environment) {
4281
5474
  }
4282
5475
  return new NastiModuleRunner(environment);
4283
5476
  }
4284
- var import_node_path13, import_node_fs10, import_node_module6, import_node_url4, debug5, NODE_BUILTINS3, NastiModuleRunner, AsyncFunction;
5477
+ var import_node_path14, import_node_fs11, import_node_module6, import_node_url4, debug6, NODE_BUILTINS3, NastiModuleRunner, AsyncFunction;
4285
5478
  var init_runnable_environment = __esm({
4286
5479
  "src/server/runnable-environment.ts"() {
4287
5480
  "use strict";
4288
- import_node_path13 = __toESM(require("path"), 1);
4289
- import_node_fs10 = __toESM(require("fs"), 1);
5481
+ import_node_path14 = __toESM(require("path"), 1);
5482
+ import_node_fs11 = __toESM(require("fs"), 1);
4290
5483
  import_node_module6 = require("module");
4291
5484
  import_node_url4 = require("url");
4292
5485
  init_transformer();
4293
5486
  init_env();
4294
5487
  init_debug();
4295
- debug5 = createDebugger("nasti:ssr");
5488
+ debug6 = createDebugger("nasti:ssr");
4296
5489
  NODE_BUILTINS3 = /* @__PURE__ */ new Set([...import_node_module6.builtinModules, ...import_node_module6.builtinModules.map((m) => `node:${m}`)]);
4297
5490
  NastiModuleRunner = class {
4298
5491
  environment;
@@ -4308,7 +5501,7 @@ var init_runnable_environment = __esm({
4308
5501
  this.config.mode,
4309
5502
  ssrDefineOverrides(environment.consumer)
4310
5503
  );
4311
- this.require = (0, import_node_module6.createRequire)(import_node_path13.default.join(this.config.root, "package.json"));
5504
+ this.require = (0, import_node_module6.createRequire)(import_node_path14.default.join(this.config.root, "package.json"));
4312
5505
  const handlers = {
4313
5506
  fetchModule: async (id, importer) => this.fetchModule(id, importer),
4314
5507
  getBuiltins: () => [/^node:/, ...import_node_module6.builtinModules]
@@ -4332,9 +5525,9 @@ var init_runnable_environment = __esm({
4332
5525
  this.cache.clear();
4333
5526
  }
4334
5527
  resolveToId(rawUrl) {
4335
- if (import_node_path13.default.isAbsolute(rawUrl) && import_node_fs10.default.existsSync(rawUrl.split("?")[0])) return rawUrl;
5528
+ if (import_node_path14.default.isAbsolute(rawUrl) && import_node_fs11.default.existsSync(rawUrl.split("?")[0])) return rawUrl;
4336
5529
  const clean = rawUrl.replace(/^\//, "");
4337
- return import_node_path13.default.resolve(this.config.root, clean);
5530
+ return import_node_path14.default.resolve(this.config.root, clean);
4338
5531
  }
4339
5532
  /**
4340
5533
  * fetchModule(invoke 契约方法):环境管线产出 runner 可执行代码。
@@ -4343,14 +5536,14 @@ var init_runnable_environment = __esm({
4343
5536
  */
4344
5537
  async fetchModule(id, importer) {
4345
5538
  if (NODE_BUILTINS3.has(id)) return { externalize: id };
4346
- if (!id.startsWith(".") && !import_node_path13.default.isAbsolute(id) && !id.startsWith("\0")) {
5539
+ if (!id.startsWith(".") && !import_node_path14.default.isAbsolute(id) && !id.startsWith("\0")) {
4347
5540
  return { externalize: id };
4348
5541
  }
4349
5542
  const container = this.environment.pluginContainer;
4350
5543
  let resolvedId = id;
4351
5544
  if (id.startsWith(".") && importer) {
4352
5545
  const resolved = await container.resolveId(id, importer);
4353
- resolvedId = resolved ? typeof resolved === "string" ? resolved : resolved.id : import_node_path13.default.resolve(import_node_path13.default.dirname(importer.split("?")[0]), id);
5546
+ resolvedId = resolved ? typeof resolved === "string" ? resolved : resolved.id : import_node_path14.default.resolve(import_node_path14.default.dirname(importer.split("?")[0]), id);
4354
5547
  }
4355
5548
  resolvedId = this.completeExtension(resolvedId);
4356
5549
  const cleanId = resolvedId.split("?")[0];
@@ -4358,8 +5551,8 @@ var init_runnable_environment = __esm({
4358
5551
  const loaded = await container.load(resolvedId);
4359
5552
  if (loaded != null) {
4360
5553
  code = typeof loaded === "string" ? loaded : loaded.code;
4361
- } else if (import_node_fs10.default.existsSync(cleanId)) {
4362
- code = import_node_fs10.default.readFileSync(cleanId, "utf-8");
5554
+ } else if (import_node_fs11.default.existsSync(cleanId)) {
5555
+ code = import_node_fs11.default.readFileSync(cleanId, "utf-8");
4363
5556
  } else {
4364
5557
  throw new Error(`[nasti:ssr] cannot load module: ${resolvedId}`);
4365
5558
  }
@@ -4370,6 +5563,7 @@ var init_runnable_environment = __esm({
4370
5563
  if (shouldTransform(cleanId)) {
4371
5564
  const result = transformCode(cleanId, code, {
4372
5565
  sourcemap: false,
5566
+ target: this.environment.options.build.target,
4373
5567
  jsxRuntime: "automatic",
4374
5568
  jsxImportSource: this.config.framework === "vue" ? "vue" : "react"
4375
5569
  });
@@ -4386,25 +5580,25 @@ var init_runnable_environment = __esm({
4386
5580
  );
4387
5581
  }
4388
5582
  const runnerResult = await moduleRunnerTransform(resolvedId, code);
4389
- debug5?.(`fetchModule ${resolvedId} (${runnerResult.deps?.length ?? 0} deps)`);
5583
+ debug6?.(`fetchModule ${resolvedId} (${runnerResult.deps?.length ?? 0} deps)`);
4390
5584
  return { id: resolvedId, code: runnerResult.code };
4391
5585
  }
4392
5586
  completeExtension(id) {
4393
5587
  const clean = id.split("?")[0];
4394
5588
  const query = id.includes("?") ? id.slice(id.indexOf("?")) : "";
4395
- if (import_node_fs10.default.existsSync(clean) && import_node_fs10.default.statSync(clean).isFile()) return id;
5589
+ if (import_node_fs11.default.existsSync(clean) && import_node_fs11.default.statSync(clean).isFile()) return id;
4396
5590
  const jsMatch = clean.match(/^(.*)\.([mc]?)jsx?$/);
4397
5591
  if (jsMatch) {
4398
5592
  for (const tsExt of [`.${jsMatch[2]}ts`, `.${jsMatch[2]}tsx`]) {
4399
- if (import_node_fs10.default.existsSync(jsMatch[1] + tsExt)) return jsMatch[1] + tsExt + query;
5593
+ if (import_node_fs11.default.existsSync(jsMatch[1] + tsExt)) return jsMatch[1] + tsExt + query;
4400
5594
  }
4401
5595
  }
4402
5596
  for (const ext of this.config.resolve.extensions) {
4403
- if (import_node_fs10.default.existsSync(clean + ext)) return clean + ext + query;
5597
+ if (import_node_fs11.default.existsSync(clean + ext)) return clean + ext + query;
4404
5598
  }
4405
5599
  for (const ext of this.config.resolve.extensions) {
4406
- const indexPath = import_node_path13.default.join(clean, `index${ext}`);
4407
- if (import_node_fs10.default.existsSync(indexPath)) return indexPath;
5600
+ const indexPath = import_node_path14.default.join(clean, `index${ext}`);
5601
+ if (import_node_fs11.default.existsSync(indexPath)) return indexPath;
4408
5602
  }
4409
5603
  return id;
4410
5604
  }
@@ -4431,10 +5625,10 @@ var init_runnable_environment = __esm({
4431
5625
  return;
4432
5626
  }
4433
5627
  const ssrImport = async (dep) => {
4434
- if (NODE_BUILTINS3.has(dep) || !dep.startsWith(".") && !import_node_path13.default.isAbsolute(dep) && !dep.startsWith("\0")) {
5628
+ if (NODE_BUILTINS3.has(dep) || !dep.startsWith(".") && !import_node_path14.default.isAbsolute(dep) && !dep.startsWith("\0")) {
4435
5629
  return this.importExternal(dep);
4436
5630
  }
4437
- const depId = dep.startsWith(".") ? this.completeExtension(import_node_path13.default.resolve(import_node_path13.default.dirname(fetched.id.split("?")[0]), dep)) : this.completeExtension(dep);
5631
+ const depId = dep.startsWith(".") ? this.completeExtension(import_node_path14.default.resolve(import_node_path14.default.dirname(fetched.id.split("?")[0]), dep)) : this.completeExtension(dep);
4438
5632
  return this.instantiate(depId);
4439
5633
  };
4440
5634
  const ssrExportAll = (sourceModule) => {
@@ -4466,7 +5660,7 @@ var init_runnable_environment = __esm({
4466
5660
  }
4467
5661
  async importExternal(spec) {
4468
5662
  try {
4469
- return await (spec.startsWith("node:") || !import_node_path13.default.isAbsolute(spec) ? import(this.resolveExternalSpecifier(spec)) : import((0, import_node_url4.pathToFileURL)(spec).href));
5663
+ return await (spec.startsWith("node:") || !import_node_path14.default.isAbsolute(spec) ? import(this.resolveExternalSpecifier(spec)) : import((0, import_node_url4.pathToFileURL)(spec).href));
4470
5664
  } catch (err) {
4471
5665
  throw new Error(`[nasti:ssr] failed to import external "${spec}": ${err.message}`);
4472
5666
  }
@@ -4506,7 +5700,7 @@ async function createBundledDevServer(opts) {
4506
5700
  }
4507
5701
  } catch (err) {
4508
5702
  throw new Error(
4509
- `[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.`
5703
+ `[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.`
4510
5704
  );
4511
5705
  }
4512
5706
  const html = await readHtmlFile(config.root, config.environments.client?.html);
@@ -4524,7 +5718,7 @@ async function createBundledDevServer(opts) {
4524
5718
  createReactRefreshRuntimePlugin(entryPoints),
4525
5719
  createBundledOxcRefreshPlugin()
4526
5720
  ] : [],
4527
- ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins)),
5721
+ ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins, clientEnv)),
4528
5722
  ...useReactRefresh ? [
4529
5723
  refreshWrapperFn({
4530
5724
  cwd: config.root,
@@ -4559,7 +5753,7 @@ async function createBundledDevServer(opts) {
4559
5753
  for (const { clientId, update } of updates) {
4560
5754
  if (update.type === "Noop") continue;
4561
5755
  if (update.type === "FullReload") {
4562
- debug6?.(`full reload for ${clientId}: ${update.reason ?? ""}`);
5756
+ debug7?.(`full reload for ${clientId}: ${update.reason ?? ""}`);
4563
5757
  needsLatestOutput = true;
4564
5758
  continue;
4565
5759
  }
@@ -4573,7 +5767,7 @@ async function createBundledDevServer(opts) {
4573
5767
  }
4574
5768
  const url = `/${patchPath}`;
4575
5769
  logger.info(
4576
- import_picocolors8.default.green("hmr update ") + import_picocolors8.default.dim(changedFiles.map((f) => import_node_path14.default.relative(config.root, f)).join(", ")),
5770
+ import_picocolors8.default.green("hmr update ") + import_picocolors8.default.dim(changedFiles.map((f) => import_node_path15.default.relative(config.root, f)).join(", ")),
4577
5771
  { timestamp: true }
4578
5772
  );
4579
5773
  sendTo(clientId, { type: "hmr:update", path: url, url });
@@ -4609,7 +5803,7 @@ async function createBundledDevServer(opts) {
4609
5803
  },
4610
5804
  {
4611
5805
  watch: { skipWrite: true },
4612
- rebuildStrategy: "auto",
5806
+ rebuildStrategy: "never",
4613
5807
  onOutput(result) {
4614
5808
  if (result instanceof Error) {
4615
5809
  logger.error(import_picocolors8.default.red(`[bundled] build error: ${result.message}`), { error: result });
@@ -4625,7 +5819,13 @@ async function createBundledDevServer(opts) {
4625
5819
  memoryFiles.set(`${file.fileName}.map`, JSON.stringify(file.map));
4626
5820
  }
4627
5821
  }
4628
- debug6?.(`bundle output refreshed (${result.output.length} files)`);
5822
+ debug7?.(`bundle output refreshed (${result.output.length} files)`);
5823
+ },
5824
+ onAdditionalAssets(result) {
5825
+ for (const file of result.output) {
5826
+ const content = file.type === "chunk" ? file.code : file.source;
5827
+ if (content != null) memoryFiles.set(file.fileName, content);
5828
+ }
4629
5829
  },
4630
5830
  async onHmrUpdates(result) {
4631
5831
  if (result instanceof Error) {
@@ -4634,7 +5834,7 @@ async function createBundledDevServer(opts) {
4634
5834
  return;
4635
5835
  }
4636
5836
  const { updates, changedFiles } = result;
4637
- debug6?.(
5837
+ debug7?.(
4638
5838
  `onHmrUpdates(engine watcher): ${changedFiles.length} changed, ${updates.length} updates`
4639
5839
  );
4640
5840
  if (changedFiles.length === 0) return;
@@ -4653,24 +5853,29 @@ async function createBundledDevServer(opts) {
4653
5853
  if (!clientId) return;
4654
5854
  wss.handleUpgrade(req, socket, head, (ws) => {
4655
5855
  bundledClients.set(clientId, ws);
4656
- debug6?.(`bundled client connected: ${clientId}`);
4657
- ws.send(JSON.stringify({ type: "connected" }));
5856
+ debug7?.(`bundled client connected: ${clientId}`);
5857
+ void engine.registerClient(clientId).then(async () => {
5858
+ for (const fileName of entryFileNames.values()) {
5859
+ await engine.notifyPayloadDelivered(fileName);
5860
+ }
5861
+ ws.send(JSON.stringify({ type: "connected" }));
5862
+ }).catch((err) => {
5863
+ debug7?.(`registerClient failed for ${clientId}: ${err?.message ?? err}`);
5864
+ ws.close();
5865
+ });
4658
5866
  ws.on("message", async (raw) => {
4659
5867
  try {
4660
5868
  const msg = JSON.parse(String(raw));
4661
- if (msg.type === "hmr:module-registered" && Array.isArray(msg.modules)) {
4662
- await engine.registerModules(clientId, msg.modules);
4663
- debug6?.(`registered ${msg.modules.length} modules for ${clientId}`);
4664
- } else if (msg.type === "hmr:invalidate") {
5869
+ if (msg.type === "hmr:invalidate") {
4665
5870
  scheduleFullReload();
4666
5871
  }
4667
5872
  } catch (err) {
4668
- debug6?.(`bundled ws message error: ${err.message}`);
5873
+ debug7?.(`bundled ws message error: ${err.message}`);
4669
5874
  }
4670
5875
  });
4671
5876
  ws.on("close", () => {
4672
5877
  bundledClients.delete(clientId);
4673
- engine.removeClient(clientId).catch((err) => debug6?.(`removeClient failed for ${clientId}: ${err?.message ?? err}`));
5878
+ engine.removeClient(clientId).catch((err) => debug7?.(`removeClient failed for ${clientId}: ${err?.message ?? err}`));
4674
5879
  });
4675
5880
  });
4676
5881
  });
@@ -4687,10 +5892,18 @@ async function createBundledDevServer(opts) {
4687
5892
  res.end("// [nasti] lazy endpoint requires id & clientId");
4688
5893
  return;
4689
5894
  }
4690
- const code = await engine.compileEntry(id, clientId);
5895
+ const output = await engine.compileEntry(id, clientId);
5896
+ if (output.sourcemap && output.sourcemapFilename) {
5897
+ memoryFiles.set(output.sourcemapFilename, output.sourcemap);
5898
+ }
5899
+ res.once("finish", () => {
5900
+ void engine.notifyPayloadDelivered(output.filename).catch(
5901
+ (err) => debug7?.(`notifyPayloadDelivered failed: ${err?.message ?? err}`)
5902
+ );
5903
+ });
4691
5904
  res.setHeader("Content-Type", "application/javascript");
4692
5905
  res.setHeader("Cache-Control", "no-store");
4693
- res.end(code + "\n;export {}");
5906
+ res.end(output.code + "\n;export {}");
4694
5907
  return;
4695
5908
  }
4696
5909
  const patchHit = patches.get(pathname.replace(/^\//, ""));
@@ -4709,8 +5922,13 @@ async function createBundledDevServer(opts) {
4709
5922
  return;
4710
5923
  }
4711
5924
  res.setHeader("ETag", hit.etag);
4712
- res.setHeader("Content-Type", MIME_TYPES[import_node_path14.default.extname(fileName)] ?? "application/octet-stream");
5925
+ res.setHeader("Content-Type", MIME_TYPES[import_node_path15.default.extname(fileName)] ?? "application/octet-stream");
4713
5926
  res.setHeader("Cache-Control", "no-cache");
5927
+ res.once("finish", () => {
5928
+ void engine.notifyPayloadDelivered(fileName).catch(
5929
+ (err) => debug7?.(`notifyPayloadDelivered failed: ${err?.message ?? err}`)
5930
+ );
5931
+ });
4714
5932
  res.end(hit.content);
4715
5933
  return;
4716
5934
  }
@@ -4745,7 +5963,7 @@ function stripCatchAllLoad(plugins) {
4745
5963
  );
4746
5964
  }
4747
5965
  function createReactRefreshRuntimePlugin(entryPoints) {
4748
- const entryIds = new Set(entryPoints.map((p) => import_node_path14.default.resolve(p)));
5966
+ const entryIds = new Set(entryPoints.map((p) => import_node_path15.default.resolve(p)));
4749
5967
  return {
4750
5968
  name: "nasti:bundled-react-refresh",
4751
5969
  resolveId(source) {
@@ -4763,7 +5981,7 @@ function createReactRefreshRuntimePlugin(entryPoints) {
4763
5981
  return null;
4764
5982
  },
4765
5983
  transform(code, id) {
4766
- if (!entryIds.has(import_node_path14.default.resolve(id.split("?")[0]))) return null;
5984
+ if (!entryIds.has(import_node_path15.default.resolve(id.split("?")[0]))) return null;
4767
5985
  return { code: `import ${JSON.stringify(PREAMBLE_SPEC)};
4768
5986
  ${code}`, map: null };
4769
5987
  }
@@ -4810,11 +6028,11 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
4810
6028
  }
4811
6029
  return processed;
4812
6030
  }
4813
- var import_node_path14, import_node_crypto3, import_ws2, import_picocolors8, debug6, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
6031
+ var import_node_path15, import_node_crypto3, import_ws2, import_picocolors8, debug7, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
4814
6032
  var init_dev_engine = __esm({
4815
6033
  "src/server/bundled/dev-engine.ts"() {
4816
6034
  "use strict";
4817
- import_node_path14 = __toESM(require("path"), 1);
6035
+ import_node_path15 = __toESM(require("path"), 1);
4818
6036
  import_node_crypto3 = __toESM(require("crypto"), 1);
4819
6037
  import_ws2 = require("ws");
4820
6038
  import_picocolors8 = __toESM(require("picocolors"), 1);
@@ -4823,7 +6041,7 @@ var init_dev_engine = __esm({
4823
6041
  init_transformer();
4824
6042
  init_middleware();
4825
6043
  init_debug();
4826
- debug6 = createDebugger("nasti:bundled");
6044
+ debug7 = createDebugger("nasti:bundled");
4827
6045
  MIME_TYPES = {
4828
6046
  ".js": "application/javascript",
4829
6047
  ".mjs": "application/javascript",
@@ -4941,14 +6159,16 @@ async function createServer(inlineConfig = {}) {
4941
6159
  const startTime = performance.now();
4942
6160
  const config = await resolveConfig(inlineConfig, "serve");
4943
6161
  const logger = config.logger;
4944
- const allPlugins = resolvePluginList(config, config.plugins);
6162
+ const allPlugins = resolvePluginList(config, config.plugins, {
6163
+ environmentName: "client"
6164
+ });
4945
6165
  const configWithPlugins = { ...config, plugins: allPlugins };
4946
6166
  const app = (0, import_connect.default)();
4947
6167
  const httpServer = import_node_http.default.createServer(app);
4948
6168
  const ws = createWebSocketServer(httpServer);
4949
6169
  const pluginApi = getPluginApi(config);
4950
6170
  const clientEnv = new NastiEnvironment("client", config, {
4951
- hot: createWsHotChannel(ws),
6171
+ hot: createWsHotChannel(ws, "client"),
4952
6172
  mode: "dev",
4953
6173
  plugins: allPlugins,
4954
6174
  pluginApi
@@ -4958,15 +6178,45 @@ async function createServer(inlineConfig = {}) {
4958
6178
  for (const name of Object.keys(config.environments)) {
4959
6179
  if (name === "client") continue;
4960
6180
  const consumer = config.environments[name].consumer;
4961
- const envPlugins = resolvePluginList(config, config.plugins, { consumer });
6181
+ const envPlugins = resolvePluginList(config, config.plugins, {
6182
+ consumer,
6183
+ environmentName: name
6184
+ });
4962
6185
  environments[name] = new NastiEnvironment(name, config, {
6186
+ hot: consumer === "client" ? createWsHotChannel(ws, name) : void 0,
4963
6187
  mode: "dev",
4964
6188
  plugins: envPlugins,
4965
6189
  pluginApi
4966
6190
  });
4967
6191
  }
4968
6192
  for (const [name, environment] of Object.entries(environments)) {
4969
- if (name !== "client" && environment.options.driver) await environment.init();
6193
+ if (name === "client" || environment.consumer === "client" || environment.options.driver) {
6194
+ await environment.init();
6195
+ }
6196
+ }
6197
+ const transformContexts = /* @__PURE__ */ new Map();
6198
+ for (const environment of Object.values(environments)) {
6199
+ if (environment.consumer !== "client" || environment.driver) continue;
6200
+ const environmentConfig = {
6201
+ ...configWithPlugins,
6202
+ resolve: environment.options.resolve,
6203
+ build: environment.options.build,
6204
+ plugins: environment.plugins
6205
+ };
6206
+ const context = {
6207
+ config: environmentConfig,
6208
+ pluginContainer: environment.pluginContainer,
6209
+ moduleGraph: environment.moduleGraph,
6210
+ environment,
6211
+ envDefine: buildEnvDefine(
6212
+ loadEnv(environmentConfig.mode, environmentConfig.root, environmentConfig.envPrefix),
6213
+ environmentConfig.mode,
6214
+ ssrDefineOverrides(environment.consumer)
6215
+ ),
6216
+ onPrune: (paths) => environment.hot.send({ type: "prune", paths })
6217
+ };
6218
+ transformContexts.set(environment.name, context);
6219
+ environment.configureDevPipeline((url) => transformRequest(url, context));
4970
6220
  }
4971
6221
  let ssrRunner = null;
4972
6222
  async function getSsrRunner() {
@@ -4981,7 +6231,6 @@ async function createServer(inlineConfig = {}) {
4981
6231
  return ssrRunner;
4982
6232
  }
4983
6233
  const moduleGraph = clientEnv.moduleGraph;
4984
- const pluginContainer = clientEnv.pluginContainer;
4985
6234
  let bundledServer = null;
4986
6235
  if (config.experimental.bundledDev) {
4987
6236
  const { createBundledDevServer: createBundledDevServer2 } = await Promise.resolve().then(() => (init_dev_engine(), dev_engine_exports));
@@ -4993,14 +6242,14 @@ async function createServer(inlineConfig = {}) {
4993
6242
  app.use(bundledServer.middleware);
4994
6243
  }
4995
6244
  const ignoredSegments = /* @__PURE__ */ new Set(["node_modules", ".git", ".nasti"]);
4996
- const outDirAbs = import_node_path15.default.resolve(config.root, config.build.outDir);
6245
+ const outDirAbs = import_node_path16.default.resolve(config.root, config.build.outDir);
4997
6246
  const watcher = (0, import_chokidar.watch)(config.root, {
4998
6247
  ignored: (filePath) => {
4999
6248
  if (filePath === config.root) return false;
5000
- if (filePath === outDirAbs || filePath.startsWith(outDirAbs + import_node_path15.default.sep)) return true;
5001
- const rel = import_node_path15.default.relative(config.root, filePath);
5002
- if (!rel || rel.startsWith("..") || import_node_path15.default.isAbsolute(rel)) return false;
5003
- for (const seg of rel.split(import_node_path15.default.sep)) {
6249
+ if (filePath === outDirAbs || filePath.startsWith(outDirAbs + import_node_path16.default.sep)) return true;
6250
+ const rel = import_node_path16.default.relative(config.root, filePath);
6251
+ if (!rel || rel.startsWith("..") || import_node_path16.default.isAbsolute(rel)) return false;
6252
+ for (const seg of rel.split(import_node_path16.default.sep)) {
5004
6253
  if (ignoredSegments.has(seg)) return true;
5005
6254
  }
5006
6255
  return false;
@@ -5010,6 +6259,15 @@ async function createServer(inlineConfig = {}) {
5010
6259
  let server;
5011
6260
  const environmentServices = {};
5012
6261
  let environmentDriversStarted = false;
6262
+ let devPipelinesStarted = false;
6263
+ const startDevPipelines = async () => {
6264
+ if (devPipelinesStarted) return;
6265
+ devPipelinesStarted = true;
6266
+ for (const environment of Object.values(environments)) {
6267
+ if (!transformContexts.has(environment.name)) continue;
6268
+ await environment.pluginContainer.buildStart();
6269
+ }
6270
+ };
5013
6271
  const logCloseError = (target, error) => {
5014
6272
  const normalized = error instanceof Error ? error : new Error(String(error));
5015
6273
  logger.error(`[nasti] failed to close ${target}`, { error: normalized });
@@ -5061,14 +6319,61 @@ async function createServer(inlineConfig = {}) {
5061
6319
  });
5062
6320
  }
5063
6321
  };
6322
+ const updateClientEnvironments = async (file) => {
6323
+ const timestamp = Date.now();
6324
+ const results = {};
6325
+ for (const environment of Object.values(environments)) {
6326
+ if (environment.consumer !== "client" || environment.driver) continue;
6327
+ try {
6328
+ const result = await handleFileChange(file, server, environment.name, timestamp);
6329
+ if (result) results[environment.name] = result;
6330
+ } catch (error) {
6331
+ const normalized = error instanceof Error ? error : new Error(String(error));
6332
+ logger.error(
6333
+ `[nasti] HMR failed for environment "${environment.name}": ${normalized.message}`,
6334
+ { error: normalized }
6335
+ );
6336
+ try {
6337
+ environment.hot.send({
6338
+ type: "error",
6339
+ err: { message: normalized.message, stack: normalized.stack }
6340
+ });
6341
+ } catch (channelError) {
6342
+ const channelFailure = channelError instanceof Error ? channelError : new Error(String(channelError));
6343
+ logger.error(
6344
+ `[nasti] failed to deliver HMR error to environment "${environment.name}"`,
6345
+ { error: channelFailure }
6346
+ );
6347
+ }
6348
+ }
6349
+ }
6350
+ if (Object.keys(results).length === 0) return;
6351
+ const context = {
6352
+ file,
6353
+ timestamp,
6354
+ environments: Object.freeze({ ...results }),
6355
+ server
6356
+ };
6357
+ for (const plugin of config.plugins) {
6358
+ await plugin.handleHotUpdateApp?.(context);
6359
+ }
6360
+ };
6361
+ const queueClientEnvironmentUpdate = (file) => {
6362
+ void updateClientEnvironments(file).catch((error) => {
6363
+ const normalized = error instanceof Error ? error : new Error(String(error));
6364
+ logger.error(`[nasti] multi-environment HMR failed: ${normalized.message}`, {
6365
+ error: normalized
6366
+ });
6367
+ });
6368
+ };
5064
6369
  watcher.on("change", (file) => {
5065
6370
  ssrRunner?.invalidateFile(file);
5066
- handleFileChange(file, server);
6371
+ queueClientEnvironmentUpdate(file);
5067
6372
  notifyEnvironmentDrivers(file, "change");
5068
6373
  });
5069
6374
  watcher.on("add", (file) => {
5070
6375
  ssrRunner?.invalidateFile(file);
5071
- handleFileChange(file, server);
6376
+ queueClientEnvironmentUpdate(file);
5072
6377
  notifyEnvironmentDrivers(file, "add");
5073
6378
  });
5074
6379
  watcher.on("unlink", (file) => {
@@ -5086,7 +6391,7 @@ async function createServer(inlineConfig = {}) {
5086
6391
  async listen(port) {
5087
6392
  const finalPort = port ?? config.server.port;
5088
6393
  const host = config.server.host === true ? "0.0.0.0" : config.server.host;
5089
- await pluginContainer.buildStart();
6394
+ await startDevPipelines();
5090
6395
  await startEnvironmentDrivers();
5091
6396
  return new Promise((resolve, reject) => {
5092
6397
  let currentPort = finalPort;
@@ -5101,7 +6406,7 @@ async function createServer(inlineConfig = {}) {
5101
6406
  const readyIn = Math.ceil(performance.now() - startTime);
5102
6407
  logger.info(
5103
6408
  `
5104
- ${import_picocolors9.default.cyan(import_picocolors9.default.bold("NASTI"))} ${import_picocolors9.default.cyan(`v${"2.3.1"}`)} ${import_picocolors9.default.dim("ready in")} ${import_picocolors9.default.bold(readyIn)} ${import_picocolors9.default.dim("ms")}
6409
+ ${import_picocolors9.default.cyan(import_picocolors9.default.bold("NASTI"))} ${import_picocolors9.default.cyan(`v${"2.4.1"}`)} ${import_picocolors9.default.dim("ready in")} ${import_picocolors9.default.bold(readyIn)} ${import_picocolors9.default.dim("ms")}
5105
6410
  `
5106
6411
  );
5107
6412
  printServerUrls(
@@ -5128,15 +6433,26 @@ async function createServer(inlineConfig = {}) {
5128
6433
  });
5129
6434
  },
5130
6435
  async transformRequest(url) {
5131
- const { transformRequest: transformRequest2 } = await Promise.resolve().then(() => (init_middleware(), middleware_exports));
5132
- return transformRequest2(url, { config: configWithPlugins, pluginContainer, moduleGraph });
6436
+ return clientEnv.transformRequest(url);
6437
+ },
6438
+ async transformEnvironmentRequest(environmentName, url) {
6439
+ const environment = environments[environmentName];
6440
+ if (!environment) {
6441
+ throw new Error(`[nasti] unknown dev environment "${environmentName}"`);
6442
+ }
6443
+ return environment.transformRequest(url);
5133
6444
  },
5134
6445
  async ssrLoadModule(url) {
5135
6446
  const runner = await getSsrRunner();
5136
6447
  return runner.import(url);
5137
6448
  },
5138
6449
  async close() {
5139
- await pluginContainer.buildEnd();
6450
+ if (devPipelinesStarted) {
6451
+ for (const environment of Object.values(environments).reverse()) {
6452
+ if (!transformContexts.has(environment.name)) continue;
6453
+ await environment.pluginContainer.buildEnd();
6454
+ }
6455
+ }
5140
6456
  await bundledServer?.close();
5141
6457
  let environmentCloseFailed = false;
5142
6458
  let firstEnvironmentCloseError;
@@ -5186,12 +6502,8 @@ async function createServer(inlineConfig = {}) {
5186
6502
  }
5187
6503
  throw error;
5188
6504
  }
5189
- app.use(transformMiddleware({
5190
- config: configWithPlugins,
5191
- pluginContainer,
5192
- moduleGraph
5193
- }));
5194
- const publicDir = import_node_path15.default.resolve(config.root, "public");
6505
+ app.use(transformMiddleware(transformContexts.get("client")));
6506
+ const publicDir = import_node_path16.default.resolve(config.root, "public");
5195
6507
  app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
5196
6508
  app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
5197
6509
  const postMiddlewares = [];
@@ -5217,12 +6529,12 @@ function getNetworkAddress() {
5217
6529
  }
5218
6530
  return "localhost";
5219
6531
  }
5220
- var import_node_http, import_node_path15, import_node_os, import_connect, import_sirv, import_chokidar, import_picocolors9;
6532
+ var import_node_http, import_node_path16, import_node_os, import_connect, import_sirv, import_chokidar, import_picocolors9;
5221
6533
  var init_server = __esm({
5222
6534
  "src/server/index.ts"() {
5223
6535
  "use strict";
5224
6536
  import_node_http = __toESM(require("http"), 1);
5225
- import_node_path15 = __toESM(require("path"), 1);
6537
+ import_node_path16 = __toESM(require("path"), 1);
5226
6538
  import_node_os = __toESM(require("os"), 1);
5227
6539
  import_connect = __toESM(require("connect"), 1);
5228
6540
  import_sirv = __toESM(require("sirv"), 1);
@@ -5237,6 +6549,7 @@ var init_server = __esm({
5237
6549
  init_hmr();
5238
6550
  init_builtins();
5239
6551
  init_plugin_api();
6552
+ init_env();
5240
6553
  }
5241
6554
  });
5242
6555
 
@@ -5271,8 +6584,8 @@ init_config();
5271
6584
  init_build();
5272
6585
 
5273
6586
  // src/build/electron.ts
5274
- var import_node_path10 = __toESM(require("path"), 1);
5275
- var import_node_fs7 = __toESM(require("fs"), 1);
6587
+ var import_node_path11 = __toESM(require("path"), 1);
6588
+ var import_node_fs8 = __toESM(require("fs"), 1);
5276
6589
  var import_rolldown2 = require("rolldown");
5277
6590
  var import_picocolors5 = __toESM(require("picocolors"), 1);
5278
6591
  init_config();
@@ -5318,16 +6631,16 @@ async function buildElectron(inlineConfig = {}) {
5318
6631
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
5319
6632
  const startTime = performance.now();
5320
6633
  assertElectronVersion(config);
5321
- console.log(import_picocolors5.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors5.default.dim(` v${"2.3.1"}`));
6634
+ console.log(import_picocolors5.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors5.default.dim(` v${"2.4.1"}`));
5322
6635
  console.log(import_picocolors5.default.dim(` root: ${config.root}`));
5323
6636
  console.log(import_picocolors5.default.dim(` mode: ${config.mode}`));
5324
6637
  console.log(import_picocolors5.default.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
5325
- const outDir = import_node_path10.default.resolve(config.root, config.build.outDir);
5326
- if (config.build.emptyOutDir && import_node_fs7.default.existsSync(outDir)) {
5327
- import_node_fs7.default.rmSync(outDir, { recursive: true, force: true });
6638
+ const outDir = import_node_path11.default.resolve(config.root, config.build.outDir);
6639
+ if (config.build.emptyOutDir && import_node_fs8.default.existsSync(outDir)) {
6640
+ import_node_fs8.default.rmSync(outDir, { recursive: true, force: true });
5328
6641
  }
5329
- import_node_fs7.default.mkdirSync(outDir, { recursive: true });
5330
- const rendererOutDir = import_node_path10.default.join(outDir, "renderer");
6642
+ import_node_fs8.default.mkdirSync(outDir, { recursive: true });
6643
+ const rendererOutDir = import_node_path11.default.join(outDir, "renderer");
5331
6644
  const { build: build2 } = await Promise.resolve().then(() => (init_build(), build_exports));
5332
6645
  await build2(createElectronRendererConfig(config, inlineConfig, {
5333
6646
  build: {
@@ -5336,8 +6649,8 @@ async function buildElectron(inlineConfig = {}) {
5336
6649
  emptyOutDir: false
5337
6650
  }
5338
6651
  }));
5339
- const mainEntry = import_node_path10.default.resolve(config.root, config.electron.main);
5340
- if (!import_node_fs7.default.existsSync(mainEntry)) {
6652
+ const mainEntry = import_node_path11.default.resolve(config.root, config.electron.main);
6653
+ if (!import_node_fs8.default.existsSync(mainEntry)) {
5341
6654
  throw new Error(
5342
6655
  `Electron main entry not found: ${config.electron.main}
5343
6656
  \u5728 nasti.config.ts \u7684 electron.main \u6307\u5B9A\u4E3B\u8FDB\u7A0B\u5165\u53E3\u6587\u4EF6\u3002`
@@ -5351,11 +6664,11 @@ async function buildElectron(inlineConfig = {}) {
5351
6664
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
5352
6665
  const preloadFiles = [];
5353
6666
  for (const entry of preloadEntries) {
5354
- if (!import_node_fs7.default.existsSync(entry)) {
6667
+ if (!import_node_fs8.default.existsSync(entry)) {
5355
6668
  console.warn(import_picocolors5.default.yellow(` \u26A0 preload entry not found, skipped: ${entry}`));
5356
6669
  continue;
5357
6670
  }
5358
- const base = import_node_path10.default.basename(entry).replace(/\.[^.]+$/, "");
6671
+ const base = import_node_path11.default.basename(entry).replace(/\.[^.]+$/, "");
5359
6672
  const out = outFileName(outDir, base, config.electron.preloadFormat);
5360
6673
  await bundleNode(config, entry, {
5361
6674
  outFile: out,
@@ -5367,10 +6680,10 @@ async function buildElectron(inlineConfig = {}) {
5367
6680
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
5368
6681
  console.log(import_picocolors5.default.green(`
5369
6682
  \u2713 Electron build complete in ${elapsed}s`));
5370
- console.log(import_picocolors5.default.dim(` renderer: ${import_node_path10.default.relative(config.root, rendererOutDir)}/`));
5371
- console.log(import_picocolors5.default.dim(` main: ${import_node_path10.default.relative(config.root, mainFile)}`));
6683
+ console.log(import_picocolors5.default.dim(` renderer: ${import_node_path11.default.relative(config.root, rendererOutDir)}/`));
6684
+ console.log(import_picocolors5.default.dim(` main: ${import_node_path11.default.relative(config.root, mainFile)}`));
5372
6685
  for (const pf of preloadFiles) {
5373
- console.log(import_picocolors5.default.dim(` preload: ${import_node_path10.default.relative(config.root, pf)}`));
6686
+ console.log(import_picocolors5.default.dim(` preload: ${import_node_path11.default.relative(config.root, pf)}`));
5374
6687
  }
5375
6688
  console.log();
5376
6689
  return { rendererOutDir, mainFile, preloadFiles };
@@ -5408,7 +6721,7 @@ async function bundleNode(config, entry, opts) {
5408
6721
  },
5409
6722
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5410
6723
  });
5411
- import_node_fs7.default.mkdirSync(import_node_path10.default.dirname(opts.outFile), { recursive: true });
6724
+ import_node_fs8.default.mkdirSync(import_node_path11.default.dirname(opts.outFile), { recursive: true });
5412
6725
  await bundle2.write({
5413
6726
  sourcemap: !!config.build.sourcemap,
5414
6727
  minify: !!config.build.minify,
@@ -5419,7 +6732,7 @@ async function bundleNode(config, entry, opts) {
5419
6732
  codeSplitting: false
5420
6733
  });
5421
6734
  await bundle2.close();
5422
- console.log(import_picocolors5.default.dim(` \u2713 ${opts.label} \u2192 ${import_node_path10.default.relative(config.root, opts.outFile)}`));
6735
+ console.log(import_picocolors5.default.dim(` \u2713 ${opts.label} \u2192 ${import_node_path11.default.relative(config.root, opts.outFile)}`));
5423
6736
  return opts.outFile;
5424
6737
  }
5425
6738
  function createElectronRendererConfig(config, inlineConfig = {}, overrides = {}) {
@@ -5443,11 +6756,11 @@ function createElectronRendererConfig(config, inlineConfig = {}, overrides = {})
5443
6756
  }
5444
6757
  function outFileName(outDir, base, format) {
5445
6758
  const ext = format === "cjs" ? ".cjs" : ".mjs";
5446
- return import_node_path10.default.join(outDir, base + ext);
6759
+ return import_node_path11.default.join(outDir, base + ext);
5447
6760
  }
5448
6761
  function normalizePreload(preload, root) {
5449
6762
  const list = Array.isArray(preload) ? preload : preload ? [preload] : [];
5450
- return list.map((p) => import_node_path10.default.resolve(root, p));
6763
+ return list.map((p) => import_node_path11.default.resolve(root, p));
5451
6764
  }
5452
6765
  function assertElectronVersion(config) {
5453
6766
  const min = config.electron.minVersion;
@@ -5462,9 +6775,9 @@ function assertElectronVersion(config) {
5462
6775
  }
5463
6776
  function detectInstalledElectron(root) {
5464
6777
  try {
5465
- const pkgPath = import_node_path10.default.resolve(root, "node_modules/electron/package.json");
5466
- if (!import_node_fs7.default.existsSync(pkgPath)) return null;
5467
- const pkg = JSON.parse(import_node_fs7.default.readFileSync(pkgPath, "utf-8"));
6778
+ const pkgPath = import_node_path11.default.resolve(root, "node_modules/electron/package.json");
6779
+ if (!import_node_fs8.default.existsSync(pkgPath)) return null;
6780
+ const pkg = JSON.parse(import_node_fs8.default.readFileSync(pkgPath, "utf-8"));
5468
6781
  const major = parseInt(String(pkg.version).split(".")[0], 10);
5469
6782
  return Number.isFinite(major) ? major : null;
5470
6783
  } catch {
@@ -5476,8 +6789,8 @@ function detectInstalledElectron(root) {
5476
6789
  init_server();
5477
6790
 
5478
6791
  // src/server/electron-dev.ts
5479
- var import_node_path16 = __toESM(require("path"), 1);
5480
- var import_node_fs11 = __toESM(require("fs"), 1);
6792
+ var import_node_path17 = __toESM(require("path"), 1);
6793
+ var import_node_fs12 = __toESM(require("fs"), 1);
5481
6794
  var import_node_module7 = require("module");
5482
6795
  var import_node_child_process = require("child_process");
5483
6796
  var import_chokidar2 = __toESM(require("chokidar"), 1);
@@ -5491,7 +6804,7 @@ async function startElectronDev(inlineConfig = {}) {
5491
6804
  const { noSpawn, ...rest } = inlineConfig;
5492
6805
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
5493
6806
  warnElectronVersion(config);
5494
- console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.3.1"}`));
6807
+ console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.4.1"}`));
5495
6808
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5496
6809
  const server = await createServer2({
5497
6810
  ...rest,
@@ -5501,11 +6814,11 @@ async function startElectronDev(inlineConfig = {}) {
5501
6814
  await server.listen();
5502
6815
  const devUrl = `http://localhost:${server.config.server.port}` + electronRendererDevPath(config.electron.renderer);
5503
6816
  console.log(import_picocolors10.default.dim(` renderer: ${devUrl}`));
5504
- const stageDir = import_node_path16.default.resolve(config.root, ".nasti");
5505
- import_node_fs11.default.mkdirSync(stageDir, { recursive: true });
5506
- const mainEntry = import_node_path16.default.resolve(config.root, config.electron.main);
6817
+ const stageDir = import_node_path17.default.resolve(config.root, ".nasti");
6818
+ import_node_fs12.default.mkdirSync(stageDir, { recursive: true });
6819
+ const mainEntry = import_node_path17.default.resolve(config.root, config.electron.main);
5507
6820
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
5508
- const builtMainFile = import_node_path16.default.join(stageDir, "main" + extFor(config.electron.mainFormat));
6821
+ const builtMainFile = import_node_path17.default.join(stageDir, "main" + extFor(config.electron.mainFormat));
5509
6822
  const builtPreloadFiles = [];
5510
6823
  const compileAll = async () => {
5511
6824
  await compileNode(config, mainEntry, {
@@ -5515,9 +6828,9 @@ async function startElectronDev(inlineConfig = {}) {
5515
6828
  });
5516
6829
  builtPreloadFiles.length = 0;
5517
6830
  for (const entry of preloadEntries) {
5518
- if (!import_node_fs11.default.existsSync(entry)) continue;
5519
- const base = import_node_path16.default.basename(entry).replace(/\.[^.]+$/, "");
5520
- const out = import_node_path16.default.join(stageDir, base + extFor(config.electron.preloadFormat));
6831
+ if (!import_node_fs12.default.existsSync(entry)) continue;
6832
+ const base = import_node_path17.default.basename(entry).replace(/\.[^.]+$/, "");
6833
+ const out = import_node_path17.default.join(stageDir, base + extFor(config.electron.preloadFormat));
5521
6834
  await compileNode(config, entry, {
5522
6835
  outFile: out,
5523
6836
  format: config.electron.preloadFormat,
@@ -5556,7 +6869,7 @@ async function startElectronDev(inlineConfig = {}) {
5556
6869
  };
5557
6870
  spawnElectron();
5558
6871
  if (config.electron.autoRestart) {
5559
- const watchTargets = [mainEntry, ...preloadEntries].filter(import_node_fs11.default.existsSync);
6872
+ const watchTargets = [mainEntry, ...preloadEntries].filter(import_node_fs12.default.existsSync);
5560
6873
  const watcher = import_chokidar2.default.watch(watchTargets, { ignoreInitial: true });
5561
6874
  let restarting = null;
5562
6875
  let pending = false;
@@ -5636,7 +6949,7 @@ async function compileNode(config, entry, opts) {
5636
6949
  platform: "node",
5637
6950
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5638
6951
  });
5639
- import_node_fs11.default.mkdirSync(import_node_path16.default.dirname(opts.outFile), { recursive: true });
6952
+ import_node_fs12.default.mkdirSync(import_node_path17.default.dirname(opts.outFile), { recursive: true });
5640
6953
  await bundle2.write({
5641
6954
  file: opts.outFile,
5642
6955
  format: opts.format === "cjs" ? "cjs" : "esm",
@@ -5649,18 +6962,18 @@ async function compileNode(config, entry, opts) {
5649
6962
  await bundle2.close();
5650
6963
  }
5651
6964
  function electronRendererDevPath(renderer) {
5652
- const normalized = renderer.split(import_node_path16.default.sep).join("/").replace(/^\.?\//, "");
6965
+ const normalized = renderer.split(import_node_path17.default.sep).join("/").replace(/^\.?\//, "");
5653
6966
  return normalized === "index.html" ? "/" : `/${normalized}`;
5654
6967
  }
5655
6968
  function resolveElectronBinary(config) {
5656
- if (config.electron.electronPath && import_node_fs11.default.existsSync(config.electron.electronPath)) {
6969
+ if (config.electron.electronPath && import_node_fs12.default.existsSync(config.electron.electronPath)) {
5657
6970
  return config.electron.electronPath;
5658
6971
  }
5659
6972
  try {
5660
- const require2 = (0, import_node_module7.createRequire)(import_node_path16.default.resolve(config.root, "package.json"));
6973
+ const require2 = (0, import_node_module7.createRequire)(import_node_path17.default.resolve(config.root, "package.json"));
5661
6974
  const pathFile = require2.resolve("electron");
5662
6975
  const electronModule = require2(pathFile);
5663
- if (typeof electronModule === "string" && import_node_fs11.default.existsSync(electronModule)) {
6976
+ if (typeof electronModule === "string" && import_node_fs12.default.existsSync(electronModule)) {
5664
6977
  return electronModule;
5665
6978
  }
5666
6979
  } catch {
@@ -5687,8 +7000,8 @@ function warnElectronVersion(config) {
5687
7000
  }
5688
7001
 
5689
7002
  // src/plugins/monaco-editor.ts
5690
- var import_node_path17 = __toESM(require("path"), 1);
5691
- var import_node_fs12 = __toESM(require("fs"), 1);
7003
+ var import_node_path18 = __toESM(require("path"), 1);
7004
+ var import_node_fs13 = __toESM(require("fs"), 1);
5692
7005
  var import_node_crypto4 = __toESM(require("crypto"), 1);
5693
7006
  var import_node_module8 = require("module");
5694
7007
  var DEFAULT_WORKERS = {
@@ -5709,9 +7022,9 @@ function normalizePublicPath(p) {
5709
7022
  }
5710
7023
  function readMonacoVersion(root) {
5711
7024
  try {
5712
- const require2 = (0, import_node_module8.createRequire)(import_node_path17.default.resolve(root, "package.json"));
7025
+ const require2 = (0, import_node_module8.createRequire)(import_node_path18.default.resolve(root, "package.json"));
5713
7026
  const pkgJsonPath = require2.resolve("monaco-editor/package.json", { paths: [root] });
5714
- const pkg = JSON.parse(import_node_fs12.default.readFileSync(pkgJsonPath, "utf-8"));
7027
+ const pkg = JSON.parse(import_node_fs13.default.readFileSync(pkgJsonPath, "utf-8"));
5715
7028
  return typeof pkg.version === "string" ? pkg.version : "unknown";
5716
7029
  } catch {
5717
7030
  return "unknown";
@@ -5731,20 +7044,20 @@ function monacoEditorPlugin(options = {}) {
5731
7044
  let cacheDir = "";
5732
7045
  const building = /* @__PURE__ */ new Map();
5733
7046
  async function buildWorker(worker) {
5734
- const cacheFile = import_node_path17.default.join(cacheDir, `${worker.label}.worker.js`);
5735
- if (import_node_fs12.default.existsSync(cacheFile)) return cacheFile;
7047
+ const cacheFile = import_node_path18.default.join(cacheDir, `${worker.label}.worker.js`);
7048
+ if (import_node_fs13.default.existsSync(cacheFile)) return cacheFile;
5736
7049
  const existing = building.get(worker.label);
5737
7050
  if (existing) return existing;
5738
7051
  const task = (async () => {
5739
7052
  const { rolldown: rolldown4 } = await import("rolldown");
5740
- const require2 = (0, import_node_module8.createRequire)(import_node_path17.default.resolve(resolvedConfig.root, "package.json"));
7053
+ const require2 = (0, import_node_module8.createRequire)(import_node_path18.default.resolve(resolvedConfig.root, "package.json"));
5741
7054
  let entry;
5742
7055
  try {
5743
7056
  entry = require2.resolve(worker.entry, { paths: [resolvedConfig.root] });
5744
7057
  } catch {
5745
7058
  entry = require2.resolve(worker.entry + ".js", { paths: [resolvedConfig.root] });
5746
7059
  }
5747
- import_node_fs12.default.mkdirSync(cacheDir, { recursive: true });
7060
+ import_node_fs13.default.mkdirSync(cacheDir, { recursive: true });
5748
7061
  const bundle2 = await rolldown4({
5749
7062
  input: entry,
5750
7063
  platform: "browser"
@@ -5804,12 +7117,12 @@ function monacoEditorPlugin(options = {}) {
5804
7117
  resolvedConfig = config;
5805
7118
  const version = readMonacoVersion(config.root);
5806
7119
  const key = import_node_crypto4.default.createHash("sha1").update(version + "|" + publicPath).digest("hex").slice(0, 8);
5807
- cacheDir = import_node_path17.default.resolve(config.root, "node_modules/.nasti/monaco", key);
7120
+ cacheDir = import_node_path18.default.resolve(config.root, "node_modules/.nasti/monaco", key);
5808
7121
  },
5809
7122
  async configureServer(server) {
5810
7123
  const shouldBuild = !isCDN(publicPath) || forceBuildCDN;
5811
7124
  const watcher = server.watcher;
5812
- const monacoDir = import_node_path17.default.resolve(resolvedConfig.root, "node_modules/monaco-editor");
7125
+ const monacoDir = import_node_path18.default.resolve(resolvedConfig.root, "node_modules/monaco-editor");
5813
7126
  try {
5814
7127
  watcher?.unwatch?.(monacoDir);
5815
7128
  } catch {
@@ -5839,7 +7152,7 @@ function monacoEditorPlugin(options = {}) {
5839
7152
  const file = await buildWorker(worker);
5840
7153
  res.setHeader("Content-Type", "application/javascript; charset=utf-8");
5841
7154
  res.setHeader("Cache-Control", "public, max-age=604800, immutable");
5842
- import_node_fs12.default.createReadStream(file).pipe(res);
7155
+ import_node_fs13.default.createReadStream(file).pipe(res);
5843
7156
  } catch (e) {
5844
7157
  res.statusCode = 500;
5845
7158
  res.end(`Monaco worker build failed: ${e.message}`);
@@ -5874,16 +7187,16 @@ self.monaco = monaco;`,
5874
7187
  resolvedConfig.root,
5875
7188
  resolvedConfig.build.outDir,
5876
7189
  resolvedConfig.base
5877
- ) : isCDN(publicPath) ? import_node_path17.default.resolve(resolvedConfig.root, resolvedConfig.build.outDir, "monaco") : import_node_path17.default.resolve(
7190
+ ) : isCDN(publicPath) ? import_node_path18.default.resolve(resolvedConfig.root, resolvedConfig.build.outDir, "monaco") : import_node_path18.default.resolve(
5878
7191
  resolvedConfig.root,
5879
7192
  resolvedConfig.build.outDir,
5880
7193
  publicPath.replace(/^\//, "")
5881
7194
  );
5882
- import_node_fs12.default.mkdirSync(outDir, { recursive: true });
7195
+ import_node_fs13.default.mkdirSync(outDir, { recursive: true });
5883
7196
  for (const worker of workers) {
5884
7197
  try {
5885
7198
  const cacheFile = await buildWorker(worker);
5886
- import_node_fs12.default.copyFileSync(cacheFile, import_node_path17.default.join(outDir, `${worker.label}.worker.js`));
7199
+ import_node_fs13.default.copyFileSync(cacheFile, import_node_path18.default.join(outDir, `${worker.label}.worker.js`));
5887
7200
  } catch (e) {
5888
7201
  throw new Error(
5889
7202
  `[nasti:monaco-editor] worker build failed for "${worker.label}": ${e.message}