Webpack 自动化打包构建

Webpack 是一个模块打包器,它可以将许多模块打包成一个或多个静态资源文件。它通常用于构建前端应用程序,但是也可以用于构建后端应用程序。
使用 Webpack 进行自动化构建通常包括以下步骤:

  • 安装 Webpack:使用 npm 命令 npm install webpack 安装 Webpack。
  • 配置 Webpack:创建一个 webpack.config.js 文件,并在其中配置 Webpack。这包括设置入口文件、出口文件、加载器等。
  • 运行 Webpack:使用命令 npx webpack 运行 Webpack。这将执行构建流程,并生成打包后的文件。
  • 使用插件和工具:可以使用各种插件和工具来增强 Webpack 的功能,例如压缩文件、创建 source maps 等。

项目目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
├── webpack-test (项目根目录)
├── config (Webpack配置文件目录)
│ ├── webpack.dev.js(开发模式配置文件)
│ └── webpack.prod.js(生产模式配置文件)
├── node_modules (下载包存放目录)
├── src (项目源码目录,除了html其他都在src里面)
│ └── 略
├── public (项目html文件)
│ └── index.html
├── .eslintrc.js(Eslint配置文件)
├── babel.config.js(Babel配置文件)
└── package.json (包的依赖管理配置文件)

开发模式配置

这个模式下我们主要做两件事:

  1. 编译代码,使浏览器能识别运行
  • 开发时我们有样式资源、字体图标、图片资源、html 资源等,webpack 默认都不能处理这些资源,所以我们要加载配置来编译这些资源
  1. 代码质量检查,树立代码规范
  • 提前检查代码的一些隐患,让代码运行时能更加健壮。
  • 提前检查代码规范和格式,统一团队编码风格,让代码更优雅美观。

webpack配置文件config/webpack.dev.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Node.js的核心模块,专门用来处理文件路径
const path = require("path");
// 引入 eslint 插件
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
// 引入处理 html 资源的插件
const HtmlWebpackPlugin = require("html-webpack-plugin");
const os = require("os");
// cpu核数
const threads = os.cpus().length;

module.exports = {
// 入口
// 相对路径和绝对路径都行
entry: "./src/main.js",
// 输出
output: {
// path: 文件输出目录,必须是绝对路径
// path.resolve()方法返回一个绝对路径
// __dirname 当前文件的文件夹绝对路径
// path: path.resolve(__dirname, "dist"),
path: undefined, // 开发模式没有输出,不需要指定输出目录
// filename: 输出文件名
// filename: "static/js/main.js",
filename: "static/js/[name].js", // 入口文件打包输出资源命名方式
chunkFilename: "static/js/[name].chunk.js", // 动态导入输出资源命名方式
assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)
// clean: true, // 开发模式没有输出,自动将上次打包目录资源清空
},
// 加载器 loader
module: {
rules: [
// OneOf 配置 每个文件只能被一个loader配置处理
{
oneOf: [
// css-loader
{
// 正则用来匹配 .css 结尾的文件
test: /\.css$/,
// use 数组里面 Loader 执行顺序是从右到左(下到上)
use: [
"style-loader", // 将js中css通过创建style标签添加到html文件中生效
"css-loader" // 将css资源编译成commonjs的模块到js文件中
],
},
// less-loader
{
test: /\.less$/,
use: [
"style-loader",
"css-loader",
"less-loader" // 将less文件编译为css文件
],
},
// sacc scss loader
{
test: /\.s[ac]ss$/,
use: [
"style-loader",
"css-loader",
"sass-loader" // 将 sass文件编译为css文件
],
},
// stylus-loader
{
test: /\.styl$/,
use: ["style-loader", "css-loader", "stylus-loader"],
},
// 图片资源
{
test: /\.(png|jpe?g|gif|webp)$/,
type: "asset", //将文件转化成 Webpack 能识别的资源,同时小于某个大小的资源会处理成 data URI 形式
parser: {
dataUrlCondition: {
maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
}
},
// generator: {
// // 将图片文件输出到 static/imgs 目录中
// // 将图片文件命名 [hash:8][ext][query]
// // [hash:8]: hash值取8位
// // [ext]: 使用之前的文件扩展名
// // [query]: 添加之前的query参数
// filename: "static/imgs/[hash:8][ext][query]",
// },
},
// 字体图标资源
{
test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
type: "asset/resource", // 将文件转化成 Webpack 能识别的资源,其他不做处理
// generator: {
// filename: "static/media/[hash:8][ext][query]",
// },
},
// babel-loader 处理 js 文件
{
test: /\.js$/,
// exclude: /node_modules/, // 排除node_modules代码不编译
include: path.resolve(__dirname, '../src'), // 只处理src下的文件,其他文件不做处理
use: [
{
loader: "thread-loader", // 开启多进程
options: {
workers: threads, // 数量
},
},
{
loader: "babel-loader",
options: {
cacheDirectory: true, // 开启babel缓存 提升第二次编译
cacheCompression: false, // 关闭缓存文件压缩
plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
}
}
],
},
],
}
],
},
// plugin 插件的配置
plugins: [
// Eslint 配置
new ESLintWebpackPlugin({
// 指定检查文件的根目录
context: path.resolve(__dirname, "../src"),
// 排除 node_modules
exclude: "node_modules",
threads, // 开启多进程和设置进程数量
}),
// 处理 Html 资源的配置
new HtmlWebpackPlugin({
// 以 public/index.html 为模板创建文件
// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
template: path.resolve(__dirname, "../public/index.html"),
}),
],
// 开发服务器 & 自动化
// 并且当你使用开发服务器时,所有代码都会在内存中编译打包,并不会输出到 dist 目录下
devServer: {
host: "localhost", // 启动服务器域名
port: "3000", // 启动服务器端口号
open: true, // 是否自动打开浏览器
hot: true, // 只对有改动的模块代码重新打包编译,其他模块不变,这样提升打包速度 style-loader 默认已实现
},
// 模式
mode: "development", // 开发模式
devtool: "cheap-module-source-map", // 用来生成源代码与构建后代码一一映射的文件的方案
};

