从webpack到vite——配置与特性全面对比

533 阅读15分钟

前言

本文将从webpack出发,探索其配置和特性在vite中的对应配置,为webpack迁移到vite提供参考,因此不过多介绍webpack中该配置项的作用。本文比对的配置项涵盖webpack中的绝大部分配置项,但也会忽略部分极少用到的配置。

webpack打包的目标环境,既支持浏览器环境,也支持node环境,而vite整体上以web环境优先。实际开发中,如果打包的目标环境以node为主,并不建议从webpack迁移vite。因此本文对两者配置项或特性的对比,也主要考虑web环境下的差异。

webpack配置/特性在vite中的对应

mode和环境变量

webpackvite说明
modemode效果基本一致,vite无需手动设置
DefinePlugin.env文件暴露对象不一致(process.envimport.meta.env

mode

webpack和vite都有mode字段,设置的值为developmentproduction,作用基本一致。区别主要是webpack需要手动设置,vite由dev和build命令自动设置。

环境变量

两者都自动将mode的值设置到process.env.NODE_ENV,其他环境变量webpack通过DefinePlugin设置到process.env上,而vite通过.env文件设置到import.meta.env

构建目标环境(target)和兼容性

webpackvite说明
targetbuild.target默认值不一致

webpack中默认是browserslist,且支持node环境,而vite默认是baseline-widely-available(['chrome107', 'edge107', 'firefox104', 'safari16']),可以设置为合法的esbuild的target选项esbuild.github.io/api/#target

入口entry和多入口打包

webpackvite说明
entryindex.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

webpackvite说明
publicPathbase
pathbuild.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

webpackvite说明
devServerserver
devServer.portserver.port
devServer.openserver.open
devServer.allowedHostsserver.allowedHosts
devServer.proxyserver.proxy除pathRewrite配置不一样以及不支持compress外,其他基本一致
devServer.serverserver.https
devServer.watchFilesserver.watch不要监听node_modules目录下文件
devServer.compress
devServer.bonjour开发环境一般都能ip访问,该配置使用较少
devServer.clientlogLevel + server.hmrvite不支持progress,该配置也比较鸡肋
devServer.liveReload该配置和热更新冲突,一般使用热更新而关闭该配置
devServer.hotvite默认热更新,无需配置
devServer.staticpublic目录

mock

webpack一般通过devserver的setupMiddlewares配置mock,vite有多个mock相关插件,如vite-plugin-mock-dev-server

热更新

webpackvite说明
配置复杂开箱即用
按chunk编译文件级按需编译vite启动快,加载慢;webpack启动慢,加载快

webpack仅提供了热更新的能力,但热更新的具体行为,还是需要通过配置实现。

比如React项目热更新,webpack需要额外配置react-refresh@pmmmwh/react-refresh-webpack-plugin ,vite中由@vitejs/plugin-react提供热更新能力,无需额外配置。

库打包模式

webpackvite说明
librarylib + 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

webpackvite说明
cache支持默认开启无需配置

webpack中cache默认是内存缓存,一般需要手动设置typefilesystem,可设置缓存的保存目录/压缩/过期/间隔等,对应vite中的cacheDir,一般不需要配置

resolve

webpackvite说明
resolveresolve
resolve.aliasresolve.alias一致
resolve.mainFieldsresolve.mainFields一致
resolve.extensionsresolve.extensions一致
resolve.symlinksresolve.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无该配置

文件解析编译

webpackvite说明
js/tsbabelesbuildvite默认支持,无需配置
jsx/tsx@babel/preset-react@vitejs/plugin-react
ts类型检查fork-ts-checker-webpack-pluginvite-plugin-checkervite默认不处理,由IDE检查
eslinteslint-webpack-pluginvite-plugin-eslint
csscss-loader/style-loader/mini-css-extract-plugincssvite默认支持,无需配置
less/sassless-loader/sass-loadercss.preprocessorOptions
postcsspostcss-loadercss.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

静态文件处理

webpackvite说明
普通静态文件assetasset moduleassetsDir + assetsInlineLimit功能基本一致,vite配置简单
json5json5-loadervite-plugin-json5普通json都可以直接import
svg@svgr/webpackvite-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上需要额外加查询参数reactimport Logo from "./logo.svg?react";

webpack常用插件在vite中的对应配置

webpackvite说明
terser-webpack-plugin内置默认esbuild
css-minimizer-webpack-plugin内置默认esbuild
html-webpack-pluginvite-plugin-html
fork-ts-checker-webpack-plugin
eslint-webpack-pluginvite-plugin-eslint
webpack-manifest-pluginbuild.manifest
copy-webpack-pluginvite-plugin-static-copy默认复制public目录下的内容
code-inspector-plugincode-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优化压缩与分包

webpackvite说明
minimize/minimizerbuild.minifyvite默认使用esbuild压缩,而webpack一般配置css-minimizer-webpack-plugin和terser-webpack-plugin
sideEffectsrollup与webpack对sideEffect的判断不一样,见下文 tree shaking 和 sideEffects
splitChunksvite依赖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

webpackvite说明
devtoolbuild.sourcemapvite可选类型较少

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,需要关注的点是它的转译能力:

情况Babelesbuild
新语法(如 ?.、??、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对照

babelvite/esbuild说明
@babel/preset-react@vitejs/plugin-react@vitejs/plugin-react包括了jsx编译和热更新
@babel/preset-envesbuild不支持自动polyfill
@babel/preset-typescriptesbuild不支持d.ts文件生成
babel-plugin-transform-typescript-metadata不支持详见下文装饰器问题
@babel/plugin-proposal-decoratorsesbuild详见下文装饰器问题
@babel/plugin-proposal-class-propertiesesbuild现代浏览器和 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如下:

terseresbuild说明
targetecmatargetterser 支持解析/压缩/输出各自设定target
name保留keep_classnames/keep_fnameskeepNames
删除console和debuggerdrop_console/drop_debuggerdrop
压缩针对不同case的丰富的配置项minifyesbuild配置简单,但粒度较粗
混淆manglemangleProps支持正则匹配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对此做了至少两种优化:

  1. 预构建node_modules中的依赖文件,用esbuild将其打包为esm;
  2. 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。