前言
本文将从webpack出发,探索其配置和特性在vite中的对应配置,为webpack迁移到vite提供参考,因此不过多介绍webpack中该配置项的作用。本文比对的配置项涵盖webpack中的绝大部分配置项,但也会忽略部分极少用到的配置。
webpack打包的目标环境,既支持浏览器环境,也支持node环境,而vite整体上以web环境优先。实际开发中,如果打包的目标环境以node为主,并不建议从webpack迁移vite。因此本文对两者配置项或特性的对比,也主要考虑web环境下的差异。
webpack配置/特性在vite中的对应
mode和环境变量
webpack |
vite |
说明 |
mode |
mode |
效果基本一致,vite无需手动设置 |
DefinePlugin |
.env文件 |
暴露对象不一致(process.env 和 import.meta.env ) |
mode
webpack和vite都有mode
字段,设置的值为development
或production
,作用基本一致。区别主要是webpack需要手动设置,vite由dev和build命令自动设置。
环境变量
两者都自动将mode
的值设置到process.env.NODE_ENV
,其他环境变量webpack通过DefinePlugin
设置到process.env
上,而vite通过.env文件设置到import.meta.env
构建目标环境(target)和兼容性
webpack |
vite |
说明 |
target |
build.target |
默认值不一致 |
webpack中默认是browserslist
,且支持node环境,而vite默认是baseline-widely-available
(['chrome107', 'edge107', 'firefox104', 'safari16']),可以设置为合法的esbuild的target
选项esbuild.github.io/api/#target
入口entry和多入口打包
webpack |
vite |
说明 |
entry |
index.html中指定 |
多入口需要通过rollup配置 |
vite没有webpack的entry配置,对应的是index.html中通过script的src指定入口文件(库打包模式下在build.lib中指定),如果是多入口,则需要通过rollup进行配置
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
admin: resolve(__dirname, 'admin.html')
}
}
vite自动处理多入口共享依赖(不需要类似webpack配置dependOn)`
输出output
webpack |
vite |
说明 |
publicPath |
base |
|
path |
build.outDir |
|
hash文件名 |
默认不需要配置 |
vite可通过 rollupOptions.output 设置文件名 |
clean |
内置 |
|
hash生成算法 |
仅支持hash长度配置 |
|
iife |
自身无对应配置 |
可参考库打包中的iife模式 |
importFunctionName |
不支持 |
一般不需要设置 |
library |
基本覆盖 |
见下文见库打包模式
|
pathinfo |
不支持 |
source-map已覆盖该需求 |
wasmLoading |
不支持 |
见下文 worker和wasm
|
workerChunkLoading |
不支持 |
见下文 worker和wasm
|
对一些配置项的特殊说明
-
filename/chunkFilename/assetModuleFilename
: 对应vite的rollupOptions.output中的entryFileNames/chunkFileNames/assetFileNames
,vite一般不需要配置
-
clean
:对应vite的build.emptyOutDir
,vite默认清理,不需要设置
-
auxiliaryComment
: 类似rollup中的banner
-
globalObject
: vite自动判断,不需要设置
-
hash
: webpack相关配置或插件有hashDigest/hashDigestLength/hashFunction/hashSalt/webpack.ids.HashedModuleIdsPlugin
,但一般不需要配置。vite中可以使用[hash:8]
形式调整长度,不支持调整算法
-
importFunctionName
: webpack可以调整import的名字,主要为了使用dynamic import
的polyfill时可能会用到。目前dynamic import
已经被广泛支持了,该配置项已过期。
-
pathinfo
:webpack能够对代码添加类似/*! ./src/utils.js */的路径信息注释,
方便调试,vite中无法配置,但该需求实际上被source-map覆盖,也无需配置
devServer
webpack |
vite |
说明 |
devServer |
server |
|
devServer.port |
server.port |
|
devServer.open |
server.open |
|
devServer.allowedHosts |
server.allowedHosts |
|
devServer.proxy |
server.proxy |
除pathRewrite配置不一样以及不支持compress外,其他基本一致 |
devServer.server |
server.https |
|
devServer.watchFiles |
server.watch |
不要监听node_modules目录下文件 |
devServer.compress |
无 |
|
devServer.bonjour |
无 |
开发环境一般都能ip访问,该配置使用较少 |
devServer.client |
logLevel + server.hmr
|
vite不支持progress ,该配置也比较鸡肋 |
devServer.liveReload |
无 |
该配置和热更新冲突,一般使用热更新而关闭该配置 |
devServer.hot |
无 |
vite默认热更新,无需配置 |
devServer.static |
public目录 |
|
mock
webpack一般通过devserver的setupMiddlewares
配置mock,vite有多个mock相关插件,如vite-plugin-mock-dev-server
热更新
webpack |
vite |
说明 |
配置复杂 |
开箱即用 |
|
按chunk编译 |
文件级按需编译 |
vite启动快,加载慢;webpack启动慢,加载快 |
webpack仅提供了热更新的能力,但热更新的具体行为,还是需要通过配置实现。
比如React项目热更新,webpack需要额外配置react-refresh
和@pmmmwh/react-refresh-webpack-plugin
,vite中由@vitejs/plugin-react
提供热更新能力,无需额外配置。
库打包模式
webpack |
vite |
说明 |
library |
lib + rollupOptions
|
支持目标格式较webpack少,但基本能满足需求 |
webpack相关配置有library/libraryTarget(library.type)/libraryExport(library.export),对应vite库打包模式,build配置项demo如下
lib: {
entry: 'src/index.js', // 需要指定库入口文件
name: 'MyLibrary', // 对应 Webpack 的 library.name
formats: ['umd', 'es'], // 对应 libraryTarget(支持多格式)
fileName: (format) => `my-lib.${format}.js`
},
rollupOptions: {
output: {
exports: 'default', // 对应 libraryExport: 'default'
external: ['vue'],
globals: { // 外部依赖的全局变量映射(如 Vue -> window.Vue)
vue: 'Vue'
}
}
}
webpack的libraryTarget考虑多种环境(浏览器/worker/node)的兼容问题,因此有各种细分类型:
assign/assign-properties/commjs2/var/this/global/self等,rollup针对现代标准规范,支持'es' | 'cjs' | 'umd' | 'iife',基本满足需求,对assign等特殊场景,需要通过自定义插件实现
cache
webpack |
vite |
说明 |
cache |
支持 |
默认开启无需配置 |
webpack中cache默认是内存缓存,一般需要手动设置type
为filesystem
,可设置缓存的保存目录/压缩/过期/间隔等,对应vite中的cacheDir
,一般不需要配置
resolve
webpack |
vite |
说明 |
resolve |
resolve |
|
resolve.alias |
resolve.alias |
一致 |
resolve.mainFields |
resolve.mainFields |
一致 |
resolve.extensions |
resolve.extensions |
一致 |
resolve.symlinks |
resolve.preserveSymlinks |
默认值相反,效果一致 |
resolve.modules |
不支持 |
webpack中极少配置 |
resolve.mainFiles |
不支持 |
webpack中极少配置 |
特殊说明
以下配置项webpack与vite基本一致
-
alias
: 路径别名,如'@/pages'
-
mainFields
: package.json入口字段优先级
-
extensions
:按顺序解析文件后缀名
以下配置项存在差异:
-
symlinks
: webpack默认值为true,vite对应配置项为preserveSymlinks
,默认false,效果一致
其余配置项,基本用于如何确定import
引入对象文件,webpack中一般采用默认值,vite并无对应,但也无需配置,比如
-
modules
: 模块查找目录,默认['node_modules'],webpack一般不需要配置,vite无该配置
-
mainFiles
:模块入口文件,默认['index'],webpack一般不需要配置,vite无该配置
文件解析编译
|
webpack |
vite |
说明 |
js/ts |
babel |
esbuild |
vite默认支持,无需配置 |
jsx/tsx |
@babel/preset-react |
@vitejs/plugin-react |
|
ts类型检查 |
fork-ts-checker-webpack-plugin |
vite-plugin-checker |
vite默认不处理,由IDE检查 |
eslint |
eslint-webpack-plugin |
vite-plugin-eslint |
|
css |
css-loader/style-loader/mini-css-extract-plugin |
css |
vite默认支持,无需配置 |
less/sass |
less-loader/sass-loader |
css.preprocessorOptions |
|
postcss |
postcss-loader |
css.postcss |
|
j(t)s(x)文件解析和编译
vite中对jsx/tsx的解析,需要额外使用@vitejs/plugin-react,对js/ts的解析默认不需要配置。具体差异见babel/terser vs esbuild
样式文件编译抽离
webpack需要配置css-loader,less-loader等loader进行样式文件解析,通过style-loader注入style标签,mini-css-extract-plugin抽离为css文件以link引入,vite中无需配置,开发环境将样式注入style标签,生产环境则抽离为css文件。
webpack可通过loader选项进行精细化控制,vite对应的是css配置项:
-
module
: postcss-modules
-
postcss
: 格式同postcss.config.js,相当于webpack的postcss-loader
-
preprocessorOptions
: 对不同预处理器进行配置,类似less-loader
/sass-loader
中的选项
-
preprocessorMaxWorkers
:CSS 预处理器可以使用的最大线程数,类似webpack中的thread-loader
-
devSourcemap
:启用sourcemap,对应webpack css-loader
中的sourcemap
配置
此外vite可以通过transformer配置项,设置lightningcss 以代替postcss,处理速度更快,不过生态不如postcss,目前不建议。
vite 生产打包时的额外可配置项如下:
-
cssCodeSplit
:css拆分,相当于webpack中的mini-css-extract-plugin
-
cssTarget
: css兼容性,webpack中通过postcss-loader
+ postcss-preset-env
实现
-
cssMinify
: 可以指定esbuild还是lightningcss压缩,webpack中采用mini-css-extract-plugin
-
sourcemap
:同上devSourcemap
静态文件处理
|
webpack |
vite |
说明 |
普通静态文件asset |
asset module |
assetsDir + assetsInlineLimit |
功能基本一致,vite配置简单 |
json5 |
json5-loader |
vite-plugin-json5 |
普通json都可以直接import |
svg |
@svgr/webpack |
vite-plugin-svgr |
|
asset
webpack5通过asset module
处理,
-
asset/resource
发送一个单独的文件并导出 URL。之前通过使用 file-loader
实现。
-
asset/inline
导出一个资源的 data URI。之前通过使用 url-loader
实现。
-
asset/source
导出资源的源代码。之前通过使用 raw-loader
实现。
-
asset
在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader
,并且配置资源体积限制实现。
vite中配置项只有两个
-
assetsDir
: 静态文件目录,webpack中通过assetModuleFilename
实现
-
assetsInlineLimit
:相当于webpack的asset
中设置dataUrlCondition
JSON
webpack和vite都可以直接引入json文件,但如果需要在json中添加注释,webpack需要配置json5-loader,vite对应插件为vite-plugin-json5
svg引用
webpack中可以使用@svgr/webpack
将svg转为组件,对应vite插件vite-plugin-svgr
,import url上需要额外加查询参数react
:import Logo from "./logo.svg?react";
webpack常用插件在vite中的对应配置
webpack |
vite |
说明 |
terser-webpack-plugin |
内置默认esbuild |
|
css-minimizer-webpack-plugin |
内置默认esbuild |
|
html-webpack-plugin |
vite-plugin-html |
|
fork-ts-checker-webpack-plugin |
无 |
|
eslint-webpack-plugin |
vite-plugin-eslint |
|
webpack-manifest-plugin |
build.manifest |
|
copy-webpack-plugin |
vite-plugin-static-copy |
默认复制public目录下的内容 |
code-inspector-plugin |
code-inspector-plugin |
|
webpack-bundle-analyzer |
本身不支持 |
可通过roll配置rollup-plugin-visualizer |
case-sensitive-paths-webpack-plugin |
本身不支持 |
可通过自定义vite plugin实现 |
html模板编译
webpack中的html-webpack-plugin
对应vite-plugin-html
,但vite中可以向html中注入环境变量,一般情况也不需要配置该插件
ts类型检查
webpack通常用fork-ts-checker-webpack-plugin
进行类型检查,vite默认不做类型检查,检查功能由IDE提供,可以通过插件vite-plugin-checker实现类似效果
eslint
webpack通过eslint-webpack-plugin
配置,vite对应的是vite-plugin-eslint,eslint配置.eslintrc.js
内容一致
manifest
webpack中的webpack-manifest-plugin
对应vite中的build.manifest
文件复制
webpack中的copy-webpack-plugin
对应到vite中可以使用vite-plugin-static-copy
,但默认下,vite会将public目录下的文件复制到构建产物根目录中( copyPublicDir)
code-inspector-plugin
code-inspector-plugin
同时支持webpack和vite
bundle分析
webpack-bundle-analyzer
,vite默认提供了build.reportCompressedSize,需要分析bundle,可以配置rollup-plugin-visualizer
大小写敏感
webpack中的case-sensitive-paths-webpack-plugin
,vite没有对应插件,需要自行编写插件实现
Optimization优化压缩与分包
webpack |
vite |
说明 |
minimize/minimizer |
build.minify |
vite默认使用esbuild压缩,而webpack一般配置css-minimizer-webpack-plugin和terser-webpack-plugin |
sideEffects |
无 |
rollup与webpack对sideEffect的判断不一样,见下文 tree shaking 和 sideEffects
|
splitChunks |
无 |
vite依赖rollup的manualChunks,配置能力较弱 |
webpack常用的配置项(minimize、minimizer、splitChunks、sideEffects),vite或rollup 都有对应的配置,其他比如chunkId等等,vite无对应配置,不过这些配置项在webpack中也极少用到。
这里最显著的差异是压缩插件,webpack大多数都是配置terser和css-minimizer-webpack-plugin,而vite默认esbuild,下文会对此作更具体比较。
另一个需要注意的点是分包。rollup的manualChunks的配置仅相当于webpack的splitChunks.cacheGroups的部分功能,对于运行时访问速度较高的C端项目,以及一些超大型项目,可能需要webpack的splitChunks的能力,如果仍然想用vite,可以尝试rolldown。
devtool和sourcemap
webpack |
vite |
说明 |
devtool |
build.sourcemap |
vite可选类型较少 |
webpack的devtool
对应vite的build.sourcemap
,vite只有true/false/inline/hidden
, true
相当于webpack的source-map
,vite配置虽少,但可以满足开发和生产需求
babel/terser vs esbuild
esbuild优势主要在性能上,虽然同时也具备了打包(转义+压缩)能力,但是生态与覆盖场景较babel少很多。
babel vs esbuild
esbuild相比babel,需要关注的点是它的转译能力:
情况 |
Babel |
esbuild |
新语法(如 ?.、??、class fields) |
✔️ 转换成旧语法等价物 |
✔️ 也能转换,但仅限标准语法 |
实验性语法(如 decorators) |
✔️ 有插件支持 |
见下文装饰器问题
|
运行时 API(如 Promise、Map) |
✔️ 可自动注入 polyfill(core-js) |
❌ 不注入,需外部手动引 polyfill |
async/await |
✔️ 转换为 generator + regenerator-runtime |
✔️ 转换为 Promise 链,但不提供 Promise polyfill |
如果要兼容旧浏览器,比如IE11,esbuild需要自行额外引入polyfill,而babel则通过@babel/preset-env
自动注入。
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry",
"corejs": "3.22"
}
]
]
}
常用babel插件与vite/esbuild对照
babel |
vite/esbuild |
说明 |
@babel/preset-react |
@vitejs/plugin-react |
@vitejs/plugin-react包括了jsx编译和热更新 |
@babel/preset-env |
esbuild |
不支持自动polyfill |
@babel/preset-typescript |
esbuild |
不支持d.ts文件生成 |
babel-plugin-transform-typescript-metadata |
不支持 |
详见下文装饰器问题
|
@babel/plugin-proposal-decorators |
esbuild |
详见下文装饰器问题
|
@babel/plugin-proposal-class-properties |
esbuild |
现代浏览器和 esbuild 已原生支持类属性语法,无需额外配置。若需兼容旧浏览器,通过 build.target 控制降级 |
transform-react-remove-prop-types |
不支持 |
生产构建时移除 React 组件的 PropTypes 检查代码,减小体积,vite不支持 |
对esbuild或vite不支持的配置,可以采用以下方式使用babel插件(使用babel插件会影响打包速度)
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: [
process.env.NODE_ENV === 'production' && 'transform-react-remove-prop-types'
].filter(Boolean)
}
})
]
});
terser vs esbuild
terser常用特性对比esbuild如下:
|
terser |
esbuild |
说明 |
target |
ecma |
target |
terser 支持解析/压缩/输出各自设定target |
name保留 |
keep_classnames/keep_fnames |
keepNames |
|
删除console和debugger |
drop_console/drop_debugger |
drop |
|
压缩 |
针对不同case的丰富的配置项 |
minify |
esbuild配置简单,但粒度较粗 |
混淆 |
mangle |
mangleProps支持正则匹配 |
esbuild远弱于terser |
tree shaking |
不支持,但可以删除dead code |
支持 |
terser由bundler处理tree shaking |
兼容性 |
好 |
差 |
esbuild目标是现代浏览器,不具备类似terser的safari10/ie8配置项 |
从功能上来说,无论是配置能力,还是兼容性,terser完胜esbuild;在压缩体积上,由于terser更精细化的配置,其结果也比esbuild略小。
不过只要不考虑兼容性,对大多数项目来说,esbuild的性能收益要远大于功能和体积上的代价了。
装饰器问题
esbuild能转换当前的装饰器提案(stage3)和ts的旧版装饰器(stage1)语法,如果在tsconfig中配置了experimentalDecorators
,则按照ts的旧版装饰器语法进行转译。
esbuild不支持emitDecoratorMetadata
。因此如果代码存在元数据编程,不要使用esbuild。
装饰器如果使用了反射,必须保留类名,因此在压缩上需要额外配置 keep names。
装饰器历史问题
现行主流装饰器语法有两套:当前es中的装饰器(stage3)与ts 对stage1装饰器的实现(tsconfig中的experimentalDecorators)。
babel编译插件@babel/plugin-proposal-decorators的legacy模式 ,针对的不是ts的装饰器的实现,而是babel自己对es装饰器规范stage1的实现。
其区别体现在:ts属性装饰器,接收两个参数,且不需要返回属性描述符,而babel的@babel/plugin-proposal-decorators 的legacy,属性装饰器接收三个参数,第三个参数为属性描述符,且必须返回属性描述符。
vite中esbuild对装饰器的解析,默认会根据tsconfig中experimentalDecorators字段处理,为true时,是对ts的装饰器stage1的实现进行的转换。
tree shaking 和 sideEffects
sideEffects字段并不是package.json中的标准字段,但webpack的tree shaking依赖该字段,因此大多数npm包也会声明sideEffects。vite生产环境由rollup打包,它主要依赖于静态分析代码的导入和导出,本身并不处理sideEffects字段,但可能有部分插件会处理该字段。
vite的devserver真的比webpack更快吗
一般情况下,vite的开发服务器启动速度确实要比webpack更快,然而对一个超大型项目(或超大型ui库)而言,vite的esm按需加载机制,可能在首屏加载数千乃至数万个文件,这会显著增加页面加载时间,甚至有可能超过webpack的bundle时间。
vite对此做了至少两种优化:
- 预构建node_modules中的依赖文件,用esbuild将其打包为esm;
- server.warmup 预热常用文件
因此对于单个大型项目来说,要尽可能考虑将通用模块或独立的聚合业务模块封装为npm包,而超大型项目,应该向底座化或平台化方向发展,以微前端或微模块做内容加载。
worker和wasm
这两个单独列出来,是因为两者原生的文件引入方式和在bundler中的引入方式不一样——在webpack和vite中如果按照原生写法,打包后会报错404。
在bundler中,通用的写法是通过new Url(path,import.meta.url)
,webpack5和vite都会将这个path打包为单个chunk,并将path编译为引用路径
worker
原生
const worker = new Worker('./worker.js');
webpack
const worker = new Worker(new URL('./worker.js', import.meta.url));
webpack4中可以使用worker-loader或多入口打包,将worker单独打包为一个chunk
vite
const worker = new Worker(new URL('./worker.js', import.meta.url))
或
import MyWorker from './worker?worker'
const worker = new MyWorker()
wasm
原生
fetch('./module.wasm').then((response) => response.arrayBuffer()).then((bytes) => WebAssembly.instantiate(bytes)).then((result) => {});
webpack
import('./module.wasm').then((wasmModule) => { wasmModule.doSomething(); });
vite
import init from './example.wasm?init'
init().then((instance) => {
instance.exports.test()
})
或
import wasmUrl from 'foo.wasm?url'
const main = async () => {
const responsePromise = fetch(wasmUrl)
const { module, instance } =
await WebAssembly.instantiateStreaming(responsePromise)
/* ... */
}
main()
模块联邦
vite本身不支持,但可以尝试@module-federation/vite
(有不少bug)。
这里多说一句,在实践中,远程组件的最佳技术方案不是模块联邦,而是微模块。和微前端一样,微模块的实施并不是单个技术(比如hel-micro)的实践,而是应作为公司前端基础设施平台之一进行建设。
结论
如果没有兼容性负担,没有使用模块联邦,并且项目中也没有深度实践元数据编程,可以从webpack安全地迁移到vite。