生产模式配置

生产模式是开发完成代码后,我们需要得到代码将来部署上线。这个模式下我们主要对代码进行优化,让其运行性能更好。
优化主要从两个角度出发:

  • 优化代码运行性能
  • 优化代码打包速度

webpack配置文件config/webpack.prod.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 应该是单独的 Css 文件,而不是通过创建style标签来生成样式 通过 link 标签加载性能才好
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 压缩 css
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const os = require("os");
const TerserPlugin = require("terser-webpack-plugin"); // 压缩插件
// cpu核数
const threads = os.cpus().length;
// 图片压缩
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
// 预加载
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
// PWA 离线也能访问应用
const WorkboxPlugin = require("workbox-webpack-plugin");

// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
return [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env", // 能解决大多数样式兼容性问题
],
},
},
},
preProcessor,
].filter(Boolean);
};

module.exports = {
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
// filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
// [contenthash:8]使用contenthash,取8位长度
filename: "static/js/[name].[contenthash:8].js", // 入口文件打包输出资源命名方式
chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 动态导入输出资源命名方式
assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)
clean: true,
},
module: {
rules: [
{
oneOf: [
{
// 用来匹配 .css 结尾的文件
test: /\.css$/,
// use 数组里面 Loader 执行顺序是从右到左
// use 数组里面 Loader 执行顺序是从右到左
use: getStyleLoaders(),
},
{
test: /\.less$/,
// use: ["style-loader", "css-loader", "less-loader"],
use: getStyleLoaders("less-loader"),
},
{
test: /\.s[ac]ss$/,
// use: ["style-loader", "css-loader", "sass-loader"],
use: getStyleLoaders("sass-loader"),
},
{
test: /\.styl$/,
// use: ["style-loader", "css-loader", "stylus-loader"],
use: getStyleLoaders("stylus-loader"),
},
{
test: /\.(png|jpe?g|gif|webp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
},
},
// generator: {
// // 将图片文件输出到 static/imgs 目录中
// // 将图片文件命名 [hash:8][ext][query]
// // [hash:8]: hash值取8位
// // [ext]: 使用之前的文件扩展名
// // [query]: 添加之前的query参数
// filename: "static/imgs/[hash:8][ext][query]",
// },
},
{
test: /\.(ttf|woff2?)$/,
type: "asset/resource",
// generator: {
// filename: "static/media/[hash:8][ext][query]",
// },
},
{
test: /\.js$/,
exclude: /node_modules/, // 排除node_modules代码不编译
use: [
{
loader: "thread-loader", // 开启多进程
options: {
workers: threads, // 数量
},
},
{
loader: "babel-loader",
options: {
cacheDirectory: true, // 开启babel缓存 提升第二次编译
cacheCompression: false, // 关闭缓存文件压缩
plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
}
}
],
},
]
}
],
},
plugins: [
new ESLintWebpackPlugin({
// 指定检查文件的根目录
context: path.resolve(__dirname, "../src"),
cache: true, // 开启eslint缓存 提升第二次编译速度
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/eslintcache"
),
threads, // 开启多进程和设置进程数量
}),
new HtmlWebpackPlugin({
// 以 public/index.html 为模板创建文件
// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
template: path.resolve(__dirname, "../public/index.html"),
}),
// 提取css成单独文件
new MiniCssExtractPlugin({
// 定义输出文件名和目录
// filename: "static/css/main.css",
filename: "static/css/[name].[contenthash:8].css",
chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
}),
// css压缩
// new CssMinimizerPlugin(), //合并到了optimization
// new TerserPlugin(),
new PreloadWebpackPlugin({
rel: "preload", // preload兼容性更好
as: "script",
// rel: 'prefetch' // prefetch兼容性更差 懒加载 加载下一个页面需要使用的资源
}),
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true,
}),
],
optimization: {
minimize: true,
minimizer: [
// css压缩也可以写到optimization.minimizer里面,效果一样的
new CssMinimizerPlugin(),
// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
new TerserPlugin({
parallel: threads // 开启多进程压缩和设置进程数量
}),
// 压缩图片 无损压缩
// 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",
// },
// },
// ],
// },
// ],
// ],
// },
// },
// }),
],
// 代码分割配置
splitChunks: {
chunks: "all", // 对所有模块都进行分割
// 以下是默认值
// minSize: 20000, // 分割代码最小的大小
// minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
// minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
// maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
// maxInitialRequests: 30, // 入口js文件最大并行请求数量
// enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
// cacheGroups: { // 组,哪些模块要打包到一个组
// defaultVendors: { // 组名
// test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
// priority: -10, // 权重(越大越高)
// reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
// },
// default: { // 其他没有写的配置会使用上面的默认值
// minChunks: 2, // 这里的minChunks权重更大
// priority: -20,
// reuseExistingChunk: true,
// },
// },
},
// 提取runtime文件
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名规则
},
},
// devServer: {
// host: "localhost", // 启动服务器域名
// port: "3000", // 启动服务器端口号
// open: true, // 是否自动打开浏览器
// },
mode: "production",
devtool: "source-map", // 对构建后的代码和源代码进行行列一一映射
};

