@nasti-toolchain/nasti 2.4.0 → 2.4.2

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
@@ -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,7 +485,11 @@ 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,
477
495
  buildEnabled: envOptions.buildEnabled ?? true,
@@ -483,7 +501,8 @@ async function resolveConfig(inlineConfig = {}, command) {
483
501
  driver: envOptions.driver,
484
502
  // 同引用 —— 精确镜像(assertClientEnvironmentMirror 校验)
485
503
  resolve: resolved.resolve,
486
- build: resolved.build
504
+ build: resolved.build,
505
+ vue: vueOptions
487
506
  };
488
507
  continue;
489
508
  }
@@ -493,6 +512,7 @@ async function resolveConfig(inlineConfig = {}, command) {
493
512
  entry: normalizeEnvironmentEntries(envOptions.entry, root),
494
513
  html: envOptions.consumer === "client" && envOptions.html ? import_node_path.default.resolve(root, envOptions.html) : void 0,
495
514
  driver: envOptions.driver,
515
+ vue: vueOptions,
496
516
  resolve: {
497
517
  alias: { ...resolved.resolve.alias, ...envOptions.resolve?.alias },
498
518
  extensions: envOptions.resolve?.extensions ?? [...resolved.resolve.extensions],
@@ -503,6 +523,7 @@ async function resolveConfig(inlineConfig = {}, command) {
503
523
  build: {
504
524
  ...resolved.build,
505
525
  ...envOptions.build,
526
+ css: { ...resolved.build.css, ...envOptions.build?.css },
506
527
  // 非 client 环境默认产出到 <outDir>/<envName>(如 dist/ssr),可显式覆盖
507
528
  outDir: envOptions.build?.outDir ?? import_node_path.default.join(resolved.build.outDir, name),
508
529
  // server 产物默认不压缩(可调试性优先,与 Vite SSR 默认一致),可显式覆盖
@@ -735,7 +756,7 @@ function resolvePlugin(config) {
735
756
  const content = import_node_fs2.default.readFileSync(id, "utf-8");
736
757
  return `export default ${content}`;
737
758
  }
738
- return import_node_fs2.default.readFileSync(id, "utf-8");
759
+ return null;
739
760
  }
740
761
  };
741
762
  }
@@ -1696,12 +1717,31 @@ var init_node = __esm({
1696
1717
  function createCssEngine() {
1697
1718
  return {
1698
1719
  styles: /* @__PURE__ */ new Map(),
1720
+ modules: /* @__PURE__ */ new Map(),
1721
+ chunks: /* @__PURE__ */ new Map(),
1699
1722
  entryCss: /* @__PURE__ */ new Map(),
1700
1723
  allCss: [],
1701
1724
  pendingSingle: [],
1702
1725
  singleFileName: null
1703
1726
  };
1704
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
+ }
1705
1745
  function normalizeCssModuleId(id) {
1706
1746
  return id.startsWith("\0") ? id.slice(1) : id;
1707
1747
  }
@@ -1821,15 +1861,29 @@ function cssPlugin(config, engine, consumer = "client") {
1821
1861
  }
1822
1862
  const rewritten = rewriteCssUrls(cssSource, file, config.root);
1823
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);
1824
1869
  if (query === "inline") {
1825
1870
  return { code: `export default ${escaped};
1826
- `, moduleType: "js" };
1871
+ `, map, moduleType: "js" };
1827
1872
  }
1828
1873
  if (consumer === "server") {
1829
1874
  return { code: `export default ${escaped};
1830
- `, moduleType: "js" };
1875
+ `, map, moduleType: "js" };
1831
1876
  }
1832
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
+ }
1833
1887
  return {
1834
1888
  code: `
1835
1889
  const css = ${escaped};
@@ -1853,16 +1907,18 @@ if (import.meta.hot) {
1853
1907
 
1854
1908
  export default css;
1855
1909
  `,
1910
+ map,
1856
1911
  // bundled dev(DevEngine)下该模块会进 Rolldown:不标 js 会按 .css
1857
1912
  // 扩展名走 CSS 管线触发 #4271 报错;unbundled 中间件忽略此字段
1858
1913
  moduleType: "js"
1859
1914
  };
1860
1915
  }
