@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.js CHANGED
@@ -70,7 +70,10 @@ var init_defaults = __esm({
70
70
  target: "es2022",
71
71
  rolldownOptions: {},
72
72
  emptyOutDir: true,
73
- css: {},
73
+ css: {
74
+ inject: true,
75
+ emit: true
76
+ },
74
77
  reportCompressedSize: true,
75
78
  chunkSizeWarningLimit: 500,
76
79
  cssCodeSplit: true,
@@ -425,7 +428,11 @@ async function resolveConfig(inlineConfig = {}, command) {
425
428
  allowClearScreen: clearScreen2,
426
429
  customLogger: merged.customLogger
427
430
  });
428
- const mergedBuild = { ...defaults.build, ...merged.build };
431
+ const mergedBuild = {
432
+ ...defaults.build,
433
+ ...merged.build,
434
+ css: { ...defaults.build.css, ...merged.build?.css }
435
+ };
429
436
  if (merged.build?.cssMinify === void 0) {
430
437
  mergedBuild.cssMinify = !!mergedBuild.minify;
431
438
  }
@@ -456,11 +463,17 @@ async function resolveConfig(inlineConfig = {}, command) {
456
463
  bundledDev: merged.experimental?.bundledDev ?? defaults.experimental.bundledDev
457
464
  }
458
465
  };