其他项目文件

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{
"name": "project",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"start": "npm run dev",
"dev": "npx webpack serve --config ./config/webpack.dev.js",
"build": "npx webpack --config ./config/webpack.prod.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.19.1",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "^7.19.1",
"@babel/preset-env": "^7.19.1",
"@vue/preload-webpack-plugin": "^2.0.0",
"babel-loader": "^8.2.5",
"core-js": "^3.25.2",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.1.0",
"eslint": "^8.23.1",
"eslint-plugin-import": "^2.26.0",
"eslint-webpack-plugin": "^3.2.0",
"html-webpack-plugin": "^5.5.0",
"image-minimizer-webpack-plugin": "^3.6.1",
"imagemin": "^8.0.1",
"imagemin-gifsicle": "^7.0.0",
"imagemin-jpegtran": "^7.0.0",
"imagemin-optipng": "^8.0.0",
"imagemin-svgo": "^10.0.1",
"less": "^4.1.3",
"less-loader": "^11.0.0",
"mini-css-extract-plugin": "^2.6.1",
"postcss": "^8.4.16",
"postcss-loader": "^7.0.1",
"postcss-preset-env": "^7.8.2",
"sass": "^1.54.9",
"sass-loader": "^13.0.2",
"style-loader": "^3.3.1",
"stylus": "^0.59.0",
"stylus-loader": "^7.0.0",
"thread-loader": "^3.0.4",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1",
"workbox-webpack-plugin": "^6.5.4"
},
"browserslist": [
"last 2 version",
"> 1%",
"not dead"
],
"dependencies": {
"core-js": "^3.25.2"
}
}

.eslintignore

1
2
3
4
# Eslint 插件对项目所有文件默认进行 Eslint 检查了

# 忽略dist目录下所有文件
dist

eslintrc.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// Eslit
// 针对代码格式,我们使用 Eslint 来完成
// 可组装的 JavaScript 和 JSX 检查工具。
// 这句话意思就是:它是用来检测 js 和 jsx 语法的工具,可以配置各项功能
// 我们使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查

// module.exports = {
// // 解析选项
// parserOptions: {
// ecmaVersion: 6, // ES 语法版本
// sourceType: "module", // ES 模块化
// ecmaFeatures: { // ES 其他特性
// jsx: true // 如果是 React 项目,就需要开启 jsx 语法
// }
// },
// // 具体检查规则
// rules: {
// semi: "error", // 禁止使用分号
// 'array-callback-return': 'warn', // 强制数组方法的回调函数中有 return 语句,否则警告
// 'default-case': [
// 'warn', // 要求 switch 语句中有 default 分支,否则警告
// { commentPattern: '^no default$' } // 允许在最后注释 no default, 就不会有警告了
// ],
// eqeqeq: [
// 'warn', // 强制使用 === 和 !==,否则警告
// 'smart' // https://eslint.bootcss.com/docs/rules/eqeqeq#smart 除了少数情况下不会有警告
// ],
// },
// // 继承其他规则 例如在React项目中,我们可以这样写配置
// extends: ["react-app"],
// // ...
// // 其他规则详见:https://eslint.bootcss.com/docs/user-guide/configuring
// };

