@hzab/vite-config 0.0.2-alpha.0 → 0.0.3-alpha.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +95 -83
  2. package/LICENSE +21 -21
  3. package/README.md +184 -173
  4. package/dist/create-app-vite-config.cjs +160 -27
  5. package/dist/create-app-vite-config.cjs.map +1 -1
  6. package/dist/create-app-vite-config.js +160 -27
  7. package/dist/create-app-vite-config.js.map +1 -1
  8. package/dist/create-library-vite-config.cjs +39 -4
  9. package/dist/create-library-vite-config.cjs.map +1 -1
  10. package/dist/create-library-vite-config.js +38 -3
  11. package/dist/create-library-vite-config.js.map +1 -1
  12. package/dist/create-package-vitest-config.cjs.map +1 -1
  13. package/dist/create-package-vitest-config.js.map +1 -1
  14. package/dist/create-vite-config.cjs +160 -27
  15. package/dist/create-vite-config.cjs.map +1 -1
  16. package/dist/create-vite-config.js +160 -27
  17. package/dist/create-vite-config.js.map +1 -1
  18. package/dist/index.cjs +198 -30
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +199 -31
  23. package/dist/index.js.map +1 -1
  24. package/dist/plugins/copy.cjs.map +1 -1
  25. package/dist/plugins/copy.js.map +1 -1
  26. package/dist/plugins/hash-deploy.cjs +150 -25
  27. package/dist/plugins/hash-deploy.cjs.map +1 -1
  28. package/dist/plugins/hash-deploy.d.cts +16 -5
  29. package/dist/plugins/hash-deploy.d.ts +16 -5
  30. package/dist/plugins/hash-deploy.js +150 -25
  31. package/dist/plugins/hash-deploy.js.map +1 -1
  32. package/dist/plugins/less-tilde.cjs.map +1 -1
  33. package/dist/plugins/less-tilde.d.cts +17 -2
  34. package/dist/plugins/less-tilde.d.ts +17 -2
  35. package/dist/plugins/less-tilde.js.map +1 -1
  36. package/dist/plugins/lifecycle.cjs.map +1 -1
  37. package/dist/plugins/lifecycle.js.map +1 -1
  38. package/dist/plugins/public-assets.cjs.map +1 -1
  39. package/dist/plugins/public-assets.js.map +1 -1
  40. package/dist/plugins/public-config.cjs.map +1 -1
  41. package/dist/plugins/public-config.js.map +1 -1
  42. package/dist/plugins/react-compiler.cjs.map +1 -1
  43. package/dist/plugins/react-compiler.js.map +1 -1
  44. package/dist/plugins/relative-base.cjs.map +1 -1
  45. package/dist/plugins/relative-base.js.map +1 -1
  46. package/dist/plugins/stub-less.cjs.map +1 -1
  47. package/dist/plugins/stub-less.js.map +1 -1
  48. package/dist/plugins/svg-react.cjs.map +1 -1
  49. package/dist/plugins/svg-react.js.map +1 -1
  50. package/dist/types.d.cts +19 -8
  51. package/dist/types.d.ts +19 -8
  52. package/dist/vitest.cjs.map +1 -1
  53. package/dist/vitest.js.map +1 -1
  54. package/docs/README.md +15 -15
  55. package/docs/abt-management-ui/vite.config.mts +226 -226
  56. package/docs/api.md +404 -366
  57. package/docs/migration-from-webpack.md +723 -722
  58. package/docs/sync-public-path.md +52 -52
  59. package/docs/vite-config-integration.md +477 -476
  60. package/docs/vite-pnpm-integration.md +279 -279
  61. package/docs/vs-ccc-vite-config.md +26 -26
  62. package/docs/webpack-gap.md +108 -107
  63. package/package.json +1 -1
  64. package/src/client.d.ts +9 -9
  65. package/src/create-app-vite-config.ts +236 -218
  66. package/src/create-library-vite-config.ts +248 -199
  67. package/src/create-package-vitest-config.ts +54 -54
  68. package/src/create-vite-config.ts +22 -22
  69. package/src/index.ts +41 -42
  70. package/src/plugins/copy.ts +25 -25
  71. package/src/plugins/hash-deploy.ts +239 -199
  72. package/src/plugins/hash-dir-name.ts +212 -0
  73. package/src/plugins/less-tilde.ts +72 -44
  74. package/src/plugins/lifecycle.ts +40 -40
  75. package/src/plugins/public-assets.ts +28 -28
  76. package/src/plugins/public-config.ts +34 -34
  77. package/src/plugins/react-compiler.ts +32 -32
  78. package/src/plugins/relative-base.ts +20 -20
  79. package/src/plugins/stub-less.ts +26 -26
  80. package/src/plugins/svg-react.ts +90 -90
  81. package/src/svg-react-runtime.ts +4 -4
  82. package/src/svg-react.d.ts +6 -6
  83. package/src/types.ts +325 -314
  84. package/src/vitest-types.ts +20 -20
  85. package/src/vitest.ts +2 -2
  86. package/templates/src/vite-env.d.ts +2 -2
  87. package/templates/tsconfig.json +12 -12
  88. package/templates/vite.config.ts +26 -26
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import { execFileSync } from 'child_process';
7
7
  import fs from 'fs';
