@nasti-toolchain/nasti 1.6.5 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -88,6 +88,31 @@ export default defineConfig({
88
88
  })
89
89
  ```
90
90
 
91
+ ### 生产构建:手动代码拆分 & Tree-shaking
92
+
93
+ `build.rolldownOptions` 透传 Rolldown 底层选项:input 侧(如 `treeshake`、`resolve`、`external`)合并进打包,`output` 合并进产物写出阶段,用于手动控制 vendor 分包与 Tree-shaking。`input` / `plugins` 由 Nasti 接管,`output.dir` 始终由 `build.outDir` 决定。
94
+
95
+ ```ts
96
+ export default defineConfig({
97
+ build: {
98
+ rolldownOptions: {
99
+ // Tree-shaking(input 选项)
100
+ treeshake: { moduleSideEffects: [{ test: /\/barrel\//, sideEffects: false }] },
101
+ // 代码拆分(output 选项)
102
+ output: {
103
+ advancedChunks: {
104
+ groups: [
105
+ { name: 'react-vendor', test: /node_modules[\\/]react/, priority: 20 },
106
+ { name: 'vendor', test: /node_modules/, priority: 10 },
107
+ ],
108
+ },
109
+ chunkFileNames: 'assets/chunks/[name].[hash].js',
110
+ },
111
+ },
112
+ },
113
+ })
114
+ ```
115
+
91
116
  ## CLI
92
117
 
93
118
  ```bash
package/dist/cli.cjs CHANGED
@@ -1589,6 +1589,17 @@ function resolvePlugin(config) {
1589
1589
  const aliasEntries = Object.entries(alias).sort(
1590
1590
  ([a], [b]) => b.length - a.length
1591
1591
  );
1592
+ let vueRuntimeEntry = null;
1593
+ if (config.framework === "vue") {
1594
+ try {
1595
+ const vuePkgJson = require2.resolve("vue/package.json", { paths: [config.root] });
1596
+ const vueDir = import_node_path6.default.dirname(vuePkgJson);
1597
+ const mod = JSON.parse(import_node_fs6.default.readFileSync(vuePkgJson, "utf-8")).module;
1598
+ const entry = import_node_path6.default.join(vueDir, mod ?? "dist/vue.runtime.esm-bundler.js");
1599
+ if (import_node_fs6.default.existsSync(entry)) vueRuntimeEntry = entry;
1600
+ } catch {
1601
+ }
1602
+ }
1592
1603
  return {
1593
1604
  name: "nasti:resolve",
1594
1605
  enforce: "pre",
@@ -1619,6 +1630,7 @@ function resolvePlugin(config) {
1619
1630
  if (resolved) return resolved;
1620
1631
  }
1621
1632
  if (!source.startsWith("/") && !source.startsWith(".")) {
1633
+ if (vueRuntimeEntry && source === "vue") return vueRuntimeEntry;
1622
1634
  try {
1623
1635
  const resolved = require2.resolve(source, {
1624
1636
  paths: [importer ? import_node_path6.default.dirname(importer) : config.root]
@@ -1704,15 +1716,15 @@ async function loadTailwind(projectRoot) {
1704
1716
  async function compileTailwind(css, fromFile, projectRoot) {
1705
1717
  const { node, oxide } = await loadTailwind(projectRoot);
1706
1718
  const dependencies = [];
1707
- const compiler = await node.compile(css, {
1719
+ const compiler2 = await node.compile(css, {
1708
1720
  base: import_node_path7.default.dirname(fromFile),
1709
1721
  from: fromFile,
1710
1722
  onDependency: (p) => dependencies.push(p)
1711
1723
  });
1712
- const scanner = new oxide.Scanner({ sources: compiler.sources });
1724
+ const scanner = new oxide.Scanner({ sources: compiler2.sources });
1713
1725
  const candidates = scanner.scan();
1714
1726
  return {
1715
- css: compiler.build(candidates),
1727
+ css: compiler2.build(candidates),
1716
1728
  dependencies: [...dependencies, ...scanner.files]
1717
1729
  };
1718
1730
  }
@@ -1898,6 +1910,178 @@ var init_assets = __esm({
1898
1910
  }
1899
1911
  });
1900
1912
 
1913
+ // src/plugins/vue.ts
1914
+ async function loadVueCompiler() {
1915
+ if (compiler) return compiler;
1916
+ try {
1917
+ compiler = await import("@vue/compiler-sfc");
1918
+ return compiler;
1919
+ } catch {
1920
+ return null;
1921
+ }
1922
+ }
1923
+ function vuePlugin(config) {
1924
+ const isDev = config.command === "serve";
1925
+ const descriptorCache = /* @__PURE__ */ new Map();
1926
+ return {
1927
+ name: "nasti:vue",
1928
+ enforce: "pre",
1929
+ async resolveId(source) {
1930
+ if (VUE_QUERY_RE.test(source)) {
1931
+ return source;
1932
+ }
1933
+ return null;
1934
+ },
1935
+ async transform(code, id) {
1936
+ if (!VUE_FILE_RE.test(id) && !VUE_QUERY_RE.test(id)) return null;
1937
+ const sfc = await loadVueCompiler();
1938
+ if (!sfc) {
1939
+ console.warn("[nasti:vue] @vue/compiler-sfc not found. Install it: npm install @vue/compiler-sfc");
1940
+ return null;
1941
+ }
1942
+ if (VUE_QUERY_RE.test(id)) {
1943
+ return handleVueSubBlock(id, sfc, descriptorCache, config);
1944
+ }
1945
+ const { descriptor, errors } = sfc.parse(code, { filename: id });
1946
+ if (errors.length) {
1947
+ console.error(`[nasti:vue] Parse error in ${id}:`, errors[0].message);
1948
+ return null;
1949
+ }
1950
+ descriptorCache.set(id, descriptor);
1951
+ const scopeId = hashId(id);
1952
+ let scriptCode = "";
1953
+ if (descriptor.script || descriptor.scriptSetup) {
1954
+ const compiled = sfc.compileScript(descriptor, {
1955
+ id: scopeId,
1956
+ isProd: !isDev,
1957
+ inlineTemplate: true,
1958
+ // 让 compileScript 产出 `const __sfc__ = ...`(而非默认的 `export default {...}`)。
1959
+ // 否则下方追加的 `__sfc__.render` / `__sfc__.__scopeId` / HMR 记录会引用一个
1960
+ // 不存在的 `__sfc__`,并与 compileScript 自带的 `export default` 形成双重默认导出。
1961
+ genDefaultAs: "__sfc__"
1962
+ });
1963
+ scriptCode = compiled.content;
1964
+ }
1965
+ let templateCode = "";
1966
+ if (descriptor.template && !descriptor.scriptSetup) {
1967
+ const compiled = sfc.compileTemplate({
1968
+ source: descriptor.template.content,
1969
+ filename: id,
1970
+ id: scopeId,
1971
+ compilerOptions: { scopeId: `data-v-${scopeId}` }
1972
+ });
1973
+ templateCode = compiled.code;
1974
+ }
1975
+ let output = scriptCode || "const __sfc__ = {}";
1976
+ if (templateCode) {
1977
+ output += `
1978
+ ${templateCode}
1979
+ `;
1980
+ output += `
1981
+ __sfc__.render = render
1982
+ `;
1983
+ }
1984
+ if (descriptor.styles.length > 0) {
1985
+ for (let i = 0; i < descriptor.styles.length; i++) {
1986
+ const style = descriptor.styles[i];
1987
+ const lang2 = style.lang ?? "css";
1988
+ output += `
1989
+ import "${id}?vue&type=style&index=${i}&lang=${lang2}"
1990
+ `;
1991
+ }
1992
+ }
1993
+ output += `
1994
+ __sfc__.__scopeId = "data-v-${scopeId}"
1995
+ `;
1996
+ if (isDev) {
1997
+ output += `
1998
+ __sfc__.__hmrId = ${JSON.stringify(scopeId)}
1999
+ if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') {
2000
+ __VUE_HMR_RUNTIME__.createRecord(__sfc__.__hmrId, __sfc__)
2001
+ }
2002
+ if (import.meta.hot) {
2003
+ import.meta.hot.accept((mod) => {
2004
+ if (!mod) return
2005
+ const { default: updated } = mod
2006
+ if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') {
2007
+ __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)
2008
+ }
2009
+ })
2010
+ }
2011
+ `;
2012
+ }
2013
+ output += `
2014
+ export default __sfc__
2015
+ `;
2016
+ const lang = descriptor.scriptSetup?.lang ?? descriptor.script?.lang;
2017
+ if (lang === "ts") {
2018
+ const transpiled = transformCode(`${id}.ts`, output, { sourcemap: false });
2019
+ return { code: transpiled.code };
2020
+ }
2021
+ return { code: output };
2022
+ },
2023
+ handleHotUpdate(ctx) {
2024
+ const { file, modules } = ctx;
2025
+ if (VUE_FILE_RE.test(file)) {
2026
+ for (const mod of modules) {
2027
+ mod.isSelfAccepting = true;
2028
+ }
2029
+ descriptorCache.delete(file);
2030
+ }
2031
+ return modules;
2032
+ }
2033
+ };
2034
+ }
2035
+ async function handleVueSubBlock(id, sfc, cache, config) {
2036
+ const match = id.match(/(.+\.vue)\?vue&type=(\w+)(?:&index=(\d+))?(?:&lang=(\w+))?/);
2037
+ if (!match) return null;
2038
+ const [, filePath, type, indexStr, lang] = match;
2039
+ const descriptor = cache.get(filePath);
2040
+ if (!descriptor) return null;
2041
+ if (type === "style") {
2042
+ const index = parseInt(indexStr ?? "0", 10);
2043
+ const style = descriptor.styles[index];
2044
+ if (!style) return null;
2045
+ const scopeId = hashId(filePath);
2046
+ const result = await sfc.compileStyleAsync({
2047
+ source: style.content,
2048
+ filename: filePath,
2049
+ id: `data-v-${scopeId}`,
2050
+ scoped: style.scoped ?? false
2051
+ });
2052
+ const cssCode = JSON.stringify(result.code);
2053
+ return {
2054
+ code: `
2055
+ const css = ${cssCode};
2056
+ const style = document.createElement('style');
2057
+ style.setAttribute('data-v-${scopeId}', '');
2058
+ style.textContent = css;
2059
+ document.head.appendChild(style);
2060
+
2061
+ if (import.meta.hot) {
2062
+ import.meta.hot.accept();
2063
+ import.meta.hot.prune(() => style.remove());
2064
+ }
2065
+ `
2066
+ };
2067
+ }
2068
+ return null;
2069
+ }
2070
+ function hashId(filename) {
2071
+ return import_node_crypto2.default.createHash("sha256").update(filename).digest("hex").slice(0, 8);
2072
+ }
2073
+ var import_node_crypto2, VUE_FILE_RE, VUE_QUERY_RE, compiler;
2074
+ var init_vue = __esm({
2075
+ "src/plugins/vue.ts"() {
2076
+ "use strict";
2077
+ import_node_crypto2 = __toESM(require("crypto"), 1);
2078
+ init_transformer();
2079
+ VUE_FILE_RE = /\.vue$/;
2080
+ VUE_QUERY_RE = /\.vue\?vue&type=(script|template|style)(&index=\d+)?(&lang=\w+)?/;
2081
+ compiler = null;
2082
+ }
2083
+ });
2084
+
1901
2085
  // src/server/index.ts
1902
2086
  var server_exports = {};
1903
2087
  __export(server_exports, {
@@ -1906,6 +2090,7 @@ __export(server_exports, {
1906
2090
  async function createServer(inlineConfig = {}) {
1907
2091
  const config = await resolveConfig(inlineConfig, "serve");
1908
2092
  const allPlugins = [
2093
+ ...config.framework === "vue" ? [vuePlugin(config)] : [],
1909
2094
  resolvePlugin(config),
1910
2095
  cssPlugin(config),
1911
2096
  assetsPlugin(config),
@@ -1966,7 +2151,7 @@ async function createServer(inlineConfig = {}) {
1966
2151
  const localUrl = `http://localhost:${actualPort}`;
1967
2152
  const networkUrl = host === "0.0.0.0" ? `http://${getNetworkAddress()}:${actualPort}` : null;
1968
2153
  console.log();
1969
- console.log(import_picocolors.default.cyan(" nasti dev server") + import_picocolors.default.dim(` v${"1.6.5"}`));
2154
+ console.log(import_picocolors.default.cyan(" nasti dev server") + import_picocolors.default.dim(` v${"1.7.1"}`));
1970
2155
  console.log();
1971
2156
  console.log(` ${import_picocolors.default.green(">")} Local: ${import_picocolors.default.cyan(localUrl)}`);
1972
2157
  if (networkUrl) {
@@ -2042,6 +2227,7 @@ var init_server = __esm({
2042
2227
  init_resolve();
2043
2228
  init_css();
2044
2229
  init_assets();
2230
+ init_vue();
2045
2231
  init_html();
2046
2232
  }
2047
2233
  });
@@ -2093,7 +2279,7 @@ __export(build_exports, {
2093
2279
  async function build(inlineConfig = {}) {
2094
2280
  const config = await resolveConfig(inlineConfig, "build");
2095
2281
  const startTime = performance.now();
2096
- console.log(import_picocolors2.default.cyan("\n\u{1F528} nasti build") + import_picocolors2.default.dim(` v${"1.6.5"}`));
2282
+ console.log(import_picocolors2.default.cyan("\n\u{1F528} nasti build") + import_picocolors2.default.dim(` v${"1.7.1"}`));
2097
2283
  console.log(import_picocolors2.default.dim(` root: ${config.root}`));
2098
2284
  console.log(import_picocolors2.default.dim(` mode: ${config.mode}`));
2099
2285
  const outDir = import_node_path11.default.resolve(config.root, config.build.outDir);
@@ -2126,6 +2312,7 @@ async function build(inlineConfig = {}) {
2126
2312
  throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
2127
2313
  }
2128
2314
  const builtinPlugins = [
2315
+ ...config.framework === "vue" ? [vuePlugin(config)] : [],
2129
2316
  resolvePlugin(config),
2130
2317
  cssPlugin(config),
2131
2318
  assetsPlugin(config)
@@ -2147,11 +2334,17 @@ async function build(inlineConfig = {}) {
2147
2334
  };
2148
2335
  const env = loadEnv(config.mode, config.root, config.envPrefix);
2149
2336
  const envDefine = buildEnvDefine(env, config.mode);
2150
- const existingTransform = config.build.rolldownOptions?.transform;
2151
- const mergedDefine = { ...existingTransform?.define ?? {}, ...envDefine };
2337
+ const { output: userOutput, transform: userTransform, ...restInputOptions } = config.build.rolldownOptions;
2338
+ const vueDefine = config.framework === "vue" ? {
2339
+ __VUE_OPTIONS_API__: "true",
2340
+ __VUE_PROD_DEVTOOLS__: "false",
2341
+ __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "false"
2342
+ } : {};
2343
+ const mergedDefine = { ...vueDefine, ...userTransform?.define ?? {}, ...envDefine };
2152
2344
  const bundle = await (0, import_rolldown.rolldown)({
2345
+ ...restInputOptions,
2153
2346
  input: entryPoints,
2154
- transform: { ...existingTransform, define: mergedDefine },
2347
+ transform: { ...userTransform, define: mergedDefine },
2155
2348
  plugins: [
2156
2349
  oxcTransformPlugin,
2157
2350
  // 转换 Nasti 插件为 Rolldown 插件格式
@@ -2167,17 +2360,19 @@ async function build(inlineConfig = {}) {
2167
2360
  // manifest/SW writers) rely on for final-stage artifact emission.
2168
2361
  closeBundle: p.closeBundle
2169
2362
  }))
2170
- ],
2171
- ...config.build.rolldownOptions
2363
+ ]
2172
2364
  });
2173
2365
  const { output } = await bundle.write({
2174
- dir: outDir,
2175
2366
  format: "esm",
2176
2367
  sourcemap: !!config.build.sourcemap,
2177
2368
  minify: !!config.build.minify,
2178
2369
  entryFileNames: "assets/[name].[hash].js",
2179
2370
  chunkFileNames: "assets/[name].[hash].js",
2180
- assetFileNames: "assets/[name].[hash][extname]"
2371
+ assetFileNames: "assets/[name].[hash][extname]",
2372
+ // 用户可覆盖默认输出:代码拆分(advancedChunks / codeSplitting)、chunk 命名等
2373
+ ...userOutput,
2374
+ // dir 始终由 Nasti 掌管 —— 下方 HTML 改写依赖固定的产物目录,故放在最后强制生效
2375
+ dir: outDir
2181
2376
  });
2182
2377
  await bundle.close();
2183
2378
  await pluginContainer.buildEnd();
@@ -2241,6 +2436,7 @@ var init_build = __esm({
2241
2436
  init_resolve();
2242
2437
  init_css();
2243
2438
  init_assets();
2439
+ init_vue();
2244
2440
  init_html();
2245
2441
  init_transformer();
2246
2442
  init_env();
@@ -2260,7 +2456,7 @@ async function buildElectron(inlineConfig = {}) {
2260
2456
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
2261
2457
  const startTime = performance.now();
2262
2458
  assertElectronVersion(config);
2263
- console.log(import_picocolors3.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors3.default.dim(` v${"1.6.5"}`));
2459
+ console.log(import_picocolors3.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors3.default.dim(` v${"1.7.1"}`));
2264
2460
  console.log(import_picocolors3.default.dim(` root: ${config.root}`));
2265
2461
  console.log(import_picocolors3.default.dim(` mode: ${config.mode}`));
2266
2462
  console.log(import_picocolors3.default.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
@@ -2338,21 +2534,23 @@ async function bundleNode(config, entry, opts) {
2338
2534
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
2339
2535
  }
2340
2536
  };
2341
- const existingTransform = config.build.rolldownOptions?.transform;
2342
- const mergedDefine = { ...existingTransform?.define ?? {}, ...envDefine };
2537
+ const { output: userOutput, transform: userTransform, ...restInputOptions } = config.build.rolldownOptions;
2538
+ const mergedDefine = { ...userTransform?.define ?? {}, ...envDefine };
2343
2539
  const bundle = await (0, import_rolldown2.rolldown)({
2540
+ ...restInputOptions,
2344
2541
  input: entry,
2345
- transform: { ...existingTransform, define: mergedDefine },
2346
2542
  platform: "node",
2347
- plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)],
2348
- ...config.build.rolldownOptions
2543
+ transform: { ...userTransform, define: mergedDefine },
2544
+ plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
2349
2545
  });
2350
2546
  import_node_fs9.default.mkdirSync(import_node_path12.default.dirname(opts.outFile), { recursive: true });
2351
2547
  await bundle.write({
2352
- file: opts.outFile,
2353
- format: opts.format === "cjs" ? "cjs" : "esm",
2354
2548
  sourcemap: !!config.build.sourcemap,
2355
2549
  minify: !!config.build.minify,
2550
+ // 允许用户微调 output;但主进程 / preload 的单文件约束由下方键强制保证
2551
+ ...userOutput,
2552
+ file: opts.outFile,
2553
+ format: opts.format === "cjs" ? "cjs" : "esm",
2356
2554
  codeSplitting: false
2357
2555
  });
2358
2556
  await bundle.close();
@@ -2414,7 +2612,7 @@ async function startElectronDev(inlineConfig = {}) {
2414
2612
  const { noSpawn, ...rest } = inlineConfig;
2415
2613
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
2416
2614
  warnElectronVersion(config);
2417
- console.log(import_picocolors4.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors4.default.dim(` v${"1.6.5"}`));
2615
+ console.log(import_picocolors4.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors4.default.dim(` v${"1.7.1"}`));
2418
2616
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
2419
2617
  const server = await createServer2({ ...rest, target: "electron" });
2420
2618
  await server.listen();
@@ -2749,6 +2947,6 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
2749
2947
  }
2750
2948
  });
2751
2949
  cli.help();
2752
- cli.version("1.6.5");
2950
+ cli.version("1.7.1");
2753
2951
  cli.parse();
2754
2952
  //# sourceMappingURL=cli.cjs.map