module.exports = {
// 继承 Eslint 规则
extends: ["eslint:recommended"],
parser: "@babel/eslint-parser", // 支持最新的最终 ECMAScript 标准
env: {
node: true, // 启用node中全局变量
browser: true, // 启用浏览器中全局变量
// es6: true
},
plugins: ["import"], // 解决动态导入import语法报错问题 --> 实际使用eslint-plugin-import的规则解决的
parserOptions: {
ecmaVersion: 6,
sourceType: "module",
},
rules: {
"no-var": 2, // 不能使用 var 定义变量
},
};

babel.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Babel
// JavaScript 编译器
// 主要用于将 ES6 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中
// 一组 Babel 插件, 扩展 Babel 功能
// @babel/preset-env: 一个智能预设,允许您使用最新的 JavaScript。
// @babel/preset-react:一个用来编译 React jsx 语法的预设
// @babel/preset-typescript:一个用来编译 TypeScript 语法的预设

module.exports = {
// 智能预设 能够编译ES6语法
presets: [
[
"@babel/preset-env",
// 按需加载core-js的polyfill
{
useBuiltIns: "usage", // 按需加载自动引入
corejs: {
version: "3",
proposals: true
}
},
]
],
};

src/main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// import "core-js/es/promise";
import count from "./js/count";
// import sum from "./js/sum";

// 引入 字体图标资源
import "./css/iconfont.css";
// 引入 Css 资源,Webpack才会对其打包
import "./css/index.css";
// 引入 less
import "./less/index.less";
// 引入 sass scss
import "./sass/index.sass";
import "./sass/index.scss";
// 引入 stylus
import "./stylus/index.styl";


// eslintrc.js 配置文件禁止使用 var 会报错
// var result1 = count(2, 1);
// console.log(result1);
const result1 = count(2, 1);
console.log(result1);
// const result2 = sum(1, 2, 3, 4);
// console.log(result2);

// console.log(sum(1,2,3,4) + sum(3,4,5));


console.log(count(2, 1));
// console.log(sum(1, 2, 3, 4));


let btn = document.getElementById('btn');
btn.addEventListener('click', function () {
import(/* webpackChunkName: "sum" */ './js/sum.js').then((res)=>{
console.log(res.default(1,2));
})

})

// 开发模式 模块热替换
if (module.hot) {
// 开启 js 文件热模块替换
module.hot.accept("./js/count");
module.hot.accept("./js/sum");
}

// 添加promise代码
const promise = Promise.resolve();
promise.then(() => {
console.log("hello promise");
});


// 注册 serviceWorker
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker
.register("/service-worker.js")
.then((registration) => {
console.log("SW registered: ", registration);
})
.catch((registrationError) => {
console.log("SW registration failed: ", registrationError);
});
});
}

Vue-Cli

webpack.config.js