459
- const userEnvironments = {
466
+ const rawUserEnvironments = {
460
467
  client: {},
461
468
  ssr: {},
462
469
  ...merged.environments ?? {}
463
470
  };
471
+ const userEnvironments = Object.fromEntries(
472
+ Object.entries(rawUserEnvironments).map(([name, options]) => [
473
+ name,
474
+ deepMerge({}, options)
475
+ ])
476
+ );
464
477
  for (const [name, envOptions] of Object.entries(userEnvironments)) {
465
478
  for (const plugin of rawPlugins) {
466
479
  if (plugin.configEnvironment) {
@@ -471,6 +484,7 @@ async function resolveConfig(inlineConfig = {}, command) {
471
484
  }
472
485
  for (const [name, envOptions] of Object.entries(userEnvironments)) {
473
486
  const consumer = envOptions.consumer ?? (name === "client" ? "client" : "server");
487
+ const vueOptions = deepMerge({}, envOptions.vue ?? {});
474
488
  if (name === "client") {
475
489
  if (envOptions.resolve) {
476
490
  Object.assign(resolved.resolve, {
@@ -478,7 +492,11 @@ async function resolveConfig(inlineConfig = {}, command) {
478
492
  alias: { ...resolved.resolve.alias, ...envOptions.resolve.alias }
479
493
  });
480
494
  }
481
- if (envOptions.build) Object.assign(resolved.build, envOptions.build);
495
+ if (envOptions.build) {
496
+ const { css, ...environmentBuild } = envOptions.build;
497
+ Object.assign(resolved.build, environmentBuild);
498
+ if (css) resolved.build.css = { ...resolved.build.css, ...css };
499
+ }
482
500
  resolved.environments.client = {
483
501
  consumer,
484
502
  buildEnabled: envOptions.buildEnabled ?? true,
@@ -490,7 +508,8 @@ async function resolveConfig(inlineConfig = {}, command) {
490
508
  driver: envOptions.driver,
491
509
  // 同引用 —— 精确镜像(assertClientEnvironmentMirror 校验)
492
510
  resolve: resolved.resolve,
493
- build: resolved.build
511
+ build: resolved.build,
512
+ vue: vueOptions
494
513
  };
495
514
  continue;
496
515
  }
@@ -500,6 +519,7 @@ async function resolveConfig(inlineConfig = {}, command) {
500
519
  entry: normalizeEnvironmentEntries(envOptions.entry, root),
501
520
  html: envOptions.consumer === "client" && envOptions.html ? path.resolve(root, envOptions.html) : void 0,
502
521
  driver: envOptions.driver,
522
+ vue: vueOptions,
503
523
  resolve: {
504
524
  alias: { ...resolved.resolve.alias, ...envOptions.resolve?.alias },
505
525
  extensions: envOptions.resolve?.extensions ?? [...resolved.resolve.extensions],
@@ -510,6 +530,7 @@ async function resolveConfig(inlineConfig = {}, command) {
510
530
  build: {
511
531
  ...resolved.build,
512
532
  ...envOptions.build,
533
+ css: { ...resolved.build.css, ...envOptions.build?.css },
513
534
  // 非 client 环境默认产出到 <outDir>/<envName>(如 dist/ssr),可显式覆盖
514
535
  outDir: envOptions.build?.outDir ?? path.join(resolved.build.outDir, name),
515
536
  // server 产物默认不压缩(可调试性优先,与 Vite SSR 默认一致),可显式覆盖
@@ -742,7 +763,7 @@ function resolvePlugin(config) {
742
763
  const content = fs2.readFileSync(id, "utf-8");
743
764
  return `export default ${content}`;
744
765
  }
745
- return fs2.readFileSync(id, "utf-8");
766
+ return null;
746
767
  }
747
768
  };
748
769
  }
@@ -1699,12 +1720,31 @@ var init_node = __esm({
1699
1720
  function createCssEngine() {
1700
1721
  return {
1701
1722
  styles: /* @__PURE__ */ new Map(),
1723
+ modules: /* @__PURE__ */ new Map(),
1724
+ chunks: /* @__PURE__ */ new Map(),
1702
1725
  entryCss: /* @__PURE__ */ new Map(),
1703
1726
  allCss: [],
1704
1727
  pendingSingle: [],
1705
1728
  singleFileName: null
1706
1729
  };
1707
1730
  }
1731
+ function getCssMetadata(engine) {
1732
+ return {
1733
+ modules: Object.fromEntries(
1734
+ [...engine.modules].map(([id, module]) => [id, { ...module }])
1735
+ ),
1736
+ chunks: Object.fromEntries(
1737
+ [...engine.chunks].map(([fileName, chunk]) => [
1738
+ fileName,
1739
+ {
1740
+ fileName,
1741
+ moduleIds: [...chunk.moduleIds],
1742
+ cssFileNames: [...chunk.cssFileNames]
1743
+ }
1744
+ ])
1745
+ )
1746
+ };
1747
+ }
1708
1748
  function normalizeCssModuleId(id) {
1709
1749
  return id.startsWith("\0") ? id.slice(1) : id;
1710
1750
  }
@@ -1806,6 +1846,7 @@ var init_tailwind = __esm({
1806
1846
 
1807
1847
  // src/plugins/css.ts
1808
1848
  import path4 from "path";
1849
+ import { SourceMapGenerator } from "source-map-js";
1809
1850
  function cssPlugin(config, engine, consumer = "client") {
1810
1851
  return {
1811
1852
  name: "nasti:css",
@@ -1825,15 +1866,29 @@ function cssPlugin(config, engine, consumer = "client") {
1825
1866
  }
1826
1867
  const rewritten = rewriteCssUrls(cssSource, file, config.root);
1827
1868
  const escaped = JSON.stringify(rewritten);
1869
+ const normalizedId = normalizeCssModuleId(id);
1870
+ const cssModule = { id: normalizedId, source: code, code: rewritten };
1871
+ const map = config.build.sourcemap ? createIdentitySourceMap(code, id) : void 0;
1872
+ engine?.modules.set(normalizedId, cssModule);
1873
+ this.environment?.setCssModule?.(cssModule);
1828
1874
  if (query === "inline") {
1829
1875
  return { code: `export default ${escaped};
1830
- `, moduleType: "js" };
1876
+ `, map, moduleType: "js" };
1831
1877
  }
1832
1878
  if (consumer === "server") {
1833
1879
  return { code: `export default ${escaped};
1834
- `, moduleType: "js" };
1880
+ `, map, moduleType: "js" };
1835
1881
  }
1836
1882
  if (config.command === "serve") {
1883
+ if (config.build.css.inject === false) {
1884
+ return {
1885
+ code: `export default ${escaped};
1886
+ `,
1887
+ map,
1888
+ moduleType: "js",
1889
+ moduleSideEffects: "no-treeshake"
1890
+ };
1891
+ }
1837
1892
  return {
1838
1893
  code: `
1839
1894
  const css = ${escaped};
@@ -1857,16 +1912,18 @@ if (import.meta.hot) {
1857
1912
 
1858
1913
  export default css;
1859
1914
  `,
1915
+ map,
1860
1916
  // bundled dev(DevEngine)下该模块会进 Rolldown:不标 js 会按 .css
1861
1917
  // 扩展名走 CSS 管线触发 #4271 报错;unbundled 中间件忽略此字段
1862
1918
  moduleType: "js"
1863
1919
  };
1864
1920
  }
1865
1921
  if (engine) {
1866
- engine.styles.set(normalizeCssModuleId(id), rewritten);
1922
+ engine.styles.set(normalizedId, rewritten);
1867
1923
  return {
1868
1924
  code: `export default '';
1869
1925
  `,
1926
+ map,
1870
1927
  moduleType: "js",
1871
1928
  // 防止空 stub 被 tree-shake 出 chunk.moduleIds(css-post 靠它定位)
1872
1929
  moduleSideEffects: "no-treeshake"
@@ -1886,11 +1943,27 @@ document.head.appendChild(style);
1886
1943
 
1887
1944
  export default css;
1888
1945
  `,
1946
+ map,
1889
1947
  moduleType: "js"
1890
1948
  };
1891
1949
  }
1892
1950
  };
1893
1951
  }
1952
+ function createIdentitySourceMap(code, id) {
1953
+ const map = new SourceMapGenerator({ file: id });
1954
+ const lines = code.split("\n");
1955
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
1956
+ for (let column = 0; column <= lines[lineIndex].length; column++) {
1957
+ map.addMapping({
1958
+ generated: { line: lineIndex + 1, column },
1959
+ original: { line: lineIndex + 1, column },
1960
+ source: id
1961
+ });
1962
+ }
1963
+ }
1964
+ map.setSourceContent(id, code);
1965
+ return map.toJSON();
1966
+ }
1894
1967
  function rewriteCssUrls(css, from, root) {
1895
1968
  return css.replace(/url\(\s*['"]?([^'")\s]+)['"]?\s*\)/g, (match, url) => {
1896
1969
  if (url.startsWith("/") || url.startsWith("data:") || url.startsWith("http")) {
@@ -1913,19 +1986,27 @@ var init_css = __esm({
1913
1986
  function collectChunkCss(chunk, engine) {
1914
1987
  const ids = chunk.moduleIds ?? Object.keys(chunk.modules);
1915
1988
  let css = "";
1989
+ const moduleIds = [];
1916
1990
  for (const id of ids) {
1917
- const styles = engine.styles.get(normalizeCssModuleId(id));
1918
- if (styles) css += styles + "\n";
1991
+ const normalizedId = normalizeCssModuleId(id);
1992
+ const styles = engine.styles.get(normalizedId);
1993
+ if (styles) {
1994
+ css += styles + "\n";
1995
+ moduleIds.push(normalizedId);
1996
+ }
1919
1997
  }
1920
- return css;
1998
+ return { css, moduleIds };
1921
1999
  }
1922
2000
  function cssPostPlugin(config, engine) {
1923
2001
  return {
1924
2002
  name: "nasti:css-post",
1925
2003
  enforce: "post",
1926
2004
  async renderChunk(code, chunk) {
1927
- const css = collectChunkCss(chunk, engine);
2005
+ const { css, moduleIds } = collectChunkCss(chunk, engine);
1928
2006
  if (!css) return null;
2007
+ const ownership = { moduleIds, cssFileNames: [] };
2008
+ engine.chunks.set(chunk.fileName, ownership);
2009
+ if (config.build.css.emit === false) return null;
1929
2010
  if (!config.build.cssCodeSplit) {
1930
2011
  engine.pendingSingle.push(css);
1931
2012
  return null;
@@ -1938,6 +2019,7 @@ function cssPostPlugin(config, engine) {
1938
2019
  });
1939
2020
  const fileName = this.getFileName(ref);
1940
2021
  engine.allCss.push(fileName);
2022
+ ownership.cssFileNames.push(fileName);
1941
2023
  if (chunk.isEntry) {
1942
2024
  const key = chunk.facadeModuleId ?? chunk.name;
1943
2025
  const existing = engine.entryCss.get(key) ?? [];
@@ -1945,13 +2027,14 @@ function cssPostPlugin(config, engine) {
1945
2027
  engine.entryCss.set(key, existing);
1946
2028
  return null;
1947
2029
  }
2030
+ if (config.build.css.inject === false) return null;
1948
2031
  const href = JSON.stringify(config.base + fileName);
1949
2032
  const snippet = `
1950
2033
  ;(function(){try{var d=document,h=${href};if(!d.querySelector('link[data-nasti-css="'+h+'"]')){var l=d.createElement('link');l.rel='stylesheet';l.href=h;l.setAttribute('data-nasti-css',h);d.head.appendChild(l);}}catch(e){}})();`;
1951
2034
  return { code: code + snippet, map: null };
1952
2035
  },
1953
2036
  augmentChunkHash(chunk) {
1954
- const css = collectChunkCss(chunk, engine);
2037
+ const { css } = collectChunkCss(chunk, engine);
1955
2038
  return css || void 0;
1956
2039
  },
1957
2040
  async generateBundle() {
@@ -1962,6 +2045,9 @@ function cssPostPlugin(config, engine) {
1962
2045
  const fileName = this.getFileName(ref);
1963
2046
  engine.singleFileName = fileName;
1964
2047
  engine.allCss.push(fileName);
2048
+ for (const ownership of engine.chunks.values()) {
2049
+ if (ownership.moduleIds.length > 0) ownership.cssFileNames.push(fileName);
2050
+ }
1965
2051
  }
1966
2052
  };
1967
2053
  }
@@ -1977,6 +2063,7 @@ import path5 from "path";
1977
2063
  import fs3 from "fs";
1978
2064
  import crypto from "crypto";
1979
2065
  function assetsPlugin(config) {
2066
+ const emittedAssets = /* @__PURE__ */ new Set();
1980
2067
  return {
1981
2068
  name: "nasti:assets",
1982
2069
  resolveId(source) {
@@ -2005,12 +2092,29 @@ function assetsPlugin(config) {
2005
2092
  const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 8);
2006
2093
  const basename = path5.basename(file, ext);
2007
2094
  const hashedName = `${config.build.assetsDir}/${basename}.${hash}${ext}`;
2095
+ const environment = this.environment;
2096
+ if (!environment) {
2097
+ throw new Error("[nasti:assets] build environment is not initialized");
2098
+ }
2099
+ if (!emittedAssets.has(hashedName)) {
2100
+ this.emitFile({
2101
+ type: "asset",
2102
+ fileName: hashedName,
2103
+ source: content
2104
+ });
2105
+ emittedAssets.add(hashedName);
2106
+ }
2107
+ environment.setAssetModule(file, hashedName);
2008
2108
  return `export default ${JSON.stringify(config.base + hashedName)}`;
2009
2109
  }
2010
2110
  return null;
2011
2111
  }
2012
2112
  };
2013
2113
  }
2114
+ function isAssetFile(id) {
2115
+ const ext = path5.extname(id.replace(/\?.*$/, ""));
2116
+ return ASSET_EXTENSIONS.has(ext);
2117
+ }
2014
2118
  var ASSET_EXTENSIONS;
2015
2119
  var init_assets = __esm({
2016
2120
  "src/plugins/assets.ts"() {
@@ -2082,6 +2186,11 @@ var init_transformer = __esm({
2082
2186
 
2083
2187
  // src/plugins/vue.ts
2084
2188
  import crypto2 from "crypto";
2189
+ import {
2190
+ SourceMapConsumer,
2191
+ SourceMapGenerator as SourceMapGenerator2,
2192
+ SourceNode
2193
+ } from "source-map-js";
2085
2194
  async function loadVueCompiler() {
2086
2195
  if (compiler) return compiler;
2087
2196
  try {
@@ -2091,9 +2200,10 @@ async function loadVueCompiler() {
2091
2200
  return null;
2092
2201
  }
2093
2202
  }
2094
- function vuePlugin(config) {
2203
+ function vuePlugin(config, environmentName = "client") {
2095
2204
  const isDev = config.command === "serve";
2096
2205
  const descriptorCache = /* @__PURE__ */ new Map();
2206
+ const vueOptions = config.environments[environmentName]?.vue ?? {};
2097
2207
  return {
2098
2208
  name: "nasti:vue",
2099
2209
  enforce: "pre",
@@ -2113,32 +2223,63 @@ function vuePlugin(config) {
2113
2223
  const sfc = await loadVueCompiler();
2114
2224
  if (!sfc) return null;
2115
2225
  const [, filePath, indexStr] = match;
2116
- let descriptor = descriptorCache.get(filePath);
2117
- if (!descriptor) {
2226
+ let cached2 = descriptorCache.get(filePath);
2227
+ if (!cached2) {
2118
2228
  try {
2119
2229
  const fs14 = await import("fs");
2120
- const source = fs14.readFileSync(filePath, "utf-8");
2121
- const parsed = sfc.parse(source, { filename: filePath });
2230
+ const rawSource = fs14.readFileSync(filePath, "utf-8");
2231
+ const transformedSfc = await applySourceTransform(
2232
+ vueOptions.transformSfc,
2233
+ rawSource,
2234
+ { filename: filePath, environmentName, type: "sfc" }
2235
+ );
2236
+ const parsed = sfc.parse(transformedSfc.code, {
2237
+ ...vueOptions.parse,
2238
+ filename: filePath,
2239
+ sourceMap: true
2240
+ });
2122
2241
  if (parsed.errors.length) return null;
2123
- descriptor = parsed.descriptor;
2124
- descriptorCache.set(filePath, descriptor);
2242
+ cached2 = {
2243
+ descriptor: parsed.descriptor,
2244
+ sourceMap: transformedSfc.map
2245
+ };
2246
+ descriptorCache.set(filePath, cached2);
2125
2247
  } catch {
2126
2248
  return null;
2127
2249
  }
2128
2250
  }
2251
+ const { descriptor, sourceMap: sfcSourceMap } = cached2;
2129
2252
  const index2 = parseInt(indexStr ?? "0", 10);
2130
2253
  const style = descriptor.styles[index2];
2131
2254
  if (!style) return null;
2132
2255
  const scopeId = hashId(filePath);
2256
+ const transformedStyle = await applySourceTransform(
2257
+ vueOptions.transformStyle,
2258
+ style.content,
2259
+ { filename: filePath, environmentName, type: "style", index: index2 }
2260
+ );
2261
+ const wantsStyleSourceMap = !!config.build.sourcemap || transformedStyle.map != null || sfcSourceMap != null;
2262
+ const styleInputMap = wantsStyleSourceMap ? composeSourceMapChain(
2263
+ [transformedStyle.map, style.map, sfcSourceMap],
2264
+ { filename: filePath, environmentName, type: "style", index: index2 }
2265
+ ) : void 0;
2133
2266
  const result = await sfc.compileStyleAsync({
2134
- source: style.content,
2267
+ ...vueOptions.style,
2268
+ source: transformedStyle.code,
2135
2269
  filename: filePath,
2136
2270
  id: `data-v-${scopeId}`,
2137
2271
  scoped: style.scoped ?? false,
2272
+ inMap: styleInputMap,
2138
2273
  // <style lang="scss|less|stylus"> 需经对应预处理器(缺省 undefined = 纯 CSS)
2139
2274
  preprocessLang: style.lang
2140
2275
  });
2141
- return result.code;
2276
+ if (transformedStyle.map != null && result.map == null) {
2277
+ warnUnchainableMap(
2278
+ { filename: filePath, environmentName, type: "style", index: index2 },
2279
+ "compiler-sfc did not return a style map"
2280
+ );
2281
+ }
2282
+ return wantsStyleSourceMap ? { code: result.code, map: result.map } : result.code;
2142
2283
  },
2143
2284
  async transform(code, id) {
2144
2285
  if (!VUE_FILE_RE.test(id) && !VUE_QUERY_RE.test(id)) return null;
@@ -2150,57 +2291,144 @@ function vuePlugin(config) {
2150
2291
  if (VUE_QUERY_RE.test(id)) {
2151
2292
  return null;
2152
2293
  }
2153
- const { descriptor, errors } = sfc.parse(code, { filename: id });
2294
+ const transformedSfc = await applySourceTransform(
2295
+ vueOptions.transformSfc,
2296
+ code,
2297
+ { filename: id, environmentName, type: "sfc" }
2298
+ );
2299
+ code = transformedSfc.code;
2300
+ const { descriptor, errors } = sfc.parse(code, {
2301
+ ...vueOptions.parse,
2302
+ filename: id,
2303
+ sourceMap: true
2304
+ });
2154
2305
  if (errors.length) {
2155
- console.error(`[nasti:vue] Parse error in ${id}:`, errors[0].message);
2306
+ const firstError = errors[0];
2307
+ console.error(
2308
+ `[nasti:vue] Parse error in ${id}:`,
2309
+ typeof firstError === "string" ? firstError : firstError.message
2310
+ );
2156
2311
  return null;
2157
2312
  }
2158
- descriptorCache.set(id, descriptor);
2313
+ descriptorCache.set(id, {
2314
+ descriptor,
2315
+ sourceMap: transformedSfc.map
2316
+ });
2159
2317
  const scopeId = hashId(id);
2318
+ const wantsSourceMap = !!config.build.sourcemap || transformedSfc.map != null;
2160
2319
  let scriptCode = "";
2320
+ let scriptMap;
2161
2321
  if (descriptor.script || descriptor.scriptSetup) {
2322
+ const inlineTemplate = vueOptions.script?.inlineTemplate !== false;
2162
2323
  const compiled = sfc.compileScript(descriptor, {
2324
+ ...vueOptions.script,
2163
2325
  id: scopeId,
2164
2326
  isProd: !isDev,
2165
- inlineTemplate: true,
2327
+ inlineTemplate,
2328
+ sourceMap: wantsSourceMap,
2166
2329
  // 让 compileScript 产出 `const __sfc__ = ...`(而非默认的 `export default {...}`)。
2167
2330
  // 否则下方追加的 `__sfc__.render` / `__sfc__.__scopeId` / HMR 记录会引用一个
2168
2331
  // 不存在的 `__sfc__`,并与 compileScript 自带的 `export default` 形成双重默认导出。
2169
2332
  genDefaultAs: "__sfc__"
2170
2333
  });
2171
2334
  scriptCode = compiled.content;
2335
+ scriptMap = composeSourceMapChain(
2336
+ [compiled.map, transformedSfc.map],
2337
+ { filename: id, environmentName, type: "sfc" }
2338
+ );
2339
+ if (transformedSfc.map != null && scriptMap == null) {
2340
+ warnUnchainableMap(
2341
+ { filename: id, environmentName, type: "sfc" },
2342
+ "compiler-sfc did not return a script map"
2343
+ );
2344
+ }
2172
2345
  }
2173
2346
  let templateCode = "";
2174
- if (descriptor.template && !descriptor.scriptSetup) {
2347
+ let templateMap;
2348
+ const scriptSetupIsInline = !!descriptor.scriptSetup && vueOptions.script?.inlineTemplate !== false;
2349
+ if (descriptor.template && !scriptSetupIsInline) {
2350
+ const transformedTemplate = await applySourceTransform(
2351
+ vueOptions.transformTemplate,
2352
+ descriptor.template.content,
2353
+ { filename: id, environmentName, type: "template" }
2354
+ );
2355
+ const templateInputMap = composeSourceMapChain(
2356
+ [
2357
+ transformedTemplate.map,
2358
+ descriptor.template.map,
2359
+ transformedSfc.map
2360
+ ],
2361
+ { filename: id, environmentName, type: "template" }
2362
+ );
2363
+ const customCompilerOptions = vueOptions.template?.compilerOptions ?? {};
2175
2364
  const compiled = sfc.compileTemplate({
2176
- source: descriptor.template.content,
2365
+ ...vueOptions.template,
2366
+ source: transformedTemplate.code,
2177
2367
  filename: id,
2178
2368
  id: scopeId,
2179
- compilerOptions: { scopeId: `data-v-${scopeId}` }
2369
+ inMap: templateInputMap,
2370
+ compilerOptions: {
2371
+ ...customCompilerOptions,
2372
+ scopeId: `data-v-${scopeId}`
2373
+ }
2180
2374
  });
2181
2375
  templateCode = compiled.code;
2376
+ if (wantsSourceMap || transformedTemplate.map != null) {
2377
+ templateMap = compiled.map;
2378
+ }
2379
+ if (transformedTemplate.map != null && templateMap == null) {
2380
+ warnUnchainableMap(
2381
+ { filename: id, environmentName, type: "template" },
2382
+ "compiler-sfc did not return a template map"
2383
+ );
2384
+ }
2182
2385
  }
2183
- let output = scriptCode || "const __sfc__ = {}";
2386
+ const outputNode = new SourceNode();
2387
+ let hasMappedOutput = false;
2388
+ const append = (fragment, map) => {
2389
+ const normalizedMap = normalizeSourceMap(
2390
+ map,
2391
+ { filename: id, environmentName, type: "sfc" }
2392
+ );
2393
+ if (!normalizedMap) {
2394
+ outputNode.add(fragment);
2395
+ return;
2396
+ }
2397
+ try {
2398
+ outputNode.add(
2399
+ SourceNode.fromStringWithSourceMap(
2400
+ fragment,
2401
+ new SourceMapConsumer(normalizedMap)
2402
+ )
2403
+ );
2404
+ hasMappedOutput = true;
2405
+ } catch (error) {
2406
+ warnUnchainableMap(
2407
+ { filename: id, environmentName, type: "sfc" },
2408
+ `source-map assembly failed: ${error instanceof Error ? error.message : String(error)}`
2409
+ );
2410
+ outputNode.add(fragment);
2411
+ }
2412
+ };
2413
+ append(scriptCode || "const __sfc__ = {}", scriptMap);
2184
2414
  if (templateCode) {
2185
- output += `
2186
- ${templateCode}
2187
- `;
2188
- output += `
2189
- __sfc__.render = render
2190
- `;
2415
+ append("\n");
2416
+ append(templateCode, templateMap);
2417
+ append("\n");
2418
+ append("\n__sfc__.render = render\n");
2191
2419
  }
2192
2420
  if (descriptor.styles.length > 0) {
2193
2421
  for (let i = 0; i < descriptor.styles.length; i++) {
2194
- output += `
2422
+ append(`
2195
2423
  import "${id}?vue&type=style&index=${i}&lang.css"
2196
- `;
2424
+ `);
2197
2425
  }
2198
2426
  }
2199
- output += `
2427
+ append(`
2200
2428
  __sfc__.__scopeId = "data-v-${scopeId}"
2201
- `;
2429
+ `);
2202
2430
  if (isDev) {
2203
- output += `
2431
+ append(`
2204
2432
  __sfc__.__hmrId = ${JSON.stringify(scopeId)}
2205
2433
  if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') {
2206
2434
  __VUE_HMR_RUNTIME__.createRecord(__sfc__.__hmrId, __sfc__)
@@ -2214,17 +2442,34 @@ if (import.meta.hot) {
2214
2442
  }
2215
2443
  })
2216
2444
  }
2217
- `;
2445
+ `);
2446
+ }
2447
+ append("\nexport default __sfc__\n");
2448
+ const renderedOutput = outputNode.toStringWithSourceMap({ file: id });
2449
+ const output = renderedOutput.code;
2450
+ const outputMap = hasMappedOutput ? renderedOutput.map.toJSON() : void 0;
2451
+ if (transformedSfc.map != null && outputMap == null) {
2452
+ warnUnchainableMap(
2453
+ { filename: id, environmentName, type: "sfc" },
2454
+ "the compiled SFC output contained no chainable mappings"
2455
+ );
2218
2456
  }
2219
- output += `
2220
- export default __sfc__
2221
- `;
2222
2457
  const lang = descriptor.scriptSetup?.lang ?? descriptor.script?.lang;
2223
2458
  if (lang === "ts") {
2224
- const transpiled = transformCode(`${id}.ts`, output, { sourcemap: false });
2225
- return { code: transpiled.code };
2459
+ const transpiled = transformCode(`${id}.ts`, output, {
2460
+ sourcemap: wantsSourceMap,
2461
+ target: config.build.target
2462
+ });
2463
+ const transpiledMap = transpiled.map ? JSON.parse(transpiled.map) : void 0;
2464
+ return {
2465
+ code: transpiled.code,
2466
+ map: composeSourceMapChain(
2467
+ [transpiledMap, outputMap],
2468
+ { filename: id, environmentName, type: "sfc" }
2469
+ )
2470
+ };
2226
2471
  }
2227
- return { code: output };
2472
+ return { code: output, map: outputMap };
2228
2473
  },
2229
2474
  handleHotUpdate(ctx) {
2230
2475
  const { file, modules } = ctx;
@@ -2238,16 +2483,75 @@ export default __sfc__
2238
2483
  }
2239
2484
  };
2240
2485
  }
2486
+ async function applySourceTransform(transform2, source, context) {
2487
+ if (!transform2) return { code: source };
2488
+ const result = await transform2(source, context);
2489
+ return typeof result === "string" ? { code: result } : result;
2490
+ }
2491
+ function normalizeSourceMap(map, context) {
2492
+ if (map == null) return void 0;
2493
+ try {
2494
+ const value = typeof map === "string" ? JSON.parse(map) : map;
2495
+ if (value && typeof value === "object" && Array.isArray(value.sources) && Array.isArray(value.names) && typeof value.mappings === "string") {
2496
+ return value;
2497
+ }
2498
+ } catch {
2499
+ }
2500
+ warnUnchainableMap(context, "the provided map is not a valid source map");
2501
+ return void 0;
2502
+ }
2503
+ function composeSourceMapChain(maps, context) {
2504
+ const pending = maps.filter((map) => map != null);
2505
+ if (pending.length === 0) return void 0;
2506
+ let composed = normalizeSourceMap(pending.shift(), context);
2507
+ for (const map of pending) {
2508
+ const input = normalizeSourceMap(map, context);
2509
+ if (!input) continue;
2510
+ if (!composed) {
2511
+ composed = input;
2512
+ continue;
2513
+ }
2514
+ try {
2515
+ const consumer = new SourceMapConsumer(composed);
2516
+ if (consumer.sources.length !== 1) {
2517
+ warnUnchainableMap(
2518
+ context,
2519
+ "a generated map has multiple sources and cannot be chained safely"
2520
+ );
2521
+ continue;
2522
+ }
2523
+ const generator = SourceMapGenerator2.fromSourceMap(consumer);
2524
+ generator.applySourceMap(
2525
+ new SourceMapConsumer(input),
2526
+ consumer.sources[0]
2527
+ );
2528
+ composed = generator.toJSON();
2529
+ } catch (error) {
2530
+ warnUnchainableMap(
2531
+ context,
2532
+ `source-map composition failed: ${error instanceof Error ? error.message : String(error)}`
2533
+ );
2534
+ }
2535
+ }
2536
+ return composed;
2537
+ }
2538
+ function warnUnchainableMap(context, reason) {
2539
+ debug2?.(
2540
+ `source map warning for ${context.filename} (${context.type}, ${context.environmentName}): ${reason}`
2541
+ );
2542
+ }
2241
2543
  function hashId(filename) {
2242
2544
  return crypto2.createHash("sha256").update(filename).digest("hex").slice(0, 8);
2243
2545
  }
2244
- var VUE_FILE_RE, VUE_QUERY_RE, compiler;
2546
+ var VUE_FILE_RE, VUE_QUERY_RE, debug2, compiler;
2245
2547
  var init_vue = __esm({
2246
2548
  "src/plugins/vue.ts"() {
2247
2549
  "use strict";
2248
2550
  init_transformer();
2551
+ init_debug();
2249
2552
  VUE_FILE_RE = /\.vue$/;
2250
2553
  VUE_QUERY_RE = /\.vue\?vue&type=(script|template|style)(&index=\d+)?(&lang[.=]\w+)?/;
2554
+ debug2 = createDebugger("nasti:vue");
2251
2555
  compiler = null;
2252
2556
  }
2253
2557
  });
@@ -2351,7 +2655,7 @@ function resolvePluginList(config, userPlugins, opts = {}) {
2351
2655
  const consumer = opts.consumer ?? environmentOptions?.consumer;
2352
2656
  return [
2353
2657
  // vuePlugin 排最前(enforce: 'pre' 语义):.vue 先编译成 JS 再走后续管道
2354
- ...config.framework === "vue" ? [vuePlugin(pluginConfig)] : [],
2658
+ ...config.framework === "vue" ? [vuePlugin(pluginConfig, opts.environmentName ?? "client")] : [],
2355
2659
  resolvePlugin(pluginConfig),
2356
2660
  cssPlugin(pluginConfig, opts.cssEngine, consumer),
2357
2661
  assetsPlugin(pluginConfig),
@@ -2455,17 +2759,23 @@ var init_plugin_container = __esm({
2455
2759
  }
2456
2760
  async transform(code, id) {
2457
2761
  let currentCode = code;
2762
+ let lastResult;
2458
2763
  for (const plugin of this.plugins) {
2459
2764
  if (!plugin.transform) continue;
2460
2765
  const result = await plugin.transform.call(this.ctx, currentCode, id);
2461
2766
  if (result == null) continue;
2462
2767
  if (typeof result === "string") {
2463
2768
  currentCode = result;
2769
+ lastResult = void 0;
2464
2770
  } else {
2465
2771
  currentCode = result.code;
2772
+ lastResult = result;
2466
2773
  }
2467
2774
  }
2468
- return currentCode === code ? null : { code: currentCode };
2775
+ return currentCode === code ? null : {
2776
+ ...lastResult,
2777
+ code: currentCode
2778
+ };
2469
2779
  }
2470
2780
  /** 完整的模块处理管道: resolveId → load → transform */
2471
2781
  async processModule(source, importer) {
@@ -2512,9 +2822,13 @@ var init_module_graph = __esm({
2512
2822
  "use strict";
2513
2823
  init_url();
2514
2824
  ModuleGraph = class {
2825
+ environmentName;
2515
2826
  urlToModuleMap = /* @__PURE__ */ new Map();
2516
2827
  idToModuleMap = /* @__PURE__ */ new Map();
2517
2828
  fileToModulesMap = /* @__PURE__ */ new Map();
2829
+ constructor(environmentName = "client") {
2830
+ this.environmentName = environmentName;
2831
+ }
2518
2832
  getModuleByUrl(url) {
2519
2833
  return this.urlToModuleMap.get(removeTimestampQuery(url));
2520
2834
  }
@@ -2544,7 +2858,8 @@ var init_module_graph = __esm({
2544
2858
  transformResult: null,
2545
2859
  lastHMRTimestamp: 0,
2546
2860
  invalidationVersion: 0,
2547
- isSelfAccepting: false
2861
+ isSelfAccepting: false,
2862
+ environment: this.environmentName
2548
2863
  };
2549
2864
  this.idToModuleMap.set(mod.id, mod);
2550
2865
  return mod;
@@ -2702,12 +3017,12 @@ function createNoopHotChannel() {
2702
3017
  }
2703
3018
  };
2704
3019
  }
2705
- function createWsHotChannel(ws) {
3020
+ function createWsHotChannel(ws, environmentName = "client") {
2706
3021
  const listeners = /* @__PURE__ */ new Map();
2707
3022
  let invokeHandlers;
2708
3023
  return {
2709
3024
  send(payload) {
2710
- ws.send(payload);
3025
+ ws.send({ ...payload, environment: payload.environment ?? environmentName });
2711
3026
  },
2712
3027
  on(event, listener) {
2713
3028
  let set = listeners.get(event);
@@ -2719,8 +3034,8 @@ function createWsHotChannel(ws) {
2719
3034
  },
2720
3035
  listen() {
2721
3036
  },
3037
+ // 多个 environment 共享底层 WebSocket server;它由 DevServer.close() 统一关闭。
2722
3038
  close() {
2723
- ws.close();
2724
3039
  },
2725
3040
  setInvokeHandler(handlers) {
2726
3041
  invokeHandlers = handlers;
@@ -2749,7 +3064,7 @@ function resolveEnvironmentPlugins(environment, plugins) {
2749
3064
  }
2750
3065
  });
2751
3066
  }
2752
- var debug2, NastiEnvironment;
3067
+ var debug3, NastiEnvironment;
2753
3068
  var init_environment = __esm({
2754
3069
  "src/core/environment.ts"() {
2755
3070
  "use strict";
@@ -2758,7 +3073,7 @@ var init_environment = __esm({
2758
3073
  init_hot_channel();
2759
3074
  init_debug();
2760
3075
  init_plugin_api();
2761
- debug2 = createDebugger("nasti:environment");
3076
+ debug3 = createDebugger("nasti:environment");
2762
3077
  NastiEnvironment = class {
2763
3078
  name;
2764
3079
  consumer;
@@ -2776,6 +3091,9 @@ var init_environment = __esm({
2776
3091
  candidatePlugins;
2777
3092
  pluginApi;
2778
3093
  buildMetadata = {};
3094
+ cssModules = /* @__PURE__ */ new Map();
3095
+ assetModules = /* @__PURE__ */ new Map();
3096
+ transformRequestHandler;
2779
3097
  initialized = false;
2780
3098
  constructor(name, config, init = {}) {
2781
3099
  const options = config.environments[name];
@@ -2790,7 +3108,7 @@ var init_environment = __esm({
2790
3108
  this.config = config;
2791
3109
  this.options = options;
2792
3110
  this.hot = init.hot ?? createNoopHotChannel();
2793
- this.moduleGraph = new ModuleGraph();
3111
+ this.moduleGraph = new ModuleGraph(name);
2794
3112
  this.candidatePlugins = init.plugins ?? config.plugins;
2795
3113
  this.pluginApi = init.pluginApi ?? getPluginApi(config);
2796
3114
  }
@@ -2820,9 +3138,9 @@ var init_environment = __esm({
2820
3138
  );
2821
3139
  }
2822
3140
  this.driver = claimed[0].driver;
2823
- debug2?.(`env "${this.name}" uses driver "${this.driver.name}"`);
3141
+ debug3?.(`env "${this.name}" uses driver "${this.driver.name}"`);
2824
3142
  }
2825
- debug2?.(`env "${this.name}" initialized (${this.plugins.length} plugins)`);
3143
+ debug3?.(`env "${this.name}" initialized (${this.plugins.length} plugins)`);
2826
3144
  }
2827
3145
  getDriverContext() {
2828
3146
  return {
@@ -2832,6 +3150,37 @@ var init_environment = __esm({
2832
3150
  logger: this.config.logger
2833
3151
  };
2834
3152
  }
3153
+ configureDevPipeline(transformRequest2) {
3154
+ this.transformRequestHandler = transformRequest2;
3155
+ }
3156
+ async transformRequest(url) {
3157
+ if (!this.transformRequestHandler) {
3158
+ throw new Error(
3159
+ `[nasti] environment "${this.name}" does not have an initialized dev transform pipeline`
3160
+ );
3161
+ }
3162
+ return this.transformRequestHandler(url);
3163
+ }
3164
+ setCssModule(module) {
3165
+ this.cssModules.set(module.id, { ...module });
3166
+ }
3167
+ getCssModule(id) {
3168
+ const module = this.cssModules.get(id);
3169
+ return module ? { ...module } : void 0;
3170
+ }
3171
+ getCssModules() {
3172
+ return Object.freeze(
3173
+ Object.fromEntries(
3174
+ [...this.cssModules].map(([id, module]) => [id, { ...module }])
3175
+ )
3176
+ );
3177
+ }
3178
+ setAssetModule(id, fileName) {
3179
+ this.assetModules.set(id, fileName);
3180
+ }
3181
+ getAssetModules() {
3182
+ return Object.freeze(Object.fromEntries(this.assetModules));
3183
+ }
2835
3184
  setBuildMetadata(metadata) {
2836
3185
  const { entries: currentEntries, ...currentMetadata } = this.buildMetadata;
2837
3186
  const { entries, ...nextMetadata } = metadata;
@@ -2944,7 +3293,7 @@ async function tryNativeReporterPlugin(config, logger) {
2944
3293
  logInfo: (msg) => logger.info(msg)
2945
3294
  });
2946
3295
  } catch (err) {
2947
- debug3?.(`native viteReporterPlugin unavailable, falling back to JS table: ${err}`);
3296
+ debug4?.(`native viteReporterPlugin unavailable, falling back to JS table: ${err}`);
2948
3297
  return null;
2949
3298
  }
2950
3299
  }
@@ -2999,12 +3348,12 @@ function warnLargeChunks(output, config, logger) {
2999
3348
  )
3000
3349
  );
3001
3350
  }
3002
- var debug3, numberFormatter;
3351
+ var debug4, numberFormatter;
3003
3352
  var init_reporter = __esm({
3004
3353
  "src/build/reporter.ts"() {
3005
3354
  "use strict";
3006
3355
  init_debug();
3007
- debug3 = createDebugger("nasti:reporter");
3356
+ debug4 = createDebugger("nasti:reporter");
3008
3357
  numberFormatter = new Intl.NumberFormat("en", {
3009
3358
  maximumFractionDigits: 2,
3010
3359
  minimumFractionDigits: 2
@@ -3046,6 +3395,24 @@ function createBuildAppContext(config, results) {
3046
3395
  getManifest(environmentName) {
3047
3396
  return results[environmentName]?.manifest;
3048
3397
  },
3398
+ getChunk(environmentName, fileName) {
3399
+ const normalized = normalizeEnvironmentFileName(fileName);
3400
+ return results[environmentName]?.chunks?.[normalized];
3401
+ },
3402
+ getCss(environmentName) {
3403
+ return results[environmentName]?.css;
3404
+ },
3405
+ getSourceMap(environmentName, fileName) {
3406
+ const normalized = normalizeEnvironmentFileName(fileName);
3407
+ return results[environmentName]?.sourceMaps?.[normalized];
3408
+ },
3409
+ resolvePublicPath(environmentName, fileName) {
3410
+ const result = results[environmentName];
3411
+ if (!result) return void 0;
3412
+ const normalized = normalizeEnvironmentFileName(fileName);
3413
+ const base = result.publicPath ?? config.base;
3414
+ return joinPublicPath(base, normalized);
3415
+ },
3049
3416
  emitFile(file) {
3050
3417
  const fileName = normalizeAppFileName(file.fileName);
3051
3418
  const collisionKey = artifactCollisionKey(fileName);
@@ -3075,6 +3442,9 @@ function createBuildAppContext(config, results) {
3075
3442
  }
3076
3443
  };
3077
3444
  }
3445
+ function joinPublicPath(base, fileName) {
3446
+ return `${base.endsWith("/") ? base : `${base}/`}${fileName.replace(/^\//, "")}`;
3447
+ }
3078
3448
  function normalizeEnvironmentFileName(fileName) {
3079
3449
  return path9.posix.normalize(fileName.replace(/\\/g, "/").replace(/^\.\//, ""));
3080
3450
  }
@@ -3177,7 +3547,11 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
3177
3547
  const inputOptions = {
3178
3548
  ...restInputOptions,
3179
3549
  input: entryPoints,
3180
- transform: { ...userTransform, define: mergedDefine },
3550
+ transform: {
3551
+ ...userTransform,
3552
+ target: userTransform?.target ?? envOptions.build.target,
3553
+ define: mergedDefine
3554
+ },
3181
3555
  plugins: rolldownPlugins,
3182
3556
  // client/server consumer 都必须使用当前环境的 conditions/mainFields;Lynx
3183
3557
  // BG/MT 通常同为 client consumer,但仍可能声明不同的运行时条件。
@@ -3200,7 +3574,7 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
3200
3574
  };
3201
3575
  const outputOptions = isServer ? {
3202
3576
  format: "esm",
3203
- sourcemap: !!envOptions.build.sourcemap,
3577
+ sourcemap: envOptions.build.sourcemap,
3204
3578
  minify: !!envOptions.build.minify,
3205
3579
  entryFileNames: "[name].js",
3206
3580
  chunkFileNames: "chunks/[name]-[hash].js",
@@ -3209,7 +3583,7 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
3209
3583
  dir: outDir
3210
3584
  } : {
3211
3585
  format: "esm",
3212
- sourcemap: !!envOptions.build.sourcemap,
3586
+ sourcemap: envOptions.build.sourcemap,
3213
3587
  minify: !!envOptions.build.minify,
3214
3588
  entryFileNames: `${assetsDir}/[name].[hash].js`,
3215
3589
  chunkFileNames: `${assetsDir}/[name].[hash].js`,
@@ -3284,13 +3658,68 @@ function finalizeEnvironmentResult(environment, result) {
3284
3658
  return [name, normalized];
3285
3659
  })
3286
3660
  );
3661
+ const inferredMetadata = inferOutputMetadata(environment, result.output);
3287
3662
  return {
3663
+ publicPath: environment.config.base,
3664
+ ...inferredMetadata,
3288
3665
  ...metadata,
3289
3666
  ...result,
3290
3667
  output: result.output,
3668
+ chunks: {
3669
+ ...inferredMetadata.chunks,
3670
+ ...metadata.chunks,
3671
+ ...result.chunks
3672
+ },
3673
+ assets: {
3674
+ ...inferredMetadata.assets,
3675
+ ...metadata.assets,
3676
+ ...result.assets
3677
+ },
3678
+ sourceMaps: {
3679
+ ...inferredMetadata.sourceMaps,
3680
+ ...metadata.sourceMaps,
3681
+ ...result.sourceMaps
3682
+ },
3291
3683
  ...Object.keys(normalizedEntries).length > 0 ? { entries: normalizedEntries } : {}
3292
3684
  };
3293
3685
  }
3686
+ function inferOutputMetadata(environment, output) {
3687
+ const chunks = {};
3688
+ const assets = {};
3689
+ const sourceMaps = {};
3690
+ const cssChunks = environment.getBuildMetadata().css?.chunks ?? {};
3691
+ const assetModules = environment.getAssetModules();
3692
+ const publicPath = environment.config.base;
3693
+ for (const artifact of output) {
3694
+ const fileName = normalizeEnvironmentFileName(artifact.fileName);
3695
+ if (artifact.map != null) sourceMaps[fileName] = artifact.map;
3696
+ if (artifact.type === "chunk") {
3697
+ const moduleIds = [...artifact.moduleIds ?? []];
3698
+ chunks[fileName] = {
3699
+ fileName,
3700
+ name: artifact.name ?? fileName,
3701
+ isEntry: !!artifact.isEntry,
3702
+ isDynamicEntry: !!artifact.isDynamicEntry,
3703
+ imports: [...artifact.imports ?? []],
3704
+ dynamicImports: [...artifact.dynamicImports ?? []],
3705
+ moduleIds,
3706
+ css: [...cssChunks[fileName]?.cssFileNames ?? []],
3707
+ assets: [
3708
+ ...new Set(
3709
+ moduleIds.map((id) => assetModules[id]).filter((asset) => !!asset)
3710
+ )
3711
+ ]
3712
+ };
3713
+ } else if (artifact.type === "asset") {
3714
+ assets[fileName] = {
3715
+ fileName,
3716
+ names: [...artifact.names ?? (artifact.name ? [artifact.name] : [])],
3717
+ publicPath: joinPublicPath(publicPath, fileName)
3718
+ };
3719
+ }
3720
+ }
3721
+ return { chunks, assets, sourceMaps };
3722
+ }
3294
3723
  function prepareBuildOutputDirectories(config, buildableNames) {
3295
3724
  const directories = /* @__PURE__ */ new Set();
3296
3725
  const protectedPaths = /* @__PURE__ */ new Set();
@@ -3368,6 +3797,7 @@ function createOxcTransformPlugin(config, environment) {
3368
3797
  if (!shouldTransform(id)) return null;
3369
3798
  const result = transformCode(id, code, {
3370
3799
  sourcemap: !!environment.options.build.sourcemap,
3800
+ target: environment.options.build.target,
3371
3801
  jsxRuntime: "automatic",
3372
3802
  jsxImportSource: config.framework === "vue" ? "vue" : "react"
3373
3803
  });
@@ -3381,9 +3811,9 @@ async function build(inlineConfig = {}) {
3381
3811
  const startTime = performance.now();
3382
3812
  logger.info(
3383
3813
  pc4.cyan(`
3384
- nasti v${"2.4.0"} `) + pc4.green(`building for ${config.mode}...`)
3814
+ nasti v${"2.4.2"} `) + pc4.green(`building for ${config.mode}...`)
3385
3815
  );
3386
- debug4?.(`root: ${config.root}`);
3816
+ debug5?.(`root: ${config.root}`);
3387
3817
  const buildableNames = Object.keys(config.environments).filter((name) => {
3388
3818
  const environment = config.environments[name];
3389
3819
  if (!environment.buildEnabled) return false;
@@ -3405,7 +3835,7 @@ nasti v${"2.4.0"} `) + pc4.green(`building for ${config.mode}...`)
3405
3835
  environmentResults[name] = built.result;
3406
3836
  if (name === "client") clientOutput = built.result.output;
3407
3837
  if (buildableNames.length > 1) {
3408
- debug4?.(`environment "${name}" built (${built.result.output.length} files)`);
3838
+ debug5?.(`environment "${name}" built (${built.result.output.length} files)`);
3409
3839
  }
3410
3840
  }
3411
3841
  const pluginApi = getPluginApi(config);
@@ -3501,6 +3931,7 @@ async function buildClientEnvironment(config) {
3501
3931
  const bundle2 = await rolldown(inputOptions);
3502
3932
  const { output } = await bundle2.write(outputOptions);
3503
3933
  await bundle2.close();
3934
+ clientEnv.setBuildMetadata({ css: getCssMetadata(cssEngine) });
3504
3935
  if (html) {
3505
3936
  let processedHtml = html;
3506
3937
  const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
@@ -3514,7 +3945,9 @@ async function buildClientEnvironment(config) {
3514
3945
  processedHtml = processHtml(processedHtml, result);
3515
3946
  }
3516
3947
  }
3517
- processedHtml = injectCssLinks(processedHtml, cssEngine, config);
3948
+ if (clientEnv.options.build.css.inject !== false) {
3949
+ processedHtml = injectCssLinks(processedHtml, cssEngine, config);
3950
+ }
3518
3951
  for (const chunk of output) {
3519
3952
  if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
3520
3953
  processedHtml = replaceEntryScript(
@@ -3552,9 +3985,11 @@ async function buildClientEnvironment(config) {
3552
3985
  async function buildServerEnvironment(config, name) {
3553
3986
  const envOptions = config.environments[name];
3554
3987
  const logger = config.logger;
3988
+ const cssEngine = envOptions.consumer === "client" ? createCssEngine() : void 0;
3555
3989
  const pluginList = resolvePluginList(config, config.plugins, {
3556
3990
  consumer: envOptions.consumer,
3557
- environmentName: name
3991
+ environmentName: name,
3992
+ cssEngine
3558
3993
  });
3559
3994
  const environment = new NastiEnvironment(name, config, {
3560
3995
  mode: "build",
@@ -3597,6 +4032,7 @@ async function buildServerEnvironment(config, name) {
3597
4032
  const bundle2 = await rolldown(inputOptions);
3598
4033
  const { output } = await bundle2.write(outputOptions);
3599
4034
  await bundle2.close();
4035
+ if (cssEngine) environment.setBuildMetadata({ css: getCssMetadata(cssEngine) });
3600
4036
  logger.info(
3601
4037
  pc4.dim(` [${name}] `) + output.map((o) => path10.join(envOptions.build.outDir, o.fileName)).join(pc4.dim(", "))
3602
4038
  );
@@ -3648,7 +4084,7 @@ function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, ur
3648
4084
  }
3649
4085
  return processed;
3650
4086
  }
3651
- var debug4, NODE_BUILTINS;
4087
+ var debug5, NODE_BUILTINS;
3652
4088
  var init_build = __esm({
3653
4089
  "src/build/index.ts"() {
3654
4090
  "use strict";
@@ -3663,7 +4099,7 @@ var init_build = __esm({
3663
4099
  init_debug();
3664
4100
  init_plugin_api();
3665
4101
  init_build_app_context();
3666
- debug4 = createDebugger("nasti:build");
4102
+ debug5 = createDebugger("nasti:build");
3667
4103
  NODE_BUILTINS = /* @__PURE__ */ new Set([...builtinModules, ...builtinModules.map((m) => `node:${m}`)]);
3668
4104
  }
3669
4105
  });
@@ -3713,13 +4149,6 @@ var init_ws = __esm({
3713
4149
  });
3714
4150
 
3715
4151
  // src/server/middleware.ts
3716
- var middleware_exports = {};
3717
- __export(middleware_exports, {
3718
- REACT_REFRESH_GLOBAL_PREAMBLE: () => REACT_REFRESH_GLOBAL_PREAMBLE,
3719
- getReactRefreshRuntimeEsm: () => getReactRefreshRuntimeEsm,
3720
- transformMiddleware: () => transformMiddleware,
3721
- transformRequest: () => transformRequest
3722
- });
3723
4152
  import path12 from "path";
3724
4153
  import fs9 from "fs";
3725
4154
  import { createRequire as createRequire3 } from "module";
@@ -3827,7 +4256,8 @@ const __nasti_hot__ = __nasti_createHotContext__(${urlLit});
3827
4256
  function transformMiddleware(ctx) {
3828
4257
  ctx.envDefine = buildEnvDefine(
3829
4258
  loadEnv(ctx.config.mode, ctx.config.root, ctx.config.envPrefix),
3830
- ctx.config.mode
4259
+ ctx.config.mode,
4260
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
3831
4261
  );
3832
4262
  return async (req, res, next) => {
3833
4263
  const url = req.url ?? "/";
@@ -3872,7 +4302,7 @@ function transformMiddleware(ctx) {
3872
4302
  return;
3873
4303
  }
3874
4304
  }
3875
- if (isModuleRequest(url)) {
4305
+ if (isModuleRequest(url, req.headers["sec-fetch-dest"])) {
3876
4306
  try {
3877
4307
  const result = await transformRequest(url, ctx);
3878
4308
  if (result) {
@@ -3943,12 +4373,16 @@ async function transformRequest(url, ctx) {
3943
4373
  if (rawQuery && !/^t=\d+$/.test(rawQuery)) {
3944
4374
  const mod2 = await moduleGraph.ensureEntryFromUrl(url);
3945
4375
  const transformVersion2 = mod2.invalidationVersion;
3946
- const loaded = await pluginContainer.load(url);
3947
- if (loaded != null) {
3948
- let code2 = typeof loaded === "string" ? loaded : loaded.code;
4376
+ const loaded2 = await pluginContainer.load(url);
4377
+ if (loaded2 != null) {
4378
+ let code2 = typeof loaded2 === "string" ? loaded2 : loaded2.code;
4379
+ let map2 = typeof loaded2 === "string" ? void 0 : loaded2.map;
3949
4380
  const transformed = await pluginContainer.transform(code2, url);
3950
4381
  if (transformed != null) {
3951
4382
  code2 = typeof transformed === "string" ? transformed : transformed.code;
4383
+ if (typeof transformed !== "string" && transformed.map != null) {
4384
+ map2 = transformed.map;
4385
+ }
3952
4386
  }
3953
4387
  const parentFile = resolveUrlToFile(cleanReqUrl, config.root) ?? cleanReqUrl;
3954
4388
  moduleGraph.registerModule(mod2, parentFile);
@@ -3956,7 +4390,8 @@ async function transformRequest(url, ctx) {
3956
4390
  code2 = injectImportMetaHot(hotInfo2.code, url);
3957
4391
  code2 = replaceEnvInCode(code2, ctx.envDefine ?? buildEnvDefine(
3958
4392
  loadEnv(config.mode, config.root, config.envPrefix),
3959
- config.mode
4393
+ config.mode,
4394
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
3960
4395
  ));
3961
4396
  const importedUrls2 = /* @__PURE__ */ new Set();
3962
4397
  code2 = rewriteImports(code2, config, parentFile, importedUrls2, moduleGraph);
@@ -3967,7 +4402,7 @@ async function transformRequest(url, ctx) {
3967
4402
  hotInfo2.isSelfAccepting,
3968
4403
  transformVersion2
3969
4404
  );
3970
- const transformResult2 = { code: code2 };
4405
+ const transformResult2 = { code: code2, map: map2 };
3971
4406
  if (pruned2) {
3972
4407
  if (pruned2.size > 0) ctx.onPrune?.([...pruned2].map((item) => item.url));
3973
4408
  mod2.transformResult = transformResult2;
@@ -3986,10 +4421,13 @@ async function transformRequest(url, ctx) {
3986
4421
  mod.transformResult = transformResult2;
3987
4422
  return transformResult2;
3988
4423
  }
3989
- let code = fs9.readFileSync(filePath, "utf-8");
4424
+ const loaded = await pluginContainer.load(filePath);
4425
+ let code = loaded == null ? fs9.readFileSync(filePath, "utf-8") : typeof loaded === "string" ? loaded : loaded.code;
4426
+ let map = loaded && typeof loaded !== "string" ? loaded.map : void 0;
3990
4427
  const pluginResult = await pluginContainer.transform(code, filePath);
3991
4428
  if (pluginResult) {
3992
4429
  code = typeof pluginResult === "string" ? pluginResult : pluginResult.code;
4430
+ if (typeof pluginResult !== "string") map = pluginResult.map;
3993
4431
  }
3994
4432
  const stableUrl = cleanReqUrl;
3995
4433
  let wrappedWithRefresh = false;
@@ -4000,9 +4438,11 @@ async function transformRequest(url, ctx) {
4000
4438
  sourcemap: true,
4001
4439
  jsxRuntime: "automatic",
4002
4440
  jsxImportSource: config.framework === "vue" ? "vue" : "react",
4003
- reactRefresh: useRefresh
4441
+ reactRefresh: useRefresh,
4442
+ target: ctx.environment?.options.build.target ?? config.build.target
4004
4443
  });
4005
4444
  code = result.code;
4445
+ if (result.map) map = JSON.parse(result.map);
4006
4446
  if (useRefresh) {
4007
4447
  code = buildReactRefreshWrapper(stableUrl, code);
4008
4448
  wrappedWithRefresh = true;
@@ -4015,7 +4455,8 @@ async function transformRequest(url, ctx) {
4015
4455
  }
4016
4456
  const envDefine = ctx.envDefine ?? buildEnvDefine(
4017
4457
  loadEnv(config.mode, config.root, config.envPrefix),
4018
- config.mode
4458
+ config.mode,
4459
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
4019
4460
  );
4020
4461
  code = replaceEnvInCode(code, envDefine);
4021
4462
  const importedUrls = /* @__PURE__ */ new Set();
@@ -4027,7 +4468,7 @@ async function transformRequest(url, ctx) {
4027
4468
  wrappedWithRefresh || hotInfo.isSelfAccepting,
4028
4469
  transformVersion
4029
4470
  );
4030
- const transformResult = { code };
4471
+ const transformResult = { code, map };
4031
4472
  if (pruned) {
4032
4473
  if (pruned.size > 0) ctx.onPrune?.([...pruned].map((item) => item.url));
4033
4474
  mod.transformResult = transformResult;
@@ -4050,7 +4491,8 @@ async function loadVirtualModule(spec, ctx) {
4050
4491
  }
4051
4492
  code = replaceEnvInCode(code, ctx.envDefine ?? buildEnvDefine(
4052
4493
  loadEnv(config.mode, config.root, config.envPrefix),
4053
- config.mode
4494
+ config.mode,
4495
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
4054
4496
  ));
4055
4497
  const anchor = path12.join(config.root, "__nasti_virtual__.ts");
4056
4498
  code = rewriteImports(code, config, anchor);
@@ -4595,10 +5037,15 @@ function resolveUrlToFile(url, root) {
4595
5037
  }
4596
5038
  return null;
4597
5039
  }
4598
- function isModuleRequest(url) {
5040
+ function isModuleRequest(url, destination) {
4599
5041
  const cleanUrl = url.split("?")[0];
4600
5042
  if (/\.(ts|tsx|jsx|js|mjs|vue|css|json)$/.test(cleanUrl)) return true;
4601
5043
  if (cleanUrl.startsWith("/@modules/")) return true;
5044
+ if (isAssetFile(cleanUrl)) {
5045
+ const query = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
5046
+ const isExplicitAssetModule = /(?:^|&)(?:url|raw)(?:&|$)/.test(query);
5047
+ return isExplicitAssetModule || destination === "script";
5048
+ }
4602
5049
  if (!path12.extname(cleanUrl)) return true;
4603
5050
  return false;
4604
5051
  }
@@ -4611,11 +5058,15 @@ const hotModulesMap = new Map();
4611
5058
  const disposeMap = new Map();
4612
5059
  const pruneMap = new Map();
4613
5060
  const dataMap = new Map();
5061
+ const customListenersMap = new Map();
4614
5062
  let updateQueue = [];
4615
5063
  let pendingUpdateQueue = false;
4616
5064
 
4617
5065
  socket.addEventListener('message', async ({ data }) => {
4618
5066
  const payload = JSON.parse(data);
5067
+ // \u9ED8\u8BA4\u6D4F\u89C8\u5668 client \u53EA\u6D88\u8D39\u81EA\u5DF1\u7684 HMR \u6D88\u606F\uFF1Bnative/worker \u73AF\u5883\u901A\u8FC7\u5404\u81EA\u7684
5068
+ // HotChannel \u6216 app-level HMR \u534F\u8C03\u5668\u5904\u7406\u540C\u4E00 transport \u4E0A\u7684\u547D\u540D\u6D88\u606F\u3002
5069
+ if (payload.environment && payload.environment !== 'client') return;
4619
5070
  switch (payload.type) {
4620
5071
  case 'connected':
4621
5072
  console.debug('[nasti] connected.');
@@ -4648,8 +5099,24 @@ socket.addEventListener('message', async ({ data }) => {
4648
5099
  disposeMap.delete(path);
4649
5100
  pruneMap.delete(path);
4650
5101
  dataMap.delete(path);
5102
+ clearCustomListeners(path);
4651
5103
  }));
4652
5104
  break;
5105
+ case 'custom': {
5106
+ const listenersByOwner = customListenersMap.get(payload.event);
5107
+ if (!listenersByOwner) break;
5108
+ const results = await Promise.allSettled(
5109
+ [...listenersByOwner.values()]
5110
+ .flatMap((listeners) => [...listeners])
5111
+ .map((listener) => Promise.resolve().then(() => listener(payload.data)))
5112
+ );
5113
+ for (const result of results) {
5114
+ if (result.status === 'rejected') {
5115
+ console.error('[nasti] custom HMR event listener failed:', result.reason);
5116
+ }
5117
+ }
5118
+ break;
5119
+ }
4653
5120
  case 'error':
4654
5121
  console.error('[nasti] error:', payload.err.message);
4655
5122
  showErrorOverlay(payload.err);
@@ -4748,6 +5215,7 @@ export function createHotContext(ownerPath) {
4748
5215
  // \u6A21\u5757\u91CD\u65B0\u6267\u884C\u65F6\u4E22\u5F03\u65E7 accept \u56DE\u8C03\uFF0C\u4F46\u4FDD\u7559\u540C\u4E00\u4E2A hot.data \u5BF9\u8C61\u3002
4749
5216
  const existing = hotModulesMap.get(ownerPath);
4750
5217
  if (existing) existing.callbacks = [];
5218
+ clearCustomListeners(ownerPath);
4751
5219
 
4752
5220
  const acceptDeps = (deps, callback = () => {}) => {
4753
5221
  const mod = hotModulesMap.get(ownerPath) || { id: ownerPath, callbacks: [] };
@@ -4773,12 +5241,39 @@ export function createHotContext(ownerPath) {
4773
5241
  dispose(callback) {
4774
5242
  disposeMap.set(ownerPath, callback);
4775
5243
  },
5244
+ on(event, callback) {
5245
+ let listenersByOwner = customListenersMap.get(event);
5246
+ if (!listenersByOwner) {
5247
+ listenersByOwner = new Map();
5248
+ customListenersMap.set(event, listenersByOwner);
5249
+ }
5250
+ let listeners = listenersByOwner.get(ownerPath);
5251
+ if (!listeners) {
5252
+ listeners = new Set();
5253
+ listenersByOwner.set(ownerPath, listeners);
5254
+ }
5255
+ listeners.add(callback);
5256
+ },
5257
+ off(event, callback) {
5258
+ const listenersByOwner = customListenersMap.get(event);
5259
+ const listeners = listenersByOwner?.get(ownerPath);
5260
+ listeners?.delete(callback);
5261
+ if (listeners?.size === 0) listenersByOwner.delete(ownerPath);
5262
+ if (listenersByOwner?.size === 0) customListenersMap.delete(event);
5263
+ },
4776
5264
  invalidate() {
4777
5265
  location.reload();
4778
5266
  },
4779
5267
  data: dataMap.get(ownerPath),
4780
5268
  };
4781
5269
  }
5270
+
5271
+ function clearCustomListeners(ownerPath) {
5272
+ for (const [event, listenersByOwner] of customListenersMap) {
5273
+ listenersByOwner.delete(ownerPath);
5274
+ if (listenersByOwner.size === 0) customListenersMap.delete(event);
5275
+ }
5276
+ }
4782
5277
  `;
4783
5278
  }
4784
5279
  var __dirname_esm, __require2, __refreshRuntimeCache, REACT_REFRESH_BOUNDARY_HELPERS, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
@@ -4789,6 +5284,7 @@ var init_middleware = __esm({
4789
5284
  init_html();
4790
5285
  init_env();
4791
5286
  init_url();
5287
+ init_assets();
4792
5288
  __dirname_esm = path12.dirname(fileURLToPath(import.meta.url));
4793
5289
  __require2 = createRequire3(import.meta.url);
4794
5290
  __refreshRuntimeCache = null;
@@ -4873,19 +5369,25 @@ window.__vite_plugin_react_preamble_installed__ = true;
4873
5369
  import path13 from "path";
4874
5370
  import fs10 from "fs";
4875
5371
  import pc7 from "picocolors";
4876
- async function handleFileChange(file, server) {
4877
- const { moduleGraph, ws, config } = server;
5372
+ async function handleFileChange(file, server, environmentName = "client", timestamp = Date.now()) {
5373
+ const { config } = server;
5374
+ const environment = server.environments[environmentName];
5375
+ if (!environment) {
5376
+ throw new Error(`[nasti] unknown dev environment "${environmentName}"`);
5377
+ }
5378
+ const moduleGraph = environment.moduleGraph;
4878
5379
  const logger = config.logger;
4879
5380
  const relativePath = "/" + path13.relative(config.root, file);
4880
5381
  const shortFile = path13.relative(config.root, file);
4881
5382
  const mods = moduleGraph.getModulesByFile(file);
4882
5383
  if (!mods || mods.size === 0) {
4883
- return;
5384
+ return null;
4884
5385
  }
4885
5386
  const updates = [];
4886
- const timestamp = Date.now();
4887
5387
  const graph = moduleGraph;
4888
5388
  const invalidatedModules = /* @__PURE__ */ new Set();
5389
+ const affectedSet = /* @__PURE__ */ new Set();
5390
+ let fullReload = false;
4889
5391
  for (const mod of mods) {
4890
5392
  graph.invalidateModuleAndImporters(mod, timestamp, invalidatedModules);
4891
5393
  const ctx = {
@@ -4893,10 +5395,11 @@ async function handleFileChange(file, server) {
4893
5395
  timestamp,
4894
5396
  modules: [mod],
4895
5397
  read: () => fs10.readFileSync(file, "utf-8"),
4896
- server
5398
+ server,
5399
+ environment
4897
5400
  };
4898
5401
  let affectedModules = [mod];
4899
- for (const plugin of config.plugins) {
5402
+ for (const plugin of environment.plugins) {
4900
5403
  if (plugin.handleHotUpdate) {
4901
5404
  const result = await plugin.handleHotUpdate(ctx);
4902
5405
  if (result) {
@@ -4905,12 +5408,12 @@ async function handleFileChange(file, server) {
4905
5408
  }
4906
5409
  }
4907
5410
  for (const affected of affectedModules) {
5411
+ affectedSet.add(affected);
4908
5412
  graph.invalidateModuleAndImporters(affected, timestamp, invalidatedModules);
4909
5413
  const boundaries = graph.getHmrBoundaries(affected);
4910
5414
  if (boundaries.length === 0) {
4911
- logger.info(pc7.green("page reload ") + pc7.dim(shortFile), { timestamp: true });
4912
- ws.send({ type: "full-reload", path: relativePath });
4913
- return;
5415
+ fullReload = true;
5416
+ continue;
4914
5417
  }
4915
5418
  for (const { boundary, acceptedVia } of boundaries) {
4916
5419
  const update = {
@@ -4927,13 +5430,30 @@ async function handleFileChange(file, server) {
4927
5430
  }
4928
5431
  }
4929
5432
  }
4930
- if (updates.length > 0) {
5433
+ const transformed = await Promise.all(
5434
+ [...affectedSet].map(async (module) => ({
5435
+ module,
5436
+ result: await environment.transformRequest(module.url)
5437
+ }))
5438
+ );
5439
+ const logPrefix = environmentName === "client" ? "" : `[${environmentName}] `;
5440
+ if (fullReload) {
5441
+ logger.info(pc7.green(`${logPrefix}reload `) + pc7.dim(shortFile), { timestamp: true });
5442
+ environment.hot.send({ type: "full-reload", path: relativePath });
5443
+ } else if (updates.length > 0) {
4931
5444
  logger.info(
4932
- updates.map((u) => pc7.green("hmr update ") + pc7.dim(u.path)).join("\n"),
5445
+ updates.map((u) => pc7.green(`${logPrefix}hmr update `) + pc7.dim(u.path)).join("\n"),
4933
5446
  { timestamp: true }
4934
5447
  );
4935
- ws.send({ type: "update", updates });
5448
+ environment.hot.send({ type: "update", updates });
4936
5449
  }
5450
+ return {
5451
+ environment,
5452
+ modules: [...affectedSet],
5453
+ updates,
5454
+ transformed,
5455
+ fullReload
5456
+ };
4937
5457
  }
4938
5458
  var init_hmr = __esm({
4939
5459
  "src/server/hmr.ts"() {
@@ -4959,14 +5479,14 @@ function createModuleRunner(environment) {
4959
5479
  }
4960
5480
  return new NastiModuleRunner(environment);
4961
5481
  }
4962
- var debug5, NODE_BUILTINS3, NastiModuleRunner, AsyncFunction;
5482
+ var debug6, NODE_BUILTINS3, NastiModuleRunner, AsyncFunction;
4963
5483
  var init_runnable_environment = __esm({
4964
5484
  "src/server/runnable-environment.ts"() {
4965
5485
  "use strict";
4966
5486
  init_transformer();
4967
5487
  init_env();
4968
5488
  init_debug();
4969
- debug5 = createDebugger("nasti:ssr");
5489
+ debug6 = createDebugger("nasti:ssr");
4970
5490
  NODE_BUILTINS3 = /* @__PURE__ */ new Set([...builtinModules3, ...builtinModules3.map((m) => `node:${m}`)]);
4971
5491
  NastiModuleRunner = class {
4972
5492
  environment;
@@ -5044,6 +5564,7 @@ var init_runnable_environment = __esm({
5044
5564
  if (shouldTransform(cleanId)) {
5045
5565
  const result = transformCode(cleanId, code, {
5046
5566
  sourcemap: false,
5567
+ target: this.environment.options.build.target,
5047
5568
  jsxRuntime: "automatic",
5048
5569
  jsxImportSource: this.config.framework === "vue" ? "vue" : "react"
5049
5570
  });
@@ -5060,7 +5581,7 @@ var init_runnable_environment = __esm({
5060
5581
  );
5061
5582
  }
5062
5583
  const runnerResult = await moduleRunnerTransform(resolvedId, code);
5063
- debug5?.(`fetchModule ${resolvedId} (${runnerResult.deps?.length ?? 0} deps)`);
5584
+ debug6?.(`fetchModule ${resolvedId} (${runnerResult.deps?.length ?? 0} deps)`);
5064
5585
  return { id: resolvedId, code: runnerResult.code };
5065
5586
  }
5066
5587
  completeExtension(id) {
@@ -5184,7 +5705,7 @@ async function createBundledDevServer(opts) {
5184
5705
  }
5185
5706
  } catch (err) {
5186
5707
  throw new Error(
5187
- `[nasti] experimental.bundledDev requires rolldown's experimental dev() API (locked to the installed rc; got: ${err.message}). Remove --bundle / experimental.bundledDev to use the default unbundled dev server.`
5708
+ `[nasti] experimental.bundledDev requires rolldown's experimental dev() API (locked version is incompatible; got: ${err.message}). Remove --bundle / experimental.bundledDev to use the default unbundled dev server.`
5188
5709
  );
5189
5710
  }
5190
5711
  const html = await readHtmlFile(config.root, config.environments.client?.html);
@@ -5237,7 +5758,7 @@ async function createBundledDevServer(opts) {
5237
5758
  for (const { clientId, update } of updates) {
5238
5759
  if (update.type === "Noop") continue;
5239
5760
  if (update.type === "FullReload") {
5240
- debug6?.(`full reload for ${clientId}: ${update.reason ?? ""}`);
5761
+ debug7?.(`full reload for ${clientId}: ${update.reason ?? ""}`);
5241
5762
  needsLatestOutput = true;
5242
5763
  continue;
5243
5764
  }
@@ -5287,7 +5808,7 @@ async function createBundledDevServer(opts) {
5287
5808
  },
5288
5809
  {
5289
5810
  watch: { skipWrite: true },
5290
- rebuildStrategy: "auto",
5811
+ rebuildStrategy: "never",
5291
5812
  onOutput(result) {
5292
5813
  if (result instanceof Error) {
5293
5814
  logger.error(pc8.red(`[bundled] build error: ${result.message}`), { error: result });
@@ -5303,7 +5824,13 @@ async function createBundledDevServer(opts) {
5303
5824
  memoryFiles.set(`${file.fileName}.map`, JSON.stringify(file.map));
5304
5825
  }
5305
5826
  }
5306
- debug6?.(`bundle output refreshed (${result.output.length} files)`);
5827
+ debug7?.(`bundle output refreshed (${result.output.length} files)`);
5828
+ },
5829
+ onAdditionalAssets(result) {
5830
+ for (const file of result.output) {
5831
+ const content = file.type === "chunk" ? file.code : file.source;
5832
+ if (content != null) memoryFiles.set(file.fileName, content);
5833
+ }
5307
5834
  },
5308
5835
  async onHmrUpdates(result) {
5309
5836
  if (result instanceof Error) {
@@ -5312,7 +5839,7 @@ async function createBundledDevServer(opts) {
5312
5839
  return;
5313
5840
  }
5314
5841
  const { updates, changedFiles } = result;
5315
- debug6?.(
5842
+ debug7?.(
5316
5843
  `onHmrUpdates(engine watcher): ${changedFiles.length} changed, ${updates.length} updates`
5317
5844
  );
5318
5845
  if (changedFiles.length === 0) return;
@@ -5331,24 +5858,29 @@ async function createBundledDevServer(opts) {
5331
5858
  if (!clientId) return;
5332
5859
  wss.handleUpgrade(req, socket, head, (ws) => {
5333
5860
  bundledClients.set(clientId, ws);
5334
- debug6?.(`bundled client connected: ${clientId}`);
5335
- ws.send(JSON.stringify({ type: "connected" }));
5861
+ debug7?.(`bundled client connected: ${clientId}`);
5862
+ void engine.registerClient(clientId).then(async () => {
5863
+ for (const fileName of entryFileNames.values()) {
5864
+ await engine.notifyPayloadDelivered(fileName);
5865
+ }
5866
+ ws.send(JSON.stringify({ type: "connected" }));
5867
+ }).catch((err) => {
5868
+ debug7?.(`registerClient failed for ${clientId}: ${err?.message ?? err}`);
5869
+ ws.close();
5870
+ });
5336
5871
  ws.on("message", async (raw) => {
5337
5872
  try {
5338
5873
  const msg = JSON.parse(String(raw));
5339
- if (msg.type === "hmr:module-registered" && Array.isArray(msg.modules)) {
5340
- await engine.registerModules(clientId, msg.modules);
5341
- debug6?.(`registered ${msg.modules.length} modules for ${clientId}`);
5342
- } else if (msg.type === "hmr:invalidate") {
5874
+ if (msg.type === "hmr:invalidate") {
5343
5875
  scheduleFullReload();
5344
5876
  }
5345
5877
  } catch (err) {
5346
- debug6?.(`bundled ws message error: ${err.message}`);
5878
+ debug7?.(`bundled ws message error: ${err.message}`);
5347
5879
  }
5348
5880
  });
5349
5881
  ws.on("close", () => {
5350
5882
  bundledClients.delete(clientId);
5351
- engine.removeClient(clientId).catch((err) => debug6?.(`removeClient failed for ${clientId}: ${err?.message ?? err}`));
5883
+ engine.removeClient(clientId).catch((err) => debug7?.(`removeClient failed for ${clientId}: ${err?.message ?? err}`));
5352
5884
  });
5353
5885
  });
5354
5886
  });
@@ -5365,10 +5897,18 @@ async function createBundledDevServer(opts) {
5365
5897
  res.end("// [nasti] lazy endpoint requires id & clientId");
5366
5898
  return;
5367
5899
  }
5368
- const code = await engine.compileEntry(id, clientId);
5900
+ const output = await engine.compileEntry(id, clientId);
5901
+ if (output.sourcemap && output.sourcemapFilename) {
5902
+ memoryFiles.set(output.sourcemapFilename, output.sourcemap);
5903
+ }
5904
+ res.once("finish", () => {
5905
+ void engine.notifyPayloadDelivered(output.filename).catch(
5906
+ (err) => debug7?.(`notifyPayloadDelivered failed: ${err?.message ?? err}`)
5907
+ );
5908
+ });
5369
5909
  res.setHeader("Content-Type", "application/javascript");
5370
5910
  res.setHeader("Cache-Control", "no-store");
5371
- res.end(code + "\n;export {}");
5911
+ res.end(output.code + "\n;export {}");
5372
5912
  return;
5373
5913
  }
5374
5914
  const patchHit = patches.get(pathname.replace(/^\//, ""));
@@ -5389,6 +5929,11 @@ async function createBundledDevServer(opts) {
5389
5929
  res.setHeader("ETag", hit.etag);
5390
5930
  res.setHeader("Content-Type", MIME_TYPES[path15.extname(fileName)] ?? "application/octet-stream");
5391
5931
  res.setHeader("Cache-Control", "no-cache");
5932
+ res.once("finish", () => {
5933
+ void engine.notifyPayloadDelivered(fileName).catch(
5934
+ (err) => debug7?.(`notifyPayloadDelivered failed: ${err?.message ?? err}`)
5935
+ );
5936
+ });
5392
5937
  res.end(hit.content);
5393
5938
  return;
5394
5939
  }
@@ -5488,7 +6033,7 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
5488
6033
  }
5489
6034
  return processed;
5490
6035
  }
5491
- var debug6, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
6036
+ var debug7, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
5492
6037
  var init_dev_engine = __esm({
5493
6038
  "src/server/bundled/dev-engine.ts"() {
5494
6039
  "use strict";
@@ -5497,7 +6042,7 @@ var init_dev_engine = __esm({
5497
6042
  init_transformer();
5498
6043
  init_middleware();
5499
6044
  init_debug();
5500
- debug6 = createDebugger("nasti:bundled");
6045
+ debug7 = createDebugger("nasti:bundled");
5501
6046
  MIME_TYPES = {
5502
6047
  ".js": "application/javascript",
5503
6048
  ".mjs": "application/javascript",
@@ -5631,7 +6176,7 @@ async function createServer(inlineConfig = {}) {
5631
6176
  const ws = createWebSocketServer(httpServer);
5632
6177
  const pluginApi = getPluginApi(config);
5633
6178
  const clientEnv = new NastiEnvironment("client", config, {
5634
- hot: createWsHotChannel(ws),
6179
+ hot: createWsHotChannel(ws, "client"),
5635
6180
  mode: "dev",
5636
6181
  plugins: allPlugins,
5637
6182
  pluginApi
@@ -5646,13 +6191,40 @@ async function createServer(inlineConfig = {}) {
5646
6191
  environmentName: name
5647
6192
  });
5648
6193
  environments[name] = new NastiEnvironment(name, config, {
6194
+ hot: consumer === "client" ? createWsHotChannel(ws, name) : void 0,
5649
6195
  mode: "dev",
5650
6196
  plugins: envPlugins,
5651
6197
  pluginApi
5652
6198
  });
5653
6199
  }
5654
6200
  for (const [name, environment] of Object.entries(environments)) {
5655
- if (name !== "client" && environment.options.driver) await environment.init();
6201
+ if (name === "client" || environment.consumer === "client" || environment.options.driver) {
6202
+ await environment.init();
6203
+ }
6204
+ }
6205
+ const transformContexts = /* @__PURE__ */ new Map();
6206
+ for (const environment of Object.values(environments)) {
6207
+ if (environment.consumer !== "client" || environment.driver) continue;
6208
+ const environmentConfig = {
6209
+ ...configWithPlugins,
6210
+ resolve: environment.options.resolve,
6211
+ build: environment.options.build,
6212
+ plugins: environment.plugins
6213
+ };
6214
+ const context = {
6215
+ config: environmentConfig,
6216
+ pluginContainer: environment.pluginContainer,
6217
+ moduleGraph: environment.moduleGraph,
6218
+ environment,
6219
+ envDefine: buildEnvDefine(
6220
+ loadEnv(environmentConfig.mode, environmentConfig.root, environmentConfig.envPrefix),
6221
+ environmentConfig.mode,
6222
+ ssrDefineOverrides(environment.consumer)
6223
+ ),
6224
+ onPrune: (paths) => environment.hot.send({ type: "prune", paths })
6225
+ };
6226
+ transformContexts.set(environment.name, context);
6227
+ environment.configureDevPipeline((url) => transformRequest(url, context));
5656
6228
  }
5657
6229
  let ssrRunner = null;
5658
6230
  async function getSsrRunner() {
@@ -5667,7 +6239,6 @@ async function createServer(inlineConfig = {}) {
5667
6239
  return ssrRunner;
5668
6240
  }
5669
6241
  const moduleGraph = clientEnv.moduleGraph;
5670
- const pluginContainer = clientEnv.pluginContainer;
5671
6242
  let bundledServer = null;
5672
6243
  if (config.experimental.bundledDev) {
5673
6244
  const { createBundledDevServer: createBundledDevServer2 } = await Promise.resolve().then(() => (init_dev_engine(), dev_engine_exports));
@@ -5696,6 +6267,15 @@ async function createServer(inlineConfig = {}) {
5696
6267
  let server;
5697
6268
  const environmentServices = {};
5698
6269
  let environmentDriversStarted = false;
6270
+ let devPipelinesStarted = false;
6271
+ const startDevPipelines = async () => {
6272
+ if (devPipelinesStarted) return;
6273
+ devPipelinesStarted = true;
6274
+ for (const environment of Object.values(environments)) {
6275
+ if (!transformContexts.has(environment.name)) continue;
6276
+ await environment.pluginContainer.buildStart();
6277
+ }
6278
+ };
5699
6279
  const logCloseError = (target, error) => {
5700
6280
  const normalized = error instanceof Error ? error : new Error(String(error));
5701
6281
  logger.error(`[nasti] failed to close ${target}`, { error: normalized });
@@ -5747,14 +6327,61 @@ async function createServer(inlineConfig = {}) {
5747
6327
  });
5748
6328
  }
5749
6329
  };
6330
+ const updateClientEnvironments = async (file) => {
6331
+ const timestamp = Date.now();
6332
+ const results = {};
6333
+ for (const environment of Object.values(environments)) {
6334
+ if (environment.consumer !== "client" || environment.driver) continue;
6335
+ try {
6336
+ const result = await handleFileChange(file, server, environment.name, timestamp);
6337
+ if (result) results[environment.name] = result;
6338
+ } catch (error) {
6339
+ const normalized = error instanceof Error ? error : new Error(String(error));
6340
+ logger.error(
6341
+ `[nasti] HMR failed for environment "${environment.name}": ${normalized.message}`,
6342
+ { error: normalized }
6343
+ );
6344
+ try {
6345
+ environment.hot.send({
6346
+ type: "error",
6347
+ err: { message: normalized.message, stack: normalized.stack }
6348
+ });
6349
+ } catch (channelError) {
6350
+ const channelFailure = channelError instanceof Error ? channelError : new Error(String(channelError));
6351
+ logger.error(
6352
+ `[nasti] failed to deliver HMR error to environment "${environment.name}"`,
6353
+ { error: channelFailure }
6354
+ );
6355
+ }
6356
+ }
6357
+ }
6358
+ if (Object.keys(results).length === 0) return;
6359
+ const context = {
6360
+ file,
6361
+ timestamp,
6362
+ environments: Object.freeze({ ...results }),
6363
+ server
6364
+ };
6365
+ for (const plugin of config.plugins) {
6366
+ await plugin.handleHotUpdateApp?.(context);
6367
+ }
6368
+ };
6369
+ const queueClientEnvironmentUpdate = (file) => {
6370
+ void updateClientEnvironments(file).catch((error) => {
6371
+ const normalized = error instanceof Error ? error : new Error(String(error));
6372
+ logger.error(`[nasti] multi-environment HMR failed: ${normalized.message}`, {
6373
+ error: normalized
6374
+ });
6375
+ });
6376
+ };
5750
6377
  watcher.on("change", (file) => {
5751
6378
  ssrRunner?.invalidateFile(file);
5752
- handleFileChange(file, server);
6379
+ queueClientEnvironmentUpdate(file);
5753
6380
  notifyEnvironmentDrivers(file, "change");
5754
6381
  });
5755
6382
  watcher.on("add", (file) => {
5756
6383
  ssrRunner?.invalidateFile(file);
5757
- handleFileChange(file, server);
6384
+ queueClientEnvironmentUpdate(file);
5758
6385
  notifyEnvironmentDrivers(file, "add");
5759
6386
  });
5760
6387
  watcher.on("unlink", (file) => {
@@ -5772,7 +6399,7 @@ async function createServer(inlineConfig = {}) {
5772
6399
  async listen(port) {
5773
6400
  const finalPort = port ?? config.server.port;
5774
6401
  const host = config.server.host === true ? "0.0.0.0" : config.server.host;
5775
- await pluginContainer.buildStart();
6402
+ await startDevPipelines();
5776
6403
  await startEnvironmentDrivers();
5777
6404
  return new Promise((resolve, reject) => {
5778
6405
  let currentPort = finalPort;
@@ -5787,7 +6414,7 @@ async function createServer(inlineConfig = {}) {
5787
6414
  const readyIn = Math.ceil(performance.now() - startTime);
5788
6415
  logger.info(
5789
6416
  `
5790
- ${pc9.cyan(pc9.bold("NASTI"))} ${pc9.cyan(`v${"2.4.0"}`)} ${pc9.dim("ready in")} ${pc9.bold(readyIn)} ${pc9.dim("ms")}
6417
+ ${pc9.cyan(pc9.bold("NASTI"))} ${pc9.cyan(`v${"2.4.2"}`)} ${pc9.dim("ready in")} ${pc9.bold(readyIn)} ${pc9.dim("ms")}
5791
6418
  `
5792
6419
  );
5793
6420
  printServerUrls(
@@ -5814,20 +6441,26 @@ async function createServer(inlineConfig = {}) {
5814
6441
  });
5815
6442
  },
5816
6443
  async transformRequest(url) {
5817
- const { transformRequest: transformRequest2 } = await Promise.resolve().then(() => (init_middleware(), middleware_exports));
5818
- return transformRequest2(url, {
5819
- config: configWithPlugins,
5820
- pluginContainer,
5821
- moduleGraph,
5822
- onPrune: (paths) => ws.send({ type: "prune", paths })
5823
- });
6444
+ return clientEnv.transformRequest(url);
6445
+ },
6446
+ async transformEnvironmentRequest(environmentName, url) {
6447
+ const environment = environments[environmentName];
6448
+ if (!environment) {
6449
+ throw new Error(`[nasti] unknown dev environment "${environmentName}"`);
6450
+ }
6451
+ return environment.transformRequest(url);
5824
6452
  },
5825
6453
  async ssrLoadModule(url) {
5826
6454
  const runner = await getSsrRunner();
5827
6455
  return runner.import(url);
5828
6456
  },
5829
6457
  async close() {
5830
- await pluginContainer.buildEnd();
6458
+ if (devPipelinesStarted) {
6459
+ for (const environment of Object.values(environments).reverse()) {
6460
+ if (!transformContexts.has(environment.name)) continue;
6461
+ await environment.pluginContainer.buildEnd();
6462
+ }
6463
+ }
5831
6464
  await bundledServer?.close();
5832
6465
  let environmentCloseFailed = false;
5833
6466
  let firstEnvironmentCloseError;
@@ -5877,12 +6510,7 @@ async function createServer(inlineConfig = {}) {
5877
6510
  }
5878
6511
  throw error;
5879
6512
  }
5880
- app.use(transformMiddleware({
5881
- config: configWithPlugins,
5882
- pluginContainer,
5883
- moduleGraph,
5884
- onPrune: (paths) => ws.send({ type: "prune", paths })
5885
- }));
6513
+ app.use(transformMiddleware(transformContexts.get("client")));
5886
6514
  const publicDir = path16.resolve(config.root, "public");
5887
6515
  app.use(sirv(publicDir, { dev: true, etag: true }));
5888
6516
  app.use(sirv(config.root, { dev: true, etag: true }));
@@ -5921,6 +6549,7 @@ var init_server = __esm({
5921
6549
  init_hmr();
5922
6550
  init_builtins();
5923
6551
  init_plugin_api();
6552
+ init_env();
5924
6553
  }
5925
6554
  });
5926
6555
 
@@ -5976,7 +6605,7 @@ async function buildElectron(inlineConfig = {}) {
5976
6605
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
5977
6606
  const startTime = performance.now();
5978
6607
  assertElectronVersion(config);
5979
- console.log(pc5.cyan("\n\u26A1 nasti build (electron)") + pc5.dim(` v${"2.4.0"}`));
6608
+ console.log(pc5.cyan("\n\u26A1 nasti build (electron)") + pc5.dim(` v${"2.4.2"}`));
5980
6609
  console.log(pc5.dim(` root: ${config.root}`));
5981
6610
  console.log(pc5.dim(` mode: ${config.mode}`));
5982
6611
  console.log(pc5.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
@@ -6149,7 +6778,7 @@ async function startElectronDev(inlineConfig = {}) {
6149
6778
  const { noSpawn, ...rest } = inlineConfig;
6150
6779
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
6151
6780
  warnElectronVersion(config);
6152
- console.log(pc10.cyan("\n\u26A1 nasti electron dev") + pc10.dim(` v${"2.4.0"}`));
6781
+ console.log(pc10.cyan("\n\u26A1 nasti electron dev") + pc10.dim(` v${"2.4.2"}`));
6153
6782
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
6154
6783
  const server = await createServer2({
6155
6784
  ...rest,