Webpack 基础入门篇
Webpack 是面向现代 JavaScript 应用的静态模块打包工具。在处理项目时,Webpack 会从一个或多个入口构建完整的依赖图(dependency graph),把项目用到的全部模块打包输出为若干份静态 bundle 文件,用于浏览器页面加载使用。

模块打包简单理解:识别模块之间的依赖关系,按照约定规则将模块合并处理为 JavaScript 文件,也可以拆分输出为多种不同类型的资源文件。
Webpack 的设计理念是万物皆模块,JS 文件、CSS、图片、字体等资源,在 Webpack 眼中都属于模块。
核心概念
- 入口(entry):指定构建的起始模块,作为依赖图的起点,一般为 JS 文件。
- 输出(output):配置打包产物的输出路径、文件命名规则,也支持配置类库导出相关参数。
- loader:模块转换器,处理非 JS 类型模块,将各类资源转为 Webpack 可识别的有效模块。
- 插件(plugin):执行范围更广的构建任务,弥补 loader 能力边界,用于打包优化、资源处理、环境变量注入等。
- 模式(mode):可选值
development、production、none,Webpack 根据模式启用对应的内置优化策略。
浏览器兼容性:Webpack 支持所有符合 ES5 标准的浏览器,不支持 IE8 及更低版本。其中 import()、require.ensure() 依赖 Promise;如果需要兼容老旧浏览器,要提前引入 polyfill。
快速上手示例
Webpack 基于 Node.js,运行前请确保本地已经安装 Node.js 环境。
npm i webpack webpack-cli -g
// src/assets/js/a.js
console.log(1)
// src/index.js
import './assets/js/a.js'
console.log(2)
Webpack 支持零配置运行:默认入口文件为 src/index.js,产物输出到 dist/main.js;生产模式会自动开启代码压缩优化。实际项目一般会编写配置文件扩展能力。
配置文件默认放在项目根目录,命名为 webpack.config.js;也可以自定义存放路径,执行构建时通过 --config 参数指定。
备注:配置文件支持导出普通对象、函数、Promise,也可以导出配置数组实现多份构建。
build/webpack.demo.config.js 示例:
const Path = require('path')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'index.js'
}
}
执行构建命令:
webpack --config ./build/webpack.demo.config.js
entry 入口配置
entry 是 Webpack 构建依赖图的起点。Webpack 从入口模块递归查找所有直接、间接依赖的模块与第三方库。
默认入口:./src/index.js,通过 entry 属性自定义入口。
字符串形式:单入口,生成单个 bundle。
module.exports = {
entry: './path/to/my/entry/file.js'
};
// 等价简写
module.exports = {
entry: {
main: './path/to/my/entry/file.js'
}
};
数组形式:多入口合并输出为单个 bundle。
module.exports = {
entry: ['./src/index.js', './src/assets/js/b.js']
};
对象形式:多入口生成多个独立 bundle。
使用对象多入口时,output.filename 必须使用占位符区分文件名。
module.exports = {
entry: {
main: './src/index.js',
otherB: './src/assets/js/b.js'
},
output: {
filename: '[name]-[hash:16]-[id].js'
}
}
注意:key 中包含 / 会解析为目录路径
module.exports = {
entry: {
'main/assets/index': './src/index.js',
}
}
构建输出目录结构:
dist
└── main
└── assets
└── index.js
函数、Promise:动态获取入口。支持返回函数或者 Promise,适合运行时动态计算入口路径。
output 输出配置
output 用来定义打包产物输出位置、文件命名规则。单份 Webpack 配置只能有一个 output。
默认主产物输出:./dist/main.js,全部产物默认放置于 dist 目录。
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
}
};
filename
指定输出 bundle 的文件名;多入口场景必须使用占位符保证文件名唯一。
| 模板 | 说明 |
|---|---|
[hash] |
项目整体构建 hash;项目任意文件变更,hash 全部更新 |
[chunkhash] |
对应 chunk 内容的 hash,chunk 内容不变则 hash 不变 |
[name] |
入口 chunk 的名称,对应 entry 对象 key |
[id] |
chunk 的标识符 |
[query] |
文件名后 ? 携带的查询字符串 |
hash 长度可指定,例如 [hash:16],默认长度 20;也可以全局配置 hashDigestLength 修改。
filename 还支持传入函数,返回带占位符的字符串。
chunkFilename
配置非入口 chunk(异步动态导入)的文件名。
默认值:[id].js,或从 filename 推断。
备注:异步 chunk 的文件名运行时才确定,占位符会写入运行时代码,会增大 bundle 体积。
path
输出目录,必须是绝对路径,默认 dist。
const Path = require('path')
module.exports = {
output: {
filename: '[name]-[hash:16]-[id].js',
path: Path.resolve(__dirname, '../abcd/assets')
}
}
publicPath
浏览器访问静态资源的基础路径,会作用于 html 内 script、link 等资源地址。支持相对路径、服务根路径、协议相对路径、完整 CDN 绝对路径。
module.exports = {
entry: './src/index.js',
output: {
// publicPath: './assets/js', // 相对 HTML 的相对路径
// publicPath: '/assets/js', // 服务根路径
// publicPath: '//cdn.example.com', // 协议相对路径
publicPath: 'https://cdn.example.com', // CDN 绝对路径
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
}
生成 HTML:
<script src="https://cdn.example.com/main.js"></script>
编译阶段不确定 publicPath 时,可以在入口 JS 使用内置变量 __webpack_public_path__ 在运行时动态赋值。
__webpack_public_path__ = myRuntimePublicPath;
library、libraryExport、libraryTarget
该组配置用于打包第三方类库、SDK,把代码输出为可供外部调用的模块,普通业务 SPA 项目一般不需要配置。
library:定义对外暴露的库名称,可以是字符串,也可以是对象,为不同模块规范设置不同导出名。libraryTarget:指定输出的模块规范,决定产物以什么形式对外导出。常用取值:umd:通用模块定义,同时兼容浏览器全局、AMD、CommonJS,写类库最常用;commonjs2:输出 Node.js 的module.exports;amd:输出 AMD 规范模块;window:挂载到浏览器全局window对象。
libraryExport:指定导出模块内部哪一部分,默认导出整个模块;可指定default只导出默认导出,也支持数组路径导出嵌套对象。
umd 多环境兼容输出示例:
module.exports = {
output: {
library: {
root: 'MyLibrary',
amd: 'my-library',
commonjs: 'my-common-library'
},
libraryTarget: 'umd',
libraryExport: 'default' // 只导出模块的 default 导出内容
}
}
sourceMapFilename
source-map 文件输出文件名,仅开启 devtool source-map 时生效,默认 [file].map。
占位符支持 [name]、[id]、[hash]、[chunkhash]、[file]、[filebase]。
其他 output 属性
sourcePrefix:修改 bundle 每行字符串前缀,美化 sourcemap,默认空字符串。auxiliaryComment:给库导出容器插入注释。chunkLoadTimeout:异步 chunk 请求超时毫秒,默认120000。crossOriginLoading:script 加载异步 chunk 的 crossorigin 属性。jsonpScriptType:异步 chunk 的 script 标签 type。devtoolModuleFilenameTemplate、devtoolFallbackModuleFilenameTemplate:source-map sources 路径模板。devtoolNamespace:source-map 模块命名空间,防止多库 sourcemap 冲突。hashDigest、hashDigestLength、hashFunction、hashSalt:hash 算法与编码相关配置。hotUpdateChunkFilename、hotUpdateMainFilename:热更新相关文件名。hotUpdateFunction:热更新 JSONP 回调函数。jsonpFunction:按需加载 chunk JSONP 回调函数。pathinfo:bundle 内输出模块注释;development默认开启,production默认关闭。strictModuleExceptionHandling:require 模块报错时清理模块缓存,默认关闭。umdNamedDefine:umd 模式下给 AMD 模块命名。
module 模块配置
module 配置 Webpack 如何处理各类模块文件。
noParse
标记某些文件不需要 Webpack 解析;文件内不能包含 import、require、define。支持字符串、正则、数组、函数。
module.exports = {
module: {
noParse: 'jquery',
noParse: ['jquery', 'lodash'],
noParse: /jquery|lodash/,
noParse: (content) => /jquery|lodash/.test(content)
}
};
rules
模块处理规则集合,为不同文件指定 loader、parser。
一条 rule 由三部分构成:
- 条件 condition:匹配模块。分为
resource(被加载的文件路径)、issuer(导入该文件的父模块路径)。test、include、exclude匹配resource;issuer匹配父模块。多个条件需要全部满足。 - 结果 result:指定 loader 或者 parser 配置。
- 嵌套规则:支持
rules、oneOf做更细粒度分支匹配。
loader 加载器
Webpack 原生只识别 JS、JSON 文件。loader 将其它类型文件转为 Webpack 可识别的模块,加入依赖图。
备注:loader 属于 Webpack 特有扩展,其他打包工具不一定支持。
两种使用方式
配置方式(webpack.config.js):test 匹配文件,use 指定 loader;多个 loader 顺序从右向左执行。
module.exports = {
module: {
rules: [
{
test: /\.txt$/,
use: 'raw-loader'
}
]
}
};
备注:正则不要加引号,/\.txt$/ 代表匹配后缀 txt 的文件。
内联方式 import 中书写:使用 ! 分隔多个 loader,执行顺序从右向左。
import Styles from 'style-loader!css-loader?modules!./styles.css';
前缀可以覆盖配置内 loader:
!xxx:关闭普通 normal loader!!xxx:关闭全部 pre、normal、post loader-!xxx:关闭 pre、normal,保留 post loader
import Styles from '!style-loader!css-loader?modules!./styles.css';
参数支持 query 字符串,或者 JSON 对象,例如 ?key=value、?{"importLoaders":1}。
rules 条件语法
属于 rule.condition 的扩展配置,用来精细化控制文件匹配逻辑,写在 rule 顶层。
条件支持字符串、正则、函数、条件数组、条件对象;支持 test、include、exclude、and、or、not。
resourceQuery 匹配 URL 查询参数,例如 import './foo.css?inline':
module.exports = {
module: {
rules: [
{
test: /\.css$/,
resourceQuery: /inline/,
use: 'url-loader'
}
]
}
}
use
指定 loader 数组,从右向左(由后往前)执行;支持字符串、对象、返回数组的函数。
module.exports = {
module: {
rules: [
{
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
{
loader: 'less-loader',
options: {
noIeCompat: true
}
}
]
}
]
}
};
备注:Rule.loader 等价 Rule.use:[{loader}];Rule.options 等价 Rule.use:[{options}]。
parser
开启、关闭模块解析语法(amd、commonjs、harmony、require.context 等)。
module.exports = {
module: {
rules: [
{
parser: {
amd: false,
commonjs: false,
harmony: false
}
}
]
}
}
oneOf
匹配多条规则时,只取第一条命中的规则。适合同一个后缀,根据 query 参数走不同 loader。
module.exports = {
module: {
rules: [
{
test: /\.css$/,
oneOf: [
{
resourceQuery: /inline/,
use: 'url-loader'
},
{
resourceQuery: /external/,
use: 'file-loader'
}
]
}
]
}
};
rule 其他属性
enforce:pre前置 loader、post后置 loader;不设置为普通 loader。issuer:匹配导入当前模块的父模块路径。type:设置模块类型,javascript/auto、json、webassembly/experimental,覆盖默认解析行为。
plugin 插件
loader 负责模块转换,plugin 负责更广泛的构建任务,做 loader 无法实现的能力:打包优化、资源输出、环境变量注入、文件复制等。
使用插件:require 引入插件包,在 plugins 数组实例化;同一个插件可以多次 new 实例。
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
},
plugins: [
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
resolve 模块解析
控制 Webpack 如何寻找 import、require 的模块。
alias
路径别名,简化导入路径。
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
}
}
}
使用别名:
// import utils from '../../../utils/index.js';
import utils from '@/utils/index.js';
extensions
自动补全导入文件扩展名,默认 ['.wasm', '.mjs', '.js', '.json']。
mainFields
解析 npm 包时读取 package.json 的字段;target 为 web、webworker 默认 ['browser','module','main'];node 环境默认 ['module','main']。
mainFiles
解析目录时默认查找的文件名,默认 ['index']。
modules
查找模块的目录,默认 ['node_modules'];支持绝对路径与相对路径。
resolveLoader
专门用于解析 loader 包,配置项和 resolve 一致。可以设置 loader 别名。
module.exports = {
resolveLoader: {
alias: {
txt: 'raw-loader'
}
}
};
resolve 其余配置
aliasFields:读取 package.json 中的别名字段,典型如browser,用于浏览器环境替换模块;descriptionFiles:读取包描述的 JSON 文件,默认读取package.json;enforceExtension:是否强制导入文件必须带扩展名;enforceModuleExtension:针对模块导入强制扩展名;symlinks:是否跟随符号链接,开启会解析符号链接指向真实文件,关闭直接使用链接路径;unsafeCache:缓存解析结果,提升构建速度;正则可以指定哪些模块开启缓存;cachePredicate:自定义函数,判断某个模块解析结果是否加入缓存。
externals 外部扩展
将部分依赖排除在打包产物之外,运行时外部 CDN、环境提供该模块,减小打包体积。
例如 HTML 通过 CDN 引入 jQuery:
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
webpack 配置:
module.exports = {
externals: {
jquery: 'jQuery'
}
};
业务代码仍然可以正常 import,但是产物不包含 jQuery:
import $ from 'jquery';
$('.my-element');
支持配置对象、数组、函数、正则,兼容 root、commonjs、commonjs2、amd、umd。
module.exports = {
externals: {
jquery: 'jQuery',
subtract: ['./math', 'subtract'],
lodash : {
commonjs: 'lodash',
amd: 'lodash',
root: '_'
}
}
};
mode 模式
可选 development、production、none,启用 Webpack 内置优化,默认 production。
module.exports = {
mode: 'production'
};
- development:设置
process.env.NODE_ENV=development,开启命名模块插件,便于调试。 - production:设置
process.env.NODE_ENV=production,开启压缩、tree-shaking、模块合并等全套生产优化。 - none:关闭全部内置优化。
devServer 开发服务器
仅用于开发环境,不要部署生产环境。 webpack-dev-server 会启动内存 web 服务器,提供静态资源访问、热模块替换 HMR、接口代理、history 路由 fallback 等开发能力;默认不会输出文件到本地磁盘,全部资源保存在内存。底层依赖
http-proxy-middleware中间件。
常用配置
contentBase:提供静态资源的目录,推荐绝对路径;新版本 webpack 使用static替代该配置。host:服务主机,0.0.0.0允许局域网其他设备访问本机服务,默认localhost。port:本地监听端口号。open:启动服务后自动打开浏览器;可以指定浏览器名称。hot:开启模块热替换 HMR,修改模块不刷新整个页面,只更新变更模块。hotOnly:构建报错时,依然保留旧模块的 HMR,不整页刷新。overlay:浏览器全屏弹窗展示编译错误与警告。historyApiFallback:SPA history 模式路由,访问路由 404 时重定向到index.html,也支持自定义重写规则。https:开启 HTTPS,支持使用自签名证书,也可以传入自定义证书密钥。publicPath:开发服务器内打包资源访问基础路径。writeToDisk:把内存中的打包产物写入本地磁盘,一般调试时开启。compress:开启 gzip 压缩,返回压缩后的静态资源。before、after:注册自定义中间件,在 dev-server 内部中间件之前、之后执行,可以拦截请求、mock 接口。
proxy 跨域代理
解决前端开发跨域问题,转发 API 请求到后端服务。
简单代理:
// localhost:9600/api/users → http://localhost:3000/api/users
module.exports = {
devServer: {
proxy: {
'/api': 'http://localhost:3000'
}
}
};
完整配置,路径重写、修改 cookie:
// localhost:9600/api/users → http://localhost:3000/users
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
},
cookieDomainRewrite: {
"old.domain": "new.domain"
}
}
}
}
};
devServer 其他属性
stats 控制控制台构建日志输出等级、watchContentBase 监听静态目录文件变更、allowedHosts 设置访问域名白名单等。
顶层其他配置
context
基础目录,绝对路径;解析 entry、loader 的相对路径以此目录为基准,默认执行 webpack 的当前工作目录。
target 构建目标
告知 webpack,打包出来的代码运行在哪种宿主环境,webpack 会根据目标自动注入对应 polyfill、修改模块解析规则、加载内置插件。
常用取值:
web:默认,输出浏览器环境运行代码;webworker:输出 web worker 脚本;node:输出 Node.js 服务端代码;electron-main:electron 主进程产物;electron-renderer:electron 渲染进程产物;async-node:适配老版本 node,异步加载 chunk。
注意:target 只修改构建适配逻辑,不会自动转换 JS 语法,ES6+ 转 ES5 仍然需要 babel-loader。
应用示例
raw-loader
读取文件内容作为字符串导入。
<script>${require('raw-loader!./meta.html')}</script>
html-webpack-externals-plugin
自动处理 externals,复制 node_modules 静态资源并注入 HTML。
const webpackConfig = {
plugins: [
new HtmlWebpackPlugin(),
new HtmlWebpackExternalsPlugin({
externals: [
{
module: 'vue',
entry: 'https://unpkg.com/vue@3/dist/vue.global.js',
global: 'Vue',
}
]
}),
]
}
vue.config.js 开启 eslint-loader 自动修复
module.exports = {
chainWebpack: config => {
if (process.env.NODE_ENV !== "production") {
config.module
.rule("eslint")
.use("eslint-loader")
.options({
fix: true
});
}
}
};
常用完整配置
完整配置应包含:入口输出、CSS 处理、资源处理、HTML 生成、devServer、sourceMap、环境变量、代码压缩、清理输出目录等高频配置。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/main.js',
output: {
filename: 'js/bundle.js',
path: path.resolve(__dirname, 'dist'),
publicPath: '/'
},
devtool: 'cheap-module-eval-source-map',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env']
]
}
}
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|jpg|jpeg|gif|svg)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: 'images/[name].[hash:8].[ext]'
}
},
{
test: /\.(woff|woff2|ttf|eot|otf)$/,
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]'
}
}
]
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
},
extensions: ['.js', '.json']
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './src/index.html'),
filename: 'index.html',
title: 'Webpack4 Demo',
minify: false // 开发环境不压缩html
})
],
devServer: {
contentBase: path.resolve(__dirname, 'dist'),
host: '0.0.0.0',
port: 8080,
open: true, // 启动自动打开浏览器
hot: true, // 开启热模块替换 HMR
compress: true, // gzip压缩
overlay: true, // 浏览器页面上显示错误
historyApiFallback: true, // SPA history路由404重定向
proxy: {
'/api': {
target: 'http://127.0.0.1:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
},
optimization: {
minimize: false
}
};
常见问题
Webpack 和 Gulp、Grunt 的区别
Gulp、Grunt 属于任务运行工具(Task Runner),核心是定义一系列自动化任务,按顺序执行。
- Grunt:基于配置文件描述任务,每个任务处理完把结果写入磁盘临时目录,大量磁盘 IO,构建速度慢;擅长压缩、编译 sass、文件复制、代码校验这类通用文件处理。它不会分析 JS 的 import、require 依赖,只是遍历匹配到的文件,无脑执行任务,不管文件是否真正被项目使用。
- Gulp:基于 Node 流(Stream),处理流程在内存流转,减少磁盘读写,速度更快。同样是任务驱动,需要手动编排任务执行顺序;本身不做 JS 模块依赖解析。
Webpack 是模块打包工具,和任务工具定位完全不同。
- 以入口文件为起点,自动递归扫描所有 import、require,生成完整依赖图,只打包项目真正用到的代码;
- 内置模块处理、代码分割、tree-shaking、按需加载能力,专门解决前端 JS、CSS、静态资源的模块依赖问题;
- 同时也可以借助插件完成压缩、编译、复制文件这类 Gulp、Grunt 的工作。
简单对比:
- Gulp、Grunt:我拿到一批文件,按照配置对每个文件做 A、B、C 操作;
- Webpack:我分析代码依赖,只把需要的模块组织合并输出。
在早期项目中,经常会 Gulp + Webpack 配合使用:webpack 负责模块打包,gulp 负责做部署、静态资源拷贝等外围任务。现在很多项目直接使用 webpack 插件、npm scripts 替代 Gulp、Grunt。
Webpack 和 Rollup 的区别
- Rollup:面向类库开发,原生充分利用 ES Module,打包产物干净扁平,运行时代码少,适合开发工具库。不擅长处理图片、CSS 等异构资源,HMR 能力弱。
- Webpack:面向复杂 SPA 应用;强大 loader 处理各种异构资源、内置 HMR、代码分割、公共代码提取,适合业务应用项目。
Webpack 识别哪些算模块
- ES Module
import、export - CommonJS
require() - AMD
define、require - CSS 的
@import - 样式
url()、HTMLsrc
loader 可以扩展识别更多格式模块。
Webpack 模块路径解析逻辑
enhanced-resolve 库负责解析模块绝对路径。
- 绝对路径:直接使用。
- 相对路径:以当前导入文件所在目录拼接。
- 模块路径(不带
./):去resolve.modules目录查找;配合alias别名。
找到路径后区分文件、目录:
- 文件:尝试追加
resolve.extensions扩展名。 - 目录:读取 package.json 的
mainFields,找不到则读取目录下mainFiles。
commonjs 与 commonjs2 的区别
- commonjs:导出为
exports['xxx'] = value。 - commonjs2:导出为
module.exports = value,Node.js 使用这套。
备注:Node.js 中 exports 只是 module.exports 的引用,不能直接赋值覆盖。
// libraryTarget: 'commonjs'
exports['MyLibrary'] = _entry_return_;
// libraryTarget: 'commonjs2'
module.exports = _entry_return_;