生产模式相比开发模式:

  • 处理了文件输出路径
  • 处理了样式,提取成单独文件,压缩文件
  • 处理js,取消 HMR,压缩文件,取消devServer
  • 处理图片,压缩图片
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    const path = require('path')
    const EslintWebpackPlugin = require('eslint-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    // 提取 css 成单独文件
    const MiniCssExtractPlugin= require('mini-css-extract-plugin')
    // 配置压缩 css 文件的插件
    const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin')
    // js 压缩
    const TerserWebpackPlugin = require('terser-webpack-plugin')
    // 压缩图片
    // 无损 npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D
    const ImageMinimizerWebpackPlugin = require('image-minimizer-webpack-plugin')
    // 配置直接复制的插件 将网站图标复制到dist目录下
    const CopyPlugin = require("copy-webpack-plugin");
    const { VueLoaderPlugin } = require('vue-loader')
    const { DefinePlugin } = require('webpack')

    // 按需引入element-plus 配置
    const AutoImport = require('unplugin-auto-import/webpack')
    const Components = require('unplugin-vue-components/webpack')
    const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')

    // 判断开发模式是否是生产模式
    const isProduction = process.env.NODE_ENV === "production";

    // 处理样式 loader 函数
    const getStyleLoaders = (preLoader) => {
    return [
    // "vue-style-loader", // 取消打包样式在 js 文件中
    // MiniCssExtractPlugin.loader, // 使用单独打包 css 文件
    isProduction ? MiniCssExtractPlugin.loader : "vue-style-loader",
    "css-loader",
    {
    // 处理css兼容性
    // 配合package.json browserslist
    loader: "postcss-loader",
    options: {
    postcssOptions: {
    ident: 'postcss',
    plugins: [
    require('postcss-preset-env')()
    ]
    }
    }
    },
    preLoader && {
    loader: preLoader,
    options: preLoader === "sass-loader" ? {
    additionalData: `@use "@/styles/element/index.scss" as *;`,
    } : {}
    }
    ].filter(Boolean); // 过滤undefine的preLoader
    }

    // process.env.NODE_ENV = "development";

    module.exports = {
    entry: "./src/main.js",
    output: {
    path: isProduction ? path.resolve(__dirname, "../dist") : undefined, // 生产模式需要输出
    filename: isProduction ? "static/js/[name].[contenthash:10].js" : "static/js/[name].js",
    chunkFilename: isProduction ? "static/js/[name].[contenthash:10].chunk.js" : "static/js/[name].chunk.js",
    assetModuleFilename: "static/medie/[hash:10][ext][query]",
    clean: true,
    },
    module: {
    rules: [
    // 处理css
    {
    test: /\.css$/,
    use: getStyleLoaders()
    },
    // 处理less
    {
    test: /\.less$/,
    use: getStyleLoaders("less-loader")
    },
    // 处理sass
    {
    test: /\.s[ac]ss$/,
    use: getStyleLoaders("sass-loader")
    },
    // 处理stylus
    {
    test: /\.styl$/,
    use: getStyleLoaders("stylus-loader")
    },
    // 处理图片
    {
    test: /\.(jpe?g|png|gif|webp|svg)$/,
    type: "asset", // 将文件转换成 webpack 能识别的资源
    parser: {
    dataUrlCondition: {
    maxSize: 10 * 1024 // 小于10kb的图片转换成base64格式
    }
    },
    },
    // 字体图标资源
    {
    test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
    type: "asset/resource", // 将文件转化成 Webpack 能识别的资源,其他不做处理
    },
    // 处理js文件
    {
    test: /\.js$/,
    exclude: /node_modules/,
    loader: "babel-loader", // 搭配 babel.config.js
    options: {
    cacheDirectory: true, // 开启缓存 第二次打包快点
    cacheCompression: false, // 缓存取消压缩
    // plugins: [
    // 'react-refresh/babel', // 生产模式取消 react HMR 热模块替换
    // ],
    }
    },
    {
    test: /\.vue$/,
    loader: 'vue-loader',
    options: {
    // 开启缓存
    cacheDirectory: path.resolve(__dirname, "../node_modules/.cache/vue-loader")
    }
    }
    ]
    },
    plugins: [
    // 处理js 格式语法检查 搭配 .eslintrc.js
    new EslintWebpackPlugin({
    context: path.resolve(__dirname, "../src"),
    exclude: "node_modules",
    cache: true,
    cacheLocation: path.resolve(__dirname, "../node_modules/.cache/.eslintcache"),
    }),

    // 处理 html
    new HtmlWebpackPlugin({
    // 以这个模板创建新的html文件 1. 内容和源文件一致 2. 自动引入打包生成的js等资源
    template: path.resolve(__dirname, "../public/index.html"),
    }),

    // 生成单独打包 css 文件的插件实例
    isProduction && new MiniCssExtractPlugin({
    filename: "static/css/[name].[contenthash:10].css",
    chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
    }),

    // 复制public目录下文件到dist
    isProduction && new CopyPlugin({
    patterns: [
    {
    from: path.resolve(__dirname,"../public"),
    to: path.resolve(__dirname,"../dist"),
    // 忽略index.html
    globOptions: {
    ignore: ["**/index.html"],
    }
    },
    ],
    }),
    // Vue loader 插件
    new VueLoaderPlugin(),
    // cross-env 定义的环境变量是给webpack用的
    // DefinePlugin 定义的是给Vue使用的
    new DefinePlugin({
    __VUE_OPTIONS_API__: true,
    __VUE_PROD_DEVTOOLS__: false,
    }),

    // 按需引入element-plus 配置
    AutoImport({
    resolvers: [ElementPlusResolver()],
    }),
    Components({
    resolvers: [ElementPlusResolver({
    // 自定义主题
    importStyle: "sass",
    })],
    }),
    ].filter(Boolean),

    // 优化打包策略配置
    optimization: {
    minimize: isProduction, // 判断是否需要压缩 js 代码
    // 将文件不要打包到同一个文件,分割打包
    splitChunks: {
    chunks: 'all',
    cacheGroups: {
    // layouts通常是admin项目的主体布局组件,所有路由组件都要使用的
    // 可以单独打包,从而复用
    // 如果项目中没有,请删除
    // layouts: {
    // name: "layouts",
    // test: path.resolve(__dirname, "../src/layouts"),
    // priority: 40,
    // },
    // 如果项目中使用element-plus,此时将所有node_modules打包在一起,那么打包输出文件会比较大。
    // 所以我们将node_modules中比较大的模块单独打包,从而并行加载速度更好
    // 如果项目中没有,请删除
    elementUI: {
    name: "chunk-elementPlus",
    test: /[\\/]node_modules[\\/]_?element-plus(.*)/,
    priority: 30,
    },
    // 将vue相关的库单独打包,减少node_modules的chunk体积。
    vue: {
    name: "vue",
    test: /[\\/]node_modules[\\/]vue(.*)[\\/]/,
    chunks: "initial",
    priority: 20,
    },
    libs: {
    name: "chunk-libs",
    test: /[\\/]node_modules[\\/]/,
    priority: 10, // 权重最低,优先考虑前面内容
    chunks: "initial",
    },
    },
    },
    // 避免因代码分割导致缓存失效
    runtimeChunk: {
    name: entrypoint => `runtime~${entrypoint.name}.js`
    },
    minimizer: [
    // css 压缩 与 js 压缩 一起配置
    new CssMinimizerWebpackPlugin(),
    new TerserWebpackPlugin(),
    // 压缩图片
    new ImageMinimizerWebpackPlugin({
    minimizer: {
    implementation: ImageMinimizerWebpackPlugin.imageminGenerate,
    options: {
    plugins: [
    ["gifsicle", { interlaced: true }],
    ["jpegtran", { progressive: true }],
    ["optipng", { optimizationLevel: 5 }],
    [
    "svgo",
    {
    plugins: [
    "preset-default",
    "prefixIds",
    {
    name: "sortAttrs",
    params: {
    xmlnsOrder: "alphabetical",
    },
    },
    ],
    },
    ],
    ],
    },
    },
    }),
    ]
    },

    // webapck解析模块加载选项
    resolve: {
    // 配置自动补全文件扩展名,使之可以加载jsx文件
    extensions: [".vue", ".js", ".json"],
    // 路径别名
    alias: {
    '@': path.resolve(__dirname, "../src"),
    },
    },

    mode: isProduction ? "production" : "development",
    devtool: isProduction ? "source-map" : "cheap-module-source-map", // 资源映射

    // 生产模式不需要 devServer
    devServer: {
    host: "localhost",
    port: 3000,
    open: true, // 自动开启浏览器
    hot: true, // 开启热模块替换
    historyApiFallback: true, // 解决前端路由刷新返回404
    },

    // 关闭性能分析
    performance: false
    }

其他配置文件

  • .eslintrc.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    module.exports = {
    root: true,
    env: {
    node: true,
    },
    extends: ["plugin:vue/vue3-essential", "eslint:recommended"],
    parserOptions: {
    parser: "@babel/eslint-parser",
    }
    }
  • babel.config.js

    1
    2
    3
    module.exports = {
    presets: ["@vue/cli-plugin-babel/preset"],
    }
  • package.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    {
    "name": "vue-cli",
    "version": "1.0.0",
    "description": "",
    "main": "main.js",
    "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"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
    "@babel/eslint-parser": "^7.19.1",
    "@vue/cli-plugin-babel": "^5.0.8",
    "babel-loader": "^8.2.5",
    "copy-webpack-plugin": "^11.0.0",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.1",
    "css-minimizer-webpack-plugin": "^4.2.2",
    "eslint-plugin-vue": "^9.6.0",
    "eslint-webpack-plugin": "^3.2.0",
    "html-webpack-plugin": "^5.5.0",
    "image-minimizer-webpack-plugin": "^3.6.1",
    "imagemin-gifsicle": "^7.0.0",
    "imagemin-jpegtran": "^7.0.0",
    "imagemin-optipng": "^8.0.0",
    "imagemin-svgo": "^10.0.1",
    "less-loader": "^11.1.0",
    "mini-css-extract-plugin": "^2.6.1",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.8.2",
    "sass-loader": "^13.1.0",
    "stylus-loader": "^7.1.0",
    "terser-webpack-plugin": "^5.3.6",
    "unplugin-auto-import": "^0.11.2",
    "unplugin-vue-components": "^0.22.8",
    "vue-loader": "^17.0.0",
    "vue-style-loader": "^4.1.3",
    "vue-template-compiler": "^2.7.13",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.11.1"
    },
    "dependencies": {
    "element-plus": "^2.2.18",
    "path-browserify": "^1.0.1",
    "sass": "^1.55.0",
    "vue": "^3.2.41",
    "vue-router": "^4.1.5"
    },
    "browserslist": [
    "last 2 version",
    "> 1%",
    "not dead"
    ]
    }

