Webpack 自动化打包构建
Webpack 是一个模块打包器,它可以将许多模块打包成一个或多个静态资源文件。它通常用于构建前端应用程序,但是也可以用于构建后端应用程序。
使用 Webpack 进行自动化构建通常包括以下步骤:
- 安装 Webpack:使用 npm 命令
npm install webpack
安装 Webpack。 - 配置 Webpack:创建一个
webpack.config.js
文件,并在其中配置 Webpack。这包括设置入口文件、出口文件、加载器等。 - 运行 Webpack:使用命令
npx webpack
运行 Webpack。这将执行构建流程,并生成打包后的文件。 - 使用插件和工具:可以使用各种插件和工具来增强 Webpack 的功能,例如压缩文件、创建 source maps 等。
项目目录结构
1 | ├── webpack-test (项目根目录) |
开发模式配置
这个模式下我们主要做两件事:
- 编译代码,使浏览器能识别运行
- 开发时我们有样式资源、字体图标、图片资源、html 资源等,webpack 默认都不能处理这些资源,所以我们要加载配置来编译这些资源
- 代码质量检查,树立代码规范
- 提前检查代码的一些隐患,让代码运行时能更加健壮。
- 提前检查代码规范和格式,统一团队编码风格,让代码更优雅美观。
webpack配置文件config/webpack.dev.js
1 | // Node.js的核心模块,专门用来处理文件路径 |
生产模式配置
生产模式是开发完成代码后,我们需要得到代码将来部署上线。这个模式下我们主要对代码进行优化,让其运行性能更好。
优化主要从两个角度出发:
- 优化代码运行性能
- 优化代码打包速度
webpack配置文件config/webpack.prod.js
1 | const path = require("path"); |
其他项目文件
package.json
1 | { |
.eslintignore
1 | # Eslint 插件对项目所有文件默认进行 Eslint 检查了 |
eslintrc.js
1 | // Eslit |
babel.config.js
1 | // Babel |
src/main.js
1 | // import "core-js/es/promise"; |
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
282const 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
10module.exports = {
root: true,
env: {
node: true,
},
extends: ["plugin:vue/vue3-essential", "eslint:recommended"],
parserOptions: {
parser: "@babel/eslint-parser",
}
}babel.config.js
1
2
3module.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 | const path = require('path') |
其他配置文件
.eslintrc.js
1
2
3
4
5
6
7
8
9
10
11
12module.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
6module.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"
}
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 CrazyKong!
评论