@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/cli.cjs CHANGED
@@ -184,7 +184,10 @@ var init_defaults = __esm({
184
184
  target: "es2022",
185
185
  rolldownOptions: {},
186
186
  emptyOutDir: true,
187
- css: {},
187
+ css: {
188
+ inject: true,
189
+ emit: true
190
+ },
188
191
  reportCompressedSize: true,
189
192
  chunkSizeWarningLimit: 500,
190
193
  cssCodeSplit: true,
@@ -420,7 +423,11 @@ async function resolveConfig(inlineConfig = {}, command) {
420
423
  allowClearScreen: clearScreen2,
421
424
  customLogger: merged.customLogger
422
425
  });
423
- const mergedBuild = { ...defaults.build, ...merged.build };
426
+ const mergedBuild = {
427
+ ...defaults.build,
428
+ ...merged.build,
429
+ css: { ...defaults.build.css, ...merged.build?.css }
430
+ };
424
431
  if (merged.build?.cssMinify === void 0) {
425
432
  mergedBuild.cssMinify = !!mergedBuild.minify;
426
433
  }
@@ -451,11 +458,17 @@ async function resolveConfig(inlineConfig = {}, command) {
451
458
  bundledDev: merged.experimental?.bundledDev ?? defaults.experimental.bundledDev
452
459
  }
453
460
  };