React-Cli

生产模式相比开发模式:

  • 处理了文件输出路径
  • 处理了样式,提取成单独文件,压缩文件
  • 处理js,取消 HMR,压缩文件,取消devServer
  • 处理图片,压缩图片

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
const path = require('path')
const EslintWebpackPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// react 热模块替换插件配置 生产模式不需要
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
// 提取 css 成单独文件
const MiniCssExtractPlugin= require('mini-css-extract-plugin')
// 配置压缩 css 文件的插件
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin')
// js 压缩
const TerserWebpackPlugin = require('terser-webpack-plugin')
// 压缩图片
// 无损 npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D
// 有损 npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D
// const ImageMinimizerWebpackPlugin = require('image-minimizer-webpack-plugin')
// 配置直接复制的插件 将网站图标复制到dist目录下
const CopyPlugin = require("copy-webpack-plugin");


// 获取 cross-env定义的环境变量
const isProduction = process.env.NODE_ENV === "production";

// 处理样式 loader 函数
const getStyleLoaders = (preLoader) => {
return [
// "style-loader", // 取消打包样式在 js 文件中
// MiniCssExtractPlugin.loader, // 使用单独打包 css 文件
isProduction ? MiniCssExtractPlugin.loader : "style-loader",
"css-loader",
{
// 处理css兼容性
// 配合package.json browserslist
loader: "postcss-loader",
options: {
postcssOptions: {
ident: 'postcss',
plugins: [
require('postcss-preset-env')()
]
}
}
},
preLoader && {
loader: preLoader,
options:
preLoader === "less-loader" ? {
lessOptions: {
// 其他主题色:https://ant.design/docs/react/customize-theme-cn
modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true,
}
} : {}
}
].filter(Boolean); // 过滤undefine的preLoader
}


