Webpack 是现代 JavaScript 应用最流行的静态模块打包工具,它能将各种资源(JS、CSS、图片等)打包成浏览器可识别的静态资源。
| 概念 | 说明 |
|---|---|
| Entry | 入口,构建的起点 |
| Output | 输出,打包结果的配置 |
| Loader | 加载器,处理非 JS 文件 |
| Plugin | 插件,扩展 Webpack 功能 |
| Mode | 模式,development 或 production |
// webpack.config.js
const path = require('path')
module.exports = {
// 入口
entry: './src/index.js',
// 输出
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
clean: true
},
// 模式
mode: 'development',
// 模块规则
module: {
rules: []
},
// 插件
plugins: [],
// 解析
resolve: {
extensions: ['.js', '.json'],
alias: {
'@': path.resolve(__dirname, 'src')
}
}
}
module.exports = {
entry: './src/index.js'
}
module.exports = {
entry: {
main: './src/index.js',
admin: './src/admin.js',
vendor: ['lodash', 'axios']
}
}
const glob = require('glob')
module.exports = {
entry: glob.sync('./src/pages/*/index.js').reduce((entries, path) => {
const name = path.match(/\/pages\/(.*)\/index\.js/)[1]
entries[name] = path
return entries
}, {})
}
module.exports = {
output: {
// 输出目录
path: path.resolve(__dirname, 'dist'),
// 输出文件名
filename: 'bundle.js',
// 多入口文件名
filename: '[name].[contenthash].js',
// 静态资源文件名
assetModuleFilename: 'assets/[hash][ext]',
// 清理输出目录
clean: true,
// 公共路径
publicPath: '/',
// 库名称(库模式)
library: {
name: 'DongbaUtils',
type: 'umd'
}
}
}
| 占位符 | 说明 |
|---|---|
[name] |
入口名称 |
[id] |
chunk id |
[hash] |
构建哈希 |
[chunkhash] |
chunk 哈希 |
[contenthash] |
内容哈希 |
[ext] |
文件扩展名 |
Loader 用于处理非 JavaScript 文件。
module.exports = {
module: {
rules: [
// CSS
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
// Sass
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
additionalData: `$primary: #1890ff;`
}
}
]
},
// Less
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
// CSS Modules
{
test: /\.module\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]--[hash:base64:5]'
}
}
}
]
},
// PostCSS
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: { importLoaders: 1 }
},
'postcss-loader'
]
}
]
}
}
module.exports = {
module: {
rules: [
// Babel
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime']
}
}
},
// TypeScript
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
},
// ESLint
{
test: /\.js$/,
exclude: /node_modules/,
enforce: 'pre',
use: 'eslint-loader'
}
]
}
}
module.exports = {
module: {
rules: [
// 图片(Webpack 5 资源模块)
{
test: /\.(png|jpg|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8KB 以下转 base64
}
},
generator: {
filename: 'images/[hash][ext]'
}
},
// 字体
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[hash][ext]'
}
},
// 文件
{
test: /\.(pdf|docx?)$/,
type: 'asset/resource',
generator: {
filename: 'files/[hash][ext]'
}
}
]
}
}
module.exports = {
module: {
rules: [
// Vue
{
test: /\.vue$/,
loader: 'vue-loader'
},
// HTML 模板
{
test: /\.html$/,
loader: 'html-loader'
},
// Markdown
{
test: /\.md$/,
use: [
'html-loader',
'markdown-loader'
]
},
// CSV/TSV
{
test: /\.(csv|tsv)$/,
use: 'csv-loader'
},
// XML
{
test: /\.xml$/,
use: 'xml-loader'
},
// YAML
{
test: /\.ya?ml$/,
use: 'yaml-loader'
}
]
}
}
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyPlugin = require('copy-webpack-plugin')
const DefinePlugin = require('webpack').DefinePlugin
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
plugins: [
// HTML 模板
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
title: '东巴文',
minify: {
collapseWhitespace: true,
removeComments: true
}
}),
// 多页面
new HtmlWebpackPlugin({
template: './src/admin.html',
filename: 'admin.html',
chunks: ['admin']
}),
// 提取 CSS
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash].css',
chunkFilename: 'css/[id].[contenthash].css'
}),
// 清理目录
new CleanWebpackPlugin(),
// 复制文件
new CopyPlugin({
patterns: [
{ from: 'public', to: 'public' },
{ from: 'static', to: 'static' }
]
}),
// 环境变量
new DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.API_URL': JSON.stringify('https://api.db-w.cn')
}),
// 打包分析
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false
})
]
}
module.exports = {
optimization: {
// 压缩 JS
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
compress: {
drop_console: true
}
}
}),
new CssMinimizerPlugin()
],
// 代码分割
splitChunks: {
chunks: 'all',
minSize: 20000,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
name: 'vendors'
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
},
// 运行时代码
runtimeChunk: 'single',
// 模块 ID
moduleIds: 'deterministic'
}
}
module.exports = {
devServer: {
// 静态文件目录
static: {
directory: path.join(__dirname, 'public'),
publicPath: '/public'
},
// 端口
port: 3000,
// 自动打开浏览器
open: true,
// 热更新
hot: true,
// 启用 gzip
compress: true,
// history 路由回退
historyApiFallback: true,
// 代理
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
},
// CORS
headers: {
'Access-Control-Allow-Origin': '*'
},
// 控制台输出
client: {
logging: 'info',
overlay: {
errors: true,
warnings: false
}
}
}
}
module.exports = {
// 开发环境
devtool: 'eval-cheap-module-source-map',
// 生产环境
// devtool: 'source-map'
}
| 类型 | 构建速度 | 重建速度 | 质量 | 适用场景 |
|---|---|---|---|---|
eval |
最快 | 最快 | 无 | 开发 |
eval-cheap-source-map |
快 | 快 | 行映射 | 开发 |
eval-cheap-module-source-map |
较快 | 快 | 行映射 | 开发推荐 |
source-map |
慢 | 慢 | 完整 | 生产 |
hidden-source-map |
慢 | 慢 | 完整 | 生产 |
nosources-source-map |
慢 | 慢 | 无源码 | 生产 |
module.exports = {
output: {
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js'
},
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
},
cacheDirectory: path.resolve(__dirname, '.webpack_cache')
},
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
// webpack.common.js
const { merge } = require('webpack-merge')
const baseConfig = {
// 公共配置
}
// webpack.dev.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
devServer: {
hot: true
}
})
// webpack.prod.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
optimization: {
minimize: true
}
})
{
"scripts": {
"dev": "webpack serve --config webpack.dev.js",
"build": "webpack --config webpack.prod.js",
"build:analyze": "webpack --config webpack.prod.js --profile --json > stats.json"
}
}
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const { DefinePlugin } = require('webpack')
const isProduction = process.env.NODE_ENV === 'production'
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[contenthash:8].js',
chunkFilename: 'js/[name].[contenthash:8].chunk.js',
assetModuleFilename: 'assets/[hash][ext]',
clean: true,
publicPath: '/'
},
mode: isProduction ? 'production' : 'development',
devtool: isProduction ? 'source-map' : 'eval-cheap-module-source-map',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.css$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader'
]
},
{
test: /\.scss$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
'postcss-loader',
'sass-loader'
]
},
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024
}
}
},
{
test: /\.(woff2?|eot|ttf|otf)$/,
type: 'asset/resource'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
title: '东巴文',
minify: isProduction
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[id].[contenthash:8].css'
}),
new DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
})
],
optimization: {
minimize: isProduction,
minimizer: [
new TerserPlugin(),
new CssMinimizerPlugin()
],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
},
runtimeChunk: 'single'
},
resolve: {
extensions: ['.js', '.json'],
alias: {
'@': path.resolve(__dirname, 'src')
}
},
devServer: {
static: './public',
hot: true,
port: 3000,
historyApiFallback: true,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
},
performance: {
hints: isProduction ? 'warning' : false,
maxAssetSize: 244 * 1024,
maxEntrypointSize: 244 * 1024
}
}
掌握 Webpack 后,你可以继续学习:
东巴文(db-w.cn)—— 让 Webpack 配置更简单