1861
1916
  if (engine) {
1862
- engine.styles.set(normalizeCssModuleId(id), rewritten);
1917
+ engine.styles.set(normalizedId, rewritten);
1863
1918
  return {
1864
1919
  code: `export default '';
1865
1920
  `,
1921
+ map,
1866
1922
  moduleType: "js",
1867
1923
  // 防止空 stub 被 tree-shake 出 chunk.moduleIds(css-post 靠它定位)
1868
1924
  moduleSideEffects: "no-treeshake"
@@ -1882,11 +1938,27 @@ document.head.appendChild(style);
1882
1938
 
1883
1939
  export default css;
1884
1940
  `,
1941
+ map,
1885
1942
  moduleType: "js"
1886
1943
  };
1887
1944
  }
1888
1945
  };
1889
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
+ }
1890
1962
  function rewriteCssUrls(css, from, root) {
1891
1963
  return css.replace(/url\(\s*['"]?([^'")\s]+)['"]?\s*\)/g, (match, url) => {
1892
1964
  if (url.startsWith("/") || url.startsWith("data:") || url.startsWith("http")) {
@@ -1897,11 +1969,12 @@ function rewriteCssUrls(css, from, root) {
1897
1969
  return `url(${relative})`;
1898
1970
  });
1899
1971
  }
1900
- var import_node_path4;
1972
+ var import_node_path4, import_source_map_js;
1901
1973
  var init_css = __esm({
1902
1974
  "src/plugins/css.ts"() {
1903
1975
  "use strict";
1904
1976
  import_node_path4 = __toESM(require("path"), 1);
1977
+ import_source_map_js = require("source-map-js");
1905
1978
  init_css_engine();
1906
1979
  init_tailwind();
1907
1980
  }
@@ -1911,19 +1984,27 @@ var init_css = __esm({
1911
1984
  function collectChunkCss(chunk, engine) {
1912
1985
  const ids = chunk.moduleIds ?? Object.keys(chunk.modules);
1913
1986
  let css = "";
1987
+ const moduleIds = [];
1914
1988
  for (const id of ids) {
1915
- const styles = engine.styles.get(normalizeCssModuleId(id));
1916
- 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
+ }
1917
1995
  }
1918
- return css;
1996
+ return { css, moduleIds };
1919
1997
  }
1920
1998
  function cssPostPlugin(config, engine) {
1921
1999
  return {
1922
2000
  name: "nasti:css-post",
1923
2001
  enforce: "post",
1924
2002
  async renderChunk(code, chunk) {
1925
- const css = collectChunkCss(chunk, engine);
2003
+ const { css, moduleIds } = collectChunkCss(chunk, engine);
1926
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;
1927
2008
  if (!config.build.cssCodeSplit) {
1928
2009
  engine.pendingSingle.push(css);
1929
2010
  return null;
@@ -1936,6 +2017,7 @@ function cssPostPlugin(config, engine) {
1936
2017
  });
1937
2018
  const fileName = this.getFileName(ref);
1938
2019
  engine.allCss.push(fileName);
2020
+ ownership.cssFileNames.push(fileName);
1939
2021
  if (chunk.isEntry) {
1940
2022
  const key = chunk.facadeModuleId ?? chunk.name;
1941
2023
  const existing = engine.entryCss.get(key) ?? [];
@@ -1943,13 +2025,14 @@ function cssPostPlugin(config, engine) {
1943
2025
  engine.entryCss.set(key, existing);
1944
2026
  return null;
1945
2027
  }
2028
+ if (config.build.css.inject === false) return null;
1946
2029
  const href = JSON.stringify(config.base + fileName);
1947
2030
  const snippet = `
1948
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){}})();`;
1949
2032
  return { code: code + snippet, map: null };
1950
2033
  },
1951
2034
  augmentChunkHash(chunk) {
1952
- const css = collectChunkCss(chunk, engine);
2035
+ const { css } = collectChunkCss(chunk, engine);
1953
2036
  return css || void 0;
1954
2037
  },
1955
2038
  async generateBundle() {
@@ -1960,6 +2043,9 @@ function cssPostPlugin(config, engine) {
1960
2043
  const fileName = this.getFileName(ref);
1961
2044
  engine.singleFileName = fileName;
1962
2045
  engine.allCss.push(fileName);
2046
+ for (const ownership of engine.chunks.values()) {
2047
+ if (ownership.moduleIds.length > 0) ownership.cssFileNames.push(fileName);
2048
+ }
1963
2049
  }
1964
2050
  };
1965
2051
  }
@@ -1972,6 +2058,7 @@ var init_css_post = __esm({
1972
2058
 
1973
2059
  // src/plugins/assets.ts
1974
2060
  function assetsPlugin(config) {
2061
+ const emittedAssets = /* @__PURE__ */ new Set();
1975
2062
  return {
1976
2063
  name: "nasti:assets",
1977
2064
  resolveId(source) {
@@ -2000,12 +2087,29 @@ function assetsPlugin(config) {
2000
2087
  const hash = import_node_crypto.default.createHash("sha256").update(content).digest("hex").slice(0, 8);
2001
2088
  const basename = import_node_path5.default.basename(file, ext);
2002
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);
2003
2103
  return `export default ${JSON.stringify(config.base + hashedName)}`;
2004
2104
  }
2005
2105
  return null;
2006
2106
  }
2007
2107
  };
2008
2108
  }
2109
+ function isAssetFile(id) {
2110
+ const ext = import_node_path5.default.extname(id.replace(/\?.*$/, ""));
2111
+ return ASSET_EXTENSIONS.has(ext);
2112
+ }
2009
2113
  var import_node_path5, import_node_fs3, import_node_crypto, ASSET_EXTENSIONS;
2010
2114
  var init_assets = __esm({
2011
2115
  "src/plugins/assets.ts"() {
@@ -2088,9 +2192,10 @@ async function loadVueCompiler() {
2088
2192
  return null;
2089
2193
  }
2090
2194
  }
2091
- function vuePlugin(config) {
2195
+ function vuePlugin(config, environmentName = "client") {
2092
2196
  const isDev = config.command === "serve";
2093
2197
  const descriptorCache = /* @__PURE__ */ new Map();
2198
+ const vueOptions = config.environments[environmentName]?.vue ?? {};
2094
2199
  return {
2095
2200
  name: "nasti:vue",
2096
2201
  enforce: "pre",
@@ -2110,32 +2215,63 @@ function vuePlugin(config) {
2110
2215
  const sfc = await loadVueCompiler();
2111
2216
  if (!sfc) return null;
2112
2217
  const [, filePath, indexStr] = match;
2113
- let descriptor = descriptorCache.get(filePath);
2114
- if (!descriptor) {
2218
+ let cached2 = descriptorCache.get(filePath);
2219
+ if (!cached2) {
2115
2220
  try {
2116
2221
  const fs14 = await import("fs");
2117
- const source = fs14.readFileSync(filePath, "utf-8");
2118
- const parsed = sfc.parse(source, { filename: filePath });
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
+ });
2119
2233
  if (parsed.errors.length) return null;
2120
- descriptor = parsed.descriptor;
2121
- descriptorCache.set(filePath, descriptor);
2234
+ cached2 = {
2235
+ descriptor: parsed.descriptor,
2236
+ sourceMap: transformedSfc.map
2237
+ };
2238
+ descriptorCache.set(filePath, cached2);
2122
2239
  } catch {
2123
2240
  return null;
2124
2241
  }
2125
2242
  }
2243
+ const { descriptor, sourceMap: sfcSourceMap } = cached2;
2126
2244
  const index2 = parseInt(indexStr ?? "0", 10);
2127
2245
  const style = descriptor.styles[index2];
2128
2246
  if (!style) return null;
2129
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;
2130
2258
  const result = await sfc.compileStyleAsync({
2131
- source: style.content,
2259
+ ...vueOptions.style,
2260
+ source: transformedStyle.code,
2132
2261
  filename: filePath,
2133
2262
  id: `data-v-${scopeId}`,
2134
2263
  scoped: style.scoped ?? false,
2264
+ inMap: styleInputMap,
2135
2265
  // <style lang="scss|less|stylus"> 需经对应预处理器(缺省 undefined = 纯 CSS)
2136
2266
  preprocessLang: style.lang
2137
2267
  });
2138
- 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;
2139
2275
  },
2140
2276
  async transform(code, id) {
2141
2277
  if (!VUE_FILE_RE.test(id) && !VUE_QUERY_RE.test(id)) return null;
@@ -2147,57 +2283,144 @@ function vuePlugin(config) {
2147
2283
  if (VUE_QUERY_RE.test(id)) {
2148
2284
  return null;
2149
2285
  }
2150
- 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
+ });
2151
2297
  if (errors.length) {
2152
- 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
+ );
2153
2303
  return null;
2154
2304
  }
2155
- descriptorCache.set(id, descriptor);
2305
+ descriptorCache.set(id, {
2306
+ descriptor,
2307
+ sourceMap: transformedSfc.map
2308
+ });
2156
2309
  const scopeId = hashId(id);
2310
+ const wantsSourceMap = !!config.build.sourcemap || transformedSfc.map != null;
2157
2311
  let scriptCode = "";
2312
+ let scriptMap;
2158
2313
  if (descriptor.script || descriptor.scriptSetup) {
2314
+ const inlineTemplate = vueOptions.script?.inlineTemplate !== false;
2159
2315
  const compiled = sfc.compileScript(descriptor, {
2316
+ ...vueOptions.script,
2160
2317
  id: scopeId,
2161
2318
  isProd: !isDev,
2162
- inlineTemplate: true,
2319
+ inlineTemplate,
2320
+ sourceMap: wantsSourceMap,
2163
2321
  // 让 compileScript 产出 `const __sfc__ = ...`(而非默认的 `export default {...}`)。
2164
2322
  // 否则下方追加的 `__sfc__.render` / `__sfc__.__scopeId` / HMR 记录会引用一个
2165
2323
  // 不存在的 `__sfc__`,并与 compileScript 自带的 `export default` 形成双重默认导出。
2166
2324
  genDefaultAs: "__sfc__"
2167
2325
  });
2168
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
+ }
2169
2337
  }
2170
2338
  let templateCode = "";
2171
- 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 ?? {};
2172
2356
  const compiled = sfc.compileTemplate({
2173
- source: descriptor.template.content,
2357
+ ...vueOptions.template,
2358
+ source: transformedTemplate.code,
2174
2359
  filename: id,
2175
2360
  id: scopeId,
2176
- compilerOptions: { scopeId: `data-v-${scopeId}` }
2361
+ inMap: templateInputMap,
2362
+ compilerOptions: {
2363
+ ...customCompilerOptions,
2364
+ scopeId: `data-v-${scopeId}`
2365
+ }
2177
2366
  });
2178
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
+ }
2179
2377
  }
2180
- 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);
2181
2406
  if (templateCode) {
2182
- output += `
2183
- ${templateCode}
2184
- `;
2185
- output += `
2186
- __sfc__.render = render
2187
- `;
2407
+ append("\n");
2408
+ append(templateCode, templateMap);
2409
+ append("\n");
2410
+ append("\n__sfc__.render = render\n");
2188
2411
  }
2189
2412
  if (descriptor.styles.length > 0) {
2190
2413
  for (let i = 0; i < descriptor.styles.length; i++) {
2191
- output += `
2414
+ append(`
2192
2415
  import "${id}?vue&type=style&index=${i}&lang.css"
2193
- `;
2416
+ `);
2194
2417
  }
2195
2418
  }
2196
- output += `
2419
+ append(`
2197
2420
  __sfc__.__scopeId = "data-v-${scopeId}"
2198
- `;
2421
+ `);
2199
2422
  if (isDev) {
2200
- output += `
2423
+ append(`
2201
2424
  __sfc__.__hmrId = ${JSON.stringify(scopeId)}
2202
2425
  if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') {
2203
2426
  __VUE_HMR_RUNTIME__.createRecord(__sfc__.__hmrId, __sfc__)
@@ -2211,17 +2434,34 @@ if (import.meta.hot) {
2211
2434
  }
2212
2435
  })
2213
2436
  }
2214
- `;
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
+ );
2215
2448
  }
2216
- output += `
2217
- export default __sfc__
2218
- `;
2219
2449
  const lang = descriptor.scriptSetup?.lang ?? descriptor.script?.lang;
2220
2450
  if (lang === "ts") {
2221
- const transpiled = transformCode(`${id}.ts`, output, { sourcemap: false });
2222
- 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
+ };
2223
2463
  }