module.exports = {
entry: "./src/main.js",
output: {
path: isProduction ? path.resolve(__dirname, "../dist") : undefined, // 生产模式需要输出
filename: isProduction
? "static/js/[name].[contenthash:10].js"
: "static/js/[name].js",
chunkFilename: isProduction
? "static/js/[name].[contenthash:10].chunk.js"
: "static/js/[name].chunk.js",
assetModuleFilename: "static/medie/[hash:10][ext][query]",
clean: true,
},

// 加载器
module: {
rules: [
// 处理css
{
test: /\.css$/,
use: getStyleLoaders()
},
// 处理less
{
test: /\.less$/,
use: getStyleLoaders("less-loader")
},
// 处理sass
{
test: /\.s[ac]ss$/,
use: getStyleLoaders("sass-loader")
},
// 处理stylus
{
test: /\.styl$/,
use: getStyleLoaders("stylus-loader")
},
// 处理图片
{
test: /\.(jpe?g|png|gif|webp|svg)$/,
type: "asset", // 将文件转换成 webpack 能识别的资源
parser: {
dataUrlCondition: {
maxSize: 10 * 1024 // 小于10kb的图片转换成base64格式
}
},
},
// 字体图标资源
{
test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
type: "asset/resource", // 将文件转化成 Webpack 能识别的资源,其他不做处理
},
// 处理js文件
{
test: /\.(jsx|js)$/,
exclude: /node_modules/,
loader: "babel-loader", // 搭配 babel.config.js
options: {
cacheDirectory: true, // 开启缓存 第二次打包快点
cacheCompression: false, // 缓存文件取消压缩
plugins: [
!isProduction && 'react-refresh/babel', // 生产模式取消 react HMR 热模块替换
].filter(Boolean),
}
},
]
},

// 插件
plugins: [
// 处理js 格式语法检查 搭配 .eslintrc.js
new EslintWebpackPlugin({
extensions: ['.jsx','.js'],
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
cache: true,
cacheLocation: path.resolve(__dirname, "../node_modules/.cache/.eslintcache"),
}),

// 处理 html
new HtmlWebpackPlugin({
// 以这个模板创建新的html文件 1. 内容和源文件一致 2. 自动引入打包生成的js等资源
template: path.resolve(__dirname, "../public/index.html"),
}),

// 生产模式下生成单独打包 css 文件的插件实例
isProduction &&
new MiniCssExtractPlugin({
filename: "static/css/[name].[contenthash:10].css",
chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
}),

// 生产模式取消激活js HMR 的插件实例生成
!isProduction && new ReactRefreshWebpackPlugin(),

// 复制public目录下文件到dist
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname,"../public"),
to: path.resolve(__dirname,"../dist"),
// 忽略index.html
globOptions: {
ignore: ["**/index.html"],
}
},
],
}),
].filter(Boolean),

