@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.
- package/CHANGELOG.md +95 -83
- package/LICENSE +21 -21
- package/README.md +184 -173
- package/dist/create-app-vite-config.cjs +160 -27
- package/dist/create-app-vite-config.cjs.map +1 -1
- package/dist/create-app-vite-config.js +160 -27
- package/dist/create-app-vite-config.js.map +1 -1
- package/dist/create-library-vite-config.cjs +39 -4
- package/dist/create-library-vite-config.cjs.map +1 -1
- package/dist/create-library-vite-config.js +38 -3
- package/dist/create-library-vite-config.js.map +1 -1
- package/dist/create-package-vitest-config.cjs.map +1 -1
- package/dist/create-package-vitest-config.js.map +1 -1
- package/dist/create-vite-config.cjs +160 -27
- package/dist/create-vite-config.cjs.map +1 -1
- package/dist/create-vite-config.js +160 -27
- package/dist/create-vite-config.js.map +1 -1
- package/dist/index.cjs +198 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +199 -31
- package/dist/index.js.map +1 -1
- package/dist/plugins/copy.cjs.map +1 -1
- package/dist/plugins/copy.js.map +1 -1
- package/dist/plugins/hash-deploy.cjs +150 -25
- package/dist/plugins/hash-deploy.cjs.map +1 -1
- package/dist/plugins/hash-deploy.d.cts +16 -5
- package/dist/plugins/hash-deploy.d.ts +16 -5
- package/dist/plugins/hash-deploy.js +150 -25
- package/dist/plugins/hash-deploy.js.map +1 -1
- package/dist/plugins/less-tilde.cjs.map +1 -1
- package/dist/plugins/less-tilde.d.cts +17 -2
- package/dist/plugins/less-tilde.d.ts +17 -2
- package/dist/plugins/less-tilde.js.map +1 -1
- package/dist/plugins/lifecycle.cjs.map +1 -1
- package/dist/plugins/lifecycle.js.map +1 -1
- package/dist/plugins/public-assets.cjs.map +1 -1
- package/dist/plugins/public-assets.js.map +1 -1
- package/dist/plugins/public-config.cjs.map +1 -1
- package/dist/plugins/public-config.js.map +1 -1
- package/dist/plugins/react-compiler.cjs.map +1 -1
- package/dist/plugins/react-compiler.js.map +1 -1
- package/dist/plugins/relative-base.cjs.map +1 -1
- package/dist/plugins/relative-base.js.map +1 -1
- package/dist/plugins/stub-less.cjs.map +1 -1
- package/dist/plugins/stub-less.js.map +1 -1
- package/dist/plugins/svg-react.cjs.map +1 -1
- package/dist/plugins/svg-react.js.map +1 -1
- package/dist/types.d.cts +19 -8
- package/dist/types.d.ts +19 -8
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.js.map +1 -1
- package/docs/README.md +15 -15
- package/docs/abt-management-ui/vite.config.mts +226 -226
- package/docs/api.md +404 -366
- package/docs/migration-from-webpack.md +723 -722
- package/docs/sync-public-path.md +52 -52
- package/docs/vite-config-integration.md +477 -476
- package/docs/vite-pnpm-integration.md +279 -279
- package/docs/vs-ccc-vite-config.md +26 -26
- package/docs/webpack-gap.md +108 -107
- package/package.json +1 -1
- package/src/client.d.ts +9 -9
- package/src/create-app-vite-config.ts +236 -218
- package/src/create-library-vite-config.ts +248 -199
- package/src/create-package-vitest-config.ts +54 -54
- package/src/create-vite-config.ts +22 -22
- package/src/index.ts +41 -42
- package/src/plugins/copy.ts +25 -25
- package/src/plugins/hash-deploy.ts +239 -199
- package/src/plugins/hash-dir-name.ts +212 -0
- package/src/plugins/less-tilde.ts +72 -44
- package/src/plugins/lifecycle.ts +40 -40
- package/src/plugins/public-assets.ts +28 -28
- package/src/plugins/public-config.ts +34 -34
- package/src/plugins/react-compiler.ts +32 -32
- package/src/plugins/relative-base.ts +20 -20
- package/src/plugins/stub-less.ts +26 -26
- package/src/plugins/svg-react.ts +90 -90
- package/src/svg-react-runtime.ts +4 -4
- package/src/svg-react.d.ts +6 -6
- package/src/types.ts +325 -314
- package/src/vitest-types.ts +20 -20
- package/src/vitest.ts +2 -2
- package/templates/src/vite-env.d.ts +2 -2
- package/templates/tsconfig.json +12 -12
- package/templates/vite.config.ts +26 -26
package/src/index.ts
CHANGED
|
@@ -1,42 +1,41 @@
|
|
|
1
|
-
export { createAppViteConfig } from "./create-app-vite-config.ts";
|
|
2
|
-
export { createViteConfig } from "./create-vite-config.ts";
|
|
3
|
-
export type { CreateViteConfigOptions } from "./create-vite-config.ts";
|
|
4
|
-
export {
|
|
5
|
-
createLibraryViteConfig,
|
|
6
|
-
DEFAULT_LIBRARY_EXTERNALS,
|
|
7
|
-
DEFAULT_LIBRARY_GLOBALS,
|
|
8
|
-
} from "./create-library-vite-config.ts";
|
|
9
|
-
export { copyPlugin } from "./plugins/copy.ts";
|
|
10
|
-
export type {
|
|
11
|
-
HashBuildDirNameOptions,
|
|
12
|
-
HashCleanupOptions,
|
|
13
|
-
HashDeployPluginOptions,
|
|
14
|
-
|
|
15
|
-
} from "./plugins/hash-deploy.ts";
|
|
16
|
-
export {
|
|
17
|
-
export
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export
|
|
21
|
-
export {
|
|
22
|
-
export
|
|
23
|
-
export {
|
|
24
|
-
export
|
|
25
|
-
export {
|
|
26
|
-
export {
|
|
27
|
-
export {
|
|
28
|
-
export {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
} from "./types.ts";
|
|
1
|
+
export { createAppViteConfig } from "./create-app-vite-config.ts";
|
|
2
|
+
export { createViteConfig } from "./create-vite-config.ts";
|
|
3
|
+
export type { CreateViteConfigOptions } from "./create-vite-config.ts";
|
|
4
|
+
export {
|
|
5
|
+
createLibraryViteConfig,
|
|
6
|
+
DEFAULT_LIBRARY_EXTERNALS,
|
|
7
|
+
DEFAULT_LIBRARY_GLOBALS,
|
|
8
|
+
} from "./create-library-vite-config.ts";
|
|
9
|
+
export { copyPlugin } from "./plugins/copy.ts";
|
|
10
|
+
export type {
|
|
11
|
+
HashBuildDirNameOptions,
|
|
12
|
+
HashCleanupOptions,
|
|
13
|
+
HashDeployPluginOptions,
|
|
14
|
+
} from "./plugins/hash-deploy.ts";
|
|
15
|
+
export { createHashBuildDirName, getCommitHash, hashDeployPlugin } from "./plugins/hash-deploy.ts";
|
|
16
|
+
export type { LifecyclePluginOptions } from "./plugins/lifecycle.ts";
|
|
17
|
+
export { lifecyclePlugin } from "./plugins/lifecycle.ts";
|
|
18
|
+
export { buildLessOptions, lessTildeResolutionPlugin } from "./plugins/less-tilde.ts";
|
|
19
|
+
export type { PublicAssetsPluginOptions } from "./plugins/public-assets.ts";
|
|
20
|
+
export { publicAssetsPlugin } from "./plugins/public-assets.ts";
|
|
21
|
+
export type { PublicConfigPluginOptions } from "./plugins/public-config.ts";
|
|
22
|
+
export { publicConfigPlugin } from "./plugins/public-config.ts";
|
|
23
|
+
export type { ReactCompilerPluginOptions } from "./plugins/react-compiler.ts";
|
|
24
|
+
export { reactCompilerPlugin } from "./plugins/react-compiler.ts";
|
|
25
|
+
export { relativeBasePlugin } from "./plugins/relative-base.ts";
|
|
26
|
+
export { stubLessPlugin } from "./plugins/stub-less.ts";
|
|
27
|
+
export { svgReactPlugin } from "./plugins/svg-react.ts";
|
|
28
|
+
export type {
|
|
29
|
+
CopyOption,
|
|
30
|
+
CopyPluginOptions,
|
|
31
|
+
CopyTarget,
|
|
32
|
+
CreateAppViteConfigOptions,
|
|
33
|
+
CreateAppViteConfigResult,
|
|
34
|
+
CreateLibraryViteConfigOptions,
|
|
35
|
+
LessOptions,
|
|
36
|
+
LibraryFormats,
|
|
37
|
+
PublicAssetsOptions,
|
|
38
|
+
PublicConfigOptions,
|
|
39
|
+
ReactCompilerMode,
|
|
40
|
+
SvgReactOptions,
|
|
41
|
+
} from "./types.ts";
|
package/src/plugins/copy.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import { viteStaticCopy } from "vite-plugin-static-copy";
|
|
2
|
-
|
|
3
|
-
import type { PluginOption } from "vite";
|
|
4
|
-
import type { Target, ViteStaticCopyOptions } from "vite-plugin-static-copy";
|
|
5
|
-
|
|
6
|
-
/** @public */
|
|
7
|
-
export type CopyTarget = Target;
|
|
8
|
-
/** @public */
|
|
9
|
-
export type CopyPluginOptions = ViteStaticCopyOptions;
|
|
10
|
-
|
|
11
|
-
/** @public */
|
|
12
|
-
export type CopyOption = CopyPluginOptions | CopyTarget[];
|
|
13
|
-
|
|
14
|
-
export function resolveCopyPluginOptions(options: CopyOption): CopyPluginOptions {
|
|
15
|
-
return Array.isArray(options) ? { targets: options } : options;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 通用静态复制,封装 `vite-plugin-static-copy`。
|
|
20
|
-
* 可传完整选项,或直接传 `targets` 数组。
|
|
21
|
-
* @public
|
|
22
|
-
*/
|
|
23
|
-
export function copyPlugin(options: CopyOption): PluginOption {
|
|
24
|
-
return viteStaticCopy(resolveCopyPluginOptions(options));
|
|
25
|
-
}
|
|
1
|
+
import { viteStaticCopy } from "vite-plugin-static-copy";
|
|
2
|
+
|
|
3
|
+
import type { PluginOption } from "vite";
|
|
4
|
+
import type { Target, ViteStaticCopyOptions } from "vite-plugin-static-copy";
|
|
5
|
+
|
|
6
|
+
/** @public */
|
|
7
|
+
export type CopyTarget = Target;
|
|
8
|
+
/** @public */
|
|
9
|
+
export type CopyPluginOptions = ViteStaticCopyOptions;
|
|
10
|
+
|
|
11
|
+
/** @public */
|
|
12
|
+
export type CopyOption = CopyPluginOptions | CopyTarget[];
|
|
13
|
+
|
|
14
|
+
export function resolveCopyPluginOptions(options: CopyOption): CopyPluginOptions {
|
|
15
|
+
return Array.isArray(options) ? { targets: options } : options;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 通用静态复制,封装 `vite-plugin-static-copy`。
|
|
20
|
+
* 可传完整选项,或直接传 `targets` 数组。
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
export function copyPlugin(options: CopyOption): PluginOption {
|
|
24
|
+
return viteStaticCopy(resolveCopyPluginOptions(options));
|
|
25
|
+
}
|
|
@@ -1,199 +1,239 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
import type { PluginOption } from "vite";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import type { PluginOption } from "vite";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
compileHashDirNameMatcher,
|
|
9
|
+
DEFAULT_HASH_DIR_NAME_PATTERN,
|
|
10
|
+
expandHashDirName,
|
|
11
|
+
hasTimeToken,
|
|
12
|
+
} from "./hash-dir-name.ts";
|
|
13
|
+
import type { HashDirNameMatcher } from "./hash-dir-name.ts";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 近期构建保护窗口:目录名时间戳距今不足该时长的目录不参与清理。
|
|
17
|
+
* 同机并发构建时,先完成的构建不应删掉另一个进程正在写的目录。
|
|
18
|
+
* 判据只用目录名里的时间戳,不用文件系统 mtime——CI 恢复缓存 / 解压产物会改写 mtime。
|
|
19
|
+
*/
|
|
20
|
+
const RECENT_BUILD_PROTECT_MS = 5 * 60 * 1000;
|
|
21
|
+
|
|
22
|
+
/** @public */
|
|
23
|
+
export interface HashBuildDirNameOptions {
|
|
24
|
+
repositoryRoot?: string;
|
|
25
|
+
now?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Hash 子目录名模式串;不传时用默认 `hash-timestamp`(等价历史默认 `{commit}-{毫秒时间戳}`)。
|
|
28
|
+
* token 大小写敏感:`hash` = 12 位 commit(非 git 回退 `local`)、`timestamp` = 毫秒时间戳、
|
|
29
|
+
* `YYYY`/`MM`/`DD`/`HH`/`mm`/`ss`/`SSS` = 本地时区格式化时间;其余字符(含 `-`)为字面文本。
|
|
30
|
+
* 模式串必须至少含一个时间 token(`timestamp` 或日期 token),只含 `hash` 会被拒绝。
|
|
31
|
+
* 例:`hash-timestamp`、`hash-timestamp-YYYYMMDDHHmmssSSS`、`hash-YYYYMMDDHHmmssSSS`。
|
|
32
|
+
*/
|
|
33
|
+
hashDirName?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** @public */
|
|
37
|
+
export interface HashCleanupOptions {
|
|
38
|
+
/**
|
|
39
|
+
* 每次成功构建后保留的 Hash 版本总数(含本次构建)。
|
|
40
|
+
* 必须为大于等于 1 的整数。
|
|
41
|
+
*/
|
|
42
|
+
keep: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** @public */
|
|
46
|
+
export interface HashDeployPluginOptions {
|
|
47
|
+
rootDir: string;
|
|
48
|
+
/** hash 子目录名,如 `{commit}-{timestamp}` 或 `{commit}-YYYY-MM-DD-HH-mm-ss` */
|
|
49
|
+
buildDirStr: string;
|
|
50
|
+
/**
|
|
51
|
+
* 生成 `buildDirStr` 的模式串(来自工厂的 `hashDirName`);供 `cleanup` 精确识别历史目录,
|
|
52
|
+
* 且必须与实际 `buildDirStr` 匹配。未传时用默认模式串 `hash-timestamp`。
|
|
53
|
+
*/
|
|
54
|
+
buildDirNamePattern?: string;
|
|
55
|
+
/**
|
|
56
|
+
* 与最终 Vite `base` 一致的资源前缀。
|
|
57
|
+
* - 默认相对:`./{buildDirStr}/`
|
|
58
|
+
* - 自定义 CDN:`https://cdn.example.com/app/{buildDirStr}/`
|
|
59
|
+
*/
|
|
60
|
+
publicPath: string;
|
|
61
|
+
/** 旧 Hash 目录清理策略;未传时永久保留,由部署系统负责清理。 */
|
|
62
|
+
cleanup?: HashCleanupOptions;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 只保留最近 `keep` 个 Hash 版本(含本次构建)。
|
|
67
|
+
* 只识别 `matcher`(模式串)生成的目录名;不匹配的目录(如 `uploads-*`)一律不动。
|
|
68
|
+
* 目录名时间戳落在保护窗口内的目录视为并发构建产物,跳过清理。
|
|
69
|
+
*/
|
|
70
|
+
function cleanupOldHashBuilds(
|
|
71
|
+
deployRoot: string,
|
|
72
|
+
currentBuildDir: string,
|
|
73
|
+
keep: number,
|
|
74
|
+
matcher: HashDirNameMatcher,
|
|
75
|
+
): void {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const oldBuilds = fs
|
|
78
|
+
.readdirSync(deployRoot, { withFileTypes: true })
|
|
79
|
+
.flatMap((entry) => {
|
|
80
|
+
if (!entry.isDirectory() || entry.name === currentBuildDir) return [];
|
|
81
|
+
const timestamp = matcher.parseTimestamp(entry.name);
|
|
82
|
+
if (timestamp == null) return [];
|
|
83
|
+
// 保护窗口内的目录(含因时钟偏差落在未来的目录)保持不动
|
|
84
|
+
if (now - timestamp < RECENT_BUILD_PROTECT_MS) return [];
|
|
85
|
+
|
|
86
|
+
return [{ name: entry.name, timestamp }];
|
|
87
|
+
})
|
|
88
|
+
.sort((left, right) => right.timestamp - left.timestamp || right.name.localeCompare(left.name));
|
|
89
|
+
|
|
90
|
+
for (const build of oldBuilds.slice(Math.max(keep - 1, 0))) {
|
|
91
|
+
fs.rmSync(path.join(deployRoot, build.name), { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 构建结束后写 `dist/hash.txt`,并把 hash 子目录中的 `index.html` 复制到 `dist/index.html`。
|
|
97
|
+
* 相对 `base` 时为根入口补上 hash 前缀;绝对 `base` 时产物已含完整 URL,原样复制即可。
|
|
98
|
+
* 配置 `cleanup.keep` 时,成功写入根入口后只保留指定数量的最新 Hash 版本。
|
|
99
|
+
* @public
|
|
100
|
+
*/
|
|
101
|
+
export function hashDeployPlugin(options: HashDeployPluginOptions): PluginOption {
|
|
102
|
+
const { rootDir, buildDirStr, buildDirNamePattern, publicPath, cleanup } = options;
|
|
103
|
+
if (cleanup && (!Number.isInteger(cleanup.keep) || cleanup.keep < 1)) {
|
|
104
|
+
throw new Error("[hash-deploy] cleanup.keep must be an integer greater than or equal to 1.");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const resolvedBuildDirNamePattern = buildDirNamePattern ?? DEFAULT_HASH_DIR_NAME_PATTERN;
|
|
108
|
+
if (cleanup && !hasTimeToken(resolvedBuildDirNamePattern)) {
|
|
109
|
+
// 没有可排序的时间 token 时无法判断历史目录新旧,直接报错,否则会静默堆积
|
|
110
|
+
throw new Error("[hash-deploy] cleanup requires a buildDirNamePattern with a time token.");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const cleanupMatcher = compileHashDirNameMatcher(resolvedBuildDirNamePattern);
|
|
114
|
+
if (cleanup && !cleanupMatcher.regex.test(buildDirStr)) {
|
|
115
|
+
// 配了清理但模式串与实际目录名不符时直接报错,否则历史目录会静默堆积,
|
|
116
|
+
// 与「保留最近 N 个」的预期相反。
|
|
117
|
+
throw new Error(
|
|
118
|
+
`[hash-deploy] buildDirStr "${buildDirStr}" does not match buildDirNamePattern "${resolvedBuildDirNamePattern}".`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let outputDir: string | undefined;
|
|
123
|
+
let deployRoot: string | undefined;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
name: "hash-deploy",
|
|
127
|
+
enforce: "post",
|
|
128
|
+
configResolved(config) {
|
|
129
|
+
const resolvedRoot = path.resolve(config.root);
|
|
130
|
+
const expectedRoot = path.resolve(rootDir);
|
|
131
|
+
if (resolvedRoot !== expectedRoot) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`[hash-deploy] Vite root "${resolvedRoot}" must match rootDir "${expectedRoot}".`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
// 相对 hash:工厂 publicPath 为 `./{hash}/`,但 Vite base 必须保持 `./`。
|
|
137
|
+
const expectedBase = publicPath.startsWith("./") ? "./" : publicPath;
|
|
138
|
+
if (config.base !== expectedBase) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`[hash-deploy] Vite base "${config.base}" must remain "${expectedBase}" during hash deployment.`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
outputDir = path.resolve(resolvedRoot, config.build.outDir);
|
|
145
|
+
if (path.basename(outputDir) !== buildDirStr) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`[hash-deploy] build.outDir "${config.build.outDir}" must end with the hash directory "${buildDirStr}".`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
deployRoot = path.dirname(outputDir);
|
|
151
|
+
},
|
|
152
|
+
writeBundle: {
|
|
153
|
+
sequential: true,
|
|
154
|
+
order: "post",
|
|
155
|
+
handler() {
|
|
156
|
+
if (!outputDir || !deployRoot) {
|
|
157
|
+
throw new Error("[hash-deploy] Vite configuration was not resolved before writeBundle.");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const sourceIndex = path.join(outputDir, "index.html");
|
|
161
|
+
if (!fs.existsSync(sourceIndex)) {
|
|
162
|
+
throw new Error(`[hash-deploy] Generated entry was not found: "${sourceIndex}".`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fs.mkdirSync(deployRoot, { recursive: true });
|
|
166
|
+
fs.writeFileSync(path.join(deployRoot, "hash.txt"), buildDirStr);
|
|
167
|
+
const sourceHtml = fs.readFileSync(sourceIndex, "utf8");
|
|
168
|
+
// 相对 base(`./{hash}/`)时子目录 HTML 中的资源均相对于自身,根入口需统一补 hash;
|
|
169
|
+
// 绝对 base 时 Vite 已写出完整 URL,直接复制。
|
|
170
|
+
const rootHtml = publicPath.startsWith("./")
|
|
171
|
+
? sourceHtml.replace(/(\b(?:href|src)\s*=\s*["'])\.\//g, `$1${publicPath}`)
|
|
172
|
+
: sourceHtml;
|
|
173
|
+
fs.writeFileSync(path.join(deployRoot, "index.html"), rootHtml);
|
|
174
|
+
if (cleanup) {
|
|
175
|
+
cleanupOldHashBuilds(deployRoot, buildDirStr, cleanup.keep, cleanupMatcher);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 从指定仓库读取短 commit hash;非 git 环境回退 `local`。
|
|
184
|
+
* @public
|
|
185
|
+
*/
|
|
186
|
+
export function getCommitHash(repositoryRoot: string = process.cwd()): string {
|
|
187
|
+
try {
|
|
188
|
+
return execFileSync("git", ["rev-parse", "HEAD"], {
|
|
189
|
+
cwd: repositoryRoot,
|
|
190
|
+
encoding: "utf8",
|
|
191
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
192
|
+
})
|
|
193
|
+
.trim()
|
|
194
|
+
.slice(0, 12);
|
|
195
|
+
} catch {
|
|
196
|
+
return "local";
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 生成 hash 构建目录名;保留仅传时间戳的旧调用方式。
|
|
202
|
+
* 目录名一律由模式串展开,`hashDirName` 不传时用默认 `hash-timestamp`
|
|
203
|
+
* (等价历史默认 `{commit}-{毫秒时间戳}`)。
|
|
204
|
+
* @public
|
|
205
|
+
*/
|
|
206
|
+
export function createHashBuildDirName(now?: number): string;
|
|
207
|
+
/** @public */
|
|
208
|
+
export function createHashBuildDirName(repositoryRoot: string, now?: number): string;
|
|
209
|
+
/** @public */
|
|
210
|
+
export function createHashBuildDirName(options: HashBuildDirNameOptions): string;
|
|
211
|
+
export function createHashBuildDirName(
|
|
212
|
+
repositoryRootOrNowOrOptions?: string | number | HashBuildDirNameOptions,
|
|
213
|
+
now: number = Date.now(),
|
|
214
|
+
): string {
|
|
215
|
+
if (repositoryRootOrNowOrOptions != null && typeof repositoryRootOrNowOrOptions === "object") {
|
|
216
|
+
const {
|
|
217
|
+
repositoryRoot,
|
|
218
|
+
now: optionsNow = Date.now(),
|
|
219
|
+
hashDirName,
|
|
220
|
+
} = repositoryRootOrNowOrOptions;
|
|
221
|
+
|
|
222
|
+
return expandHashDirName({
|
|
223
|
+
pattern: hashDirName ?? DEFAULT_HASH_DIR_NAME_PATTERN,
|
|
224
|
+
commitHash: getCommitHash(repositoryRoot),
|
|
225
|
+
now: optionsNow,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const repositoryRoot =
|
|
230
|
+
typeof repositoryRootOrNowOrOptions === "string" ? repositoryRootOrNowOrOptions : undefined;
|
|
231
|
+
const timestamp =
|
|
232
|
+
typeof repositoryRootOrNowOrOptions === "number" ? repositoryRootOrNowOrOptions : now;
|
|
233
|
+
|
|
234
|
+
return expandHashDirName({
|
|
235
|
+
pattern: DEFAULT_HASH_DIR_NAME_PATTERN,
|
|
236
|
+
commitHash: getCommitHash(repositoryRoot),
|
|
237
|
+
now: timestamp,
|
|
238
|
+
});
|
|
239
|
+
}
|