简介
Vite 在开发环境(Dev)和生产环境(Build)采用不同的构建策略。开发时利用现代浏览器的原生 ESM 能力配合 esbuild 进行预构建,生产时则使用 Rollup 进行打包。本文深入解析「裸模块」解析、预构建流程,并重点探讨**动态导入(Dynamic Import)**在可选依赖场景下,开发与生产环境在解析、静态分析及 Tree-shaking 上的显著差异。
详细内容
1. 依赖解析与预构建机制(开发环境)
浏览器原生并不支持 import { someMethod } from 'my-dep' 这种写法(针对 my-dep 的识别)。Vite 必须介入处理,这个过程分为预构建和交互阶段。
1.1 启动阶段:预构建 (Pre-bundling)
Vite 服务启动时,会通过 esbuild 扫描源码和 package.json:
- 发现依赖:例如检测到
vue、axios。 - 格式转换与打包:将这些可能是 CJS 或 UMD 规范的包转换为 ESM 格式。
- 处理嵌套依赖:如果三方库(如
lib)内部导入了lodash-es,esbuild 在预构建lib时,会直接解析并重写其内部对lodash-es的引用路径。 - 缓存:产物存放在
node_modules/.vite/deps/下。
1.2 交互阶段:路径重写 (Path Rewriting)
当浏览器请求源码时(如 src/App.vue):
- 拦截:Vite Server 收到请求,解析文件内容。
- 重写:将
import { ref } from 'vue'重写为import { ref } from '/node_modules/.vite/deps/vue.js?v=xxx'。 - 响应:浏览器收到修改后的代码,发起对预构建产物的请求。
2. 动态导入与 Tree-shaking 的深度辨析
误区提示:很多人认为「没有导入就没有分析」,或者「动态导入完全是运行时行为」。实际上,构建工具(无论是 esbuild 还是 Rollup)在打包阶段都会进行静态分析。
2.1 场景复现
假设存在一个库 my-lib,它通过动态导入引用了一个可选对等依赖 lodash-es。
// my-lib/index.js
export { loadOptionalDep } from './dynamic-dep.js';
export const hello = 'world';
// my-lib/dynamic-dep.js
console.log("Side Effect: I am running"); // 副作用代码
export const loadOptionalDep = async () => {
// 动态导入可选依赖
const _ = await import('lodash-es');
return _;
};
在项目中只使用 hello:
// src/main.js
import { hello } from 'my-lib';
console.log(hello);
2.2 开发环境 (Dev) 行为
在 vite dev 模式下:
- 无 Tree-shaking:浏览器加载原生 ESM 模块。为了让
my-lib能运行,浏览器会加载my-lib/index.js,由于是 ESM,它会级联请求dynamic-dep.js。 - 副作用执行:
dynamic-dep.js被加载,console.log会执行,即使你没用loadOptionalDep。 - 缺失依赖处理:如果
lodash-es未安装:- 不会立即报错,直到你调用
loadOptionalDep。 - Vite 会在
.vite/deps生成一个占位文件,内容大概是throw new Error('Could not resolve "lodash-es"...')。这是为了防止浏览器请求 404 导致整个应用崩溃。
- 不会立即报错,直到你调用
2.3 生产构建 (Build) 行为
在 vite build 模式下(使用 Rollup):
- Tree-shaking 生效:Rollup 分析出
loadOptionalDep未被使用,因此dynamic-dep.js中的导出不会包含在最终 bundle 中,副作用代码(console.log)也被移除。 - 静态分析依然发生:
- 虽然
dynamic-dep.js最终被摇掉了,但 Rollup 必须读取并解析它,以构建完整的依赖图(AST)。 - 证据:如果将
lodash-es改为必选依赖且未安装,打包会直接报错。这证明了「树摇发生在解析之后」。
- 虽然
- 空模块替换:如果
lodash-es是可选依赖且未安装,Rollup 会将其解析为一个空模块(Mock),保证打包过程不报错。这与 Dev 环境的 Error Throw 行为不同。
代码示例
以下代码展示了如何配置可选依赖,以及 Vite 如何处理它。
package.json (Library)
{
"name": "my-lib",
"peerDependencies": {
"lodash-es": "^4.0.0"
},
"peerDependenciesMeta": {
"lodash-es": {
"optional": true
}
}
}
src/main.js (Application)
import { hello } from 'my-lib';
// 在 Build 时,loadOptionalDep 及其引用的 lodash-es 会被 Tree-shaking 移除
// 在 Dev 时,dynamic-dep.js 会被加载,控制台会打印副作用日志
console.log(hello);