2224
- return { code: output };
2464
+ return { code: output, map: outputMap };
2225
2465
  },
2226
2466
  handleHotUpdate(ctx) {
2227
2467
  const { file, modules } = ctx;
@@ -2235,17 +2475,77 @@ export default __sfc__
2235
2475
  }
2236
2476
  };
2237
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
+ }
2238
2535
  function hashId(filename) {
2239
2536
  return import_node_crypto2.default.createHash("sha256").update(filename).digest("hex").slice(0, 8);
2240
2537
  }
2241
- 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;
2242
2539
  var init_vue = __esm({
2243
2540
  "src/plugins/vue.ts"() {
2244
2541
  "use strict";
2245
2542
  import_node_crypto2 = __toESM(require("crypto"), 1);
2543
+ import_source_map_js2 = require("source-map-js");
2246
2544
  init_transformer();
2545
+ init_debug();
2247
2546
  VUE_FILE_RE = /\.vue$/;
2248
2547
  VUE_QUERY_RE = /\.vue\?vue&type=(script|template|style)(&index=\d+)?(&lang[.=]\w+)?/;
2548
+ debug2 = createDebugger("nasti:vue");
2249
2549
  compiler = null;
2250
2550
  }
2251
2551
  });
@@ -2349,7 +2649,7 @@ function resolvePluginList(config, userPlugins, opts = {}) {
2349
2649
  const consumer = opts.consumer ?? environmentOptions?.consumer;
2350
2650
  return [
2351
2651
  // vuePlugin 排最前(enforce: 'pre' 语义):.vue 先编译成 JS 再走后续管道
2352
- ...config.framework === "vue" ? [vuePlugin(pluginConfig)] : [],
2652
+ ...config.framework === "vue" ? [vuePlugin(pluginConfig, opts.environmentName ?? "client")] : [],
2353
2653
  resolvePlugin(pluginConfig),
2354
2654
  cssPlugin(pluginConfig, opts.cssEngine, consumer),
2355
2655
  assetsPlugin(pluginConfig),
@@ -2453,17 +2753,23 @@ var init_plugin_container = __esm({
2453
2753
  }
2454
2754
  async transform(code, id) {
2455
2755
  let currentCode = code;
2756
+ let lastResult;
2456
2757
  for (const plugin of this.plugins) {
2457
2758
  if (!plugin.transform) continue;
2458
2759
  const result = await plugin.transform.call(this.ctx, currentCode, id);
2459
2760
  if (result == null) continue;
2460
2761
  if (typeof result === "string") {
2461
2762
  currentCode = result;
2763
+ lastResult = void 0;
2462
2764
  } else {
2463
2765
  currentCode = result.code;
2766
+ lastResult = result;
2464
2767
  }
2465
2768
  }
2466
- return currentCode === code ? null : { code: currentCode };
2769
+ return currentCode === code ? null : {
2770
+ ...lastResult,
2771
+ code: currentCode
2772
+ };
2467
2773
  }
2468
2774
  /** 完整的模块处理管道: resolveId → load → transform */
2469
2775
  async processModule(source, importer) {
@@ -2510,9 +2816,13 @@ var init_module_graph = __esm({
2510
2816
  "use strict";
2511
2817
  init_url();
2512
2818
  ModuleGraph = class {
2819
+ environmentName;
2513
2820
  urlToModuleMap = /* @__PURE__ */ new Map();
2514
2821
  idToModuleMap = /* @__PURE__ */ new Map();
2515
2822
  fileToModulesMap = /* @__PURE__ */ new Map();
2823
+ constructor(environmentName = "client") {
2824
+ this.environmentName = environmentName;
2825
+ }
2516
2826
  getModuleByUrl(url) {
2517
2827
  return this.urlToModuleMap.get(removeTimestampQuery(url));
2518
2828
  }
@@ -2542,7 +2852,8 @@ var init_module_graph = __esm({
2542
2852
  transformResult: null,
2543
2853
  lastHMRTimestamp: 0,
2544
2854
  invalidationVersion: 0,
2545
- isSelfAccepting: false
2855
+ isSelfAccepting: false,
2856
+ environment: this.environmentName
2546
2857
  };
2547
2858
  this.idToModuleMap.set(mod.id, mod);
2548
2859
  return mod;
@@ -2700,12 +3011,12 @@ function createNoopHotChannel() {
2700
3011
  }
2701
3012
  };
2702
3013
  }
2703
- function createWsHotChannel(ws) {
3014
+ function createWsHotChannel(ws, environmentName = "client") {
2704
3015
  const listeners = /* @__PURE__ */ new Map();
2705
3016
  let invokeHandlers;
2706
3017
  return {
2707
3018
  send(payload) {
2708
- ws.send(payload);
3019
+ ws.send({ ...payload, environment: payload.environment ?? environmentName });
2709
3020
  },
2710
3021
  on(event, listener) {
2711
3022
  let set = listeners.get(event);
@@ -2717,8 +3028,8 @@ function createWsHotChannel(ws) {
2717
3028
  },
2718
3029
  listen() {
2719
3030
  },
3031
+ // 多个 environment 共享底层 WebSocket server;它由 DevServer.close() 统一关闭。
2720
3032
  close() {
2721
- ws.close();
2722
3033
  },
2723
3034
  setInvokeHandler(handlers) {
2724
3035
  invokeHandlers = handlers;
@@ -2747,7 +3058,7 @@ function resolveEnvironmentPlugins(environment, plugins) {
2747
3058
  }
2748
3059
  });
2749
3060
  }
2750
- var debug2, NastiEnvironment;
3061
+ var debug3, NastiEnvironment;
2751
3062
  var init_environment = __esm({
2752
3063
  "src/core/environment.ts"() {
2753
3064
  "use strict";
@@ -2756,7 +3067,7 @@ var init_environment = __esm({
2756
3067
  init_hot_channel();
2757
3068
  init_debug();
2758
3069
  init_plugin_api();
2759
- debug2 = createDebugger("nasti:environment");
3070
+ debug3 = createDebugger("nasti:environment");
2760
3071
  NastiEnvironment = class {
2761
3072
  name;
2762
3073
  consumer;
@@ -2774,6 +3085,9 @@ var init_environment = __esm({
2774
3085
  candidatePlugins;
2775
3086
  pluginApi;
2776
3087
  buildMetadata = {};
3088
+ cssModules = /* @__PURE__ */ new Map();
3089
+ assetModules = /* @__PURE__ */ new Map();
3090
+ transformRequestHandler;
2777
3091
  initialized = false;
2778
3092
  constructor(name, config, init = {}) {
2779
3093
  const options = config.environments[name];
@@ -2788,7 +3102,7 @@ var init_environment = __esm({
2788
3102
  this.config = config;
2789
3103
  this.options = options;
2790
3104
  this.hot = init.hot ?? createNoopHotChannel();
2791
- this.moduleGraph = new ModuleGraph();
3105
+ this.moduleGraph = new ModuleGraph(name);
2792
3106
  this.candidatePlugins = init.plugins ?? config.plugins;
2793
3107
  this.pluginApi = init.pluginApi ?? getPluginApi(config);
2794
3108
  }
@@ -2818,9 +3132,9 @@ var init_environment = __esm({
2818
3132
  );
2819
3133
  }
2820
3134
  this.driver = claimed[0].driver;
2821
- debug2?.(`env "${this.name}" uses driver "${this.driver.name}"`);
3135
+ debug3?.(`env "${this.name}" uses driver "${this.driver.name}"`);
2822
3136
  }
2823
- debug2?.(`env "${this.name}" initialized (${this.plugins.length} plugins)`);
3137
+ debug3?.(`env "${this.name}" initialized (${this.plugins.length} plugins)`);
2824
3138
  }
2825
3139
  getDriverContext() {
2826
3140
  return {
@@ -2830,6 +3144,37 @@ var init_environment = __esm({
2830
3144
  logger: this.config.logger
2831
3145
  };
2832
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
+ }
2833
3178
  setBuildMetadata(metadata) {
2834
3179
  const { entries: currentEntries, ...currentMetadata } = this.buildMetadata;
2835
3180
  const { entries, ...nextMetadata } = metadata;
@@ -2940,7 +3285,7 @@ async function tryNativeReporterPlugin(config, logger) {
2940
3285
  logInfo: (msg) => logger.info(msg)
2941
3286
  });
2942
3287
  } catch (err) {
2943
- debug3?.(`native viteReporterPlugin unavailable, falling back to JS table: ${err}`);
3288
+ debug4?.(`native viteReporterPlugin unavailable, falling back to JS table: ${err}`);
2944
3289
  return null;
2945
3290
  }
2946
3291
  }
@@ -2995,7 +3340,7 @@ function warnLargeChunks(output, config, logger) {
2995
3340
  )
2996
3341
  );
2997
3342
  }
2998
- var import_node_path8, import_node_zlib, import_picocolors3, debug3, numberFormatter;
3343
+ var import_node_path8, import_node_zlib, import_picocolors3, debug4, numberFormatter;
2999
3344
  var init_reporter = __esm({
3000
3345
  "src/build/reporter.ts"() {
3001
3346
  "use strict";
@@ -3003,7 +3348,7 @@ var init_reporter = __esm({
3003
3348
  import_node_zlib = require("zlib");
3004
3349
  import_picocolors3 = __toESM(require("picocolors"), 1);
3005
3350
  init_debug();
3006
- debug3 = createDebugger("nasti:reporter");
3351
+ debug4 = createDebugger("nasti:reporter");
3007
3352
  numberFormatter = new Intl.NumberFormat("en", {
3008
3353
  maximumFractionDigits: 2,
3009
3354
  minimumFractionDigits: 2
@@ -3043,6 +3388,24 @@ function createBuildAppContext(config, results) {
3043
3388
  getManifest(environmentName) {
3044
3389
  return results[environmentName]?.manifest;
3045
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
+ },
3046
3409
  emitFile(file) {
3047
3410
  const fileName = normalizeAppFileName(file.fileName);
3048
3411
  const collisionKey = artifactCollisionKey(fileName);
@@ -3072,6 +3435,9 @@ function createBuildAppContext(config, results) {
3072
3435
  }
3073
3436
  };
3074
3437
  }
3438
+ function joinPublicPath(base, fileName) {
3439
+ return `${base.endsWith("/") ? base : `${base}/`}${fileName.replace(/^\//, "")}`;
3440
+ }
3075
3441
  function normalizeEnvironmentFileName(fileName) {
3076
3442
  return import_node_path9.default.posix.normalize(fileName.replace(/\\/g, "/").replace(/^\.\//, ""));
3077
3443
  }
@@ -3172,7 +3538,11 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
3172
3538
  const inputOptions = {
3173
3539
  ...restInputOptions,
3174
3540
  input: entryPoints,
3175
- transform: { ...userTransform, define: mergedDefine },
3541
+ transform: {
3542
+ ...userTransform,
3543
+ target: userTransform?.target ?? envOptions.build.target,
3544
+ define: mergedDefine
3545
+ },
3176
3546
  plugins: rolldownPlugins,
3177
3547
  // client/server consumer 都必须使用当前环境的 conditions/mainFields;Lynx
3178
3548
  // BG/MT 通常同为 client consumer,但仍可能声明不同的运行时条件。
@@ -3195,7 +3565,7 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
3195
3565
  };
3196
3566
  const outputOptions = isServer ? {
3197
3567
  format: "esm",
3198
- sourcemap: !!envOptions.build.sourcemap,
3568
+ sourcemap: envOptions.build.sourcemap,
3199
3569
  minify: !!envOptions.build.minify,
3200
3570
  entryFileNames: "[name].js",
3201
3571
  chunkFileNames: "chunks/[name]-[hash].js",
@@ -3204,7 +3574,7 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
3204
3574
  dir: outDir
3205
3575
  } : {
3206
3576
  format: "esm",
3207
- sourcemap: !!envOptions.build.sourcemap,
3577
+ sourcemap: envOptions.build.sourcemap,
3208
3578
  minify: !!envOptions.build.minify,
3209
3579
  entryFileNames: `${assetsDir}/[name].[hash].js`,
3210
3580
  chunkFileNames: `${assetsDir}/[name].[hash].js`,
@@ -3279,13 +3649,68 @@ function finalizeEnvironmentResult(environment, result) {
3279
3649
  return [name, normalized];
3280
3650
  })
3281
3651
  );
3652
+ const inferredMetadata = inferOutputMetadata(environment, result.output);
3282
3653
  return {
3654
+ publicPath: environment.config.base,
3655
+ ...inferredMetadata,
3283
3656
  ...metadata,
3284
3657
  ...result,
3285
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
+ },
3286
3674
  ...Object.keys(normalizedEntries).length > 0 ? { entries: normalizedEntries } : {}
3287
3675
  };
3288
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
+ }
3289
3714
  function prepareBuildOutputDirectories(config, buildableNames) {
3290
3715
  const directories = /* @__PURE__ */ new Set();
3291
3716
  const protectedPaths = /* @__PURE__ */ new Set();
@@ -3363,6 +3788,7 @@ function createOxcTransformPlugin(config, environment) {
3363
3788
  if (!shouldTransform(id)) return null;
3364
3789
  const result = transformCode(id, code, {
3365
3790
  sourcemap: !!environment.options.build.sourcemap,
3791
+ target: environment.options.build.target,
3366
3792
  jsxRuntime: "automatic",
3367
3793
  jsxImportSource: config.framework === "vue" ? "vue" : "react"
3368
3794
  });
@@ -3376,9 +3802,9 @@ async function build(inlineConfig = {}) {
3376
3802
  const startTime = performance.now();
3377
3803
  logger.info(
3378
3804
  import_picocolors4.default.cyan(`
3379
- nasti v${"2.4.0"} `) + import_picocolors4.default.green(`building for ${config.mode}...`)
3805
+ nasti v${"2.4.2"} `) + import_picocolors4.default.green(`building for ${config.mode}...`)
3380
3806
  );
3381
- debug4?.(`root: ${config.root}`);
3807
+ debug5?.(`root: ${config.root}`);
3382
3808
  const buildableNames = Object.keys(config.environments).filter((name) => {
3383
3809
  const environment = config.environments[name];
3384
3810
  if (!environment.buildEnabled) return false;
@@ -3400,7 +3826,7 @@ nasti v${"2.4.0"} `) + import_picocolors4.default.green(`building for ${config.m
3400
3826
  environmentResults[name] = built.result;
3401
3827
  if (name === "client") clientOutput = built.result.output;
3402
3828
  if (buildableNames.length > 1) {
3403
- debug4?.(`environment "${name}" built (${built.result.output.length} files)`);
3829
+ debug5?.(`environment "${name}" built (${built.result.output.length} files)`);
3404
3830
  }
3405
3831
  }
3406
3832
  const pluginApi = getPluginApi(config);
@@ -3496,6 +3922,7 @@ async function buildClientEnvironment(config) {
3496
3922
  const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
3497
3923
  const { output } = await bundle2.write(outputOptions);
3498
3924
  await bundle2.close();
3925
+ clientEnv.setBuildMetadata({ css: getCssMetadata(cssEngine) });
3499
3926
  if (html) {
3500
3927
  let processedHtml = html;
3501
3928
  const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
@@ -3509,7 +3936,9 @@ async function buildClientEnvironment(config) {
3509
3936
  processedHtml = processHtml(processedHtml, result);
3510
3937
  }
3511
3938
  }
3512
- processedHtml = injectCssLinks(processedHtml, cssEngine, config);
3939
+ if (clientEnv.options.build.css.inject !== false) {
3940
+ processedHtml = injectCssLinks(processedHtml, cssEngine, config);
3941
+ }
3513
3942
  for (const chunk of output) {
3514
3943
  if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
3515
3944
  processedHtml = replaceEntryScript(
@@ -3547,9 +3976,11 @@ async function buildClientEnvironment(config) {
3547
3976
  async function buildServerEnvironment(config, name) {
3548
3977
  const envOptions = config.environments[name];
3549
3978
  const logger = config.logger;
3979
+ const cssEngine = envOptions.consumer === "client" ? createCssEngine() : void 0;
3550
3980
  const pluginList = resolvePluginList(config, config.plugins, {
3551
3981
  consumer: envOptions.consumer,
3552
- environmentName: name
3982
+ environmentName: name,
3983
+ cssEngine
3553
3984
  });
3554
3985
  const environment = new NastiEnvironment(name, config, {
3555
3986
  mode: "build",
@@ -3592,6 +4023,7 @@ async function buildServerEnvironment(config, name) {
3592
4023
  const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
3593
4024
  const { output } = await bundle2.write(outputOptions);
3594
4025
  await bundle2.close();
4026
+ if (cssEngine) environment.setBuildMetadata({ css: getCssMetadata(cssEngine) });
3595
4027
  logger.info(
3596
4028
  import_picocolors4.default.dim(` [${name}] `) + output.map((o) => import_node_path10.default.join(envOptions.build.outDir, o.fileName)).join(import_picocolors4.default.dim(", "))
3597
4029
  );
@@ -3643,7 +4075,7 @@ function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, ur
3643
4075
  }
3644
4076
  return processed;
3645
4077
  }
3646
- var import_node_path10, import_node_fs7, 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;
3647
4079
  var init_build = __esm({
3648
4080
  "src/build/index.ts"() {
3649
4081
  "use strict";
@@ -3663,7 +4095,7 @@ var init_build = __esm({
3663
4095
  init_plugin_api();
3664
4096
  init_build_app_context();
3665
4097
  import_picocolors4 = __toESM(require("picocolors"), 1);
3666
- debug4 = createDebugger("nasti:build");
4098
+ debug5 = createDebugger("nasti:build");
3667
4099
  NODE_BUILTINS = /* @__PURE__ */ new Set([...import_node_module3.builtinModules, ...import_node_module3.builtinModules.map((m) => `node:${m}`)]);
3668
4100
  }
3669
4101
  });
@@ -3714,13 +4146,6 @@ var init_ws = __esm({
3714
4146
  });
3715
4147
 
3716
4148
  // src/server/middleware.ts
3717
- var middleware_exports = {};
3718
- __export(middleware_exports, {
3719
- REACT_REFRESH_GLOBAL_PREAMBLE: () => REACT_REFRESH_GLOBAL_PREAMBLE,
3720
- getReactRefreshRuntimeEsm: () => getReactRefreshRuntimeEsm,
3721
- transformMiddleware: () => transformMiddleware,
3722
- transformRequest: () => transformRequest
3723
- });
3724
4149
  function getReactRefreshRuntimeEsm(includeBoundaryHelpers = false) {
3725
4150
  if (__refreshRuntimeCache) {
3726
4151
  return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
@@ -3823,7 +4248,8 @@ const __nasti_hot__ = __nasti_createHotContext__(${urlLit});
3823
4248
  function transformMiddleware(ctx) {
3824
4249
  ctx.envDefine = buildEnvDefine(
3825
4250
  loadEnv(ctx.config.mode, ctx.config.root, ctx.config.envPrefix),
3826
- ctx.config.mode
4251
+ ctx.config.mode,
4252
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
3827
4253
  );
3828
4254
  return async (req, res, next) => {
3829
4255
  const url = req.url ?? "/";
@@ -3868,7 +4294,7 @@ function transformMiddleware(ctx) {
3868
4294
  return;
3869
4295
  }
3870
4296
  }
3871
- if (isModuleRequest(url)) {
4297
+ if (isModuleRequest(url, req.headers["sec-fetch-dest"])) {
3872
4298
  try {
3873
4299
  const result = await transformRequest(url, ctx);
3874
4300
  if (result) {
@@ -3939,12 +4365,16 @@ async function transformRequest(url, ctx) {
3939
4365
  if (rawQuery && !/^t=\d+$/.test(rawQuery)) {
3940
4366
  const mod2 = await moduleGraph.ensureEntryFromUrl(url);
3941
4367
  const transformVersion2 = mod2.invalidationVersion;
3942
- const loaded = await pluginContainer.load(url);
3943
- if (loaded != null) {
3944
- let code2 = typeof loaded === "string" ? loaded : loaded.code;
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;
3945
4372
  const transformed = await pluginContainer.transform(code2, url);
3946
4373
  if (transformed != null) {
3947
4374
  code2 = typeof transformed === "string" ? transformed : transformed.code;
4375
+ if (typeof transformed !== "string" && transformed.map != null) {
4376
+ map2 = transformed.map;
4377
+ }
3948
4378
  }
3949
4379
  const parentFile = resolveUrlToFile(cleanReqUrl, config.root) ?? cleanReqUrl;
3950
4380
  moduleGraph.registerModule(mod2, parentFile);
@@ -3952,7 +4382,8 @@ async function transformRequest(url, ctx) {
3952
4382
  code2 = injectImportMetaHot(hotInfo2.code, url);
3953
4383
  code2 = replaceEnvInCode(code2, ctx.envDefine ?? buildEnvDefine(
3954
4384
  loadEnv(config.mode, config.root, config.envPrefix),
3955
- config.mode
4385
+ config.mode,
4386
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
3956
4387
  ));
3957
4388
  const importedUrls2 = /* @__PURE__ */ new Set();
3958
4389
  code2 = rewriteImports(code2, config, parentFile, importedUrls2, moduleGraph);
@@ -3963,7 +4394,7 @@ async function transformRequest(url, ctx) {
3963
4394
  hotInfo2.isSelfAccepting,
3964
4395
  transformVersion2
3965
4396
  );
3966
- const transformResult2 = { code: code2 };
4397
+ const transformResult2 = { code: code2, map: map2 };
3967
4398
  if (pruned2) {
3968
4399
  if (pruned2.size > 0) ctx.onPrune?.([...pruned2].map((item) => item.url));
3969
4400
  mod2.transformResult = transformResult2;
@@ -3982,10 +4413,13 @@ async function transformRequest(url, ctx) {
3982
4413
  mod.transformResult = transformResult2;
3983
4414
  return transformResult2;
3984
4415
  }
3985
- let code = import_node_fs9.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;
3986
4419
  const pluginResult = await pluginContainer.transform(code, filePath);
3987
4420
  if (pluginResult) {
3988
4421
  code = typeof pluginResult === "string" ? pluginResult : pluginResult.code;
4422
+ if (typeof pluginResult !== "string") map = pluginResult.map;
3989
4423
  }
3990
4424
  const stableUrl = cleanReqUrl;
3991
4425
  let wrappedWithRefresh = false;
@@ -3996,9 +4430,11 @@ async function transformRequest(url, ctx) {
3996
4430
  sourcemap: true,
3997
4431
  jsxRuntime: "automatic",
3998
4432
  jsxImportSource: config.framework === "vue" ? "vue" : "react",
3999
- reactRefresh: useRefresh
4433
+ reactRefresh: useRefresh,
4434
+ target: ctx.environment?.options.build.target ?? config.build.target
4000
4435
  });
4001
4436
  code = result.code;
4437
+ if (result.map) map = JSON.parse(result.map);
4002
4438
  if (useRefresh) {
4003
4439
  code = buildReactRefreshWrapper(stableUrl, code);
4004
4440
  wrappedWithRefresh = true;
@@ -4011,7 +4447,8 @@ async function transformRequest(url, ctx) {
4011
4447
  }
4012
4448
  const envDefine = ctx.envDefine ?? buildEnvDefine(
4013
4449
  loadEnv(config.mode, config.root, config.envPrefix),
4014
- config.mode
4450
+ config.mode,
4451
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
4015
4452
  );
4016
4453
  code = replaceEnvInCode(code, envDefine);
4017
4454
  const importedUrls = /* @__PURE__ */ new Set();
@@ -4023,7 +4460,7 @@ async function transformRequest(url, ctx) {
4023
4460
  wrappedWithRefresh || hotInfo.isSelfAccepting,
4024
4461
  transformVersion
4025
4462
  );
4026
- const transformResult = { code };
4463
+ const transformResult = { code, map };
4027
4464
  if (pruned) {
4028
4465
  if (pruned.size > 0) ctx.onPrune?.([...pruned].map((item) => item.url));
4029
4466
  mod.transformResult = transformResult;
@@ -4046,7 +4483,8 @@ async function loadVirtualModule(spec, ctx) {
4046
4483
  }
4047
4484
  code = replaceEnvInCode(code, ctx.envDefine ?? buildEnvDefine(
4048
4485
  loadEnv(config.mode, config.root, config.envPrefix),
4049
- config.mode
4486
+ config.mode,
4487
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
4050
4488
  ));
4051
4489
  const anchor = import_node_path12.default.join(config.root, "__nasti_virtual__.ts");
4052
4490
  code = rewriteImports(code, config, anchor);
@@ -4591,10 +5029,15 @@ function resolveUrlToFile(url, root) {
4591
5029
  }
4592
5030
  return null;
4593
5031
  }
4594
- function isModuleRequest(url) {
5032
+ function isModuleRequest(url, destination) {
4595
5033
  const cleanUrl = url.split("?")[0];
4596
5034
  if (/\.(ts|tsx|jsx|js|mjs|vue|css|json)$/.test(cleanUrl)) return true;
4597
5035
  if (cleanUrl.startsWith("/@modules/")) 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
+ }
4598
5041
  if (!import_node_path12.default.extname(cleanUrl)) return true;
4599
5042
  return false;
4600
5043
  }
@@ -4607,11 +5050,15 @@ const hotModulesMap = new Map();
4607
5050
  const disposeMap = new Map();
4608
5051
  const pruneMap = new Map();
4609
5052
  const dataMap = new Map();
5053
+ const customListenersMap = new Map();
4610
5054
  let updateQueue = [];
4611
5055
  let pendingUpdateQueue = false;
4612
5056
 
4613
5057
  socket.addEventListener('message', async ({ data }) => {
4614
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;
4615
5062
  switch (payload.type) {
4616
5063
  case 'connected':
4617
5064
  console.debug('[nasti] connected.');
@@ -4644,8 +5091,24 @@ socket.addEventListener('message', async ({ data }) => {
4644
5091
  disposeMap.delete(path);
4645
5092
  pruneMap.delete(path);
4646
5093
  dataMap.delete(path);
5094
+ clearCustomListeners(path);
4647
5095
  }));
4648
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
+ }
4649
5112
  case 'error':
4650
5113
  console.error('[nasti] error:', payload.err.message);
4651
5114
  showErrorOverlay(payload.err);
@@ -4744,6 +5207,7 @@ export function createHotContext(ownerPath) {
4744
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
4745
5208
  const existing = hotModulesMap.get(ownerPath);
4746
5209
  if (existing) existing.callbacks = [];
5210
+ clearCustomListeners(ownerPath);
4747
5211
 
4748
5212
  const acceptDeps = (deps, callback = () => {}) => {
4749
5213
  const mod = hotModulesMap.get(ownerPath) || { id: ownerPath, callbacks: [] };
@@ -4769,12 +5233,39 @@ export function createHotContext(ownerPath) {
4769
5233
  dispose(callback) {
4770
5234
  disposeMap.set(ownerPath, callback);
4771
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
+ },
4772
5256
  invalidate() {
4773
5257
  location.reload();
4774
5258
  },
4775
5259
  data: dataMap.get(ownerPath),
4776
5260
  };
4777
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
+ }
4778
5269
  `;
4779
5270
  }
4780
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;
@@ -4790,6 +5281,7 @@ var init_middleware = __esm({
4790
5281
  init_html();
4791
5282
  init_env();
4792
5283
  init_url();
5284
+ init_assets();
4793
5285
  import_meta = {};
4794
5286
  __dirname_esm = import_node_path12.default.dirname((0, import_node_url3.fileURLToPath)(import_meta.url));
4795
5287
  __require = (0, import_node_module5.createRequire)(import_meta.url);
@@ -4872,19 +5364,25 @@ window.__vite_plugin_react_preamble_installed__ = true;
4872
5364
  });
4873
5365
 
4874
5366
  // src/server/hmr.ts
4875
- async function handleFileChange(file, server) {
4876
- 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;
4877
5374
  const logger = config.logger;
4878
5375
  const relativePath = "/" + import_node_path13.default.relative(config.root, file);
4879
5376
  const shortFile = import_node_path13.default.relative(config.root, file);
4880
5377
  const mods = moduleGraph.getModulesByFile(file);
4881
5378
  if (!mods || mods.size === 0) {
4882
- return;
5379
+ return null;
4883
5380
  }
4884
5381
  const updates = [];
4885
- const timestamp = Date.now();
4886
5382
  const graph = moduleGraph;
4887
5383
  const invalidatedModules = /* @__PURE__ */ new Set();
5384
+ const affectedSet = /* @__PURE__ */ new Set();
5385
+ let fullReload = false;
4888
5386
  for (const mod of mods) {
4889
5387
  graph.invalidateModuleAndImporters(mod, timestamp, invalidatedModules);
4890
5388
  const ctx = {
@@ -4892,10 +5390,11 @@ async function handleFileChange(file, server) {
4892
5390
  timestamp,
4893
5391
  modules: [mod],
4894
5392
  read: () => import_node_fs10.default.readFileSync(file, "utf-8"),
4895
- server
5393
+ server,
5394
+ environment
4896
5395
  };
4897
5396
  let affectedModules = [mod];
4898
- for (const plugin of config.plugins) {
5397
+ for (const plugin of environment.plugins) {
4899
5398
  if (plugin.handleHotUpdate) {
4900
5399
  const result = await plugin.handleHotUpdate(ctx);
4901
5400
  if (result) {
@@ -4904,12 +5403,12 @@ async function handleFileChange(file, server) {
4904
5403
  }
4905
5404
  }
4906
5405
  for (const affected of affectedModules) {
5406
+ affectedSet.add(affected);
4907
5407
  graph.invalidateModuleAndImporters(affected, timestamp, invalidatedModules);
4908
5408
  const boundaries = graph.getHmrBoundaries(affected);
4909
5409
  if (boundaries.length === 0) {
4910
- logger.info(import_picocolors7.default.green("page reload ") + import_picocolors7.default.dim(shortFile), { timestamp: true });
4911
- ws.send({ type: "full-reload", path: relativePath });
4912
- return;
5410
+ fullReload = true;
5411
+ continue;
4913
5412
  }
4914
5413
  for (const { boundary, acceptedVia } of boundaries) {
4915
5414
  const update = {
@@ -4926,13 +5425,30 @@ async function handleFileChange(file, server) {
4926
5425
  }
4927
5426
  }
4928
5427
  }
4929
- 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) {
4930
5439
  logger.info(
4931
- 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"),
4932
5441
  { timestamp: true }
4933
5442
  );
4934
- ws.send({ type: "update", updates });
5443
+ environment.hot.send({ type: "update", updates });
4935
5444
  }
5445
+ return {
5446
+ environment,
5447
+ modules: [...affectedSet],
5448
+ updates,
5449
+ transformed,
5450
+ fullReload
5451
+ };
4936
5452
  }
4937
5453
  var import_node_path13, import_node_fs10, import_picocolors7;
4938
5454
  var init_hmr = __esm({
@@ -4958,7 +5474,7 @@ function createModuleRunner(environment) {
4958
5474
  }
4959
5475
  return new NastiModuleRunner(environment);
4960
5476
  }
4961
- var import_node_path14, import_node_fs11, 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;
4962
5478
  var init_runnable_environment = __esm({
4963
5479
  "src/server/runnable-environment.ts"() {
4964
5480
  "use strict";
@@ -4969,7 +5485,7 @@ var init_runnable_environment = __esm({
4969
5485
  init_transformer();
4970
5486
  init_env();
4971
5487
  init_debug();
4972
- debug5 = createDebugger("nasti:ssr");
5488
+ debug6 = createDebugger("nasti:ssr");
4973
5489
  NODE_BUILTINS3 = /* @__PURE__ */ new Set([...import_node_module6.builtinModules, ...import_node_module6.builtinModules.map((m) => `node:${m}`)]);
4974
5490
  NastiModuleRunner = class {
4975
5491
  environment;
@@ -5047,6 +5563,7 @@ var init_runnable_environment = __esm({
5047
5563
  if (shouldTransform(cleanId)) {
5048
5564
  const result = transformCode(cleanId, code, {
5049
5565
  sourcemap: false,
5566
+ target: this.environment.options.build.target,
5050
5567
  jsxRuntime: "automatic",
5051
5568
  jsxImportSource: this.config.framework === "vue" ? "vue" : "react"
5052
5569
  });
@@ -5063,7 +5580,7 @@ var init_runnable_environment = __esm({
5063
5580
  );
5064
5581
  }
5065
5582
  const runnerResult = await moduleRunnerTransform(resolvedId, code);
5066
- debug5?.(`fetchModule ${resolvedId} (${runnerResult.deps?.length ?? 0} deps)`);
5583
+ debug6?.(`fetchModule ${resolvedId} (${runnerResult.deps?.length ?? 0} deps)`);
5067
5584
  return { id: resolvedId, code: runnerResult.code };
5068
5585
  }
5069
5586
  completeExtension(id) {
@@ -5183,7 +5700,7 @@ async function createBundledDevServer(opts) {
5183
5700
  }
5184
5701
  } catch (err) {
5185
5702
  throw new Error(
5186
- `[nasti] experimental.bundledDev requires rolldown's experimental dev() API (locked to the installed rc; got: ${err.message}). Remove --bundle / experimental.bundledDev to use the default unbundled dev server.`
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.`
5187
5704
  );
5188
5705
  }
5189
5706
  const html = await readHtmlFile(config.root, config.environments.client?.html);
@@ -5236,7 +5753,7 @@ async function createBundledDevServer(opts) {
5236
5753
  for (const { clientId, update } of updates) {
5237
5754
  if (update.type === "Noop") continue;
5238
5755
  if (update.type === "FullReload") {
5239
- debug6?.(`full reload for ${clientId}: ${update.reason ?? ""}`);
5756
+ debug7?.(`full reload for ${clientId}: ${update.reason ?? ""}`);
5240
5757
  needsLatestOutput = true;
5241
5758
  continue;
5242
5759
  }
@@ -5286,7 +5803,7 @@ async function createBundledDevServer(opts) {
5286
5803
  },
5287
5804
  {
5288
5805
  watch: { skipWrite: true },
5289
- rebuildStrategy: "auto",
5806
+ rebuildStrategy: "never",
5290
5807
  onOutput(result) {
5291
5808
  if (result instanceof Error) {
5292
5809
  logger.error(import_picocolors8.default.red(`[bundled] build error: ${result.message}`), { error: result });
@@ -5302,7 +5819,13 @@ async function createBundledDevServer(opts) {
5302
5819
  memoryFiles.set(`${file.fileName}.map`, JSON.stringify(file.map));
5303
5820
  }
5304
5821
  }
5305
- 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
+ }
5306
5829
  },
5307
5830
  async onHmrUpdates(result) {
5308
5831
  if (result instanceof Error) {
@@ -5311,7 +5834,7 @@ async function createBundledDevServer(opts) {
5311
5834
  return;
5312
5835
  }
5313
5836
  const { updates, changedFiles } = result;
5314
- debug6?.(
5837
+ debug7?.(
5315
5838
  `onHmrUpdates(engine watcher): ${changedFiles.length} changed, ${updates.length} updates`
5316
5839
  );
5317
5840
  if (changedFiles.length === 0) return;
@@ -5330,24 +5853,29 @@ async function createBundledDevServer(opts) {
5330
5853
  if (!clientId) return;
5331
5854
  wss.handleUpgrade(req, socket, head, (ws) => {
5332
5855
  bundledClients.set(clientId, ws);
5333
- debug6?.(`bundled client connected: ${clientId}`);
5334
- 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
+ });
5335
5866
  ws.on("message", async (raw) => {
5336
5867
  try {
5337
5868
  const msg = JSON.parse(String(raw));
5338
- if (msg.type === "hmr:module-registered" && Array.isArray(msg.modules)) {
5339
- await engine.registerModules(clientId, msg.modules);
5340
- debug6?.(`registered ${msg.modules.length} modules for ${clientId}`);
5341
- } else if (msg.type === "hmr:invalidate") {
5869
+ if (msg.type === "hmr:invalidate") {
5342
5870
  scheduleFullReload();
5343
5871
  }
5344
5872
  } catch (err) {
5345
- debug6?.(`bundled ws message error: ${err.message}`);
5873
+ debug7?.(`bundled ws message error: ${err.message}`);
5346
5874
  }
5347
5875
  });
5348
5876
  ws.on("close", () => {
5349
5877
  bundledClients.delete(clientId);
5350
- 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}`));
5351
5879
  });
5352
5880
  });
5353
5881
  });
@@ -5364,10 +5892,18 @@ async function createBundledDevServer(opts) {
5364
5892
  res.end("// [nasti] lazy endpoint requires id & clientId");
5365
5893
  return;
5366
5894
  }
5367
- 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
+ });
5368
5904
  res.setHeader("Content-Type", "application/javascript");
5369
5905
  res.setHeader("Cache-Control", "no-store");
5370
- res.end(code + "\n;export {}");
5906
+ res.end(output.code + "\n;export {}");
5371
5907
  return;
5372
5908
  }
5373
5909
  const patchHit = patches.get(pathname.replace(/^\//, ""));
@@ -5388,6 +5924,11 @@ async function createBundledDevServer(opts) {
5388
5924
  res.setHeader("ETag", hit.etag);
5389
5925
  res.setHeader("Content-Type", MIME_TYPES[import_node_path15.default.extname(fileName)] ?? "application/octet-stream");
5390
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
+ });
5391
5932
  res.end(hit.content);
5392
5933
  return;
5393
5934
  }
@@ -5487,7 +6028,7 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
5487
6028
  }
5488
6029
  return processed;
5489
6030
  }
5490
- var import_node_path15, import_node_crypto3, import_ws2, import_picocolors8, debug6, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
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;
5491
6032
  var init_dev_engine = __esm({
5492
6033
  "src/server/bundled/dev-engine.ts"() {
5493
6034
  "use strict";
@@ -5500,7 +6041,7 @@ var init_dev_engine = __esm({
5500
6041
  init_transformer();
5501
6042
  init_middleware();
5502
6043
  init_debug();
5503
- debug6 = createDebugger("nasti:bundled");
6044
+ debug7 = createDebugger("nasti:bundled");
5504
6045
  MIME_TYPES = {
5505
6046
  ".js": "application/javascript",
5506
6047
  ".mjs": "application/javascript",
@@ -5627,7 +6168,7 @@ async function createServer(inlineConfig = {}) {
5627
6168
  const ws = createWebSocketServer(httpServer);
5628
6169
  const pluginApi = getPluginApi(config);
5629
6170
  const clientEnv = new NastiEnvironment("client", config, {
5630
- hot: createWsHotChannel(ws),
6171
+ hot: createWsHotChannel(ws, "client"),
5631
6172
  mode: "dev",
5632
6173
  plugins: allPlugins,
5633
6174
  pluginApi
@@ -5642,13 +6183,40 @@ async function createServer(inlineConfig = {}) {
5642
6183
  environmentName: name
5643
6184
  });
5644
6185
  environments[name] = new NastiEnvironment(name, config, {
6186
+ hot: consumer === "client" ? createWsHotChannel(ws, name) : void 0,
5645
6187
  mode: "dev",
5646
6188
  plugins: envPlugins,
5647
6189
  pluginApi
5648
6190
  });
5649
6191
  }
5650
6192
  for (const [name, environment] of Object.entries(environments)) {
5651
- 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));
5652
6220
  }
5653
6221
  let ssrRunner = null;
5654
6222
  async function getSsrRunner() {
@@ -5663,7 +6231,6 @@ async function createServer(inlineConfig = {}) {
5663
6231
  return ssrRunner;
5664
6232
  }
5665
6233
  const moduleGraph = clientEnv.moduleGraph;
5666
- const pluginContainer = clientEnv.pluginContainer;
5667
6234
  let bundledServer = null;
5668
6235
  if (config.experimental.bundledDev) {
5669
6236
  const { createBundledDevServer: createBundledDevServer2 } = await Promise.resolve().then(() => (init_dev_engine(), dev_engine_exports));
@@ -5692,6 +6259,15 @@ async function createServer(inlineConfig = {}) {
5692
6259
  let server;
5693
6260
  const environmentServices = {};
5694
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
+ };
5695
6271
  const logCloseError = (target, error) => {
5696
6272
  const normalized = error instanceof Error ? error : new Error(String(error));
5697
6273
  logger.error(`[nasti] failed to close ${target}`, { error: normalized });
@@ -5743,14 +6319,61 @@ async function createServer(inlineConfig = {}) {
5743
6319
  });
5744
6320
  }
5745
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
+ };
5746
6369
  watcher.on("change", (file) => {
5747
6370
  ssrRunner?.invalidateFile(file);
5748
- handleFileChange(file, server);
6371
+ queueClientEnvironmentUpdate(file);
5749
6372
  notifyEnvironmentDrivers(file, "change");
5750
6373
  });
5751
6374
  watcher.on("add", (file) => {
5752
6375
  ssrRunner?.invalidateFile(file);
5753
- handleFileChange(file, server);
6376
+ queueClientEnvironmentUpdate(file);
5754
6377
  notifyEnvironmentDrivers(file, "add");
5755
6378
  });
5756
6379
  watcher.on("unlink", (file) => {
@@ -5768,7 +6391,7 @@ async function createServer(inlineConfig = {}) {
5768
6391
  async listen(port) {
5769
6392
  const finalPort = port ?? config.server.port;
5770
6393
  const host = config.server.host === true ? "0.0.0.0" : config.server.host;
5771
- await pluginContainer.buildStart();
6394
+ await startDevPipelines();
5772
6395
  await startEnvironmentDrivers();
5773
6396
  return new Promise((resolve, reject) => {
5774
6397
  let currentPort = finalPort;
@@ -5783,7 +6406,7 @@ async function createServer(inlineConfig = {}) {
5783
6406
  const readyIn = Math.ceil(performance.now() - startTime);
5784
6407
  logger.info(
5785
6408
  `
5786
- ${import_picocolors9.default.cyan(import_picocolors9.default.bold("NASTI"))} ${import_picocolors9.default.cyan(`v${"2.4.0"}`)} ${import_picocolors9.default.dim("ready in")} ${import_picocolors9.default.bold(readyIn)} ${import_picocolors9.default.dim("ms")}
6409
+ ${import_picocolors9.default.cyan(import_picocolors9.default.bold("NASTI"))} ${import_picocolors9.default.cyan(`v${"2.4.2"}`)} ${import_picocolors9.default.dim("ready in")} ${import_picocolors9.default.bold(readyIn)} ${import_picocolors9.default.dim("ms")}
5787
6410
  `
5788
6411
  );
5789
6412
  printServerUrls(
@@ -5810,20 +6433,26 @@ async function createServer(inlineConfig = {}) {
5810
6433
  });
5811
6434
  },
5812
6435
  async transformRequest(url) {
5813
- const { transformRequest: transformRequest2 } = await Promise.resolve().then(() => (init_middleware(), middleware_exports));
5814
- return transformRequest2(url, {
5815
- config: configWithPlugins,
5816
- pluginContainer,
5817
- moduleGraph,
5818
- onPrune: (paths) => ws.send({ type: "prune", paths })
5819
- });
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);
5820
6444
  },
5821
6445
  async ssrLoadModule(url) {
5822
6446
  const runner = await getSsrRunner();
5823
6447
  return runner.import(url);
5824
6448
  },
5825
6449
  async close() {
5826
- 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
+ }
5827
6456
  await bundledServer?.close();
5828
6457
  let environmentCloseFailed = false;
5829
6458
  let firstEnvironmentCloseError;
@@ -5873,12 +6502,7 @@ async function createServer(inlineConfig = {}) {
5873
6502
  }
5874
6503
  throw error;
5875
6504
  }
5876
- app.use(transformMiddleware({
5877
- config: configWithPlugins,
5878
- pluginContainer,
5879
- moduleGraph,
5880
- onPrune: (paths) => ws.send({ type: "prune", paths })
5881
- }));
6505
+ app.use(transformMiddleware(transformContexts.get("client")));
5882
6506
  const publicDir = import_node_path16.default.resolve(config.root, "public");
5883
6507
  app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
5884
6508
  app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
@@ -5925,6 +6549,7 @@ var init_server = __esm({
5925
6549
  init_hmr();
5926
6550
  init_builtins();
5927
6551
  init_plugin_api();
6552
+ init_env();
5928
6553
  }
5929
6554
  });
5930
6555
 
@@ -6006,7 +6631,7 @@ async function buildElectron(inlineConfig = {}) {
6006
6631
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
6007
6632
  const startTime = performance.now();
6008
6633
  assertElectronVersion(config);
6009
- console.log(import_picocolors5.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors5.default.dim(` v${"2.4.0"}`));
6634
+ console.log(import_picocolors5.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors5.default.dim(` v${"2.4.2"}`));
6010
6635
  console.log(import_picocolors5.default.dim(` root: ${config.root}`));
6011
6636
  console.log(import_picocolors5.default.dim(` mode: ${config.mode}`));
6012
6637
  console.log(import_picocolors5.default.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
@@ -6179,7 +6804,7 @@ async function startElectronDev(inlineConfig = {}) {
6179
6804
  const { noSpawn, ...rest } = inlineConfig;
6180
6805
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
6181
6806
  warnElectronVersion(config);
6182
- console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.4.0"}`));
6807
+ console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.4.2"}`));
6183
6808
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
6184
6809
  const server = await createServer2({
6185
6810
  ...rest,