Webpack的基本使用
# 安装
npm install webpack webpack-cli --save-dev
1
# 配置
webpack.config.js
const path = require('path')
// npm i --save-dev html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')
// cnpm i mini-css-extract-plugin -D # 基于webpack5的,提取css到单独的文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// cnpm i css-minimizer-webpack-plugin -D # 压缩css
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// cnpm i -D terser-webpack-plugin # 压缩代码
const TerserPlugin = require('terser-webpack-plugin')
const toml = require('toml') // .toml 文件
const yaml = require('yaml') // .yaml 文件
const json5 = require('json5') // .json5 文件
module.exports = (env) => {
return {
// 单入口文件
// entry: './src/index.js',
// 多入口文件
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
// 多入口文件防文件重复
// entry: {
// index: {
// import: './src/index.js',
// // 依赖,可以把共享的文件定义出来
// dependOn: 'shared' // shared是后面定义的
// },
// another: {
// import: './src/another-module.js',
// dependOn: 'shared'
// },
// // 将lodash共享出来并取名为shared
// shared: 'lodash'
// },
// 出口
output: {
// 文件名
// filename: 'bundle.js',
// [name]:可以拿到entry定义的名字(index,another)
// [contenthash]: 根据文件内容生成hash字符串
filename: 'js/[name].[contenthash].js',
// 文件路径,必须是绝对路径
path: path.resolve(__dirname, './dist'),
// 清理上次输出(目录)
clean: true,
// 资源文件模块文件名
// [contenthash]:根据内容生成hash字符串
// [ext]:扩展名
assetModuleFilename: 'images/[contenthash][ext]',
// 设置公共路径,可以指定cdn域名等
publicPath: 'http://localhost:8080/'
},
// 模式
mode: env.production ? 'production' : 'development',
// 在开发模式下追踪代码
devtool: 'inline-source-map',
// 实现浏览器自动刷新
// 安装:npm i --save-dev webpack-dev-server
// 运行:npx webpack-dev-server
devServer: {
// 指定server的根目录
static: './dist'
},
// 插件
plugins: [
// 用来自动生成html文件
new HtmlWebpackPlugin({
// 模板
template: './index.html',
// 输出的文件名
filename: 'app.html',
// 生成标签的位置
inject: 'body'
}),
// 用来打包css到单独的文件
new MiniCssExtractPlugin({
// 自定义文件路径及名称
filename: 'styles/[contenthash].css'
})
],
// 资源模块
module: {
rules: [
{
// 定义加载的文件类型
test: /\.png$/,
// asset/resource:用于导出资源文件url
// import资源文件得到的是一个url文件地址
type: 'asset/resource',
// 优先级大于assetModuleFilename
generator: {
filename: 'images/[contenthash][ext]',
}
},
{
test: /\.jpg$/,
// asset/inline:用于导出资源文件的Data Url
type: 'asset/inline'
},
{
test: /\.txt$/,
// asset/source:用于导出资源文件的源代码
type: 'asset/source'
},
{
test: /\.gif$/,
// asset:在 resource 和 inline 中自动选择,默认小于8KB的使用inline
type: 'asset',
// 解析器
parser: {
// 设置使用inline的条件
dataUrlCondition: {
// 最大4KB
maxSize: 4 * 1024 // 单位B
}
}
},
// 加载fonts字体
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'
},
// csv、tsv文件
{
test: /\.(csv|tsv)$/i,
// cnpm i csv-loader -D
use: 'csv-loader'
},
// xml文件
{
test: /\.xml$/i,
// cnpm i xml-loader -D
use: 'xml-loader'
},
// 通过使用 自定义parse 替代指定的loader,可以将toml、yaml或json5文件作为json模块导入
// yaml
{
test: /\.yaml$/i,
// cnpm i yaml -D
type: 'json',
parser: {
parse: yaml.parse
}
},
// toml
{
test: /\.toml$/i,
// cnpm i toml -D
type: 'json',
parser: {
parse: toml.parse
}
},
// json5
{
test: /\.json5$/i,
// cnpm i json5 -D
type: 'json',
parser: {
parse: json5.parse
}
},
{
test: /\.(css|less)$/,
// 使用加载器
use: [
// style-loader会把css放到页面上(style标签里):cnpm i style-loader --save-dev
/*'style-loader',*/
MiniCssExtractPlugin.loader,
// css加载器: cnpm i css-loader --save-dev
'css-loader',
// less加载器: cnpm i less-loader less -D
'less-loader'
] // 加载器的使用顺序是从右到左的
},
// cnpm i -D babel-loader @babel/core @babel/preset-env
// babel-loader: 在webpack里应用babel解析es6
// @babel/core: babel核心模块
// @babel/preset-env: babel预设,一组babel插件的集合
// regeneratorRuntime is not defined,安装下面两个,并卑职plugin-transform-runtime
// @babel/runtime: 运行时
// @babel/plugin-transform-runtime:会在序言regeneratorRuntime的地方自动require导包
{
test: /\.js$/,
// 排除
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
[
'@babel/plugin-transform-runtime'
]
]
}
}
}
]
},
// 优化选项
optimization: {
minimizer: [
// 压缩css,这个生效必须是mode=production
new CssMinimizerPlugin(),
// 压缩代码
new TerserPlugin()
],
// 将代码分割,并将公共的代码放到单独的文件里,可以不用设置多入口防重复
splitChunks: {
// 对所有chunk进行处理
// chunks: 'all'
// 缓存组
cacheGroups:{
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
// 对哪些chunk进行处理
chunks: 'all'
}
}
}
},
performance: {
// 关闭性能提示
hints: false
}
}
}
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
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
# cli
# 观察模式
监控文件,不必每次手动构建
npx webpack --watch
1
# 指定配置文件
npx webpack -c ./config/webpack.config.dev.js # -c/--config
1
# 开发服务器
# 安装
npm i --save-dev webpack-dev-server
# 运行
npx webpack-dev-server --open
# --open 直接打开浏览器窗口
1
2
3
4
5
2
3
4
5
安装后要在webpack.config.js中配置
devServer: {
// 指定server的根目录
static: './dist'
},
1
2
3
4
2
3
4
# 动态导入
使用 import()
语法来实现动态导入,动态导入的代码会被webpack抽离出去,返回的是promise
function getComponent(){
return import('lodash)
.then(({default: _}) => {
return _.join(['a','b'], '-') // 返回的还是promise,直接.then(data)
})
}
1
2
3
4
5
6
2
3
4
5
6
# 懒加载
// src/math.js
export const add = (x, y) => {
return x + y
}
export const minus = (x, y) => {
return x - y
}
// src/index.js
const button = document.createElement('button)
button.addEventListener('click', () => {
// 这个代码块文件只要在按钮点击的时候才会加载
// /* webpackChunkName: 'math' */:这是webpack的魔法注释,给文件起名
import(/* webpackChunkName: 'math' */ './math.js').then(({add}) => {
console.log(add(1, 2))
})
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 预获取/预加载
- prefetch(预获取): 将来某些导航下可能需要的资源
- preload(预加载): 当前导航下可能需要的资源
// src/index.js
const button = document.createElement('button)
button.addEventListener('click', () => {
// /* webpackPrefetch: true */:这是webpack的魔法注释,预获取,在网络空闲的时候加载文件
// /* webpackPreload true */:这是webpack的魔法注释,预加载,和懒加载相似,点击按钮(用到)时才加载
import(/* webpackChunkName: 'math', webpackPrefetch: true */ './math.js').then(({add}) => {
console.log(add(1, 2))
})
})
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 环境变量
运行时传递环境变量
npx webpack --env production --env goal=local
1
使用
// webpack.config.js
module.exports = (env) => {
console.log(env) // {production: true, goal: 'local', ...}
return {
// 配置参数
mode: env.goal
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 合并配置文件
cnpm i -D webpack-merge
1
webpack.config.js
const {merge} = require('webpack-merge')
const commonConfig = require('./webpack.config.common')
const productionConfig = require('./webpack.config.prod')
const developmentConfig = require('./webpack.config.dev')
module.exports = (env) => {
switch(true){
case env.development:
return merge(commonConfig, developmentConfig)
case env.production:
return merge(commonConfig, productionConfig)
default:
return new Error('没有匹配到环境')
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# source-map
devtool: 'inline-source-map',
模式 | 解释 |
---|---|
eval | 默认。每个module会封装到eval里包裹起来执行,并且会在末尾追加注释//#sourceURL。 |
source-map | 生成一个SourceMap文件 |
hidden-source-map | 和source-map一样,但不会在bundle末尾追加注释。 |
inline-source-map | 生成一个DataUrl形式的SourceMap文件。 |
eval-source-map | 每个module会通过eval()来执行,并且生成一个DataUrl形式的SourceMap。 |
cheap-srouce-map | 生成一个没有列信息(column-mappings)的SourceMaps文件,不包含loader的sourcemap(譬如babel的sourcemap) |
cheap-module-source-map | 开发推荐。生成一个没有列信息(column-mappings)的SourceMaps文件,同时loder的sourcemap也被简化为只包含对应行的。 |
注意
生产环境我们一般不会开启sourcemap功能,主要有两点原因:
- 通过bundle和sourcemap文件,可以反编译出源码,也就是说,线上产物有sourcemap文件的话,就意味着有暴露源码的风险。
- 我们可以观察到,sourcemap文件的体积相对比较巨大,这跟我们生产环境的追求不同(生产环境追求更小更轻量的bundle)。
# dev-server
const path = require('path')
module.exports = {
model: 'development',
devServer: {
static: path.resolve(__dirname, './dist'),
// 是否在服务端压缩文件
compress: true,
// 配置端口号
port: 3000,
// 添加响应头
headers: {
'X-Access-Token': '123456'
},
// 设置代理
proxy: {
// 当访问`/api`的时候转到`http://localhost:9000`上去
'/api': 'http://localhost:9000'
},
// 使用https,值为对象可以配置自定义证书
// https: true,
https2: true,
// 如果是spa应用,刷新页面时会提示不存在,开启此配置将转到index.html
historyApiFallback: true
// 也可以科举不同的访问路径定制替代的页面
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html'},
{ from: /^\/subpage/, to: '/views/subpage.html'},
{ from: /./, to: '/views/404.html'},
]
},
// 开启服务主机,局域网内的用户可以通过ip访问
host: '0.0.0.0',
// HRM(Hot Modle Reolace)开启模块热替换功能,会在应用运行过程中,替换、添加或删除模块,而无需重新加载整个页面
hot: true, // 默认true
// 热加载
liveReload: true, // 默认true
}
}
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
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
# eslint
# 安装
npm i eslint -D
1
# 创建配置文件
npx eslint --init
1
# webpack集成
// webpack.config.js
module.exports = {
// ...
devServer: {
client: {
// 是否在客服端显示错误覆盖层,false不显示
overlay: false,
},
},
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader', 'eslint-loader'], // 先使用eslint-loader处理
},
],
},
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 设置路径别名
// webpack.config.js
const path = require('path');
module.exports = {
resolve: {
alias: {
// import . from '@/main.js' 就等于 ./src/main.js
'@': path.resolve(__dirname, './src')
},
// 配置文件扩展名,当请求忽略扩展名的文件时,按照顺序选择加载相应扩展名的文件(.json, .js, .vue)
extensions: ['.json', '.js', '.vue']
}
};
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 外部扩展
为了减小bundle的体积,从而把一些不变的第三方库用cdn的形式引入进来,比如Jquery
<!-- index.html -->
<script src="https://.../jquery.js"></script>
1
2
2
配置
// webpack.config.js
module.exports = {
// 定义外部扩展,键值一般是window上暴露的对象,也就是`window.`能访问到的
externals: {
jquery: 'jQuery', // jQuery是cdn打入到window中的变量名,也可以写成 `jquery: '$'`
// 可以使用数组的形式,第一个参数为库cdn地址,第二个参数为暴露的对象名
// 这种写法可以自动在页面中加入script,需要配置externalsType为script
jquery: [
'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js',
'$'
]
},
externalsType: 'script'
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
使用
// index.js
impor $ from 'jquery'
console.log($)
1
2
3
2
3
上次更新: 2023/09/22, 16:54:32