Webpack
Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具。它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个 bundle。
1. 基础配置
1.1 安装
# 安装 webpack
npm install webpack webpack-cli --save-dev
# 开发服务器
npm install webpack-dev-server --save-dev
# 常用 loader
npm install babel-loader style-loader css-loader file-loader url-loader --save-dev
# 常用插件
npm install html-webpack-plugin mini-css-extract-plugin clean-webpack-plugin --save-dev
1.2 基础配置文件
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// 入口
entry: './src/index.js',
// 输出
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true
},
// 模块处理
module: {
rules: [
// JavaScript
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
// CSS
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
},
// 图片
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource'
}
]
},
// 插件
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
})
]
};
2. 高级配置
2.1 多入口配置
module.exports = {
entry: {
app: './src/app.js',
admin: './src/admin.js'
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
2.2 开发环境配置
// webpack.dev.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
static: './dist',
hot: true,
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
});
2.3 生产环境配置
// webpack.prod.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
optimization: {
minimizer: [
new TerserPlugin(),
new CssMinimizerPlugin()
],
splitChunks: {
chunks: 'all'
}
}
});
3. 模块处理
3.1 Loader 配置
module.exports = {
module: {
rules: [
// JavaScript/TypeScript
{
test: /\.(js|ts)x?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript'
]
}
}
},
// CSS/SCSS
{
test: /\.(css|scss)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 2
}
},
'postcss-loader',
'sass-loader'
]
},
// 图片和字体
{
test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8kb
}
}
}
]
}
};
3.2 Resolve 配置
module.exports = {
resolve: {
// 扩展名
extensions: ['.js', '.jsx', '.ts', '.tsx'],
// 别名
alias: {
'@': path.resolve(__dirname, 'src'),
'components': path.resolve(__dirname, 'src/components')
},
// 模块查找目录
modules: [
'node_modules',
path.resolve(__dirname, 'src')
]
}
};
4. 性能优化
4.1 代码分割
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
4.2 缓存优化
module.exports = {
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
},
// 持久化缓存
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
}
};
4.3 Tree Shaking
// package.json
{
"sideEffects": [
"*.css",
"*.scss"
]
}
// webpack.config.js
module.exports = {
mode: 'production',
optimization: {
usedExports: true,
minimize: true,
concatenateModules: true
}
};
5. 插件系统
5.1 常用插件配置
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
plugins: [
// HTML 生成
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
}),
// CSS 提取
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
}),
// 环境变量
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}),
// 打包分析
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false
})
]
};
5.2 自定义插件
class MyPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.emit.tapAsync(
'MyPlugin',
(compilation, callback) => {
// 在生成文件中,创建一个头部注释
compilation.assets['main.js'] = {
source() {
return `/*! My Plugin */\n${compilation.assets['main.js'].source()}`;
},
size() {
return this.source().length;
}
};
callback();
}
);
}
}
module.exports = MyPlugin;
6. 开发工具
6.1 Source Map
module.exports = {
// 开发环境
devtool: 'eval-cheap-module-source-map',
// 生产环境
devtool: 'source-map'
};
6.2 热模块替换
module.exports = {
devServer: {
hot: true,
liveReload: false
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};
// 在代码中
if (module.hot) {
module.hot.accept('./print.js', function() {
console.log('Accepting the updated printMe module!');
printMe();
});
}
7. 部署配置
7.1 多环境配置
// webpack.config.js
const envConfig = require(`./config/webpack.${process.env.NODE_ENV}.js`);
const commonConfig = require('./config/webpack.common.js');
module.exports = merge(commonConfig, envConfig);
// package.json
{
"scripts": {
"dev": "webpack serve --config webpack.dev.js",
"build:dev": "webpack --config webpack.dev.js",
"build:prod": "webpack --config webpack.prod.js"
}
}
7.2 CDN 配置
module.exports = {
output: {
publicPath: 'https://cdn.example.com/assets/',
filename: '[name].[contenthash].js'
},
externals: {
react: 'React',
'react-dom': 'ReactDOM'
}
};