Skip to main content

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'
}
};

参考资源