@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
|
@@ -1,279 +1,279 @@
|
|
|
1
|
-
# Vite + pnpm 接入问题排查手册
|
|
2
|
-
|
|
3
|
-
> 适用范围:pnpm 安装、Vite 7+/8(rolldown)开发的 Web 项目,尤其是大量使用 `@hzab/*` 系列包(这些包 `main` 指向 `src`,以源码形式被 Vite 直接编译)且依赖较多旧 CJS 包的项目。
|
|
4
|
-
> 本文记录接入过程中高频踩坑的**现象 → 根因 → 诊断 → 修复**,供其他项目复用。
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## 0. 一句话总纲
|
|
9
|
-
|
|
10
|
-
**绝大多数报错都指向同一个根因:pnpm 的隔离布局不会把「传递依赖」提升到顶层 `node_modules`,导致 Vite 的依赖预构建(optimizeDeps)发现不了它们,只能按原始 CJS 文件裸供浏览器。**
|
|
11
|
-
|
|
12
|
-
于是:
|
|
13
|
-
|
|
14
|
-
- 裸供 CJS → `Uncaught ReferenceError: module is not defined`(文件里先出现 `module.exports`)
|
|
15
|
-
- 裸供 CJS → `Uncaught ReferenceError: require is not defined`(文件里先出现 `require(...)`,如 es-shims 家族)
|
|
16
|
-
- 裸供带顶层 `this` 的 UMD(`(function(global){ ... }(this,...))`)→ `Cannot set properties of undefined (setting 'X')`
|
|
17
|
-
- CJS barrel 具名导出在预构建时静态识别不出 → `does not provide an export named 'X'`
|
|
18
|
-
- 构建期老 IE hack → LightningCSS 报 `star property hack`
|
|
19
|
-
- React Compiler 默认开启却缺包 → `Cannot find package 'babel-plugin-react-compiler'`
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## 1. 常见症状与根因
|
|
24
|
-
|
|
25
|
-
| 报错 | 阶段 | 根因 |
|
|
26
|
-
| ------------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
27
|
-
| `module is not defined` | dev | 包是 CJS-only 且未被提升,Vite 未预构建,`module.exports = require(...)` 直接下发给浏览器 |
|
|
28
|
-
| `require is not defined` | dev | 同「module is not defined」一类的 CJS 裸供,只是文件内**先命中 `require(...)`**。常见于 es-shims 家族(`object-keys`、`regexp.prototype.flags`、`define-properties` 等)——它们是某个**被 `viteCommonjs.include` 转换的入口包**(如 `deep-equal`、`geojson-equality`)的依赖,入口包被转换后 `require('...')` 变成真实 import,其 CJS 依赖便被逐个独立裸供 |
|
|
29
|
-
| `does not provide an export named 'X'` | dev | 包只有 CJS 构建,其用 `__exportStar`/`module.exports = {...}` 重导出的具名导出在预构建时被优化器丢掉 |
|
|
30
|
-
| `Cannot set properties of undefined (setting 'quickselect')` | dev | 包是 UMD(顶层 `this` 当全局对象)且未被预构建、未提升,Vite 按原始 ESM 裸供,ESM 严格模式下顶层 `this === undefined` |
|
|
31
|
-
| `Failed to resolve dependency: X, present in optimizeDeps.include` | dev | 包不可从项目根解析(没有顶层 `node_modules/X`),所以 `optimizeDeps.include` 里写了也解析不到 |
|
|
32
|
-
| `[lightningcss minify] Unexpected token Semicolon` / `star property hack` | build | 旧 CSS 里的 IE hack(`*zoom`、`*display`、`*width`)被 LightningCSS 当作语法错误 |
|
|
33
|
-
| `Cannot find package 'babel-plugin-react-compiler'` | build | `@hzab/vite-config` 默认 `reactCompiler: "build-only"`,build 时加载 React Compiler preset 但包未安装 |
|
|
34
|
-
| `process is not defined`(`ReferenceError`) | dev + build | webpack 会 polyfill 全局 `process` 并经 DefinePlugin 注入 `process.env.*`;Vite 默认只替换 `process.env.NODE_ENV`,其余 `process.env.*` 在浏览器端原样残留;且 `optimizeDeps` 预构建产物**不经过** `define` |
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## 2. 判断「是否被预构建 / 是否被提升」的快速方法
|
|
39
|
-
|
|
40
|
-
在项目根目录执行:
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
# 1) 包是否在顶层 node_modules?(没有 = 被 pnpm 隔离,Vite 大概率发现不了)
|
|
44
|
-
dir node_modules\react-is # Windows
|
|
45
|
-
# ls node_modules/react-is # macOS / Linux
|
|
46
|
-
|
|
47
|
-
# 2) 从项目根能否用 node 解析到?(决定 optimizeDeps.include 是否可用)
|
|
48
|
-
node -e "console.log(require.resolve('react-is'))"
|
|
49
|
-
|
|
50
|
-
# 3) 该包在 .pnpm 里是否带 ESM 构建(esm/ 或 module 字段)?
|
|
51
|
-
dir node_modules\.pnpm\react-is@*\node_modules\react-is\esm
|
|
52
|
-
|
|
53
|
-
# 4) dev 启动后看预构建产物:是否存在对应文件?
|
|
54
|
-
dir node_modules\.vite\deps\react-is.js
|
|
55
|
-
|
|
56
|
-
# 5) dev 启动日志里是否包含该包:
|
|
57
|
-
# "dependencies optimized: xxx"(没列出 = 没被当作独立依赖预构建)
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
诊断要点:
|
|
61
|
-
|
|
62
|
-
- 存在 `node_modules\.vite\deps\<pkg>.js` → 已被预构建为 ESM,没问题。
|
|
63
|
-
- `require.resolve()` 抛 `MODULE_NOT_FOUND`,但包确实在用 → 典型的「传递依赖未提升」,需要修复(见 §4)。
|
|
64
|
-
- 包在 `.pnpm\<pkg>@...\node_modules\<pkg>\esm` 下存在 → 有 ESM 构建,可走 §4.1 的 alias 方案。
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## 3. 用 `vite build` 一次性暴露所有「具名导出丢失」
|
|
69
|
-
|
|
70
|
-
dev 阶段浏览器只在加载到某个模块时才报错,一次只露一个;而 `vite build` 会走完整依赖图并链接,**所有** `is not exported` / `does not provide an export named` 会一次性列出来,适合批量排查。
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
npm run build-flow_dev # 或项目对应的 build script
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
> 注意:如果 build 在早期就被 React Compiler 或 CSS hack 挡住,先按 §4.3 / §4.4 处理,否则根本走不到模块图。
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## 4. 修复套路(按包的类型二选一)
|
|
81
|
-
|
|
82
|
-
### 4.1 包「有 ESM 构建」→ 用 Vite alias 指向 ESM 产物
|
|
83
|
-
|
|
84
|
-
适用:安装在 `.pnpm` 里的包**其中某个实例带 `esm/`**(如 `c-formily-antd@2.3.5`),但被导入的那个实例只有 CJS(如 `c-formily-antd@2.3.1`)。
|
|
85
|
-
|
|
86
|
-
思路:把裸导入和 `lib/*` 子路径统一改写到一个「带 esm 的实例」上,走原生 ESM 的 `export *`,具名导出即可被正确解析。
|
|
87
|
-
|
|
88
|
-
`vite.config.mts` 片段:
|
|
89
|
-
|
|
90
|
-
```ts
|
|
91
|
-
// 找到 .pnpm 里第一个带 esm/ 的实例
|
|
92
|
-
function resolveEsmInstance(name: string, pnpmRoot: string) {
|
|
93
|
-
const entries = fs.readdirSync(pnpmRoot).filter((n) => n.startsWith(`${name}@`));
|
|
94
|
-
for (const entry of entries) {
|
|
95
|
-
const esm = path.resolve(pnpmRoot, entry, `node_modules/${name}/esm`);
|
|
96
|
-
if (fs.existsSync(esm)) return esm;
|
|
97
|
-
}
|
|
98
|
-
throw new Error(`[vite-config] no ${name} instance with an esm build found`);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const cFormilyAntdEsmDir = resolveEsmInstance("c-formily-antd", path.resolve(rootDir, "node_modules/.pnpm"));
|
|
102
|
-
|
|
103
|
-
alias: {
|
|
104
|
-
// 注意顺序:`lib` 前缀要放在裸包名之前,才能先匹配到 `lib/*`
|
|
105
|
-
"c-formily-antd/lib": cFormilyAntdEsmDir, // c-formily-antd/lib/* -> esm/*
|
|
106
|
-
"c-formily-antd": path.join(cFormilyAntdEsmDir, "index.js"), // c-formily-antd -> esm/index.js
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
要点:
|
|
111
|
-
|
|
112
|
-
- 顺序很重要(`find: "pkg/lib"` 要在 `find: "pkg"` 之前),否则 `pkg/lib/xxx` 会被裸包名规则吃掉。
|
|
113
|
-
- alias 的 `replacement` 用绝对路径,避免二次解析到错误的实例。
|
|
114
|
-
- 前提是各实例的 `@formily/*` 等内部依赖版本一致(否则会出现版本混用)。
|
|
115
|
-
|
|
116
|
-
#### 4.1.1 传递依赖(未被直接导入、不在顶层 `node_modules`)→ 仍用 alias,但找「`type: module` 的实例」
|
|
117
|
-
|
|
118
|
-
若包还是**另一个依赖的依赖**(如地图库 → `rbush` → `quickselect`),它既没有顶层 `node_modules/X`(所以 `optimizeDeps.include` 依然报 `Failed to resolve dependency`),也可能没有 `esm/` 目录。此时判据改为:在 `.pnpm` 里找**带 `"type": "module"`** 的实例,取其 `index.js`。
|
|
119
|
-
|
|
120
|
-
> 为什么必须 `type: module`:仅带 `module`/`main` 指向 `index.js` 而缺 `type` 的实例(如 `quickselect@2.0.0`),其 `index.js` 会被 Vite 按最近 package.json 的 `type` 判为 CJS,`export default` 会被误解析。
|
|
121
|
-
> 为什么 UMD 会炸:UMD 包裹器 `(function (global, factory) { ... }(this, ...))` 用顶层 `this` 当全局对象,Vite 裸供为 ESM 后顶层 `this === undefined`,抛 `Cannot set properties of undefined (setting 'quickselect')`。
|
|
122
|
-
|
|
123
|
-
`vite.config.mts` 片段:
|
|
124
|
-
|
|
125
|
-
```ts
|
|
126
|
-
function resolveQuickselectEsm() {
|
|
127
|
-
const pnpmRoot = path.resolve(rootDir, "node_modules/.pnpm");
|
|
128
|
-
const entries = fs.readdirSync(pnpmRoot).filter((n) => n.startsWith("quickselect@"));
|
|
129
|
-
for (const entry of entries) {
|
|
130
|
-
const pkgRoot = path.resolve(pnpmRoot, entry, "node_modules/quickselect");
|
|
131
|
-
const pkgJson = path.join(pkgRoot, "package.json");
|
|
132
|
-
if (!fs.existsSync(pkgJson)) continue;
|
|
133
|
-
try {
|
|
134
|
-
const pkg = JSON.parse(fs.readFileSync(pkgJson, "utf8"));
|
|
135
|
-
if (pkg.type !== "module") continue; // 只认 type:"module" 的原生 ESM 实例
|
|
136
|
-
const entryFile = path.resolve(pkgRoot, pkg.module || pkg.main || "index.js");
|
|
137
|
-
if (fs.existsSync(entryFile)) return entryFile;
|
|
138
|
-
} catch { /* 单个实例解析失败则跳过,继续找下一个 */ }
|
|
139
|
-
}
|
|
140
|
-
throw new Error('[vite-config] no quickselect instance with "type": "module" found');
|
|
141
|
-
}
|
|
142
|
-
const quickselectEsm = resolveQuickselectEsm();
|
|
143
|
-
|
|
144
|
-
alias: {
|
|
145
|
-
"quickselect": quickselectEsm,
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
> 各版本函数签名一致(`quickselect(arr, k, left, right, compare)`),跨版本混用安全。
|
|
150
|
-
|
|
151
|
-
### 4.2 包「没有 ESM 构建」(纯 CJS)→ 提升为直接依赖 + 显式 include
|
|
152
|
-
|
|
153
|
-
适用:`react-is` 这类只有 `index.js`(CJS)+ `cjs/` 的共享老包。
|
|
154
|
-
|
|
155
|
-
**不要用 `viteCommonjs.include`**:它只能产出 `default` 导出,而这类包多以具名导出(如 `isValidElement`)被使用,会从 `module is not defined` 变成 `does not provide an export named 'X'`。
|
|
156
|
-
|
|
157
|
-
正确做法(两步):
|
|
158
|
-
|
|
159
|
-
1. 把它加进顶层依赖,让它落到顶层 `node_modules`(从而能被根解析):
|
|
160
|
-
```jsonc
|
|
161
|
-
// package.json dependencies
|
|
162
|
-
"react-is": "16.13.1",
|
|
163
|
-
```
|
|
164
|
-
```bash
|
|
165
|
-
pnpm install
|
|
166
|
-
```
|
|
167
|
-
2. 因为现在可以从根解析,`optimizeDeps.include` 才能生效,强制预构建为 ESM:
|
|
168
|
-
```ts
|
|
169
|
-
optimizeDeps: { include: ["react-is"] },
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
> 依赖预构建产物会被标记 `needsInterop: true`,Vite 会用 CJS interop 正确改写具名导入。
|
|
173
|
-
|
|
174
|
-
> **多条 `require is not defined` / es-shims 家族闭包**:当报错包的**父包**是「被 `viteCommonjs.include` 转换」的 CJS 入口包时(如 `deep-equal`、`geojson-equality`),其纯 CJS 依赖(`object-keys`、`regexp.prototype.flags`、`define-properties`、`object-is`、`is-arguments`、`is-regex` …)会逐一被独立裸供。此时**不要**把这些叶子包逐个加入 `viteCommonjs.include`,而是**把入口包提升为直接依赖 + `optimizeDeps.include`**——预构建会把入口包的**整个 es-shims CJS 闭包**内联成一个 ESM 文件(`node_modules/.vite/deps/<entry>.js`),同时解决该链路上的全部裸供问题。记得把入口包从 `viteCommonjs.include` 里删掉,避免被二次转换。
|
|
175
|
-
|
|
176
|
-
### 4.3 构建期 CSS 老 IE hack → `errorRecovery`
|
|
177
|
-
|
|
178
|
-
```ts
|
|
179
|
-
vite: {
|
|
180
|
-
css: {
|
|
181
|
-
lightningcss: { errorRecovery: true },
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
把 `*zoom`/`*display`/`*width` 等早已失效的 IE hack 剥离,降级为 warning 而非中断构建。
|
|
187
|
-
|
|
188
|
-
### 4.4 React Compiler 缺包 → 关闭或补装
|
|
189
|
-
|
|
190
|
-
`@hzab/vite-config` 默认 `reactCompiler: "build-only"`,build 时若没有 `babel-plugin-react-compiler` 会直接失败。二选一:
|
|
191
|
-
|
|
192
|
-
```ts
|
|
193
|
-
// 方案 A:不打算启用 React Compiler
|
|
194
|
-
reactCompiler: false,
|
|
195
|
-
|
|
196
|
-
// 方案 B:真正启用(先装依赖)
|
|
197
|
-
// pnpm add -D babel-plugin-react-compiler
|
|
198
|
-
// reactCompiler: "build-only",
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### 4.5 浏览器端 `process` 未定义 → `define + process polyfill`
|
|
202
|
-
|
|
203
|
-
适用:webpack 迁移到 Vite,源码或依赖里出现 `ReferenceError: process is not defined` 的包/代码。
|
|
204
|
-
|
|
205
|
-
**为何会炸**:webpack 会 polyfill 全局 `process` 并经 DefinePlugin 注入 `process.env.*`;Vite 默认**只**替换 `process.env.NODE_ENV`,既不提供 `process` 全局,也不替换其余 `process.env.*`。于是源码/依赖里的 `process.env.BUSINESS_PLATFORM` 这类表达式在浏览器端原样保留,一遇到 `process` 即抛错。
|
|
206
|
-
|
|
207
|
-
**修复(两步,缺一不可)**:
|
|
208
|
-
|
|
209
|
-
1. **`vite.define` 注入**(对齐 webpack `processEnv`):把 webpack 侧注入的键(`WEBPACK_ENV` / `WEBPACK_PUBLIC_PATH` / `PACKAGE_VERSION`)与用户自定义键(`BUSINESS_PLATFORM`)以及源码直接引用的 `process.env.*`(如 `APP_VERSION`)全部定义。键必须带引号,值经 `JSON.stringify`:
|
|
210
|
-
|
|
211
|
-
```ts
|
|
212
|
-
vite: {
|
|
213
|
-
define: {
|
|
214
|
-
"process.env.BUSINESS_PLATFORM": JSON.stringify(process.env.BUSINESS_PLATFORM || "unknown"),
|
|
215
|
-
"process.env.WEBPACK_ENV": JSON.stringify(mode),
|
|
216
|
-
"process.env.WEBPACK_PUBLIC_PATH": JSON.stringify(""),
|
|
217
|
-
"process.env.PACKAGE_VERSION": JSON.stringify(PACKAGE_VERSION),
|
|
218
|
-
"process.env.APP_VERSION": JSON.stringify(PACKAGE_VERSION),
|
|
219
|
-
},
|
|
220
|
-
}
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
覆盖**应用源码**与 **build**:`vite build` 产物中不应再有裸 `process.env.*`。
|
|
224
|
-
|
|
225
|
-
2. **`index.html` 全局 `process` polyfill**:`vite.define` **不作用于 `optimizeDeps` 预构建产物**。被预构建的依赖(尤其 `main` 指向 `src` 的源码包,如 `@hzab/data-model`)内部的 `process.env.*` 在 dev 仍会裸供。在 `index.html` 顶部注入:
|
|
226
|
-
```html
|
|
227
|
-
<script>
|
|
228
|
-
window.process = window.process || { env: {} };
|
|
229
|
-
</script>
|
|
230
|
-
```
|
|
231
|
-
兜底所有预构建 / 第三方裸 `process` 引用。
|
|
232
|
-
|
|
233
|
-
> 验证:`vite build` 后 `grep process.env dist` 为空 = define 覆盖了应用层;但 `.vite/deps/*.js`(预构建产物)里仍有 `process.env.*` = 依赖层未被 define 覆盖,需 polyfill。二者都要做,才能同时保住 dev 与 build。
|
|
234
|
-
|
|
235
|
-
### 4.6 `.js` 里写 JSX / 默认导出缺失 / pnpm 未提升依赖
|
|
236
|
-
|
|
237
|
-
**`.js` 里的 JSX(`Unexpected JSX expression`)**:webpack 靠 babel-loader 转 `.js` 的 JSX;Vite 8 的 transform 由 **oxc** 接管——`OxcOptions` 已 `Omit lang`(按扩展名推断),`esbuild` 配置被忽略(提示 `esbuild options will be ignored`)。解法:
|
|
238
|
-
|
|
239
|
-
1. `src/` 下含 JSX 的 `.js` 改名 `.jsx`;
|
|
240
|
-
2. `node_modules` 里不可改的来源包(如 `@hzab/*` 的 `*.js`)用自定义 pre-transform 插件:`enforce:"pre"` + `transformWithOxc(code, id, { lang:"jsx", jsxRuntime:"automatic" })`,一段同时覆盖 src 与 node_modules。
|
|
241
|
-
|
|
242
|
-
**`MISSING_EXPORT "default"`**:`import style from "./x.less"`(Vite 的 `.less` 无 default export)→ 改副作用 `import "./x.less"`;`import worker from "pkg/...worker.entry"`(webpack 打包 worker)→ Vite 用 `?url` 取地址。
|
|
243
|
-
|
|
244
|
-
**`Failed to resolve import "pkg/..."`(pnpm 隔离)**:业务源码直接 import 了只作为传递依赖存在的包,未被提升到顶层。逐一提升为直接依赖 + `pnpm install`(可用脚本扫描 `src` 裸包导入、`require.resolve` 检测未提升项批量定位)。
|
|
245
|
-
|
|
246
|
-
---
|
|
247
|
-
|
|
248
|
-
## 5. 复用自查清单(新项目接入时逐条核对)
|
|
249
|
-
|
|
250
|
-
1. `pnpm install` 后,app 依赖的**共享 CJS 老包**(`react-is`、`object-assign`、`classnames`、`prop-types`、`dayjs` …)是否在顶层 `node_modules`?不在 → 走 §4.2。
|
|
251
|
-
1.1 报 `require is not defined`(而非 `module is not defined`)→ 先定位**父入口包**(被 `viteCommonjs.include` 转换的那个 CJS 包,如 `deep-equal`、`geojson-equality`),**把入口包**提升 + `optimizeDeps.include`(其 es-shims 闭包会整体内联),别逐个加叶子包。入口包同时从 `viteCommonjs.include` 移除。
|
|
252
|
-
2. 报 `does not provide an export named 'X'` → 查该包在 `.pnpm` 里是否有带 `esm/` 的实例;有 → §4.1;没有但含 `src/` 原生 TS/JS ESM(如 `@hzab/classnames-utils`)→ §4.1 alias 到 `src`;都没有 → §4.2。
|
|
253
|
-
3. 报 `Cannot set properties of undefined (setting 'X')` → UMD 顶层 `this` 裸供;查该包在 `.pnpm` 里是否有 `"type": "module"` 的实例;有 → §4.1.1。
|
|
254
|
-
4. 报 `module is not defined` → 一定是「未预构建的 CJS 被裸供」,先确认包在顶层,再 `optimizeDeps.include`。
|
|
255
|
-
5. `optimizeDeps.include` 报 `Failed to resolve dependency` → 说明还没到顶层,先加依赖 + `pnpm install`,再 include。
|
|
256
|
-
6. 别用 `viteCommonjs` 去救「具名导出」:它只产 `default` 导出。
|
|
257
|
-
7. 构建报 LightningCSS `star property hack` → §4.3。
|
|
258
|
-
8. 构建报 `babel-plugin-react-compiler` 缺失 → §4.4。
|
|
259
|
-
9. 用 `vite build` 做全量「导出缺失」巡检,能一次暴露所有同款问题。
|
|
260
|
-
10. 工具命令被钩子/脚本引用(如 `.husky/pre-commit` 里的 `npx lint-staged`)→ 该工具(`lint-staged` / `prettier`)**必须**在 `devDependencies`。pnpm 不会把传递依赖的 bin 链接到顶层 `node_modules/.bin`,只当它是直接依赖时才会(§6 末行)。
|
|
261
|
-
|
|
262
|
-
---
|
|
263
|
-
|
|
264
|
-
## 6. 本项目已落地实例
|
|
265
|
-
|
|
266
|
-
| 包 | 问题 | 采用的修复 |
|
|
267
|
-
| ----------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
268
|
-
| `c-formily-antd@2.3.1`(仅 CJS 实例) | `useFormLayout` 具名导出丢失 | §4.1 alias 到带 esm 的 2.3.5 实例 |
|
|
269
|
-
| `turf-jsts`(传递依赖) | `BufferOp` 具名导出丢失 | §4.1 alias 到原生 ESM `jsts.mjs` |
|
|
270
|
-
| `@hzab/classnames-utils@0.0.2`(webpack 打的 UMD CJS,无 `esm/`) | `NO_PREFIX_TYPE` 等具名导出丢失 | §4.1 alias 到原生 TS ESM 源 `src/index.ts` |
|
|
271
|
-
| `quickselect`(`rbush` 的传递依赖) | `Cannot set properties of undefined (setting 'quickselect')` | §4.1.1 alias 到 `type: module` 的原生 ESM `index.js` |
|
|
272
|
-
| `react-is@16.13.1`(纯 CJS、未提升) | `module is not defined` | §4.2 加直接依赖 + `optimizeDeps.include` |
|
|
273
|
-
| `deep-equal@1.1.2`(CJS 入口包) | `require is not defined`(其依赖 `object-keys` 被裸供) | §4.2 加直接依赖 + `optimizeDeps.include`;从 `viteCommonjs.include` 移除 |
|
|
274
|
-
| `geojson-equality@0.1.6`(CJS 入口包) | `require is not defined`(同经 `deep-equal`) | §4.2 加直接依赖 + `optimizeDeps.include`;从 `viteCommonjs.include` 移除 |
|
|
275
|
-
| 老 CSS 星号 hack | `lightningcss ... Semicolon` | §4.3 `errorRecovery` |
|
|
276
|
-
| React Compiler | `Cannot find package 'babel-plugin-react-compiler'` | §4.4 `reactCompiler: false` |
|
|
277
|
-
| `lint-staged`(`.husky/pre-commit` 引用) | `git commit` 报 `'lint-staged' 不是内部或外部命令`(pre-commit 退出码 1) | 加为直接依赖 `13.2.3` + `pnpm install` |
|
|
278
|
-
|
|
279
|
-
> 对应改动均在 `vite.config.mts` 与 `package.json`(react-is / deep-equal / geojson-equality 直接依赖 + `pnpm-lock.yaml`)。`lint-staged` 一行属「直接依赖缺失」的典型 pnpm 隔离问题:钩子引用的命令若未声明为直接依赖,pnpm 不会把它的 bin 链接到顶层。
|
|
1
|
+
# Vite + pnpm 接入问题排查手册
|
|
2
|
+
|
|
3
|
+
> 适用范围:pnpm 安装、Vite 7+/8(rolldown)开发的 Web 项目,尤其是大量使用 `@hzab/*` 系列包(这些包 `main` 指向 `src`,以源码形式被 Vite 直接编译)且依赖较多旧 CJS 包的项目。
|
|
4
|
+
> 本文记录接入过程中高频踩坑的**现象 → 根因 → 诊断 → 修复**,供其他项目复用。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 0. 一句话总纲
|
|
9
|
+
|
|
10
|
+
**绝大多数报错都指向同一个根因:pnpm 的隔离布局不会把「传递依赖」提升到顶层 `node_modules`,导致 Vite 的依赖预构建(optimizeDeps)发现不了它们,只能按原始 CJS 文件裸供浏览器。**
|
|
11
|
+
|
|
12
|
+
于是:
|
|
13
|
+
|
|
14
|
+
- 裸供 CJS → `Uncaught ReferenceError: module is not defined`(文件里先出现 `module.exports`)
|
|
15
|
+
- 裸供 CJS → `Uncaught ReferenceError: require is not defined`(文件里先出现 `require(...)`,如 es-shims 家族)
|
|
16
|
+
- 裸供带顶层 `this` 的 UMD(`(function(global){ ... }(this,...))`)→ `Cannot set properties of undefined (setting 'X')`
|
|
17
|
+
- CJS barrel 具名导出在预构建时静态识别不出 → `does not provide an export named 'X'`
|
|
18
|
+
- 构建期老 IE hack → LightningCSS 报 `star property hack`
|
|
19
|
+
- React Compiler 默认开启却缺包 → `Cannot find package 'babel-plugin-react-compiler'`
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 1. 常见症状与根因
|
|
24
|
+
|
|
25
|
+
| 报错 | 阶段 | 根因 |
|
|
26
|
+
| ------------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
27
|
+
| `module is not defined` | dev | 包是 CJS-only 且未被提升,Vite 未预构建,`module.exports = require(...)` 直接下发给浏览器 |
|
|
28
|
+
| `require is not defined` | dev | 同「module is not defined」一类的 CJS 裸供,只是文件内**先命中 `require(...)`**。常见于 es-shims 家族(`object-keys`、`regexp.prototype.flags`、`define-properties` 等)——它们是某个**被 `viteCommonjs.include` 转换的入口包**(如 `deep-equal`、`geojson-equality`)的依赖,入口包被转换后 `require('...')` 变成真实 import,其 CJS 依赖便被逐个独立裸供 |
|
|
29
|
+
| `does not provide an export named 'X'` | dev | 包只有 CJS 构建,其用 `__exportStar`/`module.exports = {...}` 重导出的具名导出在预构建时被优化器丢掉 |
|
|
30
|
+
| `Cannot set properties of undefined (setting 'quickselect')` | dev | 包是 UMD(顶层 `this` 当全局对象)且未被预构建、未提升,Vite 按原始 ESM 裸供,ESM 严格模式下顶层 `this === undefined` |
|
|
31
|
+
| `Failed to resolve dependency: X, present in optimizeDeps.include` | dev | 包不可从项目根解析(没有顶层 `node_modules/X`),所以 `optimizeDeps.include` 里写了也解析不到 |
|
|
32
|
+
| `[lightningcss minify] Unexpected token Semicolon` / `star property hack` | build | 旧 CSS 里的 IE hack(`*zoom`、`*display`、`*width`)被 LightningCSS 当作语法错误 |
|
|
33
|
+
| `Cannot find package 'babel-plugin-react-compiler'` | build | `@hzab/vite-config` 默认 `reactCompiler: "build-only"`,build 时加载 React Compiler preset 但包未安装 |
|
|
34
|
+
| `process is not defined`(`ReferenceError`) | dev + build | webpack 会 polyfill 全局 `process` 并经 DefinePlugin 注入 `process.env.*`;Vite 默认只替换 `process.env.NODE_ENV`,其余 `process.env.*` 在浏览器端原样残留;且 `optimizeDeps` 预构建产物**不经过** `define` |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 2. 判断「是否被预构建 / 是否被提升」的快速方法
|
|
39
|
+
|
|
40
|
+
在项目根目录执行:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# 1) 包是否在顶层 node_modules?(没有 = 被 pnpm 隔离,Vite 大概率发现不了)
|
|
44
|
+
dir node_modules\react-is # Windows
|
|
45
|
+
# ls node_modules/react-is # macOS / Linux
|
|
46
|
+
|
|
47
|
+
# 2) 从项目根能否用 node 解析到?(决定 optimizeDeps.include 是否可用)
|
|
48
|
+
node -e "console.log(require.resolve('react-is'))"
|
|
49
|
+
|
|
50
|
+
# 3) 该包在 .pnpm 里是否带 ESM 构建(esm/ 或 module 字段)?
|
|
51
|
+
dir node_modules\.pnpm\react-is@*\node_modules\react-is\esm
|
|
52
|
+
|
|
53
|
+
# 4) dev 启动后看预构建产物:是否存在对应文件?
|
|
54
|
+
dir node_modules\.vite\deps\react-is.js
|
|
55
|
+
|
|
56
|
+
# 5) dev 启动日志里是否包含该包:
|
|
57
|
+
# "dependencies optimized: xxx"(没列出 = 没被当作独立依赖预构建)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
诊断要点:
|
|
61
|
+
|
|
62
|
+
- 存在 `node_modules\.vite\deps\<pkg>.js` → 已被预构建为 ESM,没问题。
|
|
63
|
+
- `require.resolve()` 抛 `MODULE_NOT_FOUND`,但包确实在用 → 典型的「传递依赖未提升」,需要修复(见 §4)。
|
|
64
|
+
- 包在 `.pnpm\<pkg>@...\node_modules\<pkg>\esm` 下存在 → 有 ESM 构建,可走 §4.1 的 alias 方案。
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 3. 用 `vite build` 一次性暴露所有「具名导出丢失」
|
|
69
|
+
|
|
70
|
+
dev 阶段浏览器只在加载到某个模块时才报错,一次只露一个;而 `vite build` 会走完整依赖图并链接,**所有** `is not exported` / `does not provide an export named` 会一次性列出来,适合批量排查。
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm run build-flow_dev # 或项目对应的 build script
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> 注意:如果 build 在早期就被 React Compiler 或 CSS hack 挡住,先按 §4.3 / §4.4 处理,否则根本走不到模块图。
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 4. 修复套路(按包的类型二选一)
|
|
81
|
+
|
|
82
|
+
### 4.1 包「有 ESM 构建」→ 用 Vite alias 指向 ESM 产物
|
|
83
|
+
|
|
84
|
+
适用:安装在 `.pnpm` 里的包**其中某个实例带 `esm/`**(如 `c-formily-antd@2.3.5`),但被导入的那个实例只有 CJS(如 `c-formily-antd@2.3.1`)。
|
|
85
|
+
|
|
86
|
+
思路:把裸导入和 `lib/*` 子路径统一改写到一个「带 esm 的实例」上,走原生 ESM 的 `export *`,具名导出即可被正确解析。
|
|
87
|
+
|
|
88
|
+
`vite.config.mts` 片段:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
// 找到 .pnpm 里第一个带 esm/ 的实例
|
|
92
|
+
function resolveEsmInstance(name: string, pnpmRoot: string) {
|
|
93
|
+
const entries = fs.readdirSync(pnpmRoot).filter((n) => n.startsWith(`${name}@`));
|
|
94
|
+
for (const entry of entries) {
|
|
95
|
+
const esm = path.resolve(pnpmRoot, entry, `node_modules/${name}/esm`);
|
|
96
|
+
if (fs.existsSync(esm)) return esm;
|
|
97
|
+
}
|
|
98
|
+
throw new Error(`[vite-config] no ${name} instance with an esm build found`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const cFormilyAntdEsmDir = resolveEsmInstance("c-formily-antd", path.resolve(rootDir, "node_modules/.pnpm"));
|
|
102
|
+
|
|
103
|
+
alias: {
|
|
104
|
+
// 注意顺序:`lib` 前缀要放在裸包名之前,才能先匹配到 `lib/*`
|
|
105
|
+
"c-formily-antd/lib": cFormilyAntdEsmDir, // c-formily-antd/lib/* -> esm/*
|
|
106
|
+
"c-formily-antd": path.join(cFormilyAntdEsmDir, "index.js"), // c-formily-antd -> esm/index.js
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
要点:
|
|
111
|
+
|
|
112
|
+
- 顺序很重要(`find: "pkg/lib"` 要在 `find: "pkg"` 之前),否则 `pkg/lib/xxx` 会被裸包名规则吃掉。
|
|
113
|
+
- alias 的 `replacement` 用绝对路径,避免二次解析到错误的实例。
|
|
114
|
+
- 前提是各实例的 `@formily/*` 等内部依赖版本一致(否则会出现版本混用)。
|
|
115
|
+
|
|
116
|
+
#### 4.1.1 传递依赖(未被直接导入、不在顶层 `node_modules`)→ 仍用 alias,但找「`type: module` 的实例」
|
|
117
|
+
|
|
118
|
+
若包还是**另一个依赖的依赖**(如地图库 → `rbush` → `quickselect`),它既没有顶层 `node_modules/X`(所以 `optimizeDeps.include` 依然报 `Failed to resolve dependency`),也可能没有 `esm/` 目录。此时判据改为:在 `.pnpm` 里找**带 `"type": "module"`** 的实例,取其 `index.js`。
|
|
119
|
+
|
|
120
|
+
> 为什么必须 `type: module`:仅带 `module`/`main` 指向 `index.js` 而缺 `type` 的实例(如 `quickselect@2.0.0`),其 `index.js` 会被 Vite 按最近 package.json 的 `type` 判为 CJS,`export default` 会被误解析。
|
|
121
|
+
> 为什么 UMD 会炸:UMD 包裹器 `(function (global, factory) { ... }(this, ...))` 用顶层 `this` 当全局对象,Vite 裸供为 ESM 后顶层 `this === undefined`,抛 `Cannot set properties of undefined (setting 'quickselect')`。
|
|
122
|
+
|
|
123
|
+
`vite.config.mts` 片段:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
function resolveQuickselectEsm() {
|
|
127
|
+
const pnpmRoot = path.resolve(rootDir, "node_modules/.pnpm");
|
|
128
|
+
const entries = fs.readdirSync(pnpmRoot).filter((n) => n.startsWith("quickselect@"));
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
const pkgRoot = path.resolve(pnpmRoot, entry, "node_modules/quickselect");
|
|
131
|
+
const pkgJson = path.join(pkgRoot, "package.json");
|
|
132
|
+
if (!fs.existsSync(pkgJson)) continue;
|
|
133
|
+
try {
|
|
134
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJson, "utf8"));
|
|
135
|
+
if (pkg.type !== "module") continue; // 只认 type:"module" 的原生 ESM 实例
|
|
136
|
+
const entryFile = path.resolve(pkgRoot, pkg.module || pkg.main || "index.js");
|
|
137
|
+
if (fs.existsSync(entryFile)) return entryFile;
|
|
138
|
+
} catch { /* 单个实例解析失败则跳过,继续找下一个 */ }
|
|
139
|
+
}
|
|
140
|
+
throw new Error('[vite-config] no quickselect instance with "type": "module" found');
|
|
141
|
+
}
|
|
142
|
+
const quickselectEsm = resolveQuickselectEsm();
|
|
143
|
+
|
|
144
|
+
alias: {
|
|
145
|
+
"quickselect": quickselectEsm,
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
> 各版本函数签名一致(`quickselect(arr, k, left, right, compare)`),跨版本混用安全。
|
|
150
|
+
|
|
151
|
+
### 4.2 包「没有 ESM 构建」(纯 CJS)→ 提升为直接依赖 + 显式 include
|
|
152
|
+
|
|
153
|
+
适用:`react-is` 这类只有 `index.js`(CJS)+ `cjs/` 的共享老包。
|
|
154
|
+
|
|
155
|
+
**不要用 `viteCommonjs.include`**:它只能产出 `default` 导出,而这类包多以具名导出(如 `isValidElement`)被使用,会从 `module is not defined` 变成 `does not provide an export named 'X'`。
|
|
156
|
+
|
|
157
|
+
正确做法(两步):
|
|
158
|
+
|
|
159
|
+
1. 把它加进顶层依赖,让它落到顶层 `node_modules`(从而能被根解析):
|
|
160
|
+
```jsonc
|
|
161
|
+
// package.json dependencies
|
|
162
|
+
"react-is": "16.13.1",
|
|
163
|
+
```
|
|
164
|
+
```bash
|
|
165
|
+
pnpm install
|
|
166
|
+
```
|
|
167
|
+
2. 因为现在可以从根解析,`optimizeDeps.include` 才能生效,强制预构建为 ESM:
|
|
168
|
+
```ts
|
|
169
|
+
optimizeDeps: { include: ["react-is"] },
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
> 依赖预构建产物会被标记 `needsInterop: true`,Vite 会用 CJS interop 正确改写具名导入。
|
|
173
|
+
|
|
174
|
+
> **多条 `require is not defined` / es-shims 家族闭包**:当报错包的**父包**是「被 `viteCommonjs.include` 转换」的 CJS 入口包时(如 `deep-equal`、`geojson-equality`),其纯 CJS 依赖(`object-keys`、`regexp.prototype.flags`、`define-properties`、`object-is`、`is-arguments`、`is-regex` …)会逐一被独立裸供。此时**不要**把这些叶子包逐个加入 `viteCommonjs.include`,而是**把入口包提升为直接依赖 + `optimizeDeps.include`**——预构建会把入口包的**整个 es-shims CJS 闭包**内联成一个 ESM 文件(`node_modules/.vite/deps/<entry>.js`),同时解决该链路上的全部裸供问题。记得把入口包从 `viteCommonjs.include` 里删掉,避免被二次转换。
|
|
175
|
+
|
|
176
|
+
### 4.3 构建期 CSS 老 IE hack → `errorRecovery`
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
vite: {
|
|
180
|
+
css: {
|
|
181
|
+
lightningcss: { errorRecovery: true },
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
把 `*zoom`/`*display`/`*width` 等早已失效的 IE hack 剥离,降级为 warning 而非中断构建。
|
|
187
|
+
|
|
188
|
+
### 4.4 React Compiler 缺包 → 关闭或补装
|
|
189
|
+
|
|
190
|
+
`@hzab/vite-config` 默认 `reactCompiler: "build-only"`,build 时若没有 `babel-plugin-react-compiler` 会直接失败。二选一:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
// 方案 A:不打算启用 React Compiler
|
|
194
|
+
reactCompiler: false,
|
|
195
|
+
|
|
196
|
+
// 方案 B:真正启用(先装依赖)
|
|
197
|
+
// pnpm add -D babel-plugin-react-compiler
|
|
198
|
+
// reactCompiler: "build-only",
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 4.5 浏览器端 `process` 未定义 → `define + process polyfill`
|
|
202
|
+
|
|
203
|
+
适用:webpack 迁移到 Vite,源码或依赖里出现 `ReferenceError: process is not defined` 的包/代码。
|
|
204
|
+
|
|
205
|
+
**为何会炸**:webpack 会 polyfill 全局 `process` 并经 DefinePlugin 注入 `process.env.*`;Vite 默认**只**替换 `process.env.NODE_ENV`,既不提供 `process` 全局,也不替换其余 `process.env.*`。于是源码/依赖里的 `process.env.BUSINESS_PLATFORM` 这类表达式在浏览器端原样保留,一遇到 `process` 即抛错。
|
|
206
|
+
|
|
207
|
+
**修复(两步,缺一不可)**:
|
|
208
|
+
|
|
209
|
+
1. **`vite.define` 注入**(对齐 webpack `processEnv`):把 webpack 侧注入的键(`WEBPACK_ENV` / `WEBPACK_PUBLIC_PATH` / `PACKAGE_VERSION`)与用户自定义键(`BUSINESS_PLATFORM`)以及源码直接引用的 `process.env.*`(如 `APP_VERSION`)全部定义。键必须带引号,值经 `JSON.stringify`:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
vite: {
|
|
213
|
+
define: {
|
|
214
|
+
"process.env.BUSINESS_PLATFORM": JSON.stringify(process.env.BUSINESS_PLATFORM || "unknown"),
|
|
215
|
+
"process.env.WEBPACK_ENV": JSON.stringify(mode),
|
|
216
|
+
"process.env.WEBPACK_PUBLIC_PATH": JSON.stringify(""),
|
|
217
|
+
"process.env.PACKAGE_VERSION": JSON.stringify(PACKAGE_VERSION),
|
|
218
|
+
"process.env.APP_VERSION": JSON.stringify(PACKAGE_VERSION),
|
|
219
|
+
},
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
覆盖**应用源码**与 **build**:`vite build` 产物中不应再有裸 `process.env.*`。
|
|
224
|
+
|
|
225
|
+
2. **`index.html` 全局 `process` polyfill**:`vite.define` **不作用于 `optimizeDeps` 预构建产物**。被预构建的依赖(尤其 `main` 指向 `src` 的源码包,如 `@hzab/data-model`)内部的 `process.env.*` 在 dev 仍会裸供。在 `index.html` 顶部注入:
|
|
226
|
+
```html
|
|
227
|
+
<script>
|
|
228
|
+
window.process = window.process || { env: {} };
|
|
229
|
+
</script>
|
|
230
|
+
```
|
|
231
|
+
兜底所有预构建 / 第三方裸 `process` 引用。
|
|
232
|
+
|
|
233
|
+
> 验证:`vite build` 后 `grep process.env dist` 为空 = define 覆盖了应用层;但 `.vite/deps/*.js`(预构建产物)里仍有 `process.env.*` = 依赖层未被 define 覆盖,需 polyfill。二者都要做,才能同时保住 dev 与 build。
|
|
234
|
+
|
|
235
|
+
### 4.6 `.js` 里写 JSX / 默认导出缺失 / pnpm 未提升依赖
|
|
236
|
+
|
|
237
|
+
**`.js` 里的 JSX(`Unexpected JSX expression`)**:webpack 靠 babel-loader 转 `.js` 的 JSX;Vite 8 的 transform 由 **oxc** 接管——`OxcOptions` 已 `Omit lang`(按扩展名推断),`esbuild` 配置被忽略(提示 `esbuild options will be ignored`)。解法:
|
|
238
|
+
|
|
239
|
+
1. `src/` 下含 JSX 的 `.js` 改名 `.jsx`;
|
|
240
|
+
2. `node_modules` 里不可改的来源包(如 `@hzab/*` 的 `*.js`)用自定义 pre-transform 插件:`enforce:"pre"` + `transformWithOxc(code, id, { lang:"jsx", jsxRuntime:"automatic" })`,一段同时覆盖 src 与 node_modules。
|
|
241
|
+
|
|
242
|
+
**`MISSING_EXPORT "default"`**:`import style from "./x.less"`(Vite 的 `.less` 无 default export)→ 改副作用 `import "./x.less"`;`import worker from "pkg/...worker.entry"`(webpack 打包 worker)→ Vite 用 `?url` 取地址。
|
|
243
|
+
|
|
244
|
+
**`Failed to resolve import "pkg/..."`(pnpm 隔离)**:业务源码直接 import 了只作为传递依赖存在的包,未被提升到顶层。逐一提升为直接依赖 + `pnpm install`(可用脚本扫描 `src` 裸包导入、`require.resolve` 检测未提升项批量定位)。
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 5. 复用自查清单(新项目接入时逐条核对)
|
|
249
|
+
|
|
250
|
+
1. `pnpm install` 后,app 依赖的**共享 CJS 老包**(`react-is`、`object-assign`、`classnames`、`prop-types`、`dayjs` …)是否在顶层 `node_modules`?不在 → 走 §4.2。
|
|
251
|
+
1.1 报 `require is not defined`(而非 `module is not defined`)→ 先定位**父入口包**(被 `viteCommonjs.include` 转换的那个 CJS 包,如 `deep-equal`、`geojson-equality`),**把入口包**提升 + `optimizeDeps.include`(其 es-shims 闭包会整体内联),别逐个加叶子包。入口包同时从 `viteCommonjs.include` 移除。
|
|
252
|
+
2. 报 `does not provide an export named 'X'` → 查该包在 `.pnpm` 里是否有带 `esm/` 的实例;有 → §4.1;没有但含 `src/` 原生 TS/JS ESM(如 `@hzab/classnames-utils`)→ §4.1 alias 到 `src`;都没有 → §4.2。
|
|
253
|
+
3. 报 `Cannot set properties of undefined (setting 'X')` → UMD 顶层 `this` 裸供;查该包在 `.pnpm` 里是否有 `"type": "module"` 的实例;有 → §4.1.1。
|
|
254
|
+
4. 报 `module is not defined` → 一定是「未预构建的 CJS 被裸供」,先确认包在顶层,再 `optimizeDeps.include`。
|
|
255
|
+
5. `optimizeDeps.include` 报 `Failed to resolve dependency` → 说明还没到顶层,先加依赖 + `pnpm install`,再 include。
|
|
256
|
+
6. 别用 `viteCommonjs` 去救「具名导出」:它只产 `default` 导出。
|
|
257
|
+
7. 构建报 LightningCSS `star property hack` → §4.3。
|
|
258
|
+
8. 构建报 `babel-plugin-react-compiler` 缺失 → §4.4。
|
|
259
|
+
9. 用 `vite build` 做全量「导出缺失」巡检,能一次暴露所有同款问题。
|
|
260
|
+
10. 工具命令被钩子/脚本引用(如 `.husky/pre-commit` 里的 `npx lint-staged`)→ 该工具(`lint-staged` / `prettier`)**必须**在 `devDependencies`。pnpm 不会把传递依赖的 bin 链接到顶层 `node_modules/.bin`,只当它是直接依赖时才会(§6 末行)。
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## 6. 本项目已落地实例
|
|
265
|
+
|
|
266
|
+
| 包 | 问题 | 采用的修复 |
|
|
267
|
+
| ----------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
268
|
+
| `c-formily-antd@2.3.1`(仅 CJS 实例) | `useFormLayout` 具名导出丢失 | §4.1 alias 到带 esm 的 2.3.5 实例 |
|
|
269
|
+
| `turf-jsts`(传递依赖) | `BufferOp` 具名导出丢失 | §4.1 alias 到原生 ESM `jsts.mjs` |
|
|
270
|
+
| `@hzab/classnames-utils@0.0.2`(webpack 打的 UMD CJS,无 `esm/`) | `NO_PREFIX_TYPE` 等具名导出丢失 | §4.1 alias 到原生 TS ESM 源 `src/index.ts` |
|
|
271
|
+
| `quickselect`(`rbush` 的传递依赖) | `Cannot set properties of undefined (setting 'quickselect')` | §4.1.1 alias 到 `type: module` 的原生 ESM `index.js` |
|
|
272
|
+
| `react-is@16.13.1`(纯 CJS、未提升) | `module is not defined` | §4.2 加直接依赖 + `optimizeDeps.include` |
|
|
273
|
+
| `deep-equal@1.1.2`(CJS 入口包) | `require is not defined`(其依赖 `object-keys` 被裸供) | §4.2 加直接依赖 + `optimizeDeps.include`;从 `viteCommonjs.include` 移除 |
|
|
274
|
+
| `geojson-equality@0.1.6`(CJS 入口包) | `require is not defined`(同经 `deep-equal`) | §4.2 加直接依赖 + `optimizeDeps.include`;从 `viteCommonjs.include` 移除 |
|
|
275
|
+
| 老 CSS 星号 hack | `lightningcss ... Semicolon` | §4.3 `errorRecovery` |
|
|
276
|
+
| React Compiler | `Cannot find package 'babel-plugin-react-compiler'` | §4.4 `reactCompiler: false` |
|
|
277
|
+
| `lint-staged`(`.husky/pre-commit` 引用) | `git commit` 报 `'lint-staged' 不是内部或外部命令`(pre-commit 退出码 1) | 加为直接依赖 `13.2.3` + `pnpm install` |
|
|
278
|
+
|
|
279
|
+
> 对应改动均在 `vite.config.mts` 与 `package.json`(react-is / deep-equal / geojson-equality 直接依赖 + `pnpm-lock.yaml`)。`lint-staged` 一行属「直接依赖缺失」的典型 pnpm 隔离问题:钩子引用的命令若未声明为直接依赖,pnpm 不会把它的 bin 链接到顶层。
|