8
8
  import path from 'path';
9
9
  import babel from '@rolldown/plugin-babel';
10
- import fs2 from 'fs/promises';
10
+ import fs2, { rm, readFile, writeFile } from 'fs/promises';
11
11
 
12
12
  // src/create-app-vite-config.ts
13
13
  function resolveCopyPluginOptions(options) {
@@ -16,42 +16,157 @@ function resolveCopyPluginOptions(options) {
16
16
  function copyPlugin(options) {
17
17
  return viteStaticCopy(resolveCopyPluginOptions(options));
18
18
  }
19
- var HASH_BUILD_DIR_PATTERN = /^(?:[0-9a-f]{12}|local)-(\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}|\d+)$/;
20
- function parseHashTimestamp(value) {
21
- if (/^\d+$/.test(value)) return Number(value);
22
- const [year = 0, month = 1, day = 1, hour = 0, minute = 0, second = 0] = value.split("-").map(Number);
23
- return new Date(year, month - 1, day, hour, minute, second).getTime();
19
+
20
+ // src/plugins/hash-dir-name.ts
21
+ var tokenPattern = () => /YYYY|SSS|MM|DD|HH|mm|ss|timestamp|hash/g;
22
+ var DATE_TOKEN_WIDTH = { YYYY: 4, MM: 2, DD: 2, HH: 2, mm: 2, ss: 2, SSS: 3 };
23
+ var DATE_TOKEN_SLOT = {
24
+ YYYY: "year",
25
+ MM: "month",
26
+ DD: "day",
27
+ HH: "hour",
28
+ mm: "minute",
29
+ ss: "second",
30
+ SSS: "millisecond"
31
+ };
32
+ var TIME_TOKENS = ["timestamp", "YYYY", "MM", "DD", "HH", "mm", "ss", "SSS"];
33
+ var AVAILABLE_TOKENS = ["hash", ...TIME_TOKENS];
34
+ var DEFAULT_HASH_DIR_NAME_PATTERN = "hash-timestamp";
35
+ var ILLEGAL_DIR_CHAR_PATTERN = /[\\/:*?"<>|]/;
36
+ var pad = (value, length = 2) => String(value).padStart(length, "0");
37
+ function escapeRegExp(value) {
38
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
39
+ }
40
+ function assertPatternValue(pattern) {
41
+ if (pattern.trim() === "") {
42
+ throw new Error("[vite-config] hashDirName must be a non-empty string.");
43
+ }
24
44
  }
25
- function padTwo(value) {
26
- return String(value).padStart(2, "0");
45
+ function assertSafeDirName(dirName, pattern) {
46
+ const unsafe = ILLEGAL_DIR_CHAR_PATTERN.test(dirName) || dirName.includes("..") || dirName.startsWith(".") || dirName.endsWith(".") || dirName !== dirName.trim();
47
+ if (unsafe) {
48
+ throw new Error(
49
+ `[vite-config] hashDirName "${pattern}" resolves to unsafe directory name "${dirName}".`
50
+ );
51
+ }
27
52
  }
28
- function formatHashTimestamp(now, format = "epoch") {
29
- if (format !== "datetime") return String(now);
53
+ function expandHashDirName(options) {
54
+ const { pattern, commitHash, now = Date.now() } = options;
55
+ assertPatternValue(pattern);
30
56
  const date = new Date(now);
31
- return [
32
- date.getFullYear(),
33
- padTwo(date.getMonth() + 1),
34
- padTwo(date.getDate()),
35
- padTwo(date.getHours()),
36
- padTwo(date.getMinutes()),
37
- padTwo(date.getSeconds())
38
- ].join("-");
39
- }
40
- function cleanupOldHashBuilds(deployRoot, currentBuildDir, keep) {
57
+ const values = {
58
+ hash: commitHash || "local",
59
+ timestamp: String(now),
60
+ YYYY: String(date.getFullYear()),
61
+ MM: pad(date.getMonth() + 1),
62
+ DD: pad(date.getDate()),
63
+ HH: pad(date.getHours()),
64
+ mm: pad(date.getMinutes()),
65
+ ss: pad(date.getSeconds()),
66
+ SSS: pad(date.getMilliseconds(), 3)
67
+ };
68
+ let timeMatched = false;
69
+ const dirName = pattern.replace(tokenPattern(), (token) => {
70
+ if (TIME_TOKENS.includes(token)) timeMatched = true;
71
+ return values[token] ?? token;
72
+ });
73
+ if (!timeMatched) {
74
+ throw new Error(
75
+ `[vite-config] hashDirName "${pattern}" must contain a time token; available tokens: ${AVAILABLE_TOKENS.join(", ")}.`
76
+ );
77
+ }
78
+ assertSafeDirName(dirName, pattern);
79
+ return dirName;
80
+ }
81
+ function hasTimeToken(pattern) {
82
+ if (pattern.trim() === "") return false;
83
+ return [...pattern.matchAll(tokenPattern())].some(
84
+ (match) => TIME_TOKENS.includes(match[0])
85
+ );
86
+ }
87
+ function compileHashDirNameMatcher(pattern) {
88
+ assertPatternValue(pattern);
89
+ let source = "^";
90
+ let cursor = 0;
91
+ let groupIndex = 1;
92
+ let epochGroup = -1;
93
+ const dateTokens = [];
94
+ for (const match of pattern.matchAll(tokenPattern())) {
95
+ const index = match.index;
96
+ source += escapeRegExp(pattern.slice(cursor, index));
97
+ const token = match[0];
98
+ if (token === "hash") {
99
+ source += "(?:[0-9a-f]{12}|local)";
100
+ } else if (token === "timestamp") {
101
+ source += "(\\d{1,15})";
102
+ epochGroup = groupIndex;
103
+ groupIndex += 1;
104
+ } else {
105
+ const width = DATE_TOKEN_WIDTH[token];
106
+ source += `(\\d{${width}})`;
107
+ dateTokens.push(token);
108
+ groupIndex += 1;
109
+ }
110
+ cursor = index + token.length;
111
+ }
112
+ source += `${escapeRegExp(pattern.slice(cursor))}$`;
113
+ const regex = new RegExp(source);
114
+ return {
115
+ regex,
116
+ parseTimestamp(name) {
117
+ const matched = regex.exec(name);
118
+ if (!matched) return void 0;
119
+ if (epochGroup > 0) {
120
+ const epoch = matched[epochGroup];
121
+ return epoch == null ? void 0 : Number(epoch);
122
+ }
123
+ if (dateTokens.length === 0) return void 0;
124
+ const slots = {};
125
+ dateTokens.forEach((token, position) => {
126
+ const raw = matched[position + 1];
127
+ if (raw != null) slots[DATE_TOKEN_SLOT[token]] = Number(raw);
128
+ });
129
+ return new Date(
130
+ slots.year ?? 1970,
131
+ (slots.month ?? 1) - 1,
132
+ slots.day ?? 1,
133
+ slots.hour ?? 0,
134
+ slots.minute ?? 0,
135
+ slots.second ?? 0,
136
+ slots.millisecond ?? 0
137
+ ).getTime();
138
+ }
139
+ };
140
+ }
141
+ var RECENT_BUILD_PROTECT_MS = 5 * 60 * 1e3;
142
+ function cleanupOldHashBuilds(deployRoot, currentBuildDir, keep, matcher) {
143
+ const now = Date.now();
41
144
  const oldBuilds = fs.readdirSync(deployRoot, { withFileTypes: true }).flatMap((entry) => {
42
145
  if (!entry.isDirectory() || entry.name === currentBuildDir) return [];
43
- const match = HASH_BUILD_DIR_PATTERN.exec(entry.name);
44
- return match ? [{ name: entry.name, timestamp: parseHashTimestamp(match[1]) }] : [];
146
+ const timestamp = matcher.parseTimestamp(entry.name);
147
+ if (timestamp == null) return [];
148
+ if (now - timestamp < RECENT_BUILD_PROTECT_MS) return [];
149
+ return [{ name: entry.name, timestamp }];
45
150
  }).sort((left, right) => right.timestamp - left.timestamp || right.name.localeCompare(left.name));
46
151
  for (const build of oldBuilds.slice(Math.max(keep - 1, 0))) {
47
152
  fs.rmSync(path.join(deployRoot, build.name), { recursive: true });
48
153
  }
49
154
  }
50
155
  function hashDeployPlugin(options) {
51
- const { rootDir, buildDirStr, publicPath, cleanup } = options;
156
+ const { rootDir, buildDirStr, buildDirNamePattern, publicPath, cleanup } = options;
52
157
  if (cleanup && (!Number.isInteger(cleanup.keep) || cleanup.keep < 1)) {
53
158
  throw new Error("[hash-deploy] cleanup.keep must be an integer greater than or equal to 1.");
54
159
  }
160
+ const resolvedBuildDirNamePattern = buildDirNamePattern ?? DEFAULT_HASH_DIR_NAME_PATTERN;
161
+ if (cleanup && !hasTimeToken(resolvedBuildDirNamePattern)) {
162
+ throw new Error("[hash-deploy] cleanup requires a buildDirNamePattern with a time token.");
163
+ }
164
+ const cleanupMatcher = compileHashDirNameMatcher(resolvedBuildDirNamePattern);
165
+ if (cleanup && !cleanupMatcher.regex.test(buildDirStr)) {
166
+ throw new Error(
167
+ `[hash-deploy] buildDirStr "${buildDirStr}" does not match buildDirNamePattern "${resolvedBuildDirNamePattern}".`
168
+ );
169
+ }
55
170
  let outputDir;
56
171
  let deployRoot;
57
172
  return {
@@ -96,7 +211,7 @@ function hashDeployPlugin(options) {
96
211
  const rootHtml = publicPath.startsWith("./") ? sourceHtml.replace(/(\b(?:href|src)\s*=\s*["'])\.\//g, `$1${publicPath}`) : sourceHtml;
97
212
  fs.writeFileSync(path.join(deployRoot, "index.html"), rootHtml);
98
213
  if (cleanup) {
99
- cleanupOldHashBuilds(deployRoot, buildDirStr, cleanup.keep);
214
+ cleanupOldHashBuilds(deployRoot, buildDirStr, cleanup.keep, cleanupMatcher);
100
215
  }
101
216
  }
102
217
  }
@@ -118,13 +233,21 @@ function createHashBuildDirName(repositoryRootOrNowOrOptions, now = Date.now())
118
233
  const {
119
234
  repositoryRoot: repositoryRoot2,
120
235
  now: optionsNow = Date.now(),
121
- timestampFormat = "epoch"
236
+ hashDirName
122
237
  } = repositoryRootOrNowOrOptions;
123
- return `${getCommitHash(repositoryRoot2)}-${formatHashTimestamp(optionsNow, timestampFormat)}`;
238
+ return expandHashDirName({
239
+ pattern: hashDirName ?? DEFAULT_HASH_DIR_NAME_PATTERN,
240
+ commitHash: getCommitHash(repositoryRoot2),
241
+ now: optionsNow
242
+ });
124
243
  }
125
244
  const repositoryRoot = typeof repositoryRootOrNowOrOptions === "string" ? repositoryRootOrNowOrOptions : void 0;
126
245
  const timestamp = typeof repositoryRootOrNowOrOptions === "number" ? repositoryRootOrNowOrOptions : now;
127
- return `${getCommitHash(repositoryRoot)}-${formatHashTimestamp(timestamp)}`;
246
+ return expandHashDirName({
247
+ pattern: DEFAULT_HASH_DIR_NAME_PATTERN,
248
+ commitHash: getCommitHash(repositoryRoot),
249
+ now: timestamp
250
+ });
128
251
  }
129
252
 
130
253
  // src/plugins/less-tilde.ts
@@ -303,7 +426,7 @@ function createAppViteConfig(options) {
303
426
  useHttps = false,
304
427
  openBrowser = true,
305
428
  hashDeploy = false,
306
- hashTimestamp = "epoch",
429
+ hashDirName,
307
430
  base: baseOption,
308
431
  deployRoot: deployRootOption = "dist",
309
432
  hashCleanup,
@@ -327,10 +450,19 @@ function createAppViteConfig(options) {
327
450
  const isHash = isBuild && Boolean(hashDeploy) && mode !== "development";
328
451
  const deployRoot = deployRootOption.replace(/\/+$/, "") || "dist";
329
452
  const assetBase = normalizeAssetBase(baseOption);
453
+ const legacyHashTimestamp = options.hashTimestamp;
454
+ if (legacyHashTimestamp != null && legacyHashTimestamp !== "epoch") {
455
+ throw new Error(
456
+ `[vite-config] hashTimestamp: ${JSON.stringify(
457
+ legacyHashTimestamp
458
+ )} has been removed; use hashDirName instead (e.g. "hash-timestamp" or "hash-YYYY-MM-DD-HH-mm-ss").`
459
+ );
460
+ }
330
461
  const buildDirStr = isHash ? createHashBuildDirName({
331
462
  repositoryRoot: repositoryRoot ?? rootDir,
332
- timestampFormat: hashTimestamp
463
+ hashDirName
333
464
  }) : "";
465
+ const buildDirNamePattern = hashDirName ?? DEFAULT_HASH_DIR_NAME_PATTERN;
334
466
  const publicPath = isHash ? `${assetBase ?? "./"}${buildDirStr}/` : isBuild ? assetBase ?? "./" : "";
335
467
  const htmlPublicPath = isHash && publicPath.startsWith("./") ? "./" : publicPath;
336
468
  const isAbsoluteAssetBase = assetBase != null && (assetBase.startsWith("https://") || assetBase.startsWith("http://") || assetBase.startsWith("/"));
@@ -361,6 +493,7 @@ function createAppViteConfig(options) {
361
493
  hashDeployPlugin({
362
494
  rootDir,
363
495
  buildDirStr,
496
+ buildDirNamePattern,
364
497
  publicPath,
365
498
  cleanup: hashCleanup
366
499
  })
@@ -427,6 +560,29 @@ var DEFAULT_LIBRARY_GLOBALS = {
427
560
  "react-dom": "ReactDOM"
428
561
  };
429
562
  var DEFAULT_LIBRARY_FORMATS = ["umd"];
563
+ function stripCjsSourcemapPlugin() {
564
+ let outDir = "";
565
+ return {
566
+ name: "hzab:strip-cjs-sourcemap",
567
+ apply: "build",
568
+ configResolved(config) {
569
+ outDir = config.build.outDir;
570
+ },
571
+ async writeBundle(_options, bundle) {
572
+ for (const fileName of Object.keys(bundle)) {
573
+ const file = path.resolve(outDir, fileName);
574
+ if (fileName.endsWith(".cjs.map")) {
575
+ await rm(file, { force: true });
576
+ continue;
577
+ }
578
+ if (!fileName.endsWith(".cjs")) continue;
579
+ const code = await readFile(file, "utf8");
580
+ const stripped = code.replace(/\n?\/\/# sourceMappingURL=\S+\s*$/u, "\n");
581
+ if (stripped !== code) await writeFile(file, stripped, "utf8");
582
+ }
583
+ }
584
+ };
585
+ }
430
586
  function needsLibraryName(formats) {
431
587
  return formats.includes("umd") || formats.includes("iife");
432
588
  }
@@ -440,7 +596,11 @@ function resolveLibraryFileName(fileName, formats) {
440
596
  if (formats.length === 1) {
441
597
  return () => "index.js";
442
598
  }
443
- return (format) => format === "es" ? "index.js" : `index.${format}.js`;
599
+ return (format) => {
600
+ if (format === "es") return "index.js";
601
+ if (format === "cjs") return "index.cjs";
602
+ return `index.${format}.js`;
603
+ };
444
604
  }
445
605
  function resolveLibraryGlobals(externals, globals) {
446
606
  const inferred = {};
@@ -513,7 +673,8 @@ function createLibraryViteConfig(options) {
513
673
  ] : [],
514
674
  ...isDev && useHttps ? [basicSsl()] : [],
515
675
  ...extraPlugins,
516
- ...isBuild && (onRun || onDone) ? [lifecyclePlugin({ onRun, onDone })] : []
676
+ ...isBuild && (onRun || onDone) ? [lifecyclePlugin({ onRun, onDone })] : [],
677
+ ...isBuild ? [stripCjsSourcemapPlugin()] : []
517
678
  ];
518
679
  const resolvedExternals = resolveExternals(externalsOption);
519
680
  const resolvedGlobals = resolveLibraryGlobals(resolvedExternals, globalsOption);
@@ -540,6 +701,13 @@ function createLibraryViteConfig(options) {
540
701
  build: {
541
702
  outDir,
542
703
  emptyOutDir: true,
704
+ // 产物随包发布:消费方 webpack 的 devtool 会消费依赖自带的 map,缺失时
705
+ // DevTools 只能看到缩短标识符后的编译产物(见 docs/src-direct-vs-dist-evaluation.md §7.1)。
706
+ sourcemap: true,
707
+ // 降到 ES2018:webpack 4(umi3 线 abt-app)内置 acorn 6 解析到 ES2019 为止,不认 ES2020 的
708
+ // `?.` / `??`,产物保持默认 target 会让该线直接 Module parse failed。ES2018 是覆盖 acorn 6
709
+ // 边界的最小目标(对象展开仍保留),见 docs/src-direct-vs-dist-evaluation.md §7.2。
710
+ target: "es2018",
543
711
  lib: {
544
712
  entry: path.resolve(rootDir, entry),
545
713
  ...name != null && name.trim() !== "" ? { name } : {},