# webpack 项目配置
# 1. 介绍
搭建:
- react 项目脚手架
- vue 项目脚手架
# 2. react 脚手架
# 2.1. 项目结构
react-proj/
config/
webpack.config.js
webpack.dev.js
webpack.prod.js
dist/
node_modules/
public/
favicon.ico
index.html
src/
pages/
about/
index.jsx
index.scss
home/
index.jsx
index.less
App.jsx
main.js
.browserslistrc
.eslintrc.js
babel.config.js
package.json
package-lock.json
# 2.2. 安装
# webpack cli dev-server
npm i -D webpack webpack-cli webpack-dev-server
## "webpack": "^5.81.0", "webpack-cli": "^5.0.2", "webpack-dev-server": "^4.13.3"
# css
npm i -D style-loader css-loader
npm i -D less-loader
npm i -D sass sass-loader
npm i -D stylus-loader
npm i -D postcss-loader postcss postcss-preset-env
npm i -D mini-css-extract-plugin
npm i -D css-minimizer-webpack-plugin
## "style-loader": "^3.3.2", "css-loader": "^6.7.3",
## "less-loader": "^11.1.0",
## "sass": "^1.62.1", "sass-loader": "^13.2.2",
## "stylus-loader": "^7.1.0",
## "postcss": "^8.4.23", "postcss-loader": "^7.3.0", "postcss-preset-env": "^8.3.2",
## "mini-css-extract-plugin": "^2.7.5",
## "css-minimizer-webpack-plugin": "^5.0.0",
# js
npm i -D eslint-webpack-plugin eslint-config-react-app
npm i -D babel-loader @babel/core babel-preset-react-app
npm i -D terser-webpack-plugin
## "eslint-webpack-plugin": "^4.0.1", "eslint-config-react-app": "^7.0.1",
## "babel-loader": "^9.1.2", "@babel/core": "^7.21.5", "babel-preset-react-app": "^10.0.1",
## "terser-webpack-plugin": "^5.3.7",
# img min
npm i -D image-minimizer-webpack-plugin imagemin
npm i -D imagemin-gifsicle imagemin-svgo
npm i -D imagemin-jpegtran imagemin-optipng
## "image-minimizer-webpack-plugin": "^3.8.2", "imagemin": "^8.0.1",
## "imagemin-gifsicle": "^7.0.0", "imagemin-svgo": "^10.0.1",
## "imagemin-jpegtran": "^7.0.0", "imagemin-optipng": "^8.0.0",
# html
npm i -D html-webpack-plugin
## "html-webpack-plugin": "^5.5.1",
# HMR of react component
npm i -D @pmmmwh/react-refresh-webpack-plugin react-refresh
## "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", "react-refresh": "^0.14.0",
# others
npm i -D cross-env
npm i -D copy-webpack-plugin
## "cross-env": "^7.0.3",
## "copy-webpack-plugin": "^11.0.0",
# react
npm i react react-dom react-router-dom
## "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.11.0"
# 2.3. 其他配置
# 2.3.1. .browserslistrc
last 2 version
> 1%
not dead
# 2.3.2. .eslintrc.js
module.exports = {
extends: ['react-app'], // 继承 react 官方规则
parserOptions: {
babelOptions: {
presets: [
// 解决页面报错问题
['babel-preset-react-app', false],
'babel-preset-react-app/prod',
],
},
},
};
# 2.3.3. babel.config.js
module.exports = {
/*
使用 react 官方规则,包含:
@babel/preset-env
corejs
@babel/plugin-transform-runtime
*/
presets: ['react-app'],
};
# 2.3.4. HMR of react component
配置:
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = {
devServer: {
hot: true,
},
module: {
rules: [
{
test: /\.[jt]sx?$/,
use: [
{
loader: 'babel-loader',
options: { plugins: ['react-refresh/babel'] }
},
],
},
],
},
plugins: [new ReactRefreshWebpackPlugin()],
}
参考:react-refresh-webpack-plugin (opens new window)
# 2.3.5. historyApiFallback
配置:
module.exports = {
//...
devServer: {
/*
When using the HTML5 History API,
the index.html page will likely have to be served in place of any 404 responses.
Enable devServer.historyApiFallback by setting it to true:
boolean = false object
*/
historyApiFallback: true,
},
}
参考:
# 2.3.6. 拷贝 public 目录
配置:
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
// ...
plugins: [
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist'),
globOptions: {
ignore: [
// 不拷贝 index.html, 避免与 HtmlWebpackPlugin 冲突
'**/index.html',
]
}
},
],
}),
]
};
参考:
# 2.4. 报错
# 2.4.1. NODE_ENV
错误信息:
Using `babel-preset-react-app` requires that you specify `NODE_ENV` or `BABEL_ENV` environment variables.
解决方案:
安装 cross-env
npm i -D cross-env
在 npm scripts 中设置临时环境变量 NODE_ENV=development
{ "scripts": { "start": "npm run dev", "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js" } }
# 2.4.2. 省略 .jsx 后缀不识别
错误信息:
Module not found: Error: Can't resolve './App' in 'D:\dev\GitHub\blog\codes\frontend\webpack\guide\react-proj\src'
解决方案:
在 webpack.config.js 中设置
module.exports = { //... resolve: { // default: ['.js', '.json', '.wasm'] extensions: ['.js', '.jsx', '.json', '.wasm'], }, };
# 2.5. 开发配置
webpack.dev.js:
const path = require("path");
const ESLintWebpackPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const getStyleLoaders = (preProcessor) => {
return [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env',
],
},
},
},
preProcessor,
].filter(Boolean);
};
module.exports = {
mode: 'development',
devtool: 'cheap-module-source-map',
devServer: {
host: 'localhost',
port: '3000',
open: true,
hot: true,
historyApiFallback: true,
},
entry: path.resolve(__dirname, '../src/main.js'),
output: {
path: path.resolve('../dist'),
filename: 'static/js/[name].js',
chunkFilename: 'static/js/[name].chunk.js',
assetModuleFilename: 'static/media/[name].[hash][ext][query]',
},
resolve: {
extensions: ['.js', '.jsx', '.json', '.wasm'],
},
module: {
rules: [
{
oneOf: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: [ 'react-refresh/babel' ],
}
}
],
},
{ test: /\.css$/, use: getStyleLoaders() },
{ test: /\.less$/, use: getStyleLoaders('less-loader') },
{ test: /\.s[ac]ss$/, use: getStyleLoaders('sass-loader') },
{ test: /\.styl$/, use: getStyleLoaders('stylus-loader') },
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024
}
},
},
{
test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
type: 'asset/resource',
},
]
},
]
},
plugins: [
new ESLintWebpackPlugin({
context: path.resolve(__dirname, '../src'),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html'),
}),
new ReactRefreshWebpackPlugin(),
],
optimization: {
splitChunks: {
chunks: "all",
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
},
};
npm scripts:
{
"scripts": {
"start": "npm run dev",
"dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js"
}
}
# 2.6. 生产配置
webpack.prod.js:
const path = require("path");
const ESLintWebpackPlugin = require('eslint-webpack-plugin');
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 ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
const CopyPlugin = require("copy-webpack-plugin");
const getStyleLoaders = (preProcessor) => {
return [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env',
],
},
},
},
preProcessor,
].filter(Boolean);
};
module.exports = {
mode: 'production',
devtool: 'source-map',
entry: path.resolve(__dirname, '../src/main.js'),
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'static/js/[name].[contenthash:8].js',
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
assetModuleFilename: 'static/media/[name].[hash][ext][query]',
clean: true,
},
resolve: {
extensions: ['.js', '.jsx', '.json', '.wasm'],
},
module: {
rules: [
{
oneOf: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
}
}
],
},
{ test: /\.css$/, use: getStyleLoaders() },
{ test: /\.less$/, use: getStyleLoaders('less-loader') },
{ test: /\.s[ac]ss$/, use: getStyleLoaders('sass-loader') },
{ test: /\.styl$/, use: getStyleLoaders('stylus-loader') },
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024
}
},
},
{
test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
type: 'asset/resource',
},
]
},
]
},
plugins: [
new ESLintWebpackPlugin({
context: path.resolve(__dirname, '../src'),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html'),
}),
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
}),
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist'),
globOptions: {
ignore: [
// 不拷贝 index.html, 避免与 HtmlWebpackPlugin 冲突
'**/index.html',
]
}
},
],
}),
],
optimization: {
splitChunks: {
chunks: "all",
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin(),
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['optipng', { optimizationLevel: 5 }],
['svgo', { plugins: [ 'preset-default', 'prefixIds', { name: 'sortAttrs', params: { xmlnsOrder: 'alphabetical' } } ] }],
],
},
},
}),
],
},
};
npm scripts:
{
"scripts": {
"build": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"
}
}
# 2.7. 合并配置
webpack.config.js:
const path = require('path');
const ESLintWebpackPlugin = require('eslint-webpack-plugin');
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 ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const isProd = process.env.NODE_ENV === 'production';
const getStyleLoaders = (preProcessor) => {
return [
isProd ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env',
],
},
},
},
preProcessor && {
loader: preProcessor,
options: preProcessor === 'less-loader' ?
{
lessOptions: { // If you are using less-loader@5 please spread the lessOptions to options directly
modifyVars: {
'primary-color': '#1DA57A',
'link-color': '#1DA57A',
'border-radius-base': '2px',
},
javascriptEnabled: true,
},
} : {}
},
].filter(Boolean);
};
module.exports = {
mode: isProd ? 'production' : 'development',
devtool: isProd ? 'source-map' : 'cheap-module-source-map',
devServer: {
host: 'localhost',
port: '3000',
open: true,
hot: true,
historyApiFallback: true,
},
entry: path.resolve(__dirname, '../src/main.js'),
output: {
path: isProd ? path.resolve(__dirname, '../dist') : undefined,
filename: isProd ? 'static/js/[name].[contenthash:8].js' : 'static/js/[name].js',
chunkFilename: isProd ? 'static/js/[name].[contenthash:8].chunk.js' : 'static/js/[name].chunk.js',
assetModuleFilename: 'static/media/[name].[hash][ext][query]',
clean: isProd,
},
resolve: {
extensions: ['.js', '.jsx', '.json', '.wasm'],
},
module: {
rules: [
{
oneOf: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: [
!isProd && 'react-refresh/babel'
].filter(Boolean),
}
}
],
},
{test: /\.css$/, use: getStyleLoaders()},
{test: /\.less$/, use: getStyleLoaders('less-loader')},
{test: /\.s[ac]ss$/, use: getStyleLoaders('sass-loader')},
{test: /\.styl$/, use: getStyleLoaders('stylus-loader')},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024
}
},
},
{
test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
type: 'asset/resource',
},
]
},
]
},
plugins: [
!isProd && new ReactRefreshWebpackPlugin(),
new ESLintWebpackPlugin({
context: path.resolve(__dirname, '../src'),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html'),
}),
isProd && new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
}),
isProd && new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist'),
globOptions: {
ignore: [
// 不拷贝 index.html, 避免与 HtmlWebpackPlugin 冲突
'**/index.html',
]
}
},
],
}),
].filter(Boolean),
// 关闭性能分析,提升打包速度
// performance: false,
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// react react-dom react-router-dom
react: {
priority: 40,
test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
name: "lib-react",
},
// antd
antd: {
priority: 30,
test: /[\\/]node_modules[\\/]antd[\\/]/,
name: "lib-antd",
},
// others
others: {
priority: 20,
test: /[\\/]node_modules[\\/]/,
name: "lib-others",
},
},
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
// 控制是否要进行压缩
minimize: isProd,
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin(),
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['optipng', { optimizationLevel: 5 }],
['svgo', { plugins: [ 'preset-default', 'prefixIds', { name: 'sortAttrs', params: { xmlnsOrder: 'alphabetical' } } ] }],
],
},
},
}),
],
},
};
npm scripts:
{
"scripts": {
"start": "npm run dev",
"dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js",
}
}
# 2.8. 优化
# 2.8.1. antd 自定义主题
安装:
npm i antd@4
## "antd": "^4.24.9",
配置:
const getStyleLoaders = (preProcessor) => {
return [
isProd ? MiniCssExtractPlugin.loader : 'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env',
],
},
},
},
preProcessor && {
loader: preProcessor,
options: preProcessor === 'less-loader' ?
{
lessOptions: { // If you are using less-loader@5 please spread the lessOptions to options directly
modifyVars: {
'primary-color': '#1DA57A',
'link-color': '#1DA57A',
'border-radius-base': '2px',
},
javascriptEnabled: true,
},
} : {}
},
].filter(Boolean);
};
参考:
# 2.8.2. 拆分 node_modules 里面的包
配置:
module.exports = {
// ...
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// react react-dom react-router-dom
react: {
test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
name: "lib-react",
priority: 40,
},
// antd
antd: {
test: /[\\/]node_modules[\\/]antd[\\/]/,
name: "lib-antd",
priority: 30,
},
// others
libs: {
test: /[\\/]node_modules[\\/]/,
name: "lib-others",
priority: 20,
},
},
},
}
}
# 2.8.3. 关闭性能分析
module.exports = {
// ...
// 关闭性能分析,提升打包速度
performance: false,
}
# 3. vue 脚手架
# 3.1. 项目结构
vue3-proj/
config/
webpack.config.js
webpack.dev.js
webpack.prod.js
dist/
node_modules/
public/
index.html
favicon.ico
src/
pages/
About.vue
Home.vue
router/
index.js
styles/
element/
index.scss
App.vue
main.js
.browserslistrc
.eslintrc.js
babel.config.js
package.json
package-lock.json
# 3.2. 安装
# webpack cli dev-server
npm i -D webpack webpack-cli webpack-dev-server
## "webpack": "^5.81.0", "webpack-cli": "^5.0.2", "webpack-dev-server": "^4.13.3"
# css
npm i -D style-loader css-loader
npm i -D less-loader
npm i -D sass sass-loader
npm i -D stylus-loader
npm i -D postcss-loader postcss postcss-preset-env
npm i -D mini-css-extract-plugin
npm i -D css-minimizer-webpack-plugin
## "style-loader": "^3.3.2", "css-loader": "^6.7.3",
## "less-loader": "^11.1.0",
## "sass": "^1.62.1", "sass-loader": "^13.2.2",
## "stylus-loader": "^7.1.0",
## "postcss": "^8.4.23", "postcss-loader": "^7.3.0", "postcss-preset-env": "^8.3.2",
## "mini-css-extract-plugin": "^2.7.5",
## "css-minimizer-webpack-plugin": "^5.0.0",
# js
npm i -D eslint-webpack-plugin @babel/eslint-parser eslint-plugin-vue
npm i -D babel-loader @babel/core @vue/cli-plugin-babel
npm i -D terser-webpack-plugin
## "eslint-webpack-plugin": "^4.0.1", "@babel/eslint-parser": "^7.21.3", "eslint-plugin-vue": "^9.11.0",
## "babel-loader": "^9.1.2", "@babel/core": "^7.21.5", "@vue/cli-plugin-babel": "^5.0.8",
## "terser-webpack-plugin": "^5.3.7",
# img min
npm i -D image-minimizer-webpack-plugin imagemin
npm i -D imagemin-gifsicle imagemin-svgo
npm i -D imagemin-jpegtran imagemin-optipng
## "image-minimizer-webpack-plugin": "^3.8.2", "imagemin": "^8.0.1",
## "imagemin-gifsicle": "^7.0.0", "imagemin-svgo": "^10.0.1",
## "imagemin-jpegtran": "^7.0.0", "imagemin-optipng": "^8.0.0",
# html
npm i -D html-webpack-plugin
## "html-webpack-plugin": "^5.5.1",
# others
npm i -D cross-env
npm i -D copy-webpack-plugin
## "cross-env": "^7.0.3",
## "copy-webpack-plugin": "^11.0.0",
# vue
npm i -D vue-loader vue-template-compiler vue-style-loader
## "vue-loader": "^17.1.0", "vue-style-loader": "^4.1.3", "vue-template-compiler": "^2.7.14",
npm i vue vue-router
## "vue": "^3.2.47", "vue-router": "^4.1.6"
# 3.3. 其他配置
# 3.3.1. .browserslistrc
last 2 version
> 1%
not dead
# 3.3.2. .eslintrc.js
module.exports = {
root: true,
env: {
node: true,
},
// eslint-plugin-vue
extends: ['plugin:vue/vue3-essential', 'eslint:recommended'],
parserOptions: {
parser: '@babel/eslint-parser',
},
};
# 3.3.3. babel.config.js
module.exports = {
presets: ['@vue/cli-plugin-babel/preset'],
};
# 3.3.4. vue-loader
安装:
npm i -D vue-loader vue-template-compiler vue-style-loader
配置:
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
mode: 'development',
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
// this will apply to both plain `.css` files
// AND `<style>` blocks in `.vue` files
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
plugins: [
// make sure to include the plugin for the magic
new VueLoaderPlugin()
]
}
参考:
# 3.3.5. 业务级环境变量
说明:
- cross-env 设置的环境变量是给 webpack 用的,在构建代码中使用
__VUE_OPTIONS_API__
,__VUE_PROD_DEVTOOLS__
这种在业务代码中使用的环境变量,需要用 webpack 插件注入
配置:
const { DefinePlugin } = require("webpack");
module.exports = {
// ...
plugins: [
new DefinePlugin({
// 解决 vue3 页面警告的问题
__VUE_OPTIONS_API__: true, // 选项式 API
__VUE_PROD_DEVTOOLS__: false, // 生产环境是否启用 devtools
}),
]
}
参考:
# 3.4. 开发配置
webpack.dev.js:
const path = require("path");
const ESLintWebpackPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { DefinePlugin } = require("webpack");
const { VueLoaderPlugin } = require('vue-loader');
const getStyleLoaders = (preProcessor) => {
return [
'vue-style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env',
],
},
},
},
preProcessor,
].filter(Boolean);
};
module.exports = {
mode: 'development',
devtool: 'cheap-module-source-map',
devServer: {
host: 'localhost',
port: '3000',
open: true,
hot: true,
historyApiFallback: true,
},
entry: path.resolve(__dirname, '../src/main.js'),
output: {
path: path.resolve('../dist'),
filename: 'static/js/[name].js',
chunkFilename: 'static/js/[name].chunk.js',
assetModuleFilename: 'static/media/[name].[hash][ext][query]',
},
resolve: {
extensions: ['.js', '.vue', '.json', '.wasm'],
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
oneOf: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
}
}
],
},
{ test: /\.css$/, use: getStyleLoaders() },
{ test: /\.less$/, use: getStyleLoaders('less-loader') },
{ test: /\.s[ac]ss$/, use: getStyleLoaders('sass-loader') },
{ test: /\.styl$/, use: getStyleLoaders('stylus-loader') },
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024
}
},
},
{
test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
type: 'asset/resource',
},
]
},
]
},
plugins: [
new ESLintWebpackPlugin({
context: path.resolve(__dirname, '../src'),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html'),
}),
new VueLoaderPlugin(),
new DefinePlugin({
// 解决 vue3 页面警告的问题
__VUE_OPTIONS_API__: true, // 选项式 API
__VUE_PROD_DEVTOOLS__: false, // 生产环境是否启用 devtools
}),
],
optimization: {
splitChunks: {
chunks: "all",
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
},
};
npm scripts:
{
"scripts": {
"start": "npm run dev",
"dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js"
}
}
# 3.5. 生产配置
webpack.prod.js:
const path = require("path");
const ESLintWebpackPlugin = require('eslint-webpack-plugin');
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 ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
const CopyPlugin = require("copy-webpack-plugin");
const { DefinePlugin } = require("webpack");
const { VueLoaderPlugin } = require('vue-loader');
const getStyleLoaders = (preProcessor) => {
return [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env',
],
},
},
},
preProcessor,
].filter(Boolean);
};
module.exports = {
mode: 'production',
devtool: 'source-map',
entry: path.resolve(__dirname, '../src/main.js'),
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'static/js/[name].[contenthash:8].js',
chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
assetModuleFilename: 'static/media/[name].[hash][ext][query]',
clean: true,
},
resolve: {
extensions: ['.js', '.vue', '.json', '.wasm'],
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
oneOf: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
}
}
],
},
{ test: /\.css$/, use: getStyleLoaders() },
{ test: /\.less$/, use: getStyleLoaders('less-loader') },
{ test: /\.s[ac]ss$/, use: getStyleLoaders('sass-loader') },
{ test: /\.styl$/, use: getStyleLoaders('stylus-loader') },
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024
}
},
},
{
test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
type: 'asset/resource',
},
]
},
]
},
plugins: [
new ESLintWebpackPlugin({
context: path.resolve(__dirname, '../src'),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html'),
}),
new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
}),
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist'),
globOptions: {
ignore: [
// 不拷贝 index.html, 避免与 HtmlWebpackPlugin 冲突
'**/index.html',
]
}
},
],
}),
new VueLoaderPlugin(),
new DefinePlugin({
// 解决 vue3 页面警告的问题
__VUE_OPTIONS_API__: true, // 选项式 API
__VUE_PROD_DEVTOOLS__: false, // 生产环境是否启用 devtools
}),
],
optimization: {
splitChunks: {
chunks: "all",
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin(),
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['optipng', { optimizationLevel: 5 }],
['svgo', { plugins: [ 'preset-default', 'prefixIds', { name: 'sortAttrs', params: { xmlnsOrder: 'alphabetical' } } ] }],
],
},
},
}),
],
},
};
npm scripts:
{
"scripts": {
"build": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"
}
}
# 3.6. 合并配置
webpack.config.js:
const path = require("path");
const ESLintWebpackPlugin = require('eslint-webpack-plugin');
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 ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
const CopyPlugin = require("copy-webpack-plugin");
const { DefinePlugin } = require("webpack");
const { VueLoaderPlugin } = require('vue-loader');
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
const ElementPlus = require('unplugin-element-plus/webpack')
const isProd = process.env.NODE_ENV === 'production';
const getStyleLoaders = (preProcessor) => {
return [
isProd ? MiniCssExtractPlugin.loader : 'vue-style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env',
],
},
},
},
preProcessor && {
loader: preProcessor,
options: preProcessor === "sass-loader" ?
{
additionalData: `@use "@/styles/element/index.scss" as *;`,
} : {}
},
].filter(Boolean);
};
module.exports = {
mode: isProd ? 'production' : 'development',
devtool: isProd ? 'source-map' : 'cheap-module-source-map',
devServer: {
host: 'localhost',
port: '3000',
open: true,
hot: true,
historyApiFallback: true,
},
entry: path.resolve(__dirname, '../src/main.js'),
output: {
path: isProd ? path.resolve(__dirname, '../dist') : undefined,
filename: isProd ? 'static/js/[name].[contenthash:8].js' : 'static/js/[name].js',
chunkFilename: isProd ? 'static/js/[name].[contenthash:8].chunk.js' : 'static/js/[name].chunk.js',
assetModuleFilename: 'static/media/[name].[hash][ext][query]',
clean: isProd,
},
resolve: {
extensions: ['.js', '.vue', '.json', '.wasm'],
alias: {
'@': path.resolve(__dirname, '../src'),
},
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
cacheDirectory: path.resolve(__dirname, "../node_modules/.cache/vue-loader"),
},
},
{
oneOf: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
}
}
],
},
{ test: /\.css$/, use: getStyleLoaders() },
{ test: /\.less$/, use: getStyleLoaders('less-loader') },
{ test: /\.s[ac]ss$/, use: getStyleLoaders('sass-loader') },
{ test: /\.styl$/, use: getStyleLoaders('stylus-loader') },
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024
}
},
},
{
test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
type: 'asset/resource',
},
]
},
]
},
plugins: [
new ESLintWebpackPlugin({
context: path.resolve(__dirname, '../src'),
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html'),
}),
isProd && new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
}),
isProd && new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, '../public'),
to: path.resolve(__dirname, '../dist'),
globOptions: {
ignore: [
// 不拷贝 index.html, 避免与 HtmlWebpackPlugin 冲突
'**/index.html',
]
}
},
],
}),
new VueLoaderPlugin(),
new DefinePlugin({
// 解决 vue3 页面警告的问题
__VUE_OPTIONS_API__: true, // 选项式 API
__VUE_PROD_DEVTOOLS__: false, // 生产环境是否启用 devtools
}),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
ElementPlus({
useSource: true,
}),
].filter(Boolean),
optimization: {
splitChunks: {
chunks: "all",
cacheGroups: {
vue: {
test: /[\\/]node_modules[\\/]vue(.*)?[\\/]/,
name: "lib-vue",
priority: 40,
},
elementPlus: {
test: /[\\/]node_modules[\\/]element-plus[\\/]/,
name: "lib-element-plus",
priority: 30,
},
others: {
test: /[\\/]node_modules[\\/]/,
name: "lib-others",
priority: 20,
},
},
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
minimize: isProd,
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin(),
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
['gifsicle', { interlaced: true }],
['jpegtran', { progressive: true }],
['optipng', { optimizationLevel: 5 }],
['svgo', { plugins: [ 'preset-default', 'prefixIds', { name: 'sortAttrs', params: { xmlnsOrder: 'alphabetical' } } ] }],
],
},
},
}),
],
},
};
npm scripts:
{
"scripts": {
"start": "npm run dev",
"dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js",
}
}
# 3.7. 优化
# 3.7.1. element-plus
安装:
npm i element-plus
npm i -D unplugin-vue-components unplugin-auto-import
npm i -D unplugin-element-plus
## "element-plus": "^2.3.4",
## "unplugin-vue-components": "^0.24.1", "unplugin-auto-import": "^0.15.3",
## "unplugin-element-plus": "^0.7.1",
全部引入:
import { createApp } from 'vue';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import App from './App';
createApp(App)
.use(ElementPlus)
.mount('#app');
按需引入:
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = {
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
]
};
定制主题:
创建
src/styles/element/index.scss
文件@forward 'element-plus/theme-chalk/src/common/var.scss' with ( $colors: ( 'primary': ( 'base': green, ), ) );
修改 webpack.config.js
module.exports = { resolve: { alias: { '@': path.resolve(__dirname, '../src'), }, }, }
修改 sass-loader 配置
const getStyleLoaders = (preProcessor) => { return [ isProd ? MiniCssExtractPlugin.loader : 'vue-style-loader', 'css-loader', { loader: 'postcss-loader', options: { postcssOptions: { plugins: [ 'postcss-preset-env', ], }, }, }, preProcessor && { loader: preProcessor, options: preProcessor === "sass-loader" ? { additionalData: `@use "@/styles/element/index.scss" as *;`, } : {} }, ].filter(Boolean); };
使用 plugin
const ElementPlus = require('unplugin-element-plus/webpack') module.exports = { plugins: [ ElementPlus({ useSource: true, }), ] }
参考:
# 3.7.2. 拆分 node_modules
module.exports = {
optimization: {
splitChunks: {
chunks: "all",
cacheGroups: {
vue: {
test: /[\\/]node_modules[\\/]vue(.*)?[\\/]/,
name: "lib-vue",
priority: 40,
},
elementPlus: {
test: /[\\/]node_modules[\\/]element-plus[\\/]/,
name: "lib-element-plus",
priority: 30,
},
others: {
test: /[\\/]node_modules[\\/]/,
name: "lib-others",
priority: 20,
},
},
},
}
}
# 3.7.3. vue-loader 缓存
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
cacheDirectory: path.resolve(__dirname, "../node_modules/.cache/vue-loader"),
},
},
]
}
}
# 4. 总结
当项目越来越大,可以考虑进一步优化,参考上一篇文章(“webpack高级”)
上一篇: 下一篇:
本章目录