454
- const userEnvironments = {
461
+ const rawUserEnvironments = {
455
462
  client: {},
456
463
  ssr: {},
457
464
  ...merged.environments ?? {}
458
465
  };
466
+ const userEnvironments = Object.fromEntries(
467
+ Object.entries(rawUserEnvironments).map(([name, options]) => [
468
+ name,
469
+ deepMerge({}, options)
470
+ ])
471
+ );
459
472
  for (const [name, envOptions] of Object.entries(userEnvironments)) {
460
473
  for (const plugin of rawPlugins) {
461
474
  if (plugin.configEnvironment) {
@@ -466,6 +479,7 @@ async function resolveConfig(inlineConfig = {}, command) {
466
479
  }
467
480
  for (const [name, envOptions] of Object.entries(userEnvironments)) {
468
481
  const consumer = envOptions.consumer ?? (name === "client" ? "client" : "server");
482
+ const vueOptions = deepMerge({}, envOptions.vue ?? {});
469
483
  if (name === "client") {
470
484
  if (envOptions.resolve) {
471
485
  Object.assign(resolved.resolve, {
@@ -473,7 +487,11 @@ async function resolveConfig(inlineConfig = {}, command) {
473
487
  alias: { ...resolved.resolve.alias, ...envOptions.resolve.alias }
474
488
  });
475
489
  }
476
- if (envOptions.build) Object.assign(resolved.build, envOptions.build);
490
+ if (envOptions.build) {
491
+ const { css, ...environmentBuild } = envOptions.build;
492
+ Object.assign(resolved.build, environmentBuild);
493
+ if (css) resolved.build.css = { ...resolved.build.css, ...css };
494
+ }
477
495
  resolved.environments.client = {
478
496
  consumer,
479
497
  buildEnabled: envOptions.buildEnabled ?? true,
@@ -485,7 +503,8 @@ async function resolveConfig(inlineConfig = {}, command) {
485
503
  driver: envOptions.driver,
486
504
  // 同引用 —— 精确镜像(assertClientEnvironmentMirror 校验)
487
505
  resolve: resolved.resolve,
488
- build: resolved.build
506
+ build: resolved.build,
507
+ vue: vueOptions
489
508
  };
490
509
  continue;
491
510
  }
@@ -495,6 +514,7 @@ async function resolveConfig(inlineConfig = {}, command) {
495
514
  entry: normalizeEnvironmentEntries(envOptions.entry, root),
496
515
  html: envOptions.consumer === "client" && envOptions.html ? import_node_path.default.resolve(root, envOptions.html) : void 0,
497
516
  driver: envOptions.driver,
517
+ vue: vueOptions,
498
518
  resolve: {
499
519
  alias: { ...resolved.resolve.alias, ...envOptions.resolve?.alias },
500
520
  extensions: envOptions.resolve?.extensions ?? [...resolved.resolve.extensions],
@@ -505,6 +525,7 @@ async function resolveConfig(inlineConfig = {}, command) {
505
525
  build: {
506
526
  ...resolved.build,
507
527
  ...envOptions.build,
528
+ css: { ...resolved.build.css, ...envOptions.build?.css },
508
529
  // 非 client 环境默认产出到 <outDir>/<envName>(如 dist/ssr),可显式覆盖
509
530
  outDir: envOptions.build?.outDir ?? import_node_path.default.join(resolved.build.outDir, name),
510
531
  // server 产物默认不压缩(可调试性优先,与 Vite SSR 默认一致),可显式覆盖
@@ -751,17 +772,23 @@ var init_plugin_container = __esm({
751
772
  }
752
773
  async transform(code, id) {
753
774
  let currentCode = code;
775
+ let lastResult;
754
776
  for (const plugin of this.plugins) {
755
777
  if (!plugin.transform) continue;
756
778
  const result = await plugin.transform.call(this.ctx, currentCode, id);
757
779
  if (result == null) continue;
758
780
  if (typeof result === "string") {
759
781
  currentCode = result;
782
+ lastResult = void 0;
760
783
  } else {
761
784
  currentCode = result.code;
785
+ lastResult = result;
762
786
  }
763
787
  }
764
- return currentCode === code ? null : { code: currentCode };
788
+ return currentCode === code ? null : {
789
+ ...lastResult,
790
+ code: currentCode
791
+ };
765
792
  }
766
793
  /** 完整的模块处理管道: resolveId → load → transform */
767
794
  async processModule(source, importer) {
@@ -808,9 +835,13 @@ var init_module_graph = __esm({
808
835
  "use strict";
809
836
  init_url();
810
837
  ModuleGraph = class {
838
+ environmentName;
811
839
  urlToModuleMap = /* @__PURE__ */ new Map();
812
840
  idToModuleMap = /* @__PURE__ */ new Map();
813
841
  fileToModulesMap = /* @__PURE__ */ new Map();
842
+ constructor(environmentName = "client") {
843
+ this.environmentName = environmentName;
844
+ }
814
845
  getModuleByUrl(url) {
815
846
  return this.urlToModuleMap.get(removeTimestampQuery(url));
816
847
  }
@@ -840,7 +871,8 @@ var init_module_graph = __esm({
840
871
  transformResult: null,
841
872
  lastHMRTimestamp: 0,
842
873
  invalidationVersion: 0,
843
- isSelfAccepting: false
874
+ isSelfAccepting: false,
875
+ environment: this.environmentName
844
876
  };
845
877
  this.idToModuleMap.set(mod.id, mod);
846
878
  return mod;
@@ -998,12 +1030,12 @@ function createNoopHotChannel() {
998
1030
  }
999
1031
  };
1000
1032
  }
1001
- function createWsHotChannel(ws) {
1033
+ function createWsHotChannel(ws, environmentName = "client") {
1002
1034
  const listeners = /* @__PURE__ */ new Map();
1003
1035
  let invokeHandlers;
1004
1036
  return {
1005
1037
  send(payload) {
1006
- ws.send(payload);
1038
+ ws.send({ ...payload, environment: payload.environment ?? environmentName });
1007
1039
  },
1008
1040
  on(event, listener) {
1009
1041
  let set = listeners.get(event);
@@ -1015,8 +1047,8 @@ function createWsHotChannel(ws) {
1015
1047
  },
1016
1048
  listen() {
1017
1049
  },
1050
+ // 多个 environment 共享底层 WebSocket server;它由 DevServer.close() 统一关闭。
1018
1051
  close() {
1019
- ws.close();
1020
1052
  },
1021
1053
  setInvokeHandler(handlers) {
1022
1054
  invokeHandlers = handlers;
@@ -1115,6 +1147,9 @@ var init_environment = __esm({
1115
1147
  candidatePlugins;
1116
1148
  pluginApi;
1117
1149
  buildMetadata = {};
1150
+ cssModules = /* @__PURE__ */ new Map();
1151
+ assetModules = /* @__PURE__ */ new Map();
1152
+ transformRequestHandler;
1118
1153
  initialized = false;
1119
1154
  constructor(name, config, init = {}) {
1120
1155
  const options = config.environments[name];
@@ -1129,7 +1164,7 @@ var init_environment = __esm({
1129
1164
  this.config = config;
1130
1165
  this.options = options;
1131
1166
  this.hot = init.hot ?? createNoopHotChannel();
1132
- this.moduleGraph = new ModuleGraph();
1167
+ this.moduleGraph = new ModuleGraph(name);
1133
1168
  this.candidatePlugins = init.plugins ?? config.plugins;
1134
1169
  this.pluginApi = init.pluginApi ?? getPluginApi(config);
1135
1170
  }
@@ -1171,6 +1206,37 @@ var init_environment = __esm({
1171
1206
  logger: this.config.logger
1172
1207
  };
1173
1208
  }
1209
+ configureDevPipeline(transformRequest2) {
1210
+ this.transformRequestHandler = transformRequest2;
1211
+ }
1212
+ async transformRequest(url) {
1213
+ if (!this.transformRequestHandler) {
1214
+ throw new Error(
1215
+ `[nasti] environment "${this.name}" does not have an initialized dev transform pipeline`
1216
+ );
1217
+ }
1218
+ return this.transformRequestHandler(url);
1219
+ }
1220
+ setCssModule(module2) {
1221
+ this.cssModules.set(module2.id, { ...module2 });
1222
+ }
1223
+ getCssModule(id) {
1224
+ const module2 = this.cssModules.get(id);
1225
+ return module2 ? { ...module2 } : void 0;
1226
+ }
1227
+ getCssModules() {
1228
+ return Object.freeze(
1229
+ Object.fromEntries(
1230
+ [...this.cssModules].map(([id, module2]) => [id, { ...module2 }])
1231
+ )
1232
+ );
1233
+ }
1234
+ setAssetModule(id, fileName) {
1235
+ this.assetModules.set(id, fileName);
1236
+ }
1237
+ getAssetModules() {
1238
+ return Object.freeze(Object.fromEntries(this.assetModules));
1239
+ }
1174
1240
  setBuildMetadata(metadata) {
1175
1241
  const { entries: currentEntries, ...currentMetadata } = this.buildMetadata;
1176
1242
  const { entries, ...nextMetadata } = metadata;
@@ -1429,14 +1495,95 @@ var init_env = __esm({
1429
1495
  }
1430
1496
  });
1431
1497
 
1432
- // src/server/middleware.ts
1433
- var middleware_exports = {};
1434
- __export(middleware_exports, {
1435
- REACT_REFRESH_GLOBAL_PREAMBLE: () => REACT_REFRESH_GLOBAL_PREAMBLE,
1436
- getReactRefreshRuntimeEsm: () => getReactRefreshRuntimeEsm,
1437
- transformMiddleware: () => transformMiddleware,
1438
- transformRequest: () => transformRequest
1498
+ // src/plugins/assets.ts
1499
+ function assetsPlugin(config) {
1500
+ const emittedAssets = /* @__PURE__ */ new Set();
1501
+ return {
1502
+ name: "nasti:assets",
1503
+ resolveId(source) {
1504
+ if (source.endsWith("?url") || source.endsWith("?raw")) {
1505
+ return source;
1506
+ }
1507
+ return null;
1508
+ },
1509
+ load(id) {
1510
+ const ext = import_node_path4.default.extname(id.replace(/\?.*$/, ""));
1511
+ if (id.endsWith("?raw")) {
1512
+ const file = id.slice(0, -4);
1513
+ if (import_node_fs4.default.existsSync(file)) {
1514
+ const content = import_node_fs4.default.readFileSync(file, "utf-8");
1515
+ return `export default ${JSON.stringify(content)}`;
1516
+ }
1517
+ }
1518
+ if (id.endsWith("?url") || ASSET_EXTENSIONS.has(ext)) {
1519
+ const file = id.replace(/\?.*$/, "");
1520
+ if (!import_node_fs4.default.existsSync(file)) return null;
1521
+ if (config.command === "serve") {
1522
+ const url = "/" + import_node_path4.default.relative(config.root, file);
1523
+ return `export default ${JSON.stringify(url)}`;
1524
+ }
1525
+ const content = import_node_fs4.default.readFileSync(file);
1526
+ const hash = import_node_crypto.default.createHash("sha256").update(content).digest("hex").slice(0, 8);
1527
+ const basename = import_node_path4.default.basename(file, ext);
1528
+ const hashedName = `${config.build.assetsDir}/${basename}.${hash}${ext}`;
1529
+ const environment = this.environment;
1530
+ if (!environment) {
1531
+ throw new Error("[nasti:assets] build environment is not initialized");
1532
+ }
1533
+ if (!emittedAssets.has(hashedName)) {
1534
+ this.emitFile({
1535
+ type: "asset",
1536
+ fileName: hashedName,
1537
+ source: content
1538
+ });
1539
+ emittedAssets.add(hashedName);
1540
+ }
1541
+ environment.setAssetModule(file, hashedName);
1542
+ return `export default ${JSON.stringify(config.base + hashedName)}`;
1543
+ }
1544
+ return null;
1545
+ }
1546
+ };
1547
+ }
1548
+ function isAssetFile(id) {
1549
+ const ext = import_node_path4.default.extname(id.replace(/\?.*$/, ""));
1550
+ return ASSET_EXTENSIONS.has(ext);
1551
+ }
1552
+ var import_node_path4, import_node_fs4, import_node_crypto, ASSET_EXTENSIONS;
1553
+ var init_assets = __esm({
1554
+ "src/plugins/assets.ts"() {
1555
+ "use strict";
1556
+ import_node_path4 = __toESM(require("path"), 1);
1557
+ import_node_fs4 = __toESM(require("fs"), 1);
1558
+ import_node_crypto = __toESM(require("crypto"), 1);
1559
+ ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
1560
+ ".png",
1561
+ ".jpg",
1562
+ ".jpeg",
1563
+ ".gif",
1564
+ ".svg",
1565
+ ".ico",
1566
+ ".webp",
1567
+ ".avif",
1568
+ ".mp4",
1569
+ ".webm",
1570
+ ".ogg",
1571
+ ".mp3",
1572
+ ".wav",
1573
+ ".flac",
1574
+ ".aac",
1575
+ ".woff",
1576
+ ".woff2",
1577
+ ".eot",
1578
+ ".ttf",
1579
+ ".otf",
1580
+ ".pdf",
1581
+ ".txt"
1582
+ ]);
1583
+ }
1439
1584
  });
1585
+
1586
+ // src/server/middleware.ts
1440
1587
  function getReactRefreshRuntimeEsm(includeBoundaryHelpers = false) {
1441
1588
  if (__refreshRuntimeCache) {
1442
1589
  return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
@@ -1444,10 +1591,10 @@ function getReactRefreshRuntimeEsm(includeBoundaryHelpers = false) {
1444
1591
  let cjsPath;
1445
1592
  try {
1446
1593
  const pkgPath = __require.resolve("react-refresh/package.json");
1447
- cjsPath = import_node_path4.default.join(import_node_path4.default.dirname(pkgPath), "cjs", "react-refresh-runtime.development.js");
1594
+ cjsPath = import_node_path5.default.join(import_node_path5.default.dirname(pkgPath), "cjs", "react-refresh-runtime.development.js");
1448
1595
  } catch (err) {
1449
- cjsPath = import_node_path4.default.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
1450
- if (!import_node_fs4.default.existsSync(cjsPath)) {
1596
+ cjsPath = import_node_path5.default.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
1597
+ if (!import_node_fs5.default.existsSync(cjsPath)) {
1451
1598
  const origMsg = err instanceof Error ? err.message : String(err);
1452
1599
  throw new Error(
1453
1600
  `[nasti] Missing dependency "react-refresh". Install it with: npm install react-refresh
@@ -1455,7 +1602,7 @@ Original resolve error: ${origMsg}`
1455
1602
  );
1456
1603
  }
1457
1604
  }
1458
- const cjsSource = import_node_fs4.default.readFileSync(cjsPath, "utf-8");
1605
+ const cjsSource = import_node_fs5.default.readFileSync(cjsPath, "utf-8");
1459
1606
  __refreshRuntimeCache = `// Wrapped react-refresh runtime -> ESM
1460
1607
  const exports = {};
1461
1608
  const module = { exports };
@@ -1539,7 +1686,8 @@ const __nasti_hot__ = __nasti_createHotContext__(${urlLit});
1539
1686
  function transformMiddleware(ctx) {
1540
1687
  ctx.envDefine = buildEnvDefine(
1541
1688
  loadEnv(ctx.config.mode, ctx.config.root, ctx.config.envPrefix),
1542
- ctx.config.mode
1689
+ ctx.config.mode,
1690
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
1543
1691
  );
1544
1692
  return async (req, res, next) => {
1545
1693
  const url = req.url ?? "/";
@@ -1584,7 +1732,7 @@ function transformMiddleware(ctx) {
1584
1732
  return;
1585
1733
  }
1586
1734
  }
1587
- if (isModuleRequest(url)) {
1735
+ if (isModuleRequest(url, req.headers["sec-fetch-dest"])) {
1588
1736
  try {
1589
1737
  const result = await transformRequest(url, ctx);
1590
1738
  if (result) {
@@ -1625,8 +1773,8 @@ async function transformRequest(url, ctx) {
1625
1773
  let realIdValid = false;
1626
1774
  try {
1627
1775
  if (idParam) {
1628
- realId = import_node_fs4.default.realpathSync(idParam);
1629
- realIdValid = import_node_fs4.default.statSync(realId).isFile() && (realId.includes(`${import_node_path4.default.sep}node_modules${import_node_path4.default.sep}`) || isUnderRoot(realId, config.root));
1776
+ realId = import_node_fs5.default.realpathSync(idParam);
1777
+ realIdValid = import_node_fs5.default.statSync(realId).isFile() && (realId.includes(`${import_node_path5.default.sep}node_modules${import_node_path5.default.sep}`) || isUnderRoot(realId, config.root));
1630
1778
  }
1631
1779
  } catch {
1632
1780
  realId = null;
@@ -1655,12 +1803,16 @@ async function transformRequest(url, ctx) {
1655
1803
  if (rawQuery && !/^t=\d+$/.test(rawQuery)) {
1656
1804
  const mod2 = await moduleGraph.ensureEntryFromUrl(url);
1657
1805
  const transformVersion2 = mod2.invalidationVersion;
1658
- const loaded = await pluginContainer.load(url);
1659
- if (loaded != null) {
1660
- let code2 = typeof loaded === "string" ? loaded : loaded.code;
1806
+ const loaded2 = await pluginContainer.load(url);
1807
+ if (loaded2 != null) {
1808
+ let code2 = typeof loaded2 === "string" ? loaded2 : loaded2.code;
1809
+ let map2 = typeof loaded2 === "string" ? void 0 : loaded2.map;
1661
1810
  const transformed = await pluginContainer.transform(code2, url);
1662
1811
  if (transformed != null) {
1663
1812
  code2 = typeof transformed === "string" ? transformed : transformed.code;
1813
+ if (typeof transformed !== "string" && transformed.map != null) {
1814
+ map2 = transformed.map;
1815
+ }
1664
1816
  }
1665
1817
  const parentFile = resolveUrlToFile(cleanReqUrl, config.root) ?? cleanReqUrl;
1666
1818
  moduleGraph.registerModule(mod2, parentFile);
@@ -1668,7 +1820,8 @@ async function transformRequest(url, ctx) {
1668
1820
  code2 = injectImportMetaHot(hotInfo2.code, url);
1669
1821
  code2 = replaceEnvInCode(code2, ctx.envDefine ?? buildEnvDefine(
1670
1822
  loadEnv(config.mode, config.root, config.envPrefix),
1671
- config.mode
1823
+ config.mode,
1824
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
1672
1825
  ));
1673
1826
  const importedUrls2 = /* @__PURE__ */ new Set();
1674
1827
  code2 = rewriteImports(code2, config, parentFile, importedUrls2, moduleGraph);
@@ -1679,7 +1832,7 @@ async function transformRequest(url, ctx) {
1679
1832
  hotInfo2.isSelfAccepting,
1680
1833
  transformVersion2
1681
1834
  );
1682
- const transformResult2 = { code: code2 };
1835
+ const transformResult2 = { code: code2, map: map2 };
1683
1836
  if (pruned2) {
1684
1837
  if (pruned2.size > 0) ctx.onPrune?.([...pruned2].map((item) => item.url));
1685
1838
  mod2.transformResult = transformResult2;
@@ -1688,7 +1841,7 @@ async function transformRequest(url, ctx) {
1688
1841
  }
1689
1842
  }
1690
1843
  const filePath = resolveUrlToFile(url, config.root);
1691
- if (!filePath || !import_node_fs4.default.existsSync(filePath)) return null;
1844
+ if (!filePath || !import_node_fs5.default.existsSync(filePath)) return null;
1692
1845
  const mod = await moduleGraph.ensureEntryFromUrl(url);
1693
1846
  moduleGraph.registerModule(mod, filePath);
1694
1847
  const transformVersion = mod.invalidationVersion;
@@ -1698,10 +1851,13 @@ async function transformRequest(url, ctx) {
1698
1851
  mod.transformResult = transformResult2;
1699
1852
  return transformResult2;
1700
1853
  }
1701
- let code = import_node_fs4.default.readFileSync(filePath, "utf-8");
1854
+ const loaded = await pluginContainer.load(filePath);
1855
+ let code = loaded == null ? import_node_fs5.default.readFileSync(filePath, "utf-8") : typeof loaded === "string" ? loaded : loaded.code;
1856
+ let map = loaded && typeof loaded !== "string" ? loaded.map : void 0;
1702
1857
  const pluginResult = await pluginContainer.transform(code, filePath);
1703
1858
  if (pluginResult) {
1704
1859
  code = typeof pluginResult === "string" ? pluginResult : pluginResult.code;
1860
+ if (typeof pluginResult !== "string") map = pluginResult.map;
1705
1861
  }
1706
1862
  const stableUrl = cleanReqUrl;
1707
1863
  let wrappedWithRefresh = false;
@@ -1712,9 +1868,11 @@ async function transformRequest(url, ctx) {
1712
1868
  sourcemap: true,
1713
1869
  jsxRuntime: "automatic",
1714
1870
  jsxImportSource: config.framework === "vue" ? "vue" : "react",
1715
- reactRefresh: useRefresh
1871
+ reactRefresh: useRefresh,
1872
+ target: ctx.environment?.options.build.target ?? config.build.target
1716
1873
  });
1717
1874
  code = result.code;
1875
+ if (result.map) map = JSON.parse(result.map);
1718
1876
  if (useRefresh) {
1719
1877
  code = buildReactRefreshWrapper(stableUrl, code);
1720
1878
  wrappedWithRefresh = true;
@@ -1727,7 +1885,8 @@ async function transformRequest(url, ctx) {
1727
1885
  }
1728
1886
  const envDefine = ctx.envDefine ?? buildEnvDefine(
1729
1887
  loadEnv(config.mode, config.root, config.envPrefix),
1730
- config.mode
1888
+ config.mode,
1889
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
1731
1890
  );
1732
1891
  code = replaceEnvInCode(code, envDefine);
1733
1892
  const importedUrls = /* @__PURE__ */ new Set();
@@ -1739,7 +1898,7 @@ async function transformRequest(url, ctx) {
1739
1898
  wrappedWithRefresh || hotInfo.isSelfAccepting,
1740
1899
  transformVersion
1741
1900
  );
1742
- const transformResult = { code };
1901
+ const transformResult = { code, map };
1743
1902
  if (pruned) {
1744
1903
  if (pruned.size > 0) ctx.onPrune?.([...pruned].map((item) => item.url));
1745
1904
  mod.transformResult = transformResult;
@@ -1751,7 +1910,7 @@ async function loadVirtualModule(spec, ctx) {
1751
1910
  const resolved = await pluginContainer.resolveId(spec);
1752
1911
  if (resolved == null) return null;
1753
1912
  const resolvedId = typeof resolved === "string" ? resolved : resolved.id;
1754
- const looksVirtual = resolvedId.startsWith("\0") || !import_node_fs4.default.existsSync(resolvedId);
1913
+ const looksVirtual = resolvedId.startsWith("\0") || !import_node_fs5.default.existsSync(resolvedId);
1755
1914
  if (!looksVirtual) return null;
1756
1915
  const loadResult = await pluginContainer.load(resolvedId);
1757
1916
  if (loadResult == null) return null;
@@ -1762,9 +1921,10 @@ async function loadVirtualModule(spec, ctx) {
1762
1921
  }
1763
1922
  code = replaceEnvInCode(code, ctx.envDefine ?? buildEnvDefine(
1764
1923
  loadEnv(config.mode, config.root, config.envPrefix),
1765
- config.mode
1924
+ config.mode,
1925
+ ssrDefineOverrides(ctx.environment?.consumer ?? "client")
1766
1926
  ));
1767
- const anchor = import_node_path4.default.join(config.root, "__nasti_virtual__.ts");
1927
+ const anchor = import_node_path5.default.join(config.root, "__nasti_virtual__.ts");
1768
1928
  code = rewriteImports(code, config, anchor);
1769
1929
  return { id: resolvedId, result: { code } };
1770
1930
  }
@@ -1790,7 +1950,7 @@ async function doBundlePackage(entryFile, root) {
1790
1950
  await bundle2.close();
1791
1951
  let code = result.output[0].code;
1792
1952
  code = code.replace(/process\.env\.NODE_ENV/g, '"development"');
1793
- const externalBaseDir = import_node_path4.default.dirname(entryFile);
1953
+ const externalBaseDir = import_node_path5.default.dirname(entryFile);
1794
1954
  code = code.replace(
1795
1955
  /^(import\b[^;'"]*?\bfrom\s+)(['"])([^'"./][^'"]*)(\2)/gm,
1796
1956
  (_, prefix, q, spec) => `${prefix}${q}${externalSpecToModuleUrl(spec, externalBaseDir, root)}${q}`
@@ -1808,16 +1968,16 @@ async function doBundlePackage(entryFile, root) {
1808
1968
  return code;
1809
1969
  }
1810
1970
  async function tryGenerateSubpathShim(entryFile, root) {
1811
- const NM = `${import_node_path4.default.sep}node_modules${import_node_path4.default.sep}`;
1971
+ const NM = `${import_node_path5.default.sep}node_modules${import_node_path5.default.sep}`;
1812
1972
  if (!entryFile.includes(NM)) return null;
1813
1973
  let pkgDir = null;
1814
1974
  let pkgName = null;
1815
- let dir = import_node_path4.default.dirname(entryFile);
1975
+ let dir = import_node_path5.default.dirname(entryFile);
1816
1976
  while (true) {
1817
- const pkgJsonPath = import_node_path4.default.join(dir, "package.json");
1818
- if (import_node_fs4.default.existsSync(pkgJsonPath)) {
1977
+ const pkgJsonPath = import_node_path5.default.join(dir, "package.json");
1978
+ if (import_node_fs5.default.existsSync(pkgJsonPath)) {
1819
1979
  try {
1820
- const pkg = JSON.parse(import_node_fs4.default.readFileSync(pkgJsonPath, "utf-8"));
1980
+ const pkg = JSON.parse(import_node_fs5.default.readFileSync(pkgJsonPath, "utf-8"));
1821
1981
  if (typeof pkg?.name === "string" && pkg.name) {
1822
1982
  pkgDir = dir;
1823
1983
  pkgName = pkg.name;
@@ -1826,16 +1986,16 @@ async function tryGenerateSubpathShim(entryFile, root) {
1826
1986
  } catch {
1827
1987
  }
1828
1988
  }
1829
- const parent = import_node_path4.default.dirname(dir);
1989
+ const parent = import_node_path5.default.dirname(dir);
1830
1990
  if (parent === dir) return null;
1831
1991
  dir = parent;
1832
1992
  if (!dir.includes(NM)) return null;
1833
1993
  }
1834
1994
  if (!pkgDir || !pkgName) return null;
1835
- const entryExt = import_node_path4.default.extname(entryFile);
1995
+ const entryExt = import_node_path5.default.extname(entryFile);
1836
1996
  const mainEntry = pickMainEntryByExtension(pkgDir, entryExt);
1837
1997
  if (!mainEntry) return null;
1838
- if (import_node_path4.default.resolve(mainEntry) === import_node_path4.default.resolve(entryFile)) return null;
1998
+ if (import_node_path5.default.resolve(mainEntry) === import_node_path5.default.resolve(entryFile)) return null;
1839
1999
  let mainNs;
1840
2000
  let subNs;
1841
2001
  try {
@@ -1859,7 +2019,7 @@ async function tryGenerateSubpathShim(entryFile, root) {
1859
2019
  if (mainNs["default"] !== subNs["default"]) return null;
1860
2020
  }
1861
2021
  const rootMain = resolveNodeModule(root, pkgName);
1862
- const mainEntryUrl = rootMain && rootMain.startsWith(pkgDir + import_node_path4.default.sep) ? `/@modules/${pkgName}` : `/@modules/${pkgName}?id=${encodeURIComponent(mainEntry)}`;
2022
+ const mainEntryUrl = rootMain && rootMain.startsWith(pkgDir + import_node_path5.default.sep) ? `/@modules/${pkgName}` : `/@modules/${pkgName}?id=${encodeURIComponent(mainEntry)}`;
1863
2023
  const lines = [
1864
2024
  `// Nasti subpath shim \u2192 ${pkgName} (avoid duplicate bundling)`,
1865
2025
  `import * as __pkg from "${mainEntryUrl}";`
@@ -1873,10 +2033,10 @@ async function tryGenerateSubpathShim(entryFile, root) {
1873
2033
  return lines.join("\n") + "\n";
1874
2034
  }
1875
2035
  function pickMainEntryByExtension(pkgDir, preferredExt) {
1876
- const pkgJsonPath = import_node_path4.default.join(pkgDir, "package.json");
2036
+ const pkgJsonPath = import_node_path5.default.join(pkgDir, "package.json");
1877
2037
  let pkg;
1878
2038
  try {
1879
- pkg = JSON.parse(import_node_fs4.default.readFileSync(pkgJsonPath, "utf-8"));
2039
+ pkg = JSON.parse(import_node_fs5.default.readFileSync(pkgJsonPath, "utf-8"));
1880
2040
  } catch {
1881
2041
  return null;
1882
2042
  }
@@ -1895,14 +2055,14 @@ function pickMainEntryByExtension(pkgDir, preferredExt) {
1895
2055
  if (typeof pkg.module === "string") candidates.push(pkg.module);
1896
2056
  if (typeof pkg.main === "string") candidates.push(pkg.main);
1897
2057
  for (const cand of candidates) {
1898
- if (import_node_path4.default.extname(cand) === preferredExt) {
1899
- const full = import_node_path4.default.resolve(pkgDir, cand);
1900
- if (import_node_fs4.default.existsSync(full)) return full;
2058
+ if (import_node_path5.default.extname(cand) === preferredExt) {
2059
+ const full = import_node_path5.default.resolve(pkgDir, cand);
2060
+ if (import_node_fs5.default.existsSync(full)) return full;
1901
2061
  }
1902
2062
  }
1903
2063
  for (const cand of candidates) {
1904
- const full = import_node_path4.default.resolve(pkgDir, cand);
1905
- if (import_node_fs4.default.existsSync(full)) return full;
2064
+ const full = import_node_path5.default.resolve(pkgDir, cand);
2065
+ if (import_node_fs5.default.existsSync(full)) return full;
1906
2066
  }
1907
2067
  return null;
1908
2068
  }
@@ -1969,11 +2129,11 @@ function rewriteImports(code, config, filePath, importedUrls, moduleGraph) {
1969
2129
  }
1970
2130
  function createModuleSpecifierResolver(config, filePath) {
1971
2131
  const root = config.root;
1972
- const fileDir = import_node_path4.default.dirname(filePath);
2132
+ const fileDir = import_node_path5.default.dirname(filePath);
1973
2133
  const aliasEntries = Object.entries(config.resolve.alias).sort(
1974
2134
  ([a], [b]) => b.length - a.length
1975
2135
  );
1976
- const toRootUrl = (abs) => "/" + import_node_path4.default.relative(root, abs).replace(/\\/g, "/");
2136
+ const toRootUrl = (abs) => "/" + import_node_path5.default.relative(root, abs).replace(/\\/g, "/");
1977
2137
  return (specifier) => {
1978
2138
  const suffixMatch = specifier.match(/[?#].*$/);
1979
2139
  const suffix = suffixMatch ? suffixMatch[0] : "";
@@ -1982,17 +2142,17 @@ function createModuleSpecifierResolver(config, filePath) {
1982
2142
  if (baseSpec === key || baseSpec.startsWith(key + "/")) {
1983
2143
  const aliasBase = resolveAliasTarget(value, root);
1984
2144
  const sub = baseSpec.slice(key.length).replace(/^\//, "");
1985
- const target = sub ? import_node_path4.default.join(aliasBase, sub) : aliasBase;
2145
+ const target = sub ? import_node_path5.default.join(aliasBase, sub) : aliasBase;
1986
2146
  const resolved = tryResolveDiskPath(target);
1987
2147
  return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1988
2148
  }
1989
2149
  }
1990
2150
  if (baseSpec.startsWith("./") || baseSpec.startsWith("../")) {
1991
- const resolved = tryResolveDiskPath(import_node_path4.default.resolve(fileDir, baseSpec));
2151
+ const resolved = tryResolveDiskPath(import_node_path5.default.resolve(fileDir, baseSpec));
1992
2152
  return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1993
2153
  }
1994
2154
  if (baseSpec.startsWith("/") && !baseSpec.startsWith("/@")) {
1995
- const resolved = tryResolveDiskPath(import_node_path4.default.join(root, baseSpec.replace(/^\//, "")));
2155
+ const resolved = tryResolveDiskPath(import_node_path5.default.join(root, baseSpec.replace(/^\//, "")));
1996
2156
  return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1997
2157
  }
1998
2158
  if (baseSpec.startsWith("/")) return specifier;
@@ -2146,27 +2306,27 @@ function maskStringsAndComments(code) {
2146
2306
  return masked.join("");
2147
2307
  }
2148
2308
  function resolveAliasTarget(value, root) {
2149
- if (import_node_path4.default.isAbsolute(value) && import_node_fs4.default.existsSync(value)) return value;
2150
- if (value.startsWith("/")) return import_node_path4.default.join(root, value.slice(1));
2151
- return import_node_path4.default.resolve(root, value);
2309
+ if (import_node_path5.default.isAbsolute(value) && import_node_fs5.default.existsSync(value)) return value;
2310
+ if (value.startsWith("/")) return import_node_path5.default.join(root, value.slice(1));
2311
+ return import_node_path5.default.resolve(root, value);
2152
2312
  }
2153
2313
  function tryResolveDiskPath(target) {
2154
- if (import_node_fs4.default.existsSync(target) && import_node_fs4.default.statSync(target).isFile()) return target;
2314
+ if (import_node_fs5.default.existsSync(target) && import_node_fs5.default.statSync(target).isFile()) return target;
2155
2315
  for (const ext of RESOLVE_EXTENSIONS) {
2156
2316
  const withExt = target + ext;
2157
- if (import_node_fs4.default.existsSync(withExt) && import_node_fs4.default.statSync(withExt).isFile()) return withExt;
2317
+ if (import_node_fs5.default.existsSync(withExt) && import_node_fs5.default.statSync(withExt).isFile()) return withExt;
2158
2318
  }
2159
- if (import_node_fs4.default.existsSync(target) && import_node_fs4.default.statSync(target).isDirectory()) {
2319
+ if (import_node_fs5.default.existsSync(target) && import_node_fs5.default.statSync(target).isDirectory()) {
2160
2320
  for (const ext of RESOLVE_EXTENSIONS) {
2161
- const idx = import_node_path4.default.join(target, "index" + ext);
2162
- if (import_node_fs4.default.existsSync(idx) && import_node_fs4.default.statSync(idx).isFile()) return idx;
2321
+ const idx = import_node_path5.default.join(target, "index" + ext);
2322
+ if (import_node_fs5.default.existsSync(idx) && import_node_fs5.default.statSync(idx).isFile()) return idx;
2163
2323
  }
2164
2324
  }
2165
2325
  return null;
2166
2326
  }
2167
2327
  function isUnderRoot(abs, root) {
2168
- const rel = import_node_path4.default.relative(root, abs);
2169
- return !!rel && !rel.startsWith("..") && !import_node_path4.default.isAbsolute(rel);
2328
+ const rel = import_node_path5.default.relative(root, abs);
2329
+ return !!rel && !rel.startsWith("..") && !import_node_path5.default.isAbsolute(rel);
2170
2330
  }
2171
2331
  function appendTimestampQuery(url, timestamp) {
2172
2332
  const hashIndex = url.indexOf("#");
@@ -2185,7 +2345,7 @@ function resolveNodeModule(baseDir, moduleName) {
2185
2345
  const resolved = resolveNodeModuleEntry(baseDir, moduleName);
2186
2346
  if (!resolved) return null;
2187
2347
  try {
2188
- return import_node_fs4.default.realpathSync(resolved);
2348
+ return import_node_fs5.default.realpathSync(resolved);
2189
2349
  } catch {
2190
2350
  return resolved;
2191
2351
  }
@@ -2205,21 +2365,21 @@ function resolveNodeModuleEntry(root, moduleName) {
2205
2365
  let pkgDir = null;
2206
2366
  let dir = root;
2207
2367
  for (; ; ) {
2208
- const candidate = import_node_path4.default.join(dir, "node_modules", pkgName);
2209
- if (import_node_fs4.default.existsSync(candidate)) {
2368
+ const candidate = import_node_path5.default.join(dir, "node_modules", pkgName);
2369
+ if (import_node_fs5.default.existsSync(candidate)) {
2210
2370
  pkgDir = candidate;
2211
2371
  break;
2212
2372
  }
2213
- const parent = import_node_path4.default.dirname(dir);
2373
+ const parent = import_node_path5.default.dirname(dir);
2214
2374
  if (parent === dir) break;
2215
2375
  dir = parent;
2216
2376
  }
2217
2377
  if (!pkgDir) return null;
2218
- const pkgJsonPath = import_node_path4.default.join(pkgDir, "package.json");
2219
- if (!import_node_fs4.default.existsSync(pkgJsonPath)) return null;
2378
+ const pkgJsonPath = import_node_path5.default.join(pkgDir, "package.json");
2379
+ if (!import_node_fs5.default.existsSync(pkgJsonPath)) return null;
2220
2380
  let pkg;
2221
2381
  try {
2222
- pkg = JSON.parse(import_node_fs4.default.readFileSync(pkgJsonPath, "utf-8"));
2382
+ pkg = JSON.parse(import_node_fs5.default.readFileSync(pkgJsonPath, "utf-8"));
2223
2383
  } catch {
2224
2384
  return null;
2225
2385
  }
@@ -2232,32 +2392,32 @@ function resolveNodeModuleEntry(root, moduleName) {
2232
2392
  const subDirs = [""];
2233
2393
  for (const field of ["module", "main"]) {
2234
2394
  if (typeof pkg[field] === "string") {
2235
- const dir2 = import_node_path4.default.dirname(pkg[field]);
2395
+ const dir2 = import_node_path5.default.dirname(pkg[field]);
2236
2396
  if (dir2 && dir2 !== "." && !subDirs.includes(dir2)) subDirs.push(dir2);
2237
2397
  }
2238
2398
  }
2239
2399
  for (const dir2 of subDirs) {
2240
- const direct = import_node_path4.default.join(pkgDir, dir2, subpath);
2241
- if (import_node_fs4.default.existsSync(direct) && import_node_fs4.default.statSync(direct).isFile()) return direct;
2400
+ const direct = import_node_path5.default.join(pkgDir, dir2, subpath);
2401
+ if (import_node_fs5.default.existsSync(direct) && import_node_fs5.default.statSync(direct).isFile()) return direct;
2242
2402
  for (const ext of RESOLVE_EXTENSIONS) {
2243
- if (import_node_fs4.default.existsSync(direct + ext)) return direct + ext;
2403
+ if (import_node_fs5.default.existsSync(direct + ext)) return direct + ext;
2244
2404
  }
2245
2405
  }
2246
2406
  return null;
2247
2407
  }
2248
2408
  for (const field of ["module", "jsnext:main", "jsnext", "main"]) {
2249
2409
  if (typeof pkg[field] === "string") {
2250
- const entry = import_node_path4.default.join(pkgDir, pkg[field]);
2251
- if (import_node_fs4.default.existsSync(entry)) return entry;
2410
+ const entry = import_node_path5.default.join(pkgDir, pkg[field]);
2411
+ if (import_node_fs5.default.existsSync(entry)) return entry;
2252
2412
  }
2253
2413
  }
2254
- const indexFallback = import_node_path4.default.join(pkgDir, "index.js");
2255
- if (import_node_fs4.default.existsSync(indexFallback)) return indexFallback;
2414
+ const indexFallback = import_node_path5.default.join(pkgDir, "index.js");
2415
+ if (import_node_fs5.default.existsSync(indexFallback)) return indexFallback;
2256
2416
  return null;
2257
2417
  }
2258
2418
  function resolvePackageExports(exports2, key, pkgDir) {
2259
2419
  if (typeof exports2 === "string") {
2260
- return key === "." ? import_node_path4.default.join(pkgDir, exports2) : null;
2420
+ return key === "." ? import_node_path5.default.join(pkgDir, exports2) : null;
2261
2421
  }
2262
2422
  const entry = exports2[key];
2263
2423
  if (entry === void 0) {
@@ -2269,7 +2429,7 @@ function resolvePackageExports(exports2, key, pkgDir) {
2269
2429
  return resolveExportValue(entry, pkgDir);
2270
2430
  }
2271
2431
  function resolveExportValue(value, pkgDir) {
2272
- if (typeof value === "string") return import_node_path4.default.join(pkgDir, value);
2432
+ if (typeof value === "string") return import_node_path5.default.join(pkgDir, value);
2273
2433
  if (Array.isArray(value)) {
2274
2434
  for (const item of value) {
2275
2435
  const r = resolveExportValue(item, pkgDir);
@@ -2293,25 +2453,30 @@ function resolveUrlToFile(url, root) {
2293
2453
  const moduleName = cleanUrl.slice("/@modules/".length);
2294
2454
  return resolveNodeModule(root, moduleName);
2295
2455
  }
2296
- const filePath = import_node_path4.default.resolve(root, cleanUrl.replace(/^\//, ""));
2297
- if (import_node_fs4.default.existsSync(filePath) && import_node_fs4.default.statSync(filePath).isFile()) {
2456
+ const filePath = import_node_path5.default.resolve(root, cleanUrl.replace(/^\//, ""));
2457
+ if (import_node_fs5.default.existsSync(filePath) && import_node_fs5.default.statSync(filePath).isFile()) {
2298
2458
  return filePath;
2299
2459
  }
2300
2460
  for (const ext of RESOLVE_EXTENSIONS) {
2301
2461
  const withExt = filePath + ext;
2302
- if (import_node_fs4.default.existsSync(withExt)) return withExt;
2462
+ if (import_node_fs5.default.existsSync(withExt)) return withExt;
2303
2463
  }
2304
2464
  for (const ext of RESOLVE_EXTENSIONS) {
2305
- const indexFile = import_node_path4.default.join(filePath, "index" + ext);
2306
- if (import_node_fs4.default.existsSync(indexFile)) return indexFile;
2465
+ const indexFile = import_node_path5.default.join(filePath, "index" + ext);
2466
+ if (import_node_fs5.default.existsSync(indexFile)) return indexFile;
2307
2467
  }
2308
2468
  return null;
2309
2469
  }
2310
- function isModuleRequest(url) {
2470
+ function isModuleRequest(url, destination) {
2311
2471
  const cleanUrl = url.split("?")[0];
2312
2472
  if (/\.(ts|tsx|jsx|js|mjs|vue|css|json)$/.test(cleanUrl)) return true;
2313
2473
  if (cleanUrl.startsWith("/@modules/")) return true;
2314
- if (!import_node_path4.default.extname(cleanUrl)) return true;
2474
+ if (isAssetFile(cleanUrl)) {
2475
+ const query = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
2476
+ const isExplicitAssetModule = /(?:^|&)(?:url|raw)(?:&|$)/.test(query);
2477
+ return isExplicitAssetModule || destination === "script";
2478
+ }
2479
+ if (!import_node_path5.default.extname(cleanUrl)) return true;
2315
2480
  return false;
2316
2481
  }
2317
2482
  function getHmrClientCode() {
@@ -2323,11 +2488,15 @@ const hotModulesMap = new Map();
2323
2488
  const disposeMap = new Map();
2324
2489
  const pruneMap = new Map();
2325
2490
  const dataMap = new Map();
2491
+ const customListenersMap = new Map();
2326
2492
  let updateQueue = [];
2327
2493
  let pendingUpdateQueue = false;
2328
2494
 
2329
2495
  socket.addEventListener('message', async ({ data }) => {
2330
2496
  const payload = JSON.parse(data);
2497
+ // \u9ED8\u8BA4\u6D4F\u89C8\u5668 client \u53EA\u6D88\u8D39\u81EA\u5DF1\u7684 HMR \u6D88\u606F\uFF1Bnative/worker \u73AF\u5883\u901A\u8FC7\u5404\u81EA\u7684
2498
+ // HotChannel \u6216 app-level HMR \u534F\u8C03\u5668\u5904\u7406\u540C\u4E00 transport \u4E0A\u7684\u547D\u540D\u6D88\u606F\u3002
2499
+ if (payload.environment && payload.environment !== 'client') return;
2331
2500
  switch (payload.type) {
2332
2501
  case 'connected':
2333
2502
  console.debug('[nasti] connected.');
@@ -2360,8 +2529,24 @@ socket.addEventListener('message', async ({ data }) => {
2360
2529
  disposeMap.delete(path);
2361
2530
  pruneMap.delete(path);
2362
2531
  dataMap.delete(path);
2532
+ clearCustomListeners(path);
2363
2533
  }));
2364
2534
  break;
2535
+ case 'custom': {
2536
+ const listenersByOwner = customListenersMap.get(payload.event);
2537
+ if (!listenersByOwner) break;
2538
+ const results = await Promise.allSettled(
2539
+ [...listenersByOwner.values()]
2540
+ .flatMap((listeners) => [...listeners])
2541
+ .map((listener) => Promise.resolve().then(() => listener(payload.data)))
2542
+ );
2543
+ for (const result of results) {
2544
+ if (result.status === 'rejected') {
2545
+ console.error('[nasti] custom HMR event listener failed:', result.reason);
2546
+ }
2547
+ }
2548
+ break;
2549
+ }
2365
2550
  case 'error':
2366
2551
  console.error('[nasti] error:', payload.err.message);
2367
2552
  showErrorOverlay(payload.err);
@@ -2460,6 +2645,7 @@ export function createHotContext(ownerPath) {
2460
2645
  // \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
2461
2646
  const existing = hotModulesMap.get(ownerPath);
2462
2647
  if (existing) existing.callbacks = [];
2648
+ clearCustomListeners(ownerPath);
2463
2649
 
2464
2650
  const acceptDeps = (deps, callback = () => {}) => {
2465
2651
  const mod = hotModulesMap.get(ownerPath) || { id: ownerPath, callbacks: [] };
@@ -2485,20 +2671,47 @@ export function createHotContext(ownerPath) {
2485
2671
  dispose(callback) {
2486
2672
  disposeMap.set(ownerPath, callback);
2487
2673
  },
2674
+ on(event, callback) {
2675
+ let listenersByOwner = customListenersMap.get(event);
2676
+ if (!listenersByOwner) {
2677
+ listenersByOwner = new Map();
2678
+ customListenersMap.set(event, listenersByOwner);
2679
+ }
2680
+ let listeners = listenersByOwner.get(ownerPath);
2681
+ if (!listeners) {
2682
+ listeners = new Set();
2683
+ listenersByOwner.set(ownerPath, listeners);
2684
+ }
2685
+ listeners.add(callback);
2686
+ },
2687
+ off(event, callback) {
2688
+ const listenersByOwner = customListenersMap.get(event);
2689
+ const listeners = listenersByOwner?.get(ownerPath);
2690
+ listeners?.delete(callback);
2691
+ if (listeners?.size === 0) listenersByOwner.delete(ownerPath);
2692
+ if (listenersByOwner?.size === 0) customListenersMap.delete(event);
2693
+ },
2488
2694
  invalidate() {
2489
2695
  location.reload();
2490
2696
  },
2491
2697
  data: dataMap.get(ownerPath),
2492
2698
  };
2493
2699
  }
2700
+
2701
+ function clearCustomListeners(ownerPath) {
2702
+ for (const [event, listenersByOwner] of customListenersMap) {
2703
+ listenersByOwner.delete(ownerPath);
2704
+ if (listenersByOwner.size === 0) customListenersMap.delete(event);
2705
+ }
2706
+ }
2494
2707
  `;
2495
2708
  }
2496
- var import_node_path4, import_node_fs4, import_node_module, import_node_url2, import_picocolors3, import_meta, __dirname_esm, __require, __refreshRuntimeCache, REACT_REFRESH_BOUNDARY_HELPERS, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
2709
+ var import_node_path5, import_node_fs5, import_node_module, import_node_url2, import_picocolors3, import_meta, __dirname_esm, __require, __refreshRuntimeCache, REACT_REFRESH_BOUNDARY_HELPERS, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
2497
2710
  var init_middleware = __esm({
2498
2711
  "src/server/middleware.ts"() {
2499
2712
  "use strict";
2500
- import_node_path4 = __toESM(require("path"), 1);
2501
- import_node_fs4 = __toESM(require("fs"), 1);
2713
+ import_node_path5 = __toESM(require("path"), 1);
2714
+ import_node_fs5 = __toESM(require("fs"), 1);
2502
2715
  import_node_module = require("module");
2503
2716
  import_node_url2 = require("url");
2504
2717
  import_picocolors3 = __toESM(require("picocolors"), 1);
@@ -2506,8 +2719,9 @@ var init_middleware = __esm({
2506
2719
  init_html();
2507
2720
  init_env();
2508
2721
  init_url();
2722
+ init_assets();
2509
2723
  import_meta = {};
2510
- __dirname_esm = import_node_path4.default.dirname((0, import_node_url2.fileURLToPath)(import_meta.url));
2724
+ __dirname_esm = import_node_path5.default.dirname((0, import_node_url2.fileURLToPath)(import_meta.url));
2511
2725
  __require = (0, import_node_module.createRequire)(import_meta.url);
2512
2726
  __refreshRuntimeCache = null;
2513
2727
  REACT_REFRESH_BOUNDARY_HELPERS = `
@@ -2588,30 +2802,37 @@ window.__vite_plugin_react_preamble_installed__ = true;
2588
2802
  });
2589
2803
 
2590
2804
  // src/server/hmr.ts
2591
- async function handleFileChange(file, server) {
2592
- const { moduleGraph, ws, config } = server;
2805
+ async function handleFileChange(file, server, environmentName = "client", timestamp = Date.now()) {
2806
+ const { config } = server;
2807
+ const environment = server.environments[environmentName];
2808
+ if (!environment) {
2809
+ throw new Error(`[nasti] unknown dev environment "${environmentName}"`);
2810
+ }
2811
+ const moduleGraph = environment.moduleGraph;
2593
2812
  const logger = config.logger;
2594
- const relativePath = "/" + import_node_path5.default.relative(config.root, file);
2595
- const shortFile = import_node_path5.default.relative(config.root, file);
2813
+ const relativePath = "/" + import_node_path6.default.relative(config.root, file);
2814
+ const shortFile = import_node_path6.default.relative(config.root, file);
2596
2815
  const mods = moduleGraph.getModulesByFile(file);
2597
2816
  if (!mods || mods.size === 0) {
2598
- return;
2817
+ return null;
2599
2818
  }
2600
2819
  const updates = [];
2601
- const timestamp = Date.now();
2602
2820
  const graph = moduleGraph;
2603
2821
  const invalidatedModules = /* @__PURE__ */ new Set();
2822
+ const affectedSet = /* @__PURE__ */ new Set();
2823
+ let fullReload = false;
2604
2824
  for (const mod of mods) {
2605
2825
  graph.invalidateModuleAndImporters(mod, timestamp, invalidatedModules);
2606
2826
  const ctx = {
2607
2827
  file,
2608
2828
  timestamp,
2609
2829
  modules: [mod],
2610
- read: () => import_node_fs5.default.readFileSync(file, "utf-8"),
2611
- server
2830
+ read: () => import_node_fs6.default.readFileSync(file, "utf-8"),
2831
+ server,
2832
+ environment
2612
2833
  };
2613
2834
  let affectedModules = [mod];
2614
- for (const plugin of config.plugins) {
2835
+ for (const plugin of environment.plugins) {
2615
2836
  if (plugin.handleHotUpdate) {
2616
2837
  const result = await plugin.handleHotUpdate(ctx);
2617
2838
  if (result) {
@@ -2620,12 +2841,12 @@ async function handleFileChange(file, server) {
2620
2841
  }
2621
2842
  }
2622
2843
  for (const affected of affectedModules) {
2844
+ affectedSet.add(affected);
2623
2845
  graph.invalidateModuleAndImporters(affected, timestamp, invalidatedModules);
2624
2846
  const boundaries = graph.getHmrBoundaries(affected);
2625
2847
  if (boundaries.length === 0) {
2626
- logger.info(import_picocolors4.default.green("page reload ") + import_picocolors4.default.dim(shortFile), { timestamp: true });
2627
- ws.send({ type: "full-reload", path: relativePath });
2628
- return;
2848
+ fullReload = true;
2849
+ continue;
2629
2850
  }
2630
2851
  for (const { boundary, acceptedVia } of boundaries) {
2631
2852
  const update = {
@@ -2642,20 +2863,37 @@ async function handleFileChange(file, server) {
2642
2863
  }
2643
2864
  }
2644
2865
  }
2645
- if (updates.length > 0) {
2866
+ const transformed = await Promise.all(
2867
+ [...affectedSet].map(async (module2) => ({
2868
+ module: module2,
2869
+ result: await environment.transformRequest(module2.url)
2870
+ }))
2871
+ );
2872
+ const logPrefix = environmentName === "client" ? "" : `[${environmentName}] `;
2873
+ if (fullReload) {
2874
+ logger.info(import_picocolors4.default.green(`${logPrefix}reload `) + import_picocolors4.default.dim(shortFile), { timestamp: true });
2875
+ environment.hot.send({ type: "full-reload", path: relativePath });
2876
+ } else if (updates.length > 0) {
2646
2877
  logger.info(
2647
- updates.map((u) => import_picocolors4.default.green("hmr update ") + import_picocolors4.default.dim(u.path)).join("\n"),
2878
+ updates.map((u) => import_picocolors4.default.green(`${logPrefix}hmr update `) + import_picocolors4.default.dim(u.path)).join("\n"),
2648
2879
  { timestamp: true }
2649
2880
  );
2650
- ws.send({ type: "update", updates });
2881
+ environment.hot.send({ type: "update", updates });
2651
2882
  }
2883
+ return {
2884
+ environment,
2885
+ modules: [...affectedSet],
2886
+ updates,
2887
+ transformed,
2888
+ fullReload
2889
+ };
2652
2890
  }
2653
- var import_node_path5, import_node_fs5, import_picocolors4;
2891
+ var import_node_path6, import_node_fs6, import_picocolors4;
2654
2892
  var init_hmr = __esm({
2655
2893
  "src/server/hmr.ts"() {
2656
2894
  "use strict";
2657
- import_node_path5 = __toESM(require("path"), 1);
2658
- import_node_fs5 = __toESM(require("fs"), 1);
2895
+ import_node_path6 = __toESM(require("path"), 1);
2896
+ import_node_fs6 = __toESM(require("fs"), 1);
2659
2897
  import_picocolors4 = __toESM(require("picocolors"), 1);
2660
2898
  }
2661
2899
  });
@@ -2663,7 +2901,7 @@ var init_hmr = __esm({
2663
2901
  // src/plugins/resolve.ts
2664
2902
  function resolvePlugin(config) {
2665
2903
  const { alias, extensions } = config.resolve;
2666
- const require2 = (0, import_node_module2.createRequire)(import_node_path6.default.resolve(config.root, "package.json"));
2904
+ const require2 = (0, import_node_module2.createRequire)(import_node_path7.default.resolve(config.root, "package.json"));
2667
2905
  const aliasEntries = Object.entries(alias).sort(
2668
2906
  ([a], [b]) => b.length - a.length
2669
2907
  );
@@ -2671,10 +2909,10 @@ function resolvePlugin(config) {
2671
2909
  if (config.framework === "vue") {
2672
2910
  try {
2673
2911
  const vuePkgJson = require2.resolve("vue/package.json", { paths: [config.root] });
2674
- const vueDir = import_node_path6.default.dirname(vuePkgJson);
2675
- const mod = JSON.parse(import_node_fs6.default.readFileSync(vuePkgJson, "utf-8")).module;
2676
- const entry = import_node_path6.default.join(vueDir, mod ?? "dist/vue.runtime.esm-bundler.js");
2677
- if (import_node_fs6.default.existsSync(entry)) vueRuntimeEntry = entry;
2912
+ const vueDir = import_node_path7.default.dirname(vuePkgJson);
2913
+ const mod = JSON.parse(import_node_fs7.default.readFileSync(vuePkgJson, "utf-8")).module;
2914
+ const entry = import_node_path7.default.join(vueDir, mod ?? "dist/vue.runtime.esm-bundler.js");
2915
+ if (import_node_fs7.default.existsSync(entry)) vueRuntimeEntry = entry;
2678
2916
  } catch {
2679
2917
  }
2680
2918
  }
@@ -2686,24 +2924,24 @@ function resolvePlugin(config) {
2686
2924
  if (source === key || source.startsWith(key + "/")) {
2687
2925
  const aliasBase = resolveAliasTarget2(value, config.root);
2688
2926
  const sub = source.slice(key.length).replace(/^\//, "");
2689
- const target = sub ? import_node_path6.default.join(aliasBase, sub) : aliasBase;
2927
+ const target = sub ? import_node_path7.default.join(aliasBase, sub) : aliasBase;
2690
2928
  const resolved = tryResolveFile(target, extensions);
2691
2929
  if (resolved) return resolved;
2692
2930
  break;
2693
2931
  }
2694
2932
  }
2695
2933
  if (source.startsWith("/") && !source.startsWith("//")) {
2696
- const rootRelative = import_node_path6.default.join(config.root, source.slice(1));
2934
+ const rootRelative = import_node_path7.default.join(config.root, source.slice(1));
2697
2935
  const resolved = tryResolveFile(rootRelative, extensions);
2698
2936
  if (resolved) return resolved;
2699
2937
  }
2700
- if (import_node_path6.default.isAbsolute(source) && import_node_fs6.default.existsSync(source)) {
2938
+ if (import_node_path7.default.isAbsolute(source) && import_node_fs7.default.existsSync(source)) {
2701
2939
  const resolved = tryResolveFile(source, extensions);
2702
2940
  if (resolved) return resolved;
2703
2941
  }
2704
2942
  if (source.startsWith(".")) {
2705
- const dir = importer ? import_node_path6.default.dirname(importer) : config.root;
2706
- const absolute = import_node_path6.default.resolve(dir, source);
2943
+ const dir = importer ? import_node_path7.default.dirname(importer) : config.root;
2944
+ const absolute = import_node_path7.default.resolve(dir, source);
2707
2945
  const resolved = tryResolveFile(absolute, extensions);
2708
2946
  if (resolved) return resolved;
2709
2947
  }
@@ -2712,7 +2950,7 @@ function resolvePlugin(config) {
2712
2950
  if (config.command === "build") return null;
2713
2951
  try {
2714
2952
  const resolved = require2.resolve(source, {
2715
- paths: [importer ? import_node_path6.default.dirname(importer) : config.root]
2953
+ paths: [importer ? import_node_path7.default.dirname(importer) : config.root]
2716
2954
  });
2717
2955
  return resolved;
2718
2956
  } catch {
@@ -2723,46 +2961,46 @@ function resolvePlugin(config) {
2723
2961
  },
2724
2962
  load(id) {
2725
2963
  if (id.startsWith("\0")) return null;
2726
- if (!import_node_fs6.default.existsSync(id)) return null;
2964
+ if (!import_node_fs7.default.existsSync(id)) return null;
2727
2965
  if (id.endsWith(".json")) {
2728
- const content = import_node_fs6.default.readFileSync(id, "utf-8");
2966
+ const content = import_node_fs7.default.readFileSync(id, "utf-8");
2729
2967
  return `export default ${content}`;
2730
2968
  }
2731
- return import_node_fs6.default.readFileSync(id, "utf-8");
2969
+ return null;
2732
2970
  }
2733
2971
  };
2734
2972
  }
2735
2973
  function resolveAliasTarget2(value, root) {
2736
- if (import_node_path6.default.isAbsolute(value) && import_node_fs6.default.existsSync(value)) return value;
2737
- if (value.startsWith("/")) return import_node_path6.default.join(root, value.slice(1));
2738
- return import_node_path6.default.resolve(root, value);
2974
+ if (import_node_path7.default.isAbsolute(value) && import_node_fs7.default.existsSync(value)) return value;
2975
+ if (value.startsWith("/")) return import_node_path7.default.join(root, value.slice(1));
2976
+ return import_node_path7.default.resolve(root, value);
2739
2977
  }
2740
2978
  function tryResolveFile(file, extensions) {
2741
- if (import_node_fs6.default.existsSync(file) && import_node_fs6.default.statSync(file).isFile()) {
2979
+ if (import_node_fs7.default.existsSync(file) && import_node_fs7.default.statSync(file).isFile()) {
2742
2980
  return file;
2743
2981
  }
2744
2982
  for (const ext of extensions) {
2745
2983
  const withExt = file + ext;
2746
- if (import_node_fs6.default.existsSync(withExt) && import_node_fs6.default.statSync(withExt).isFile()) {
2984
+ if (import_node_fs7.default.existsSync(withExt) && import_node_fs7.default.statSync(withExt).isFile()) {
2747
2985
  return withExt;
2748
2986
  }
2749
2987
  }
2750
- if (import_node_fs6.default.existsSync(file) && import_node_fs6.default.statSync(file).isDirectory()) {
2988
+ if (import_node_fs7.default.existsSync(file) && import_node_fs7.default.statSync(file).isDirectory()) {
2751
2989
  for (const ext of extensions) {
2752
- const indexFile = import_node_path6.default.join(file, "index" + ext);
2753
- if (import_node_fs6.default.existsSync(indexFile)) {
2990
+ const indexFile = import_node_path7.default.join(file, "index" + ext);
2991
+ if (import_node_fs7.default.existsSync(indexFile)) {
2754
2992
  return indexFile;
2755
2993
  }
2756
2994
  }
2757
2995
  }
2758
2996
  return null;
2759
2997
  }
2760
- var import_node_path6, import_node_fs6, import_node_module2;
2998
+ var import_node_path7, import_node_fs7, import_node_module2;
2761
2999
  var init_resolve = __esm({
2762
3000
  "src/plugins/resolve.ts"() {
2763
3001
  "use strict";
2764
- import_node_path6 = __toESM(require("path"), 1);
2765
- import_node_fs6 = __toESM(require("fs"), 1);
3002
+ import_node_path7 = __toESM(require("path"), 1);
3003
+ import_node_fs7 = __toESM(require("fs"), 1);
2766
3004
  import_node_module2 = require("module");
2767
3005
  }
2768
3006
  });
@@ -3646,12 +3884,31 @@ var init_node = __esm({
3646
3884
  function createCssEngine() {
3647
3885
  return {
3648
3886
  styles: /* @__PURE__ */ new Map(),
3887
+ modules: /* @__PURE__ */ new Map(),
3888
+ chunks: /* @__PURE__ */ new Map(),
3649
3889
  entryCss: /* @__PURE__ */ new Map(),
3650
3890
  allCss: [],
3651
3891
  pendingSingle: [],
3652
3892
  singleFileName: null
3653
3893
  };
3654
3894
  }
3895
+ function getCssMetadata(engine) {
3896
+ return {
3897
+ modules: Object.fromEntries(
3898
+ [...engine.modules].map(([id, module2]) => [id, { ...module2 }])
3899
+ ),
3900
+ chunks: Object.fromEntries(
3901
+ [...engine.chunks].map(([fileName, chunk]) => [
3902
+ fileName,
3903
+ {
3904
+ fileName,
3905
+ moduleIds: [...chunk.moduleIds],
3906
+ cssFileNames: [...chunk.cssFileNames]
3907
+ }
3908
+ ])
3909
+ )
3910
+ };
3911
+ }
3655
3912
  function normalizeCssModuleId(id) {
3656
3913
  return id.startsWith("\0") ? id.slice(1) : id;
3657
3914
  }
@@ -3706,7 +3963,7 @@ function hasTailwindDirectives(css) {
3706
3963
  }
3707
3964
  async function loadTailwind(projectRoot) {
3708
3965
  if (cached && cachedRoot === projectRoot) return cached;
3709
- const req = (0, import_node_module3.createRequire)(import_node_path7.default.join(projectRoot, "package.json"));
3966
+ const req = (0, import_node_module3.createRequire)(import_node_path8.default.join(projectRoot, "package.json"));
3710
3967
  let nodePath;
3711
3968
  let oxidePath;
3712
3969
  try {
@@ -3727,7 +3984,7 @@ async function compileTailwind(css, fromFile, projectRoot) {
3727
3984
  const { node, oxide } = await loadTailwind(projectRoot);
3728
3985
  const dependencies = [];
3729
3986
  const compiler2 = await node.compile(css, {
3730
- base: import_node_path7.default.dirname(fromFile),
3987
+ base: import_node_path8.default.dirname(fromFile),
3731
3988
  from: fromFile,
3732
3989
  onDependency: (p) => dependencies.push(p)
3733
3990
  });
@@ -3738,11 +3995,11 @@ async function compileTailwind(css, fromFile, projectRoot) {
3738
3995
  dependencies: [...dependencies, ...scanner.files]
3739
3996
  };
3740
3997
  }
3741
- var import_node_path7, import_node_module3, import_node_url3, TAILWIND_DIRECTIVE_RE, cached, cachedRoot;
3998
+ var import_node_path8, import_node_module3, import_node_url3, TAILWIND_DIRECTIVE_RE, cached, cachedRoot;
3742
3999
  var init_tailwind = __esm({
3743
4000
  "src/plugins/tailwind.ts"() {
3744
4001
  "use strict";
3745
- import_node_path7 = __toESM(require("path"), 1);
4002
+ import_node_path8 = __toESM(require("path"), 1);
3746
4003
  import_node_module3 = require("module");
3747
4004
  import_node_url3 = require("url");
3748
4005
  TAILWIND_DIRECTIVE_RE = /@(?:import\s+["']tailwindcss(?:\b|\/)|tailwind\b|theme\b|apply\b|plugin\b|source\b|utility\b|variant\b|custom-variant\b|reference\b)/;
@@ -3771,15 +4028,29 @@ function cssPlugin(config, engine, consumer = "client") {
3771
4028
  }
3772
4029
  const rewritten = rewriteCssUrls(cssSource, file, config.root);
3773
4030
  const escaped = JSON.stringify(rewritten);
4031
+ const normalizedId = normalizeCssModuleId(id);
4032
+ const cssModule = { id: normalizedId, source: code, code: rewritten };
4033
+ const map = config.build.sourcemap ? createIdentitySourceMap(code, id) : void 0;
4034
+ engine?.modules.set(normalizedId, cssModule);
4035
+ this.environment?.setCssModule?.(cssModule);
3774
4036
  if (query === "inline") {
3775
4037
  return { code: `export default ${escaped};
3776
- `, moduleType: "js" };
4038
+ `, map, moduleType: "js" };
3777
4039
  }
3778
4040
  if (consumer === "server") {
3779
4041
  return { code: `export default ${escaped};
3780
- `, moduleType: "js" };
4042
+ `, map, moduleType: "js" };
3781
4043
  }
3782
4044
  if (config.command === "serve") {
4045
+ if (config.build.css.inject === false) {
4046
+ return {
4047
+ code: `export default ${escaped};
4048
+ `,
4049
+ map,
4050
+ moduleType: "js",
4051
+ moduleSideEffects: "no-treeshake"
4052
+ };
4053
+ }
3783
4054
  return {
3784
4055
  code: `
3785
4056
  const css = ${escaped};
@@ -3803,16 +4074,18 @@ if (import.meta.hot) {
3803
4074
 
3804
4075
  export default css;
3805
4076
  `,
4077
+ map,
3806
4078
  // bundled dev(DevEngine)下该模块会进 Rolldown:不标 js 会按 .css
3807
4079
  // 扩展名走 CSS 管线触发 #4271 报错;unbundled 中间件忽略此字段
3808
4080
  moduleType: "js"
3809
4081
  };
3810
4082
  }
3811
4083
  if (engine) {
3812
- engine.styles.set(normalizeCssModuleId(id), rewritten);
4084
+ engine.styles.set(normalizedId, rewritten);
3813
4085
  return {
3814
4086
  code: `export default '';
3815
4087
  `,
4088
+ map,
3816
4089
  moduleType: "js",
3817
4090
  // 防止空 stub 被 tree-shake 出 chunk.moduleIds(css-post 靠它定位)
3818
4091
  moduleSideEffects: "no-treeshake"
@@ -3832,26 +4105,43 @@ document.head.appendChild(style);
3832
4105
 
3833
4106
  export default css;
3834
4107
  `,
4108
+ map,
3835
4109
  moduleType: "js"
3836
4110
  };
3837
4111
  }
3838
4112
  };
3839
4113
  }
4114
+ function createIdentitySourceMap(code, id) {
4115
+ const map = new import_source_map_js.SourceMapGenerator({ file: id });
4116
+ const lines = code.split("\n");
4117
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
4118
+ for (let column = 0; column <= lines[lineIndex].length; column++) {
4119
+ map.addMapping({
4120
+ generated: { line: lineIndex + 1, column },
4121
+ original: { line: lineIndex + 1, column },
4122
+ source: id
4123
+ });
4124
+ }
4125
+ }
4126
+ map.setSourceContent(id, code);
4127
+ return map.toJSON();
4128
+ }
3840
4129
  function rewriteCssUrls(css, from, root) {
3841
4130
  return css.replace(/url\(\s*['"]?([^'")\s]+)['"]?\s*\)/g, (match, url) => {
3842
4131
  if (url.startsWith("/") || url.startsWith("data:") || url.startsWith("http")) {
3843
4132
  return match;
3844
4133
  }
3845
- const resolved = import_node_path8.default.resolve(import_node_path8.default.dirname(from), url);
3846
- const relative = "/" + import_node_path8.default.relative(root, resolved).replace(/\\/g, "/");
4134
+ const resolved = import_node_path9.default.resolve(import_node_path9.default.dirname(from), url);
4135
+ const relative = "/" + import_node_path9.default.relative(root, resolved).replace(/\\/g, "/");
3847
4136
  return `url(${relative})`;
3848
4137
  });
3849
4138
  }
3850
- var import_node_path8;
4139
+ var import_node_path9, import_source_map_js;
3851
4140
  var init_css = __esm({
3852
4141
  "src/plugins/css.ts"() {
3853
4142
  "use strict";
3854
- import_node_path8 = __toESM(require("path"), 1);
4143
+ import_node_path9 = __toESM(require("path"), 1);
4144
+ import_source_map_js = require("source-map-js");
3855
4145
  init_css_engine();
3856
4146
  init_tailwind();
3857
4147
  }
@@ -3861,19 +4151,27 @@ var init_css = __esm({
3861
4151
  function collectChunkCss(chunk, engine) {
3862
4152
  const ids = chunk.moduleIds ?? Object.keys(chunk.modules);
3863
4153
  let css = "";
4154
+ const moduleIds = [];
3864
4155
  for (const id of ids) {
3865
- const styles = engine.styles.get(normalizeCssModuleId(id));
3866
- if (styles) css += styles + "\n";
4156
+ const normalizedId = normalizeCssModuleId(id);
4157
+ const styles = engine.styles.get(normalizedId);
4158
+ if (styles) {
4159
+ css += styles + "\n";
4160
+ moduleIds.push(normalizedId);
4161
+ }
3867
4162
  }
3868
- return css;
4163
+ return { css, moduleIds };
3869
4164
  }
3870
4165
  function cssPostPlugin(config, engine) {
3871
4166
  return {
3872
4167
  name: "nasti:css-post",
3873
4168
  enforce: "post",
3874
4169
  async renderChunk(code, chunk) {
3875
- const css = collectChunkCss(chunk, engine);
4170
+ const { css, moduleIds } = collectChunkCss(chunk, engine);
3876
4171
  if (!css) return null;
4172
+ const ownership = { moduleIds, cssFileNames: [] };
4173
+ engine.chunks.set(chunk.fileName, ownership);
4174
+ if (config.build.css.emit === false) return null;
3877
4175
  if (!config.build.cssCodeSplit) {
3878
4176
  engine.pendingSingle.push(css);
3879
4177
  return null;
@@ -3886,6 +4184,7 @@ function cssPostPlugin(config, engine) {
3886
4184
  });
3887
4185
  const fileName = this.getFileName(ref);
3888
4186
  engine.allCss.push(fileName);
4187
+ ownership.cssFileNames.push(fileName);
3889
4188
  if (chunk.isEntry) {
3890
4189
  const key = chunk.facadeModuleId ?? chunk.name;
3891
4190
  const existing = engine.entryCss.get(key) ?? [];
@@ -3893,13 +4192,14 @@ function cssPostPlugin(config, engine) {
3893
4192
  engine.entryCss.set(key, existing);
3894
4193
  return null;
3895
4194
  }
4195
+ if (config.build.css.inject === false) return null;
3896
4196
  const href = JSON.stringify(config.base + fileName);
3897
4197
  const snippet = `
3898
4198
  ;(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){}})();`;
3899
4199
  return { code: code + snippet, map: null };
3900
4200
  },
3901
4201
  augmentChunkHash(chunk) {
3902
- const css = collectChunkCss(chunk, engine);
4202
+ const { css } = collectChunkCss(chunk, engine);
3903
4203
  return css || void 0;
3904
4204
  },
3905
4205
  async generateBundle() {
@@ -3910,6 +4210,9 @@ function cssPostPlugin(config, engine) {
3910
4210
  const fileName = this.getFileName(ref);
3911
4211
  engine.singleFileName = fileName;
3912
4212
  engine.allCss.push(fileName);
4213
+ for (const ownership of engine.chunks.values()) {
4214
+ if (ownership.moduleIds.length > 0) ownership.cssFileNames.push(fileName);
4215
+ }
3913
4216
  }
3914
4217
  };
3915
4218
  }
@@ -3920,76 +4223,6 @@ var init_css_post = __esm({
3920
4223
  }
3921
4224
  });
3922
4225
 
3923
- // src/plugins/assets.ts
3924
- function assetsPlugin(config) {
3925
- return {
3926
- name: "nasti:assets",
3927
- resolveId(source) {
3928
- if (source.endsWith("?url") || source.endsWith("?raw")) {
3929
- return source;
3930
- }
3931
- return null;
3932
- },
3933
- load(id) {
3934
- const ext = import_node_path9.default.extname(id.replace(/\?.*$/, ""));
3935
- if (id.endsWith("?raw")) {
3936
- const file = id.slice(0, -4);
3937
- if (import_node_fs7.default.existsSync(file)) {
3938
- const content = import_node_fs7.default.readFileSync(file, "utf-8");
3939
- return `export default ${JSON.stringify(content)}`;
3940
- }
3941
- }
3942
- if (id.endsWith("?url") || ASSET_EXTENSIONS.has(ext)) {
3943
- const file = id.replace(/\?.*$/, "");
3944
- if (!import_node_fs7.default.existsSync(file)) return null;
3945
- if (config.command === "serve") {
3946
- const url = "/" + import_node_path9.default.relative(config.root, file);
3947
- return `export default ${JSON.stringify(url)}`;
3948
- }
3949
- const content = import_node_fs7.default.readFileSync(file);
3950
- const hash = import_node_crypto.default.createHash("sha256").update(content).digest("hex").slice(0, 8);
3951
- const basename = import_node_path9.default.basename(file, ext);
3952
- const hashedName = `${config.build.assetsDir}/${basename}.${hash}${ext}`;
3953
- return `export default ${JSON.stringify(config.base + hashedName)}`;
3954
- }
3955
- return null;
3956
- }
3957
- };
3958
- }
3959
- var import_node_path9, import_node_fs7, import_node_crypto, ASSET_EXTENSIONS;
3960
- var init_assets = __esm({
3961
- "src/plugins/assets.ts"() {
3962
- "use strict";
3963
- import_node_path9 = __toESM(require("path"), 1);
3964
- import_node_fs7 = __toESM(require("fs"), 1);
3965
- import_node_crypto = __toESM(require("crypto"), 1);
3966
- ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
3967
- ".png",
3968
- ".jpg",
3969
- ".jpeg",
3970
- ".gif",
3971
- ".svg",
3972
- ".ico",
3973
- ".webp",
3974
- ".avif",
3975
- ".mp4",
3976
- ".webm",
3977
- ".ogg",
3978
- ".mp3",
3979
- ".wav",
3980
- ".flac",
3981
- ".aac",
3982
- ".woff",
3983
- ".woff2",
3984
- ".eot",
3985
- ".ttf",
3986
- ".otf",
3987
- ".pdf",
3988
- ".txt"
3989
- ]);
3990
- }
3991
- });
3992
-
3993
4226
  // src/plugins/vue.ts
3994
4227
  async function loadVueCompiler() {
3995
4228
  if (compiler) return compiler;
@@ -4000,9 +4233,10 @@ async function loadVueCompiler() {
4000
4233
  return null;
4001
4234
  }
4002
4235
  }
4003
- function vuePlugin(config) {
4236
+ function vuePlugin(config, environmentName = "client") {
4004
4237
  const isDev = config.command === "serve";
4005
4238
  const descriptorCache = /* @__PURE__ */ new Map();
4239
+ const vueOptions = config.environments[environmentName]?.vue ?? {};
4006
4240
  return {
4007
4241
  name: "nasti:vue",
4008
4242
  enforce: "pre",
@@ -4022,32 +4256,63 @@ function vuePlugin(config) {
4022
4256
  const sfc = await loadVueCompiler();
4023
4257
  if (!sfc) return null;
4024
4258
  const [, filePath, indexStr] = match;
4025
- let descriptor = descriptorCache.get(filePath);
4026
- if (!descriptor) {
4259
+ let cached2 = descriptorCache.get(filePath);
4260
+ if (!cached2) {
4027
4261
  try {
4028
4262
  const fs13 = await import("fs");
4029
- const source = fs13.readFileSync(filePath, "utf-8");
4030
- const parsed = sfc.parse(source, { filename: filePath });
4263
+ const rawSource = fs13.readFileSync(filePath, "utf-8");
4264
+ const transformedSfc = await applySourceTransform(
4265
+ vueOptions.transformSfc,
4266
+ rawSource,
4267
+ { filename: filePath, environmentName, type: "sfc" }
4268
+ );
4269
+ const parsed = sfc.parse(transformedSfc.code, {
4270
+ ...vueOptions.parse,
4271
+ filename: filePath,
4272
+ sourceMap: true
4273
+ });
4031
4274
  if (parsed.errors.length) return null;
4032
- descriptor = parsed.descriptor;
4033
- descriptorCache.set(filePath, descriptor);
4275
+ cached2 = {
4276
+ descriptor: parsed.descriptor,
4277
+ sourceMap: transformedSfc.map
4278
+ };
4279
+ descriptorCache.set(filePath, cached2);
4034
4280
  } catch {
4035
4281
  return null;
4036
4282
  }
4037
4283
  }
4284
+ const { descriptor, sourceMap: sfcSourceMap } = cached2;
4038
4285
  const index2 = parseInt(indexStr ?? "0", 10);
4039
4286
  const style = descriptor.styles[index2];
4040
4287
  if (!style) return null;
4041
4288
  const scopeId = hashId(filePath);
4289
+ const transformedStyle = await applySourceTransform(
4290
+ vueOptions.transformStyle,
4291
+ style.content,
4292
+ { filename: filePath, environmentName, type: "style", index: index2 }
4293
+ );
4294
+ const wantsStyleSourceMap = !!config.build.sourcemap || transformedStyle.map != null || sfcSourceMap != null;
4295
+ const styleInputMap = wantsStyleSourceMap ? composeSourceMapChain(
4296
+ [transformedStyle.map, style.map, sfcSourceMap],
4297
+ { filename: filePath, environmentName, type: "style", index: index2 }
4298
+ ) : void 0;
4042
4299
  const result = await sfc.compileStyleAsync({
4043
- source: style.content,
4300
+ ...vueOptions.style,
4301
+ source: transformedStyle.code,
4044
4302
  filename: filePath,
4045
4303
  id: `data-v-${scopeId}`,
4046
4304
  scoped: style.scoped ?? false,
4305
+ inMap: styleInputMap,
4047
4306
  // <style lang="scss|less|stylus"> 需经对应预处理器(缺省 undefined = 纯 CSS)
4048
4307
  preprocessLang: style.lang
4049
4308
  });
4050
- return result.code;
4309
+ if (transformedStyle.map != null && result.map == null) {
4310
+ warnUnchainableMap(
4311
+ { filename: filePath, environmentName, type: "style", index: index2 },
4312
+ "compiler-sfc did not return a style map"
4313
+ );
4314
+ }
4315
+ return wantsStyleSourceMap ? { code: result.code, map: result.map } : result.code;
4051
4316
  },
4052
4317
  async transform(code, id) {
4053
4318
  if (!VUE_FILE_RE.test(id) && !VUE_QUERY_RE.test(id)) return null;
@@ -4059,57 +4324,144 @@ function vuePlugin(config) {
4059
4324
  if (VUE_QUERY_RE.test(id)) {
4060
4325
  return null;
4061
4326
  }
4062
- const { descriptor, errors } = sfc.parse(code, { filename: id });
4327
+ const transformedSfc = await applySourceTransform(
4328
+ vueOptions.transformSfc,
4329
+ code,
4330
+ { filename: id, environmentName, type: "sfc" }
4331
+ );
4332
+ code = transformedSfc.code;
4333
+ const { descriptor, errors } = sfc.parse(code, {
4334
+ ...vueOptions.parse,
4335
+ filename: id,
4336
+ sourceMap: true
4337
+ });
4063
4338
  if (errors.length) {
4064
- console.error(`[nasti:vue] Parse error in ${id}:`, errors[0].message);
4339
+ const firstError = errors[0];
4340
+ console.error(
4341
+ `[nasti:vue] Parse error in ${id}:`,
4342
+ typeof firstError === "string" ? firstError : firstError.message
4343
+ );
4065
4344
  return null;
4066
4345
  }
4067
- descriptorCache.set(id, descriptor);
4346
+ descriptorCache.set(id, {
4347
+ descriptor,
4348
+ sourceMap: transformedSfc.map
4349
+ });
4068
4350
  const scopeId = hashId(id);
4351
+ const wantsSourceMap = !!config.build.sourcemap || transformedSfc.map != null;
4069
4352
  let scriptCode = "";
4353
+ let scriptMap;
4070
4354
  if (descriptor.script || descriptor.scriptSetup) {
4355
+ const inlineTemplate = vueOptions.script?.inlineTemplate !== false;
4071
4356
  const compiled = sfc.compileScript(descriptor, {
4357
+ ...vueOptions.script,
4072
4358
  id: scopeId,
4073
4359
  isProd: !isDev,
4074
- inlineTemplate: true,
4360
+ inlineTemplate,
4361
+ sourceMap: wantsSourceMap,
4075
4362
  // 让 compileScript 产出 `const __sfc__ = ...`(而非默认的 `export default {...}`)。
4076
4363
  // 否则下方追加的 `__sfc__.render` / `__sfc__.__scopeId` / HMR 记录会引用一个
4077
4364
  // 不存在的 `__sfc__`,并与 compileScript 自带的 `export default` 形成双重默认导出。
4078
4365
  genDefaultAs: "__sfc__"
4079
4366
  });
4080
4367
  scriptCode = compiled.content;
4368
+ scriptMap = composeSourceMapChain(
4369
+ [compiled.map, transformedSfc.map],
4370
+ { filename: id, environmentName, type: "sfc" }
4371
+ );
4372
+ if (transformedSfc.map != null && scriptMap == null) {
4373
+ warnUnchainableMap(
4374
+ { filename: id, environmentName, type: "sfc" },
4375
+ "compiler-sfc did not return a script map"
4376
+ );
4377
+ }
4081
4378
  }
4082
4379
  let templateCode = "";
4083
- if (descriptor.template && !descriptor.scriptSetup) {
4380
+ let templateMap;
4381
+ const scriptSetupIsInline = !!descriptor.scriptSetup && vueOptions.script?.inlineTemplate !== false;
4382
+ if (descriptor.template && !scriptSetupIsInline) {
4383
+ const transformedTemplate = await applySourceTransform(
4384
+ vueOptions.transformTemplate,
4385
+ descriptor.template.content,
4386
+ { filename: id, environmentName, type: "template" }
4387
+ );
4388
+ const templateInputMap = composeSourceMapChain(
4389
+ [
4390
+ transformedTemplate.map,
4391
+ descriptor.template.map,
4392
+ transformedSfc.map
4393
+ ],
4394
+ { filename: id, environmentName, type: "template" }
4395
+ );
4396
+ const customCompilerOptions = vueOptions.template?.compilerOptions ?? {};
4084
4397
  const compiled = sfc.compileTemplate({
4085
- source: descriptor.template.content,
4398
+ ...vueOptions.template,
4399
+ source: transformedTemplate.code,
4086
4400
  filename: id,
4087
4401
  id: scopeId,
4088
- compilerOptions: { scopeId: `data-v-${scopeId}` }
4402
+ inMap: templateInputMap,
4403
+ compilerOptions: {
4404
+ ...customCompilerOptions,
4405
+ scopeId: `data-v-${scopeId}`
4406
+ }
4089
4407
  });
4090
4408
  templateCode = compiled.code;
4409
+ if (wantsSourceMap || transformedTemplate.map != null) {
4410
+ templateMap = compiled.map;
4411
+ }
4412
+ if (transformedTemplate.map != null && templateMap == null) {
4413
+ warnUnchainableMap(
4414
+ { filename: id, environmentName, type: "template" },
4415
+ "compiler-sfc did not return a template map"
4416
+ );
4417
+ }
4091
4418
  }
4092
- let output = scriptCode || "const __sfc__ = {}";
4419
+ const outputNode = new import_source_map_js2.SourceNode();
4420
+ let hasMappedOutput = false;
4421
+ const append = (fragment, map) => {
4422
+ const normalizedMap = normalizeSourceMap(
4423
+ map,
4424
+ { filename: id, environmentName, type: "sfc" }
4425
+ );
4426
+ if (!normalizedMap) {
4427
+ outputNode.add(fragment);
4428
+ return;
4429
+ }
4430
+ try {
4431
+ outputNode.add(
4432
+ import_source_map_js2.SourceNode.fromStringWithSourceMap(
4433
+ fragment,
4434
+ new import_source_map_js2.SourceMapConsumer(normalizedMap)
4435
+ )
4436
+ );
4437
+ hasMappedOutput = true;
4438
+ } catch (error) {
4439
+ warnUnchainableMap(
4440
+ { filename: id, environmentName, type: "sfc" },
4441
+ `source-map assembly failed: ${error instanceof Error ? error.message : String(error)}`
4442
+ );
4443
+ outputNode.add(fragment);
4444
+ }
4445
+ };
4446
+ append(scriptCode || "const __sfc__ = {}", scriptMap);
4093
4447
  if (templateCode) {
4094
- output += `
4095
- ${templateCode}
4096
- `;
4097
- output += `
4098
- __sfc__.render = render
4099
- `;
4448
+ append("\n");
4449
+ append(templateCode, templateMap);
4450
+ append("\n");
4451
+ append("\n__sfc__.render = render\n");
4100
4452
  }
4101
4453
  if (descriptor.styles.length > 0) {
4102
4454
  for (let i = 0; i < descriptor.styles.length; i++) {
4103
- output += `
4455
+ append(`
4104
4456
  import "${id}?vue&type=style&index=${i}&lang.css"
4105
- `;
4457
+ `);
4106
4458
  }
4107
4459
  }
4108
- output += `
4460
+ append(`
4109
4461
  __sfc__.__scopeId = "data-v-${scopeId}"
4110
- `;
4462
+ `);
4111
4463
  if (isDev) {
4112
- output += `
4464
+ append(`
4113
4465
  __sfc__.__hmrId = ${JSON.stringify(scopeId)}
4114
4466
  if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') {
4115
4467
  __VUE_HMR_RUNTIME__.createRecord(__sfc__.__hmrId, __sfc__)
@@ -4123,17 +4475,34 @@ if (import.meta.hot) {
4123
4475
  }
4124
4476
  })
4125
4477
  }
4126
- `;
4478
+ `);
4479
+ }
4480
+ append("\nexport default __sfc__\n");
4481
+ const renderedOutput = outputNode.toStringWithSourceMap({ file: id });
4482
+ const output = renderedOutput.code;
4483
+ const outputMap = hasMappedOutput ? renderedOutput.map.toJSON() : void 0;
4484
+ if (transformedSfc.map != null && outputMap == null) {
4485
+ warnUnchainableMap(
4486
+ { filename: id, environmentName, type: "sfc" },
4487
+ "the compiled SFC output contained no chainable mappings"
4488
+ );
4127
4489
  }
4128
- output += `
4129
- export default __sfc__
4130
- `;
4131
4490
  const lang = descriptor.scriptSetup?.lang ?? descriptor.script?.lang;
4132
4491
  if (lang === "ts") {
4133
- const transpiled = transformCode(`${id}.ts`, output, { sourcemap: false });
4134
- return { code: transpiled.code };
4492
+ const transpiled = transformCode(`${id}.ts`, output, {
4493
+ sourcemap: wantsSourceMap,
4494
+ target: config.build.target
4495
+ });
4496
+ const transpiledMap = transpiled.map ? JSON.parse(transpiled.map) : void 0;
4497
+ return {
4498
+ code: transpiled.code,
4499
+ map: composeSourceMapChain(
4500
+ [transpiledMap, outputMap],
4501
+ { filename: id, environmentName, type: "sfc" }
4502
+ )
4503
+ };
4135
4504
  }
4136
- return { code: output };
4505
+ return { code: output, map: outputMap };
4137
4506
  },
4138
4507
  handleHotUpdate(ctx) {
4139
4508
  const { file, modules } = ctx;
@@ -4147,17 +4516,77 @@ export default __sfc__
4147
4516
  }
4148
4517
  };
4149
4518
  }
4519
+ async function applySourceTransform(transform2, source, context) {
4520
+ if (!transform2) return { code: source };
4521
+ const result = await transform2(source, context);
4522
+ return typeof result === "string" ? { code: result } : result;
4523
+ }
4524
+ function normalizeSourceMap(map, context) {
4525
+ if (map == null) return void 0;
4526
+ try {
4527
+ const value = typeof map === "string" ? JSON.parse(map) : map;
4528
+ if (value && typeof value === "object" && Array.isArray(value.sources) && Array.isArray(value.names) && typeof value.mappings === "string") {
4529
+ return value;
4530
+ }
4531
+ } catch {
4532
+ }
4533
+ warnUnchainableMap(context, "the provided map is not a valid source map");
4534
+ return void 0;
4535
+ }
4536
+ function composeSourceMapChain(maps, context) {
4537
+ const pending = maps.filter((map) => map != null);
4538
+ if (pending.length === 0) return void 0;
4539
+ let composed = normalizeSourceMap(pending.shift(), context);
4540
+ for (const map of pending) {
4541
+ const input = normalizeSourceMap(map, context);
4542
+ if (!input) continue;
4543
+ if (!composed) {
4544
+ composed = input;
4545
+ continue;
4546
+ }
4547
+ try {
4548
+ const consumer = new import_source_map_js2.SourceMapConsumer(composed);
4549
+ if (consumer.sources.length !== 1) {
4550
+ warnUnchainableMap(
4551
+ context,
4552
+ "a generated map has multiple sources and cannot be chained safely"
4553
+ );
4554
+ continue;
4555
+ }
4556
+ const generator = import_source_map_js2.SourceMapGenerator.fromSourceMap(consumer);
4557
+ generator.applySourceMap(
4558
+ new import_source_map_js2.SourceMapConsumer(input),
4559
+ consumer.sources[0]
4560
+ );
4561
+ composed = generator.toJSON();
4562
+ } catch (error) {
4563
+ warnUnchainableMap(
4564
+ context,
4565
+ `source-map composition failed: ${error instanceof Error ? error.message : String(error)}`
4566
+ );
4567
+ }
4568
+ }
4569
+ return composed;
4570
+ }
4571
+ function warnUnchainableMap(context, reason) {
4572
+ debug3?.(
4573
+ `source map warning for ${context.filename} (${context.type}, ${context.environmentName}): ${reason}`
4574
+ );
4575
+ }
4150
4576
  function hashId(filename) {
4151
4577
  return import_node_crypto2.default.createHash("sha256").update(filename).digest("hex").slice(0, 8);
4152
4578
  }
4153
- var import_node_crypto2, VUE_FILE_RE, VUE_QUERY_RE, compiler;
4579
+ var import_node_crypto2, import_source_map_js2, VUE_FILE_RE, VUE_QUERY_RE, debug3, compiler;
4154
4580
  var init_vue = __esm({
4155
4581
  "src/plugins/vue.ts"() {
4156
4582
  "use strict";
4157
4583
  import_node_crypto2 = __toESM(require("crypto"), 1);
4584
+ import_source_map_js2 = require("source-map-js");
4158
4585
  init_transformer();
4586
+ init_debug();
4159
4587
  VUE_FILE_RE = /\.vue$/;
4160
4588
  VUE_QUERY_RE = /\.vue\?vue&type=(script|template|style)(&index=\d+)?(&lang[.=]\w+)?/;
4589
+ debug3 = createDebugger("nasti:vue");
4161
4590
  compiler = null;
4162
4591
  }
4163
4592
  });
@@ -4178,7 +4607,7 @@ function resolvePluginList(config, userPlugins, opts = {}) {
4178
4607
  const consumer = opts.consumer ?? environmentOptions?.consumer;
4179
4608
  return [
4180
4609
  // vuePlugin 排最前(enforce: 'pre' 语义):.vue 先编译成 JS 再走后续管道
4181
- ...config.framework === "vue" ? [vuePlugin(pluginConfig)] : [],
4610
+ ...config.framework === "vue" ? [vuePlugin(pluginConfig, opts.environmentName ?? "client")] : [],
4182
4611
  resolvePlugin(pluginConfig),
4183
4612
  cssPlugin(pluginConfig, opts.cssEngine, consumer),
4184
4613
  assetsPlugin(pluginConfig),
@@ -4214,7 +4643,7 @@ function createModuleRunner(environment) {
4214
4643
  }
4215
4644
  return new NastiModuleRunner(environment);
4216
4645
  }
4217
- var import_node_path10, import_node_fs8, import_node_module4, import_node_url4, debug3, NODE_BUILTINS, NastiModuleRunner, AsyncFunction;
4646
+ var import_node_path10, import_node_fs8, import_node_module4, import_node_url4, debug4, NODE_BUILTINS, NastiModuleRunner, AsyncFunction;
4218
4647
  var init_runnable_environment = __esm({
4219
4648
  "src/server/runnable-environment.ts"() {
4220
4649
  "use strict";
@@ -4225,7 +4654,7 @@ var init_runnable_environment = __esm({
4225
4654
  init_transformer();
4226
4655
  init_env();
4227
4656
  init_debug();
4228
- debug3 = createDebugger("nasti:ssr");
4657
+ debug4 = createDebugger("nasti:ssr");
4229
4658
  NODE_BUILTINS = /* @__PURE__ */ new Set([...import_node_module4.builtinModules, ...import_node_module4.builtinModules.map((m) => `node:${m}`)]);
4230
4659
  NastiModuleRunner = class {
4231
4660
  environment;
@@ -4303,6 +4732,7 @@ var init_runnable_environment = __esm({
4303
4732
  if (shouldTransform(cleanId)) {
4304
4733
  const result = transformCode(cleanId, code, {
4305
4734
  sourcemap: false,
4735
+ target: this.environment.options.build.target,
4306
4736
  jsxRuntime: "automatic",
4307
4737
  jsxImportSource: this.config.framework === "vue" ? "vue" : "react"
4308
4738
  });
@@ -4319,7 +4749,7 @@ var init_runnable_environment = __esm({
4319
4749
  );
4320
4750
  }
4321
4751
  const runnerResult = await moduleRunnerTransform(resolvedId, code);
4322
- debug3?.(`fetchModule ${resolvedId} (${runnerResult.deps?.length ?? 0} deps)`);
4752
+ debug4?.(`fetchModule ${resolvedId} (${runnerResult.deps?.length ?? 0} deps)`);
4323
4753
  return { id: resolvedId, code: runnerResult.code };
4324
4754
  }
4325
4755
  completeExtension(id) {
@@ -4438,7 +4868,7 @@ async function tryNativeReporterPlugin(config, logger) {
4438
4868
  logInfo: (msg) => logger.info(msg)
4439
4869
  });
4440
4870
  } catch (err) {
4441
- debug4?.(`native viteReporterPlugin unavailable, falling back to JS table: ${err}`);
4871
+ debug5?.(`native viteReporterPlugin unavailable, falling back to JS table: ${err}`);
4442
4872
  return null;
4443
4873
  }
4444
4874
  }
@@ -4493,7 +4923,7 @@ function warnLargeChunks(output, config, logger) {
4493
4923
  )
4494
4924
  );
4495
4925
  }
4496
- var import_node_path11, import_node_zlib, import_picocolors5, debug4, numberFormatter;
4926
+ var import_node_path11, import_node_zlib, import_picocolors5, debug5, numberFormatter;
4497
4927
  var init_reporter = __esm({
4498
4928
  "src/build/reporter.ts"() {
4499
4929
  "use strict";
@@ -4501,7 +4931,7 @@ var init_reporter = __esm({
4501
4931
  import_node_zlib = require("zlib");
4502
4932
  import_picocolors5 = __toESM(require("picocolors"), 1);
4503
4933
  init_debug();
4504
- debug4 = createDebugger("nasti:reporter");
4934
+ debug5 = createDebugger("nasti:reporter");
4505
4935
  numberFormatter = new Intl.NumberFormat("en", {
4506
4936
  maximumFractionDigits: 2,
4507
4937
  minimumFractionDigits: 2
@@ -4541,6 +4971,24 @@ function createBuildAppContext(config, results) {
4541
4971
  getManifest(environmentName) {
4542
4972
  return results[environmentName]?.manifest;
4543
4973
  },
4974
+ getChunk(environmentName, fileName) {
4975
+ const normalized = normalizeEnvironmentFileName(fileName);
4976
+ return results[environmentName]?.chunks?.[normalized];
4977
+ },
4978
+ getCss(environmentName) {
4979
+ return results[environmentName]?.css;
4980
+ },
4981
+ getSourceMap(environmentName, fileName) {
4982
+ const normalized = normalizeEnvironmentFileName(fileName);
4983
+ return results[environmentName]?.sourceMaps?.[normalized];
4984
+ },
4985
+ resolvePublicPath(environmentName, fileName) {
4986
+ const result = results[environmentName];
4987
+ if (!result) return void 0;
4988
+ const normalized = normalizeEnvironmentFileName(fileName);
4989
+ const base = result.publicPath ?? config.base;
4990
+ return joinPublicPath(base, normalized);
4991
+ },
4544
4992
  emitFile(file) {
4545
4993
  const fileName = normalizeAppFileName(file.fileName);
4546
4994
  const collisionKey = artifactCollisionKey(fileName);
@@ -4570,6 +5018,9 @@ function createBuildAppContext(config, results) {
4570
5018
  }
4571
5019
  };
4572
5020
  }
5021
+ function joinPublicPath(base, fileName) {
5022
+ return `${base.endsWith("/") ? base : `${base}/`}${fileName.replace(/^\//, "")}`;
5023
+ }
4573
5024
  function normalizeEnvironmentFileName(fileName) {
4574
5025
  return import_node_path12.default.posix.normalize(fileName.replace(/\\/g, "/").replace(/^\.\//, ""));
4575
5026
  }
@@ -4670,7 +5121,11 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
4670
5121
  const inputOptions = {
4671
5122
  ...restInputOptions,
4672
5123
  input: entryPoints,
4673
- transform: { ...userTransform, define: mergedDefine },
5124
+ transform: {
5125
+ ...userTransform,
5126
+ target: userTransform?.target ?? envOptions.build.target,
5127
+ define: mergedDefine
5128
+ },
4674
5129
  plugins: rolldownPlugins,
4675
5130
  // client/server consumer 都必须使用当前环境的 conditions/mainFields;Lynx
4676
5131
  // BG/MT 通常同为 client consumer,但仍可能声明不同的运行时条件。
@@ -4693,7 +5148,7 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
4693
5148
  };
4694
5149
  const outputOptions = isServer ? {
4695
5150
  format: "esm",
4696
- sourcemap: !!envOptions.build.sourcemap,
5151
+ sourcemap: envOptions.build.sourcemap,
4697
5152
  minify: !!envOptions.build.minify,
4698
5153
  entryFileNames: "[name].js",
4699
5154
  chunkFileNames: "chunks/[name]-[hash].js",
@@ -4702,7 +5157,7 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
4702
5157
  dir: outDir
4703
5158
  } : {
4704
5159
  format: "esm",
4705
- sourcemap: !!envOptions.build.sourcemap,
5160
+ sourcemap: envOptions.build.sourcemap,
4706
5161
  minify: !!envOptions.build.minify,
4707
5162
  entryFileNames: `${assetsDir}/[name].[hash].js`,
4708
5163
  chunkFileNames: `${assetsDir}/[name].[hash].js`,
@@ -4777,13 +5232,68 @@ function finalizeEnvironmentResult(environment, result) {
4777
5232
  return [name, normalized];
4778
5233
  })
4779
5234
  );
5235
+ const inferredMetadata = inferOutputMetadata(environment, result.output);
4780
5236
  return {
5237
+ publicPath: environment.config.base,
5238
+ ...inferredMetadata,
4781
5239
  ...metadata,
4782
5240
  ...result,
4783
5241
  output: result.output,
5242
+ chunks: {
5243
+ ...inferredMetadata.chunks,
5244
+ ...metadata.chunks,
5245
+ ...result.chunks
5246
+ },
5247
+ assets: {
5248
+ ...inferredMetadata.assets,
5249
+ ...metadata.assets,
5250
+ ...result.assets
5251
+ },
5252
+ sourceMaps: {
5253
+ ...inferredMetadata.sourceMaps,
5254
+ ...metadata.sourceMaps,
5255
+ ...result.sourceMaps
5256
+ },
4784
5257
  ...Object.keys(normalizedEntries).length > 0 ? { entries: normalizedEntries } : {}
4785
5258
  };
4786
5259
  }
5260
+ function inferOutputMetadata(environment, output) {
5261
+ const chunks = {};
5262
+ const assets = {};
5263
+ const sourceMaps = {};
5264
+ const cssChunks = environment.getBuildMetadata().css?.chunks ?? {};
5265
+ const assetModules = environment.getAssetModules();
5266
+ const publicPath = environment.config.base;
5267
+ for (const artifact of output) {
5268
+ const fileName = normalizeEnvironmentFileName(artifact.fileName);
5269
+ if (artifact.map != null) sourceMaps[fileName] = artifact.map;
5270
+ if (artifact.type === "chunk") {
5271
+ const moduleIds = [...artifact.moduleIds ?? []];
5272
+ chunks[fileName] = {
5273
+ fileName,
5274
+ name: artifact.name ?? fileName,
5275
+ isEntry: !!artifact.isEntry,
5276
+ isDynamicEntry: !!artifact.isDynamicEntry,
5277
+ imports: [...artifact.imports ?? []],
5278
+ dynamicImports: [...artifact.dynamicImports ?? []],
5279
+ moduleIds,
5280
+ css: [...cssChunks[fileName]?.cssFileNames ?? []],
5281
+ assets: [
5282
+ ...new Set(
5283
+ moduleIds.map((id) => assetModules[id]).filter((asset) => !!asset)
5284
+ )
5285
+ ]
5286
+ };
5287
+ } else if (artifact.type === "asset") {
5288
+ assets[fileName] = {
5289
+ fileName,
5290
+ names: [...artifact.names ?? (artifact.name ? [artifact.name] : [])],
5291
+ publicPath: joinPublicPath(publicPath, fileName)
5292
+ };
5293
+ }
5294
+ }
5295
+ return { chunks, assets, sourceMaps };
5296
+ }
4787
5297
  function prepareBuildOutputDirectories(config, buildableNames) {
4788
5298
  const directories = /* @__PURE__ */ new Set();
4789
5299
  const protectedPaths = /* @__PURE__ */ new Set();
@@ -4861,6 +5371,7 @@ function createOxcTransformPlugin(config, environment) {
4861
5371
  if (!shouldTransform(id)) return null;
4862
5372
  const result = transformCode(id, code, {
4863
5373
  sourcemap: !!environment.options.build.sourcemap,
5374
+ target: environment.options.build.target,
4864
5375
  jsxRuntime: "automatic",
4865
5376
  jsxImportSource: config.framework === "vue" ? "vue" : "react"
4866
5377
  });
@@ -4874,9 +5385,9 @@ async function build(inlineConfig = {}) {
4874
5385
  const startTime = performance.now();
4875
5386
  logger.info(
4876
5387
  import_picocolors6.default.cyan(`
4877
- nasti v${"2.4.0"} `) + import_picocolors6.default.green(`building for ${config.mode}...`)
5388
+ nasti v${"2.4.2"} `) + import_picocolors6.default.green(`building for ${config.mode}...`)
4878
5389
  );
4879
- debug5?.(`root: ${config.root}`);
5390
+ debug6?.(`root: ${config.root}`);
4880
5391
  const buildableNames = Object.keys(config.environments).filter((name) => {
4881
5392
  const environment = config.environments[name];
4882
5393
  if (!environment.buildEnabled) return false;
@@ -4898,7 +5409,7 @@ nasti v${"2.4.0"} `) + import_picocolors6.default.green(`building for ${config.m
4898
5409
  environmentResults[name] = built.result;
4899
5410
  if (name === "client") clientOutput = built.result.output;
4900
5411
  if (buildableNames.length > 1) {
4901
- debug5?.(`environment "${name}" built (${built.result.output.length} files)`);
5412
+ debug6?.(`environment "${name}" built (${built.result.output.length} files)`);
4902
5413
  }
4903
5414
  }
4904
5415
  const pluginApi = getPluginApi(config);
@@ -4994,6 +5505,7 @@ async function buildClientEnvironment(config) {
4994
5505
  const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
4995
5506
  const { output } = await bundle2.write(outputOptions);
4996
5507
  await bundle2.close();
5508
+ clientEnv.setBuildMetadata({ css: getCssMetadata(cssEngine) });
4997
5509
  if (html) {
4998
5510
  let processedHtml = html;
4999
5511
  const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
@@ -5007,7 +5519,9 @@ async function buildClientEnvironment(config) {
5007
5519
  processedHtml = processHtml(processedHtml, result);
5008
5520
  }
5009
5521
  }
5010
- processedHtml = injectCssLinks(processedHtml, cssEngine, config);
5522
+ if (clientEnv.options.build.css.inject !== false) {
5523
+ processedHtml = injectCssLinks(processedHtml, cssEngine, config);
5524
+ }
5011
5525
  for (const chunk of output) {
5012
5526
  if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
5013
5527
  processedHtml = replaceEntryScript(
@@ -5045,9 +5559,11 @@ async function buildClientEnvironment(config) {
5045
5559
  async function buildServerEnvironment(config, name) {
5046
5560
  const envOptions = config.environments[name];
5047
5561
  const logger = config.logger;
5562
+ const cssEngine = envOptions.consumer === "client" ? createCssEngine() : void 0;
5048
5563
  const pluginList = resolvePluginList(config, config.plugins, {
5049
5564
  consumer: envOptions.consumer,
5050
- environmentName: name
5565
+ environmentName: name,
5566
+ cssEngine
5051
5567
  });
5052
5568
  const environment = new NastiEnvironment(name, config, {
5053
5569
  mode: "build",
@@ -5090,6 +5606,7 @@ async function buildServerEnvironment(config, name) {
5090
5606
  const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
5091
5607
  const { output } = await bundle2.write(outputOptions);
5092
5608
  await bundle2.close();
5609
+ if (cssEngine) environment.setBuildMetadata({ css: getCssMetadata(cssEngine) });
5093
5610
  logger.info(
5094
5611
  import_picocolors6.default.dim(` [${name}] `) + output.map((o) => import_node_path13.default.join(envOptions.build.outDir, o.fileName)).join(import_picocolors6.default.dim(", "))
5095
5612
  );
@@ -5141,7 +5658,7 @@ function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, ur
5141
5658
  }
5142
5659
  return processed;
5143
5660
  }
5144
- var import_node_path13, import_node_fs10, import_node_module5, import_rolldown, import_picocolors6, debug5, NODE_BUILTINS2;
5661
+ var import_node_path13, import_node_fs10, import_node_module5, import_rolldown, import_picocolors6, debug6, NODE_BUILTINS2;
5145
5662
  var init_build = __esm({
5146
5663
  "src/build/index.ts"() {
5147
5664
  "use strict";
@@ -5161,7 +5678,7 @@ var init_build = __esm({
5161
5678
  init_plugin_api();
5162
5679
  init_build_app_context();
5163
5680
  import_picocolors6 = __toESM(require("picocolors"), 1);
5164
- debug5 = createDebugger("nasti:build");
5681
+ debug6 = createDebugger("nasti:build");
5165
5682
  NODE_BUILTINS2 = /* @__PURE__ */ new Set([...import_node_module5.builtinModules, ...import_node_module5.builtinModules.map((m) => `node:${m}`)]);
5166
5683
  }
5167
5684
  });
@@ -5185,7 +5702,7 @@ async function createBundledDevServer(opts) {
5185
5702
  }
5186
5703
  } catch (err) {
5187
5704
  throw new Error(
5188
- `[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.`
5705
+ `[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.`
5189
5706
  );
5190
5707
  }
5191
5708
  const html = await readHtmlFile(config.root, config.environments.client?.html);
@@ -5238,7 +5755,7 @@ async function createBundledDevServer(opts) {
5238
5755
  for (const { clientId, update } of updates) {
5239
5756
  if (update.type === "Noop") continue;
5240
5757
  if (update.type === "FullReload") {
5241
- debug6?.(`full reload for ${clientId}: ${update.reason ?? ""}`);
5758
+ debug7?.(`full reload for ${clientId}: ${update.reason ?? ""}`);
5242
5759
  needsLatestOutput = true;
5243
5760
  continue;
5244
5761
  }
@@ -5288,7 +5805,7 @@ async function createBundledDevServer(opts) {
5288
5805
  },
5289
5806
  {
5290
5807
  watch: { skipWrite: true },
5291
- rebuildStrategy: "auto",
5808
+ rebuildStrategy: "never",
5292
5809
  onOutput(result) {
5293
5810
  if (result instanceof Error) {
5294
5811
  logger.error(import_picocolors7.default.red(`[bundled] build error: ${result.message}`), { error: result });
@@ -5304,7 +5821,13 @@ async function createBundledDevServer(opts) {
5304
5821
  memoryFiles.set(`${file.fileName}.map`, JSON.stringify(file.map));
5305
5822
  }
5306
5823
  }
5307
- debug6?.(`bundle output refreshed (${result.output.length} files)`);
5824
+ debug7?.(`bundle output refreshed (${result.output.length} files)`);
5825
+ },
5826
+ onAdditionalAssets(result) {
5827
+ for (const file of result.output) {
5828
+ const content = file.type === "chunk" ? file.code : file.source;
5829
+ if (content != null) memoryFiles.set(file.fileName, content);
5830
+ }
5308
5831
  },
5309
5832
  async onHmrUpdates(result) {
5310
5833
  if (result instanceof Error) {
@@ -5313,7 +5836,7 @@ async function createBundledDevServer(opts) {
5313
5836
  return;
5314
5837
  }
5315
5838
  const { updates, changedFiles } = result;
5316
- debug6?.(
5839
+ debug7?.(
5317
5840
  `onHmrUpdates(engine watcher): ${changedFiles.length} changed, ${updates.length} updates`
5318
5841
  );
5319
5842
  if (changedFiles.length === 0) return;
@@ -5332,24 +5855,29 @@ async function createBundledDevServer(opts) {
5332
5855
  if (!clientId) return;
5333
5856
  wss.handleUpgrade(req, socket, head, (ws) => {
5334
5857
  bundledClients.set(clientId, ws);
5335
- debug6?.(`bundled client connected: ${clientId}`);
5336
- ws.send(JSON.stringify({ type: "connected" }));
5858
+ debug7?.(`bundled client connected: ${clientId}`);
5859
+ void engine.registerClient(clientId).then(async () => {
5860
+ for (const fileName of entryFileNames.values()) {
5861
+ await engine.notifyPayloadDelivered(fileName);
5862
+ }
5863
+ ws.send(JSON.stringify({ type: "connected" }));
5864
+ }).catch((err) => {
5865
+ debug7?.(`registerClient failed for ${clientId}: ${err?.message ?? err}`);
5866
+ ws.close();
5867
+ });
5337
5868
  ws.on("message", async (raw) => {
5338
5869
  try {
5339
5870
  const msg = JSON.parse(String(raw));
5340
- if (msg.type === "hmr:module-registered" && Array.isArray(msg.modules)) {
5341
- await engine.registerModules(clientId, msg.modules);
5342
- debug6?.(`registered ${msg.modules.length} modules for ${clientId}`);
5343
- } else if (msg.type === "hmr:invalidate") {
5871
+ if (msg.type === "hmr:invalidate") {
5344
5872
  scheduleFullReload();
5345
5873
  }
5346
5874
  } catch (err) {
5347
- debug6?.(`bundled ws message error: ${err.message}`);
5875
+ debug7?.(`bundled ws message error: ${err.message}`);
5348
5876
  }
5349
5877
  });
5350
5878
  ws.on("close", () => {
5351
5879
  bundledClients.delete(clientId);
5352
- engine.removeClient(clientId).catch((err) => debug6?.(`removeClient failed for ${clientId}: ${err?.message ?? err}`));
5880
+ engine.removeClient(clientId).catch((err) => debug7?.(`removeClient failed for ${clientId}: ${err?.message ?? err}`));
5353
5881
  });
5354
5882
  });
5355
5883
  });
@@ -5366,10 +5894,18 @@ async function createBundledDevServer(opts) {
5366
5894
  res.end("// [nasti] lazy endpoint requires id & clientId");
5367
5895
  return;
5368
5896
  }
5369
- const code = await engine.compileEntry(id, clientId);
5897
+ const output = await engine.compileEntry(id, clientId);
5898
+ if (output.sourcemap && output.sourcemapFilename) {
5899
+ memoryFiles.set(output.sourcemapFilename, output.sourcemap);
5900
+ }
5901
+ res.once("finish", () => {
5902
+ void engine.notifyPayloadDelivered(output.filename).catch(
5903
+ (err) => debug7?.(`notifyPayloadDelivered failed: ${err?.message ?? err}`)
5904
+ );
5905
+ });
5370
5906
  res.setHeader("Content-Type", "application/javascript");
5371
5907
  res.setHeader("Cache-Control", "no-store");
5372
- res.end(code + "\n;export {}");
5908
+ res.end(output.code + "\n;export {}");
5373
5909
  return;
5374
5910
  }
5375
5911
  const patchHit = patches.get(pathname.replace(/^\//, ""));
@@ -5390,6 +5926,11 @@ async function createBundledDevServer(opts) {
5390
5926
  res.setHeader("ETag", hit.etag);
5391
5927
  res.setHeader("Content-Type", MIME_TYPES[import_node_path14.default.extname(fileName)] ?? "application/octet-stream");
5392
5928
  res.setHeader("Cache-Control", "no-cache");
5929
+ res.once("finish", () => {
5930
+ void engine.notifyPayloadDelivered(fileName).catch(
5931
+ (err) => debug7?.(`notifyPayloadDelivered failed: ${err?.message ?? err}`)
5932
+ );
5933
+ });
5393
5934
  res.end(hit.content);
5394
5935
  return;
5395
5936
  }
@@ -5489,7 +6030,7 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
5489
6030
  }
5490
6031
  return processed;
5491
6032
  }
5492
- var import_node_path14, import_node_crypto3, import_ws2, import_picocolors7, debug6, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
6033
+ var import_node_path14, import_node_crypto3, import_ws2, import_picocolors7, debug7, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
5493
6034
  var init_dev_engine = __esm({
5494
6035
  "src/server/bundled/dev-engine.ts"() {
5495
6036
  "use strict";
@@ -5502,7 +6043,7 @@ var init_dev_engine = __esm({
5502
6043
  init_transformer();
5503
6044
  init_middleware();
5504
6045
  init_debug();
5505
- debug6 = createDebugger("nasti:bundled");
6046
+ debug7 = createDebugger("nasti:bundled");
5506
6047
  MIME_TYPES = {
5507
6048
  ".js": "application/javascript",
5508
6049
  ".mjs": "application/javascript",
@@ -5629,7 +6170,7 @@ async function createServer(inlineConfig = {}) {
5629
6170
  const ws = createWebSocketServer(httpServer);
5630
6171
  const pluginApi = getPluginApi(config);
5631
6172
  const clientEnv = new NastiEnvironment("client", config, {
5632
- hot: createWsHotChannel(ws),
6173
+ hot: createWsHotChannel(ws, "client"),
5633
6174
  mode: "dev",
5634
6175
  plugins: allPlugins,
5635
6176
  pluginApi
@@ -5644,13 +6185,40 @@ async function createServer(inlineConfig = {}) {
5644
6185
  environmentName: name
5645
6186
  });
5646
6187
  environments[name] = new NastiEnvironment(name, config, {
6188
+ hot: consumer === "client" ? createWsHotChannel(ws, name) : void 0,
5647
6189
  mode: "dev",
5648
6190
  plugins: envPlugins,
5649
6191
  pluginApi
5650
6192
  });
5651
6193
  }
5652
6194
  for (const [name, environment] of Object.entries(environments)) {
5653
- if (name !== "client" && environment.options.driver) await environment.init();
6195
+ if (name === "client" || environment.consumer === "client" || environment.options.driver) {
6196
+ await environment.init();
6197
+ }
6198
+ }
6199
+ const transformContexts = /* @__PURE__ */ new Map();
6200
+ for (const environment of Object.values(environments)) {
6201
+ if (environment.consumer !== "client" || environment.driver) continue;
6202
+ const environmentConfig = {
6203
+ ...configWithPlugins,
6204
+ resolve: environment.options.resolve,
6205
+ build: environment.options.build,
6206
+ plugins: environment.plugins
6207
+ };
6208
+ const context = {
6209
+ config: environmentConfig,
6210
+ pluginContainer: environment.pluginContainer,
6211
+ moduleGraph: environment.moduleGraph,
6212
+ environment,
6213
+ envDefine: buildEnvDefine(
6214
+ loadEnv(environmentConfig.mode, environmentConfig.root, environmentConfig.envPrefix),
6215
+ environmentConfig.mode,
6216
+ ssrDefineOverrides(environment.consumer)
6217
+ ),
6218
+ onPrune: (paths) => environment.hot.send({ type: "prune", paths })
6219
+ };
6220
+ transformContexts.set(environment.name, context);
6221
+ environment.configureDevPipeline((url) => transformRequest(url, context));
5654
6222
  }
5655
6223
  let ssrRunner = null;
5656
6224
  async function getSsrRunner() {
@@ -5665,7 +6233,6 @@ async function createServer(inlineConfig = {}) {
5665
6233
  return ssrRunner;
5666
6234
  }
5667
6235
  const moduleGraph = clientEnv.moduleGraph;
5668
- const pluginContainer = clientEnv.pluginContainer;
5669
6236
  let bundledServer = null;
5670
6237
  if (config.experimental.bundledDev) {
5671
6238
  const { createBundledDevServer: createBundledDevServer2 } = await Promise.resolve().then(() => (init_dev_engine(), dev_engine_exports));
@@ -5694,6 +6261,15 @@ async function createServer(inlineConfig = {}) {
5694
6261
  let server;
5695
6262
  const environmentServices = {};
5696
6263
  let environmentDriversStarted = false;
6264
+ let devPipelinesStarted = false;
6265
+ const startDevPipelines = async () => {
6266
+ if (devPipelinesStarted) return;
6267
+ devPipelinesStarted = true;
6268
+ for (const environment of Object.values(environments)) {
6269
+ if (!transformContexts.has(environment.name)) continue;
6270
+ await environment.pluginContainer.buildStart();
6271
+ }
6272
+ };
5697
6273
  const logCloseError = (target, error) => {
5698
6274
  const normalized = error instanceof Error ? error : new Error(String(error));
5699
6275
  logger.error(`[nasti] failed to close ${target}`, { error: normalized });
@@ -5745,14 +6321,61 @@ async function createServer(inlineConfig = {}) {
5745
6321
  });
5746
6322
  }
5747
6323
  };
6324
+ const updateClientEnvironments = async (file) => {
6325
+ const timestamp = Date.now();
6326
+ const results = {};
6327
+ for (const environment of Object.values(environments)) {
6328
+ if (environment.consumer !== "client" || environment.driver) continue;
6329
+ try {
6330
+ const result = await handleFileChange(file, server, environment.name, timestamp);
6331
+ if (result) results[environment.name] = result;
6332
+ } catch (error) {
6333
+ const normalized = error instanceof Error ? error : new Error(String(error));
6334
+ logger.error(
6335
+ `[nasti] HMR failed for environment "${environment.name}": ${normalized.message}`,
6336
+ { error: normalized }
6337
+ );
6338
+ try {
6339
+ environment.hot.send({
6340
+ type: "error",
6341
+ err: { message: normalized.message, stack: normalized.stack }
6342
+ });
6343
+ } catch (channelError) {
6344
+ const channelFailure = channelError instanceof Error ? channelError : new Error(String(channelError));
6345
+ logger.error(
6346
+ `[nasti] failed to deliver HMR error to environment "${environment.name}"`,
6347
+ { error: channelFailure }
6348
+ );
6349
+ }
6350
+ }
6351
+ }
6352
+ if (Object.keys(results).length === 0) return;
6353
+ const context = {
6354
+ file,
6355
+ timestamp,
6356
+ environments: Object.freeze({ ...results }),
6357
+ server
6358
+ };
6359
+ for (const plugin of config.plugins) {
6360
+ await plugin.handleHotUpdateApp?.(context);
6361
+ }
6362
+ };
6363
+ const queueClientEnvironmentUpdate = (file) => {
6364
+ void updateClientEnvironments(file).catch((error) => {
6365
+ const normalized = error instanceof Error ? error : new Error(String(error));
6366
+ logger.error(`[nasti] multi-environment HMR failed: ${normalized.message}`, {
6367
+ error: normalized
6368
+ });
6369
+ });
6370
+ };
5748
6371
  watcher.on("change", (file) => {
5749
6372
  ssrRunner?.invalidateFile(file);
5750
- handleFileChange(file, server);
6373
+ queueClientEnvironmentUpdate(file);
5751
6374
  notifyEnvironmentDrivers(file, "change");
5752
6375
  });
5753
6376
  watcher.on("add", (file) => {
5754
6377
  ssrRunner?.invalidateFile(file);
5755
- handleFileChange(file, server);
6378
+ queueClientEnvironmentUpdate(file);
5756
6379
  notifyEnvironmentDrivers(file, "add");
5757
6380
  });
5758
6381
  watcher.on("unlink", (file) => {
@@ -5770,7 +6393,7 @@ async function createServer(inlineConfig = {}) {
5770
6393
  async listen(port) {
5771
6394
  const finalPort = port ?? config.server.port;
5772
6395
  const host = config.server.host === true ? "0.0.0.0" : config.server.host;
5773
- await pluginContainer.buildStart();
6396
+ await startDevPipelines();
5774
6397
  await startEnvironmentDrivers();
5775
6398
  return new Promise((resolve, reject) => {
5776
6399
  let currentPort = finalPort;
@@ -5785,7 +6408,7 @@ async function createServer(inlineConfig = {}) {
5785
6408
  const readyIn = Math.ceil(performance.now() - startTime);
5786
6409
  logger.info(
5787
6410
  `
5788
- ${import_picocolors8.default.cyan(import_picocolors8.default.bold("NASTI"))} ${import_picocolors8.default.cyan(`v${"2.4.0"}`)} ${import_picocolors8.default.dim("ready in")} ${import_picocolors8.default.bold(readyIn)} ${import_picocolors8.default.dim("ms")}
6411
+ ${import_picocolors8.default.cyan(import_picocolors8.default.bold("NASTI"))} ${import_picocolors8.default.cyan(`v${"2.4.2"}`)} ${import_picocolors8.default.dim("ready in")} ${import_picocolors8.default.bold(readyIn)} ${import_picocolors8.default.dim("ms")}
5789
6412
  `
5790
6413
  );
5791
6414
  printServerUrls(
@@ -5812,20 +6435,26 @@ async function createServer(inlineConfig = {}) {
5812
6435
  });
5813
6436
  },
5814
6437
  async transformRequest(url) {
5815
- const { transformRequest: transformRequest2 } = await Promise.resolve().then(() => (init_middleware(), middleware_exports));
5816
- return transformRequest2(url, {
5817
- config: configWithPlugins,
5818
- pluginContainer,
5819
- moduleGraph,
5820
- onPrune: (paths) => ws.send({ type: "prune", paths })
5821
- });
6438
+ return clientEnv.transformRequest(url);
6439
+ },
6440
+ async transformEnvironmentRequest(environmentName, url) {
6441
+ const environment = environments[environmentName];
6442
+ if (!environment) {
6443
+ throw new Error(`[nasti] unknown dev environment "${environmentName}"`);
6444
+ }
6445
+ return environment.transformRequest(url);
5822
6446
  },
5823
6447
  async ssrLoadModule(url) {
5824
6448
  const runner = await getSsrRunner();
5825
6449
  return runner.import(url);
5826
6450
  },
5827
6451
  async close() {
5828
- await pluginContainer.buildEnd();
6452
+ if (devPipelinesStarted) {
6453
+ for (const environment of Object.values(environments).reverse()) {
6454
+ if (!transformContexts.has(environment.name)) continue;
6455
+ await environment.pluginContainer.buildEnd();
6456
+ }
6457
+ }
5829
6458
  await bundledServer?.close();
5830
6459
  let environmentCloseFailed = false;
5831
6460
  let firstEnvironmentCloseError;
@@ -5875,12 +6504,7 @@ async function createServer(inlineConfig = {}) {
5875
6504
  }
5876
6505
  throw error;
5877
6506
  }
5878
- app.use(transformMiddleware({
5879
- config: configWithPlugins,
5880
- pluginContainer,
5881
- moduleGraph,
5882
- onPrune: (paths) => ws.send({ type: "prune", paths })
5883
- }));
6507
+ app.use(transformMiddleware(transformContexts.get("client")));
5884
6508
  const publicDir = import_node_path15.default.resolve(config.root, "public");
5885
6509
  app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
5886
6510
  app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
@@ -5927,6 +6551,7 @@ var init_server = __esm({
5927
6551
  init_hmr();
5928
6552
  init_builtins();
5929
6553
  init_plugin_api();
6554
+ init_env();
5930
6555
  }
5931
6556
  });
5932
6557
 
@@ -5981,7 +6606,7 @@ async function buildElectron(inlineConfig = {}) {
5981
6606
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
5982
6607
  const startTime = performance.now();
5983
6608
  assertElectronVersion(config);
5984
- console.log(import_picocolors9.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors9.default.dim(` v${"2.4.0"}`));
6609
+ console.log(import_picocolors9.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors9.default.dim(` v${"2.4.2"}`));
5985
6610
  console.log(import_picocolors9.default.dim(` root: ${config.root}`));
5986
6611
  console.log(import_picocolors9.default.dim(` mode: ${config.mode}`));
5987
6612
  console.log(import_picocolors9.default.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
@@ -6160,7 +6785,7 @@ async function startElectronDev(inlineConfig = {}) {
6160
6785
  const { noSpawn, ...rest } = inlineConfig;
6161
6786
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
6162
6787
  warnElectronVersion(config);
6163
- console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.4.0"}`));
6788
+ console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.4.2"}`));
6164
6789
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
6165
6790
  const server = await createServer2({
6166
6791
  ...rest,
@@ -6525,7 +7150,7 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
6525
7150
  const host = options.host === true ? "0.0.0.0" : options.host ?? "localhost";
6526
7151
  http2.createServer(app).listen(port, host, () => {
6527
7152
  logger.info(`
6528
- ${import_picocolors11.default.cyan(import_picocolors11.default.bold("NASTI"))} ${import_picocolors11.default.cyan(`v${"2.4.0"}`)} ${import_picocolors11.default.dim("preview")}
7153
+ ${import_picocolors11.default.cyan(import_picocolors11.default.bold("NASTI"))} ${import_picocolors11.default.cyan(`v${"2.4.2"}`)} ${import_picocolors11.default.dim("preview")}
6529
7154
  `);
6530
7155
  printServerUrls2(
6531
7156
  {
@@ -6542,6 +7167,6 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
6542
7167
  }
6543
7168
  });
6544
7169
  cli.help();
6545
- cli.version("2.4.0");
7170
+ cli.version("2.4.2");
6546
7171
  cli.parse();
6547
7172
  //# sourceMappingURL=cli.cjs.map