Webpack
webpack
一、Webpack 简介
1.1 webpack 是什么
webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。
在webpack 看来, 前端的所有资源文件(js/json/css/img/less/…)都会作为模块处理。
它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。
1.2 webpack 五个核心概念
1.2.1 Entry
入口(Entry):指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。
1.2.2 Output
输出(Output):指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名。
1.2.3 Loader
Loader:让 webpack 能够去处理那些非 JS 的文件,比如样式文件、图片文件(webpack 自身只理解
JS)
1.2.4 Plugins
插件(Plugins):可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,
一直到重新定义环境中的变量等。
1.2.5 Mode
模式(Mode):指示 webpack 使用相应模式的配置。
| 选项 | 描述 | 特点 |
|---|---|---|
| development | 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。 | 能让代码本地调试运行的环境 |
| production | 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 TerserPlugin。 | 能让代码优化上线运行的环境 |
二、Webpack 初体验
2.1 初始化配置
初始化 package.json:npm init
下载安装webpack:(webpack4以上的版本需要全局/本地都安装webpack-cli)
全局安装:cnpm i webpack webpack-cli -g
本地安装:cnpm i webpack webpack-cli -D
当前版本:
- webpack: 5.62.1
- webpack-cli: 4.9.1
2.2 编译打包应用
创建 src 下的 js 等文件后,不需要配置 webpack.config.js 文件,在命令行就可以编译打包。
指令:
开发环境:webpack ./src/index.js -o ./build/built.js –mode=development
webpack会以 ./src/index.js 为入口文件开始打包,打包后输出到 ./build/built.js 整体打包环境,是开发环境
生产环境:webpack ./src/index.js -o ./build/built.js –mode=production
webpack会以 ./src/index.js 为入口文件开始打包,打包后输出到 ./build/built.js 整体打包环境,是生产环境
结论:
- webpack 本身能处理 js/json 资源,不能处理 css/img 等其他资源
- 生产环境和开发环境将 ES6 模块化编译成浏览器能识别的模块化,但是不能处理 ES6 的基本语法转化为 ES5(需要借助 loader)
- 生产环境比开发环境多一个压缩 js 代码
三、Webpack 开发环境的基本配置
webpack.config.js 是 webpack 的配置文件。
作用: 指示 webpack 干哪些活(当你运行 webpack 指令时,会加载里面的配置)
所有构建工具都是基于 nodejs 平台运行的,模块化默认采用 commonjs。
开发环境配置主要是为了能让代码运行。主要考虑以下几个方面:
在 webpack 5 之前,通常使用:
raw-loader将文件导入为字符串url-loader将文件作为 data URI 内联到 bundle 中file-loader将文件发送到输出目录
资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:
asset/resource发送一个单独的文件并导出 URL。之前通过使用file-loader实现。asset/inline导出一个资源的 data URI。之前通过使用url-loader实现。asset/source导出资源的源代码。之前通过使用raw-loader实现。asset在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用url-loader,并且配置资源体积限制实现。
打包样式资源
- npm i css-loader style-loader less-loader -D
1 | /* |
打包 html 资源
- npm i html-webpack-plugin -D
1 | /* |
打包图片资源
webpack5以下
- npm i url-laoder file-loader -D
1 | /* |
webpack5
1 | /* |
打包其他资源
1 | /* |
devServer
- npm i webpack-dev-server -D
1 | /* |
下面是一个简单的开发环境webpack.confg.js配置文件
1 | /* |
其中,大部分配置都在注释中给出解释。
运行项目的两个指令:
webpack 会将打包结果输出出去(build文件夹)
npx webpack-dev-server 只会在内存中编译打包,没有输出loader 和 plugin 的不同:(plugin 一定要先引入才能使用)
loader:1. 下载 2. 使用(配置 loader)
plugins:1.下载 2. 引入 3. 使用
四、Webpack 生产环境的基本配置
而生产环境的配置需要考虑以下几个方面:
提取 css 成单独文件
- npm i mini-css-extract-plugin -D
1 | const {resolve} = require('path') |
css 兼容性处理(webpack5)
- npm i postcss postcss-loader postcss-preset-env -D
1 | const {resolve} = require('path') |
压缩 css
- npm i optimize-css-assets-webpack-plugin -D
1 | const {resolve} = require('path') |
js 语法检查
- npm i eslint eslint-webpack-plugin eslint-config-airbnb-base eslint-plugin-import -D
1 | const {resolve} = require('path') |
js 兼容性处理
基本js兼容性处理
- npm i babel-loader @babel/core @babel/preset-env -D
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
61const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
// 全部js兼容性处理——> @babel/polyfill
entry: './src/index.js',
output: {
filename: "js/build.js",
path: resolve(__dirname, 'build')
},
module: {
rules: [
/*
* js兼容性处理:babel-loader @babel/core @babel/preset-env
* 1. 基本js兼容性处理——> @babel/preset-env 问题:只能转换基本语法 ,例如promise不能转换
* 2. 全部js兼容性处理——> @babel/polyfill 问题:只要解决部分 兼容性问题 ,但是将所有兼容性代码全部引入,体积太大了
* 3. 需要做兼容性处理的就做按需加载 ——> corejs
* */
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
// 预设:指示babel做怎么样的兼容性处理
presets: [
[
'@babel/preset-env',
]
]
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html"
}),
/*
* webpack5
* npm i eslint eslint-webpack-plugin eslint-config-airbnb-base eslint-plugin-import -D
*"eslintConfig": {
"extends": "airbnb-base",
#!!!!如果没使用alias下面的不用配置!!!!!
"settings": {
"import/resolver": {
"webpack": {
"config": "webpack.config.js"
}
}
}
}
* */
new ESLintPlugin({
fix: true,
extensions: ['js', 'json', 'coffee'],
exclude: '/node_modules/'
})
],
mode: "development"
}全部js兼容性处理
- npm i @babel/polyfill -D
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
60const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
// 全部js兼容性处理——> @babel/polyfill
entry: ['@babel/polyfill','./src/index.js'],
output: {
filename: "js/build.js",
path: resolve(__dirname, 'build')
},
module: {
rules: [
/*
* js兼容性处理:babel-loader @babel/core @babel/preset-env
* 1. 基本js兼容性处理——> @babel/preset-env 问题:只能转换基本语法 ,例如promise不能转换
* 2. 全部js兼容性处理——> @babel/polyfill 问题:只要解决部分 兼容性问题 ,但是将所有兼容性代码全部引入,体积太大了
* 3. 需要做兼容性处理的就做按需加载 ——> corejs
* */
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
// 预设:指示babel做怎么样的兼容性处理
presets: [
[
'@babel/preset-env',
]
]
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html"
}),
/*
* webpack5
* npm i eslint eslint-webpack-plugin eslint-config-airbnb-base eslint-plugin-import -D
*"eslintConfig": {
"extends": "airbnb-base",
#!!!!如果没使用alias下面的不用配置!!!!!
"settings": {
"import/resolver": {
"webpack": {
"config": "webpack.config.js"
}
}
}
}
* */
new ESLintPlugin({
fix: true,
extensions: ['js', 'json', 'coffee'],
exclude: '/node_modules/'
})
],
mode: "development"
}按需加载兼容性处理
- npm i core-js -D
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
78const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
// 全部js兼容性处理——> @babel/polyfill
// entry: ['@babel/polyfill','./src/index.js'],
entry: './src/index.js',
output: {
filename: "js/build.js",
path: resolve(__dirname, 'build')
},
module: {
rules: [
/*
* js兼容性处理:babel-loader @babel/core @babel/preset-env
* 1. 基本js兼容性处理——> @babel/preset-env 问题:只能转换基本语法 ,例如promise不能转换
* 2. 全部js兼容性处理——> @babel/polyfill 问题:只要解决部分 兼容性问题 ,但是将所有兼容性代码全部引入,体积太大了
* 3. 需要做兼容性处理的就做按需加载 ——> corejs
* */
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
// 预设:指示babel做怎么样的兼容性处理
presets: [
[
'@babel/preset-env',
{
// 按需加载
useBuiltIns: 'usage',
// 指定corejs版本
corejs: {
version: 3
},
// 指定兼容性做到哪个版本的浏览器
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
]
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html"
}),
/*
* webpack5
* npm i eslint eslint-webpack-plugin eslint-config-airbnb-base eslint-plugin-import -D
*"eslintConfig": {
"extends": "airbnb-base",
#!!!!如果没使用alias下面的不用配置!!!!!
"settings": {
"import/resolver": {
"webpack": {
"config": "webpack.config.js"
}
}
}
}
* */
new ESLintPlugin({
fix: true,
extensions: ['js', 'json', 'coffee'],
exclude: '/node_modules/'
})
],
mode: "development"
}
js 压缩
- 将mode设置为production即可自动压缩js文件
html 压缩
1 | const {resolve} = require('path') |
一个基本的生产环境配置
1 | const {resolve} = require('path') |
五、Webpack 优化配置
5.1 开发环境性能优化配置
5.1.1优化打包构建速度
5.1.1.1HMR(模块热替换)
HMR: hot module replacement 热模块替换 / 模块热替换
作用:一个模块发生变化,只会重新打包构建这一个模块(而不是打包所有模块) ,极大提升构建速度
代码:只需要在 devServer 中设置 hot 为 true,就会自动开启HMR功能(只能在开发模式下使用)
1 | devServer: { |
每种文件实现热模块替换的情况:
样式文件:可以使用HMR功能,因为开发环境下使用的 style-loader 内部默认实现了热模块替换功能
js 文件:默认不能使用HMR功能(修改一个 js 模块所有 js 模块都会刷新)
–> 实现 HMR 需要修改 js 代码(添加支持 HMR 功能的代码)
1
2
3
4
5
6
7
8
9
10// 绑定
if (module.hot) {
// 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效
module.hot.accept('./print.js', function() {
// 方法会监听 print.js 文件的变化,一旦发生变化,只有这个模块会重新打包构建,其他模块不会。
// 会执行后面的回调函数
print();
});
}注意:HMR 功能对 js 的处理,只能处理非入口 js 文件的其他文件。
html 文件: 默认不能使用 HMR 功能(html 不用做 HMR 功能,因为只有一个 html 文件,不需要再优化)
使用 HMR 会导致问题:html 文件不能热更新了(不会自动打包构建)
解决:修改 entry 入口,将 html 文件引入(这样 html 修改整体刷新)
1
2entry: ['./src/js/index.js', './src/index.html']
5.1.2优化代码调试
5.1.2.1 source-map
source-map:一种提供源代码到构建后代码的映射的技术 (如果构建后代码出错了,通过映射可以追踪源代码错误)
参数:[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
代码:
1 | devtool: 'eval-source-map' |
可选方案:[生成source-map的位置|给出的错误代码信息]
- source-map:外部,错误代码准确信息 和 源代码的错误位置
- inline-source-map:内联,只生成一个内联 source-map,错误代码准确信息 和 源代码的错误位置
- hidden-source-map:外部,错误代码错误原因,但是没有错误位置(为了隐藏源代码),不能追踪源代码错误,只能提示到构建后代码的错误位置
- eval-source-map:内联,每一个文件都生成对应的 source-map,都在 eval 中,错误代码准确信息 和 源代码的错误位
- nosources-source-map:外部,错误代码准确信息,但是没有任何源代码信息(为了隐藏源代码)
- cheap-source-map:外部,错误代码准确信息 和 源代码的错误位置,只能把错误精确到整行,忽略列
- cheap-module-source-map:外部,错误代码准确信息 和 源代码的错误位置,module 会加入 loader 的 source-map
内联 和 外部的区别:1. 外部生成了文件,内联没有 2. 内联构建速度更快
开发/生产环境可做的选择:
开发环境:需要考虑速度快,调试更友好
- 速度快( eval > inline > cheap >… )
- eval-cheap-souce-map
- eval-source-map
- 调试更友好
- souce-map
- cheap-module-souce-map
- cheap-souce-map
最终得出最好的两种方案 –> eval-source-map(完整度高,内联速度快) / eval-cheap-module-souce-map(错误提示忽略列但是包含其他信息,内联速度快)
生产环境:需要考虑源代码要不要隐藏,调试要不要更友好
- 内联会让代码体积变大,所以在生产环境不用内联
- 隐藏源代码
- nosources-source-map 全部隐藏
- hidden-source-map 只隐藏源代码,会提示构建后代码错误信息
最终得出最好的两种方案 –> source-map(最完整) / cheap-module-souce-map(错误提示一整行忽略列)
5.2 生产环境性能优化配置
5.2.1 优化打包构建速度
5.2.1.1 oneof
1 | module: { |
5.2.1.2 babel缓存
1.babel缓存 cacheDirectory:true 让第二次打包构建速度变快
1 | { |
5.2.1.3 多进程打包
当项目中js文件较多,打包较满时,推荐配置使用
使用步骤:
npm i thread-loader -D
配置loader
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{
test: /\.js$/,
exclude: /node_modules/,
use: [
// 开启多进程打包
// 进程启动大概为600ms,进程通信也有开销,只有工作消耗比较长,才需要多进程打包
{
loader: 'thread-loader',
options: {
workers:2 // 进程2个
}
},
{
loader: "babel-loader",
options: {
// 预设:指示babel做怎么样的兼容性处理
presets: [
[
'@babel/preset-env',
{
// 按需加载
useBuiltIns: 'usage',
// 指定corejs版本
corejs: {
version: 3
},
// 指定兼容性做到哪个版本的浏览器
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
],
// 开启babel缓存
cacheDirectory: true
}
}
],
},
5.2.1.4 externals
externals主要作用是拒绝将某些包打包到文件中
1 | /* |
5.2.1.5 dll
dll是可以将某些库分离出来单独打包一次后,以后再打包项目时,不需要再多次打包,提高了打包的效率
使用流程:
创建 webpack.dll.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/*
* 使用dll技术对某些库进行打包(jquery、react、vue)
* 当你运行webpack时,默认查找 webpack.config.js 配置文件
* --》 webpack --config webpack.dll.js
*
*
* */
const {resolve} = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
// 最终打包生成的[name] --> jquery
// ['jquery'] --> 要打包的库是jquery
jquery:['jquery']
},
output: {
filename: "[name].js",
path: resolve(__dirname,'dll'),
library: '[name]_[hash]'
},
plugins: [
// 打包 生成一个 manifest.json --> 提供和jQuery的映射关系
new webpack.DllPlugin({
name:'[name]_[hash]', // 映射库的暴露内容名称
path: resolve(__dirname,'dll/manifest.json')
})
],
mode:'production'
}运行 webpack –config webpack.dll.js,打包后会在目录下生成 相应的dll目录,以及manifest.json文件
npm i add-asset-html-webpack-plugin -D
配置对应的插件
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
39const webpack = require('webpack')
const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
//
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: "js/built.js",
path: resolve(__dirname, 'build')
},
module: {
rules: [
// loader的配置
]
},
plugins: [
//plugins的配置
//html-webpack-plugin
// 功能:默认会创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS)
// 需要有结构的HTML文件
new HtmlWebpackPlugin({
// 复制 './src/index.html' 文件,自动引入打包输出的所有资源(JS/CSS)
template: "./src/index.html"
}),
// 告诉webpack哪些库不参与打包,同时使用的名称也需要改变
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 配置到后引入对应的jquery
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js'),
publicPath:'./'
})
],
mode: 'production',
}
5.2.2 优化代码运行的性能
5.2.2.1 缓存(hash-chunkhash-contenthash)
文件资源缓存 让代码上线 运行缓存更好使用
- 修改文件名[hash:10]
- 问题:由于js和css同时使用一个hash值,如果重新打包,会导致所有缓存失效(只改动一个文件
- chunkhash
- 根据chunk生成的hash值,如果 打包来源于同一个chunk,那么hash值就一样
- 问题:因为css是在js中被引入的,所以同属于一个chunk
- contenthash
- 根据文件内容生成 hash值,不同文件hash值一定不一样
1 | //output中的入口js文件 |
5.2.2.2 tree shaking
tree shaking 去除无用代码,减少代码体积
前提:
- 必须使用ES6模块化代码
- 开启production模式
注意:
- 在package.json中配置 “sideEffects”:false 所有代码没有副作用(都可以进行tree shaking)
- 问题:可能会把css / @babel/lolyfill (副作用)文件干掉
- “sideEffects”:[“*.css”] 保留css
5.2.2.3 code split
将打包后的js代码按照不同的方式进行分割。
方式一:更改入口文件配置
1 | entry: { |
方式二:optimization配置
1 | // 可以将node_modules中的代码单独打包成一个chunk最终输出 |
方式三:通过import()方式
1 | // 通过js代码,让某个文件单独打包成一个chunk |
5.2.2.4 懒加载和预加载
- 懒加载:当文件需要时才加载
- 预加载prefetch:会在使用之前,提前加载js文件
- 正常加载可以认为是并行加载(同一时间加载多个文件)
- 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,在偷偷加载资源
1 | document.getElementById('btn').onclick = function () { |
5.2.2.5 PWA
PWA:渐进式网络开发应用程序(离线可访问)
使用步骤:
下载 workbox-webpack-plugin:npm i workbox-webpack-plugin -D
使用插件
1
2
3
4
5
6
7
8
9
10
11new WorkboxWebpackPlugin.GenerateSW({
/*
* 1.帮助serviceWorker快速启动
* 2. 删除旧的serviceworker
*
* 生成一个serviceworker配置文件
* */
clientsClaim:true,
skipWaiting:true
})注册serviceWorker(入口js文件中)
1
2
3
4
5
6
7
8
9
10
11
12
13// 注册serviceworker
// 处理兼容性问题
if ('serviceWorker' in navigator){
window.addEventListener('load',() => {
navigator.serviceWorker.register('/service-worker.js')
.then(() => {
console.log('serviceworker注册成功了')
}).catch(() => {
console.log('serviceworker注册失败了')
})
})
}
注意:
1 | 1.eslint不认识 window、navigator全局变量 |
六、Webpack配置详解
6.1 entry
1.string: ‘./src/index.js’ 单入口 打包形成一个chunk,输出一个bundle文件,此时chunk的名称默认是main
1 | entry:'./src/index.js' |
2.array :[‘./src/index.js’,’./src/add.js’] 多入口 所有入口文件最终会形成一个chunk,输出出去只有 一个bundle文件。一般只有在HMR功能中让HTML热更新生效
1 | entry: ['./src/index.js','./src/add.js'] |
3.object:多入口,有几个文件就形成几个chunk,输出几个bundle文件,此时chunk的名称是key
1 | entry: { |
4.特殊用法
1 | entry: { |
6.2 output
1 | output: { |
6.3 module
1 | module: { |
6.4 resolve
1 | // 解析模块的规则 |
6.5 devServer
1 | devServer: { |
6.6 optimization
1 | optimization: { |