// 优化打包策略配置
optimization: {
minimize: isProduction,
// 将文件不要打包到同一个文件,分割打包
splitChunks: {
chunks: 'all',
// 配置各种库单独打包
cacheGroups: {
// layouts通常是admin项目的主体布局组件,所有路由组件都要使用的
// 可以单独打包,从而复用
// 如果项目中没有,请删除
// layouts: {
// name: "layouts",
// test: path.resolve(__dirname, "../src/layouts"),
// priority: 40,
// },
// 如果项目中使用antd,此时将所有node_modules打包在一起,那么打包输出文件会比较大。
// 所以我们将node_modules中比较大的模块单独打包,从而并行加载速度更好
// 如果项目中没有,请删除
antd: {
name: "chunk-antd",
test: /[\\/]node_modules[\\/]antd(.*)/,
priority: 30,
},
// 将react相关的库单独打包,减少node_modules的chunk体积。
react: {
name: "chunk-react",
test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
chunks: "initial",
priority: 20,
},
libs: {
name: "chunk-libs",
test: /[\\/]node_modules[\\/]/,
priority: 10, // 权重最低,优先考虑前面内容
chunks: "initial",
},
}
},
// 避免因代码分割导致缓存失效
runtimeChunk: {
name: entrypoint => `runtime~${entrypoint.name}.js`
},
// 压缩操作
minimizer: [
// css 压缩 与 js 压缩 一起配置
new CssMinimizerWebpackPlugin(),
new TerserWebpackPlugin(),
// 压缩图片
// new ImageMinimizerWebpackPlugin({
// 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",
// },
// },
// ],
// },
// ],
// ],
// },
// },
// }),
]
},

// webapck解析模块加载选项
resolve: {
// 配置自动补全文件扩展名,使之可以加载jsx文件
extensions: [".jsx", ".js", ".json"],
},

mode: isProduction ? "production" : "development",
devtool: isProduction ? "source-map" : "cheap-module-source-map",

// 生产模式不需要 devServer
devServer: {
open: true,
host: "localhost",
port: 3000,
hot: true,
compress: true,
historyApiFallback: true,
},

// 关闭性能分析 提升打包速度
performance: false
}

其他配置文件

  • .eslintrc.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    module.exports = {
    extends: ["react-app"], // 继承 react 官方规则 eslint-config-react-app
    parserOptions: {
    babelOptions: {
    presets: [
    // 解决页面报错问题
    ["babel-preset-react-app", false],
    "babel-preset-react-app/prod",
    ],
    },
    },
    };
  • babel.config.js

    1
    2
    3
    4
    5
    6
    module.exports = {
    // 使用react官方规则,自动编译 React 语法
    // babel-preset-react-app 但是这个预设必须指定一个环境变量
    // 可以在启动打包命令前指定 cross-env NODE_ENV=development
    presets: ["react-app"],
    };
  • package.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    {
    "name": "react-cli",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "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"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "browserslist": [
    "last 2 version",
    "> 1%",
    "not dead"
    ],
    "devDependencies": {
    "@babel/core": "^7.19.3",
    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.8",
    "babel-loader": "^8.2.5",
    "babel-preset-react-app": "^10.0.1",
    "copy-webpack-plugin": "^11.0.0",
    "cross-env": "^7.0.3",
    "css-loader": "^6.7.1",
    "css-minimizer-webpack-plugin": "^4.2.2",
    "eslint-config-react-app": "^7.0.1",
    "eslint-webpack-plugin": "^3.2.0",
    "html-webpack-plugin": "^5.5.0",
    "image-minimizer-webpack-plugin": "^3.6.1",
    "imagemin": "^8.0.1",
    "imagemin-gifsicle": "^7.0.0",
    "imagemin-jpegtran": "^7.0.0",
    "imagemin-optipng": "^8.0.0",
    "imagemin-svgo": "^10.0.1",
    "less-loader": "^11.1.0",
    "mini-css-extract-plugin": "^2.6.1",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.8.2",
    "react-refresh": "^0.14.0",
    "sass": "^1.55.0",
    "sass-loader": "^13.1.0",
    "style-loader": "^3.3.1",
    "stylus-loader": "^7.1.0",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.11.1"
    },
    "dependencies": {
    "antd": "^4.23.6",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.4.2"
    }
    }