Skip to content
webpack 配置与 Vite 配置 API 对比
概述
webpack 与 Vite 都将构建行为收敛在配置文件内。webpack 的配置是标准的 JavaScript 对象(或对象数组),通过 entry、output、module.rules、plugins 和 resolve 等字段组织打包流程;Vite 的配置由 vite.config.js 导出,采用 defineConfig 包装,核心是插件数组与 server/build 的分区。理解两者配置的差异,本质上是在理解“声明式管道”与“钩子驱动管道”两种设计思路。
基本配置
webpack 的配置文件通常命名为 webpack.config.js,其核心职责是导出一个配置对象(也可以导出数组),供 webpack 在构建时读取。
最简配置需要三个字段:
javascript
// webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
};mode取值为'development'或'production',决定对应模式下的内置优化行为。entry定义打包的起点。output指定产物的输出目录和文件名。
配置文件本质是 Node.js 脚本,因此可以导入其他模块、定义变量、使用条件语句,甚至用函数动态生成配置。不过应避免在配置中读取命令行参数或导出每次构建结果不同的值(如 Date.now()),否则会破坏构建产物的可复现性。配置过长时建议拆分成多个文件。
入口(entry)与输出(output)
entry 可以是字符串、字符串数组、对象或函数。对于单页应用,一个字符串就足够了;多页应用(MPA)通常使用对象来定义多个入口,对象的每个属性对应一个独立的页面入口。
javascript
entry: {
home: './src/home.js',
about: './src/about.js'
}入口对象的属性名会被用作产物分组的默认名称。动态加载的模块(import())不需要出现在入口声明中,webpack 会自动处理它们的依赖关系。每个入口理论上对应一个 HTML 页面,但具体的关联方式由 HTML 插件的配置决定。
output 控制产物的存放位置与命名。output.path 指向构建后的输出目录,output.filename 定义单个 bundle 的名称。多入口时需要借助占位符来区分输出文件:
javascript
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
}[name] 会被替换为入口对象的属性名(如 home、about)。另外还有 [hash]、[contenthash] 等占位符,可用于缓存策略。
模块规则(module.rules)
webpack 本身只能处理 JavaScript 和 JSON 文件。其他类型的文件(CSS、图片、TypeScript 等)需要借助 loader 转换成模块。module.rules 是一个规则数组,每条规则的核心是匹配条件和处理动作。
一条规则的基本形态:
javascript
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}test 用正则匹配文件路径,匹配到的文件才会应用 use 中声明的 loader。use 可以是单个 loader 的字符串、数组,也可以是带选项的对象。
除了 test,还可以通过 include 和 exclude 缩小或排除匹配范围。匹配基于两个路径:resource(被请求文件的绝对路径)和 issuer(发起请求的模块文件路径)。默认情况下 test、include、exclude 都是针对 resource 的,如果需要基于引用者来匹配,可以使用 issuer 条件。例如 app.js 中 import './style.css',则 resource 是 style.css 的绝对路径,issuer 是 app.js 的绝对路径。如果使用了符号链接或 npm link,由于 symlinks 默认会被解析为真实路径,某些以 /node_modules/ 为目标的匹配规则可能失效,这时可以通过 resolve.symlinks 关闭符号链接解析。
resolve 与 plugins
resolve 字段影响模块路径的解析方式,常用属性包括:
extensions– 自动补全的文件扩展名列表,例如['.js', '.json', '.ts'],这样在import时就可以省略扩展名。alias– 路径别名,用来缩短导入路径或替换某些模块。symlinks– 是否将符号链接解析为真实路径,默认true。
javascript
resolve: {
extensions: ['.js', '.ts'],
alias: {
'@': path.resolve(__dirname, 'src')
}
}plugins 数组用于加载 webpack 插件。每个插件是一个带有 apply 方法的对象(类实例),它能够在 webpack 编译生命周期的不同阶段执行任务:
javascript
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({ template: './public/index.html' })
]
};插件既可以修改编译过程,也可以操作输出产物。它与 loader 只负责转换文件内容的定位不同,两者配合可以覆盖构建所需的绝大多数扩展。
Loader 机制
链式调用与执行顺序
loader 将非 JS 文件转换为 webpack 能够识别的模块。当 use 接收一个 loader 数组时,loader 的执行顺序是从右到左(或者说从数组的最后一个元素开始向上)。这样前一个 loader 的输出就能成为后一个 loader 的输入,形成处理管线。
javascript
use: ['style-loader', 'css-loader', 'sass-loader']处理一个 .scss 文件时,实际执行顺序是:sass-loader 先将 SCSS 编译为 CSS,输出交给 css-loader 解析 @import 和 url(),最后 style-loader 将解析后的 CSS 以 <style> 标签注入 DOM。写成对象形式也能看到同样的方向:
javascript
use: [
'style-loader',
{
loader: 'css-loader',
options: { modules: true }
},
'sass-loader'
]因此,在配置链式 loader 时如果把顺序写反(例如把 style-loader 放在转换之前),不仅无意义,还会报错。
常见资源转换场景
- CSS 与预处理器:通过
css-loader处理依赖关系,style-loader或MiniCssExtractPlugin.loader负责输出。配合sass-loader、less-loader可使用预处理语言。 - TypeScript:使用
ts-loader或babel-loader(配合@babel/preset-typescript)。babel 方案允许复用相同的 JavaScript 转换管线。 - 静态资源(图片、字体等):webpack 5 内置了 Asset Modules,不再强制使用
file-loader或url-loader。配置type: 'asset/resource'即可输出文件,type: 'asset'则根据大小自动在 data URI 和单独文件间切换。
javascript
{
test: /\.(png|jpg|gif)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8KB 以下转 base64
}
}
}多个 loader 互不干扰,可以在 module.rules 中为不同类型分别定义规则。webpack 处理模块时,依次遍历规则,符合条件的规则会将其 use 链应用到该模块,最终输出转换后的有效模块。
Plugin 机制
事件钩子与基本形态
webpack 插件通过“钩子”接入构建过程。这套机制基于 Tapable,提供了同步和异步钩子,插件可以在编译、模块处理、输出等各个阶段执行自己的逻辑。
插件的标准写法是一个类,包含 apply 方法:
javascript
class ExamplePlugin {
apply(compiler) {
compiler.hooks.done.tap('ExamplePlugin', stats => {
console.log('构建完成');
});
}
}compiler 对象代表一次完整的编译过程,上面挂载了 beforeRun、compile、emit、done 等钩子。如果想介入产物生成阶段,可以监听 compilation 钩子获取 compilation 对象,它代表单次构建中的资源和大对象。无论钩子是同步还是异步,都可以通过 tap、tapAsync、tapPromise 相应注册。
典型应用场景
- 资源输出管理:
HtmlWebpackPlugin自动生成 HTML 文件并注入 bundle 脚本。 - 环境变量注入:
DefinePlugin在编译时将代码中的变量替换为指定值,用来在开发模式与产品构建之间切换。 - CSS 提取:
MiniCssExtractPlugin把 CSS 从 JS 中分离成独立文件,用于产品构建时的样式加载。 - 打包分析:
BundleAnalyzerPlugin以可视化方式展示模块体积。
javascript
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.API_BASE': JSON.stringify('https://api.example.com')
})
]
};与 loader 相比,plugin 不限于转换文件内容,它可以操控构建过程的任意环节,甚至修改打包产物的结构。两者在 webpack 生态中承担不同角色:loader 专做“文件到模块”的转换,plugin 做“构建过程扩展”。
Vite 配置文件
Vite 的配置文件 vite.config.js 通常导出使用 defineConfig 包裹的对象。与 webpack 相比,Vite 默认内置了对 TypeScript、JSX、CSS、PostCSS 以及 Sass/Less 等预处理器的支持,无需再像 webpack 那样逐个配置 loader。
一个常见配置如下:
javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: { '@': '/src' }
},
server: {
port: 3000,
proxy: { '/api': 'http://localhost:4000' }
},
build: {
outDir: 'dist',
sourcemap: true
}
})常用字段
plugins– 插件数组。Vite 插件体系基于 Rollup 并兼容 Rollup 插件。resolve– 路径解析配置,常用alias,也可以配置extensions(默认已有常用扩展名)。server– 开发服务器配置,可以定制端口、代理、host 等。build– 产品构建配置,包括产出目录outDir、是否开启 sourcemap、是否压缩等。
server 与 build:两种运行模式的边界
server 中的配置只在 vite(或 vite dev)启动开发服务器时生效。build 配置只在执行 vite build 时被读取。这样设计后,开发模式与产品构建的配置天然分开,不需要用 mode 判断显式分支。
例如开发时需要代理 API 请求,而产品构建时通常由后端提供静态文件服务,这时 proxy 只需写在 server.proxy 里,不会影响构建产物。又如 build.rollupOptions 可以自定义 Rollup 的打包细节,仅在构建时用到。
如果需要根据运行命令动态调整配置,defineConfig 可以接收函数,函数参数包含 command 和 mode:
javascript
export default defineConfig(({ command }) => {
if (command === 'serve') {
return { server: { port: 5000 } }
} else {
return { base: '/my-app/' }
}
})command 为 'serve' 时启动开发服务器,为 'build' 时执行产品构建。vite 和 vite dev 命令对应的 command 都是 'serve'。
Vite 插件体系
基本写法
Vite 插件是一个对象(或函数返回对象),必须带有 name 属性,同时可以实现多种钩子。最常用的钩子是 transform,它可以拦截并修改模块的源代码:
javascript
export default function myPlugin() {
return {
name: 'my-transform-plugin',
transform(src, id) {
if (id.endsWith('.js')) {
return { code: src + '\nconsole.log("injected");', map: null }
}
}
}
}transform 在每次模块被加载时调用,参数 src 是模块源码字符串,id 是模块的文件路径。返回的对象可以包含 code(处理后的代码)和 map(source map)。如果不需要修改,返回 null 或不返回即可。
config 钩子可以在配置被最终解析前修改它:
javascript
{
name: 'config-modifier',
config() {
return {
resolve: { alias: { foo: 'bar' } }
}
}
}执行时机与 Rollup 兼容性
Vite 的插件生命周期与 Rollup 的构建钩子高度重叠。在开发服务器下,Vite 创建一个插件容器,并调用 Rollup 通用钩子:
- 服务器启动时触发
buildStart。 - 每个模块请求时触发
resolveId、load、transform。 - 服务器关闭时触发
buildEnd和closeBundle。
但在开发模式中,某些 Rollup 专为“输出生成”设计的钩子不会被调用,比如 renderChunk、generateBundle,因为此时没有真正的打包动作。而产品构建走完整的 Rollup 流程,所有钩子都会执行。
如果已经有一个 Rollup 插件,大多数情况下可以直接在 Vite 中使用,前提是插件只依赖通用钩子,并且未使用 Rollup 独有的“输出生成”钩子(除非带有正确的过滤条件)。同时需要注意,CommonJS 兼容插件可能需要通过 @rollup/plugin-commonjs 等包装,因为 Vite 开发服务器使用原生 ESM。
javascript
// vite.config.js
import rollupPluginJson from '@rollup/plugin-json'
export default defineConfig({
plugins: [rollupPluginJson()]
})如果 Rollup 插件使用了只应在产品构建阶段执行的钩子,最好通过 apply 条件(如 apply: 'build')限制其运行场景,以免在开发模式下产生警告或错误。
对照表:Loader / Plugin 与 Vite 插件的映射
从功能角度看,webpack 的 loader 负责文件转换,对应 Vite/Rollup 插件中的 transform 钩子;webpack 的 plugin 用于构建过程扩展,对应 Vite 插件的多种钩子(resolveId、load、buildStart、config 等),也可能由内置功能直接覆盖。下表给出常见场景的对照:
| webpack 方式 | Vite / Rollup 对应 |
|---|---|
css-loader + style-loader | Vite 内置 CSS 处理,开发期直接注入,构建时自动提取 |
sass-loader / less-loader | 安装对应预处理器后 Vite 自动识别 |
file-loader / url-loader | 内置静态资源处理(assets include) |
ts-loader / babel-loader | 开发模式下由 esbuild 处理 TypeScript;产品构建可使用 Rollup 的 TypeScript 插件。.vue 文件需 @vitejs/plugin-vue |
webpack.DefinePlugin | define 配置项 |
html-webpack-plugin | index.html 直接位于根目录,或自定义插件 |
mini-css-extract-plugin | 产品构建自动抽取 CSS |
| 自定义 loader(转换 .txt 文件等) | 插件 transform 钩子 |
| 自定义 plugin(注入环境变量等) | 插件 config 或 resolveId / load 等钩子 |
配置方式的差异:webpack 依赖模块规则配置(module.rules)来串行组织多个 loader,通过 plugins 数组并行加载插件,整体偏向“声明链式操作 + 插件并行”的模式。Vite 则统一为插件钩子函数,文件转换、路径解析甚至配置修改都通过同一个插件数组依次处理,呈现为函数式、钩子驱动的管道。这种设计使得 Vite 的配置文件通常更简短,但同时也要求理解 Rollup 钩子的执行顺序和阶段。
注意点
- webpack 配置的可复现性:不要在配置中读取命令行参数,也不要导出非确定性值(如
Date.now()),否则构建产物的复现性会被破坏。长配置应拆分为多个文件。 - loader 执行顺序:牢记从右到左(从下到上)的执行顺序,把注入样式的 loader 写在转换之前不仅无意义,还会报错。
- plugin 必须实例化:plugin 必须通过
new关键字创建实例再放入数组,因为 webpack 调用的是实例上的apply方法。 - Vite 插件开发与构建差异:
transform、resolveId等钩子在开发服务器每个请求时都会执行,如果计算量较大,会直接影响页面响应速度,应在必要时使用缓存或限制文件范围。另外,closeBundle这类钩子在开发模式下不会调用,如果插件将清理逻辑放在这里,开发模式下可能不会触发。 - Rollup 插件兼容:并非所有 Rollup 插件都能直接用于 Vite,特别是在开发环境。如果插件内部使用了“输出生成”钩子而没有做模式判断,开发时可能抛出警告或无法正常工作。可以通过给插件添加
apply选项("serve"或"build")来控制使用场景。
参考链接
- [1] https://webpack.js.org/concepts/configuration
- [3] https://webpack.js.org/configuration/entry-context
- [5] https://v4.webpack.js.org/configuration/module
- [8] https://webpack.js.org/concepts/loaders/
- [9] https://webpack.js.org/concepts/plugins/
- [10] https://cn.vite.dev/guide/api-plugin
- [13] https://developer.cloud.tencent.com/article/2595855?policyId=1004
