第二节:webpack性能优化
一.概述
1.webpack性能优化
- 开发环境性能优化
- 生产环境性能优化
2.开发环境性能优化
- 优化
webpack
的打包构建速度HRM
- 优化代码调试
source-map
3.生产环境性能优化
- 优化打包构建速度
oneOf
babel
缓存- 多进程打包
- 优化代码运行的性能
- 缓存(
hash-chunkhash-contenthash
) tree shacking
code split
- 懒加载/预加载
PWA
externals
dll
- 缓存(
4.存在的问题
当我们修改某一个css
或者模块的时候,所有的文件都会被重新打包;如果模块很多的情况下,这样是很耗费时间的。我们希望只修改发生变化的模块;这就要使用webpack
中的HMR
功能;
二.HMR
hot module replacement
:热模块替换/模块热替换;
作用:一个模块发生变化,只会重新打包这一个模块,而不是打包所有;这样会极大地提升构建速度。
使用方法:只需要在devServer
中设置hot
属性为true
即可:
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
//开启HRM
hot: true
}
注意:修改了webpack
配置之后,一定要重启devServer
。
重启后,再次开启devServer
:
npx webpack-dev-server
可以看到打开的html
文件的输出中显示,已开启HRM
:
随后,修改入口文件index.js
依赖的index.less
,可以看到这一次只重新打包了index.less
模块:
所以验证了css
文件可以使用HMR
功能,其实:
-
样式文件:可以使用
HMR
功能:因为style-loader
内部实现; -
js
文件:默认不使用HMR
功能 ;-
解决方法:需要修改
js
代码,添加支持HMR
功能的代码:if (module.hot){ //一旦 module.hot 为 true ,说明开启了HMR功能。此时才让HMR功能生效 module.hot.accept('./print.js', function(){ //方法会监听print.js文件的变化,一旦发生变化,其他模块不会重新打包构建,会执行后面的回调函数 }) }
上面的
print.js
为发生变化的模块,如果有其他发生变化需要开启HMR
功能的模块,只需要添加相应路径就可以了;注意:
HMR
功能对js
文件处理,只能处理非入口js
文件。因为入口文件引入了很多依赖,它一变就要不可避免的重新引入依赖; -
-
html
文件:默认不使用HMR
功能,同时会导致问题:html
文件不能热更新了(devServer
的功能,自动更新);-
解决方法:修改
entry
入口,将html
文件引入。但是还是是用不了HMR
功能;entry: ['./src/js/index.js', './src/index.html']
-
其实
HTML
文件不需要HMR
功能,因为html
文件通常只有一个,它变了就说明有重新打包的必要;
-
三.source-map
source-map
是一种技术,提供源代码到构建后代码的映射技术。(如果构建后代码出错了,通过映射可以追踪源代码错误)
使用方法为,只需要在五大属性的同级下添加一个devtool
属性即可:
devtool: 'source-map'
执行webpack
打包后,会生成一个.map
文件:
里面存储着映射关系:
写法分为:
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
其中:
-
source-map
:外部; -
作用:显示错误代码的准确信息和源代码的错误位置;
-
inline-source-map
:内联:所有文件只生成一个source-map
文件,并且拼接在js
文件中的最后;- 作用:显示错误代码的准确信息和源代码的错误位置;
-
hidden-source-map
:外部- 作用:显示错误代码和错误原因,但是没有错误位置。不能追踪到源代码错误,只能提示到构建后代码的位置;
-
eval-source-map
:内联:每一个文件都生成对应的source-map
,并且都在eval
函数中;-
作用:显示错误代码的准确信息和源代码的错误位置;只不过提示为每个文件通过
eval
添加的哈希值:
-
-
nosources-source-map
:外部;-
作用:显示错误代码和错误原因,但是没有任何源代码信息;与
hidden-source-map
一起提供隐藏代码的服务:但是点击提示,不会显示源代码信息:
-
-
cheap-source-map
:外部;-
作用:显示错误代码的准确信息和源代码的错误位置;但是只能精确到行:
如图,只有
console
发生了错误,但是只能精确到行;
-
-
cheap-module-source-map
:外部;- 作用:显示错误代码的准确信息和源代码的错误位置;同样只能将错误精确到源码中的行;不同在于
module
会将loader
的source-map
也加进来;
- 作用:显示错误代码的准确信息和源代码的错误位置;同样只能将错误精确到源码中的行;不同在于
内联与外部的区别:
- 外部方式会生成
.map
文件而内联是没有的,而是将.map
文件的内容内嵌到打包后的js
文件的最后; - 内联构建速度更快
下面我们来一个个测试它们的作用:
1.作用演示
source-map
首先在入口文件index.js
中增添一处错误代码:
随后通过下列指令开启devServer
:
npx webpack-dev-server
在显示的网页中可以看到出现了错误提示:
并且这个提示包含了错误出现在哪个文件的哪一行中,点击这个提示可以跳转到源代码出错的地方:
所以source-map
的作用为:显示错误代码的准确信息和源代码的错误位置;
inline-source-map
注意:修改了webpack.config.js
的配置后,一定要重新启动devServer
;在开启的网页中,一样显示了错误代码的准确信息和源代码的错误位置;
点击提示即可跳转到错误源代码的位置:
2.选择
source-map
有这么多种写法,那么我们怎么选择呢?
开发环境
要求:速度快,调试友好;
-
速度方面:
eval
>inline
>cheap
...通过组合,速度从快到慢的写法如下:
eval-cheap-source-map
eval-source-map
-
调试友好方面:定位更精确,就越友好。所以友好程度排名为:
source-map
:精确到具体位置;cheap-module-source-map
:精确到行,但是包含了loader
等依赖文件;cheap-source-map
:最后是精确到行,但是不包含依赖文件;
折中方案为采用eval-source-map
或者 eval-cheap-module-source-map
,框架中的脚手架默认使用前者;
生产环境
源代码要不要隐藏?调试要不要更友好?
内联会让代码体积变大,所以在生产环境中一般不使用内联;
-
隐藏代码:
hidden-source-map
:只隐藏源代码,会提示构建后的代码错误;nosources-source-map
:全部隐藏;
最后结论:
需要调试友好时使用
source-map
,需要速度快时使用cheap-module-source-map
四.oneOf
通常一个loader
处理一类文件,也就是说并不是每一个文件都需要使用全部的loader
去处理,这样会降低性能;可以使用oneOf
设置文件只匹配oneOf
数组中的一个loader
:
rules: [
{
//oneOf的意思为:只会匹配以下中的一个loader
oneOf: [
//1.处理css文件
{
//...
},
//2.处理less文件
{
//...
},
//3.js语法检查
{
//...
},
//4.js兼容性处理
{
//...
},
//5.处理图片
{
//...
},
//6.处理html中的图片
{
//...
},
//8.处理其他文件
{
//...
}
]
}
]
需要注意的是只能选择oneOf
数组中的一个loader
执行;像eslint
和babel
这样的loader
都会处理js
文件,这就需要将其中一个loader
抽离出oneOf
数组了:
rules: [
//js语法检查eslint
{
test: /\.js$/,
exclude: /node_modules/,
//优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
oneOf: [
//js兼容性处理babel
{
//...
},
]
}
]
并且eslint-loader
中enforce
属性为pre
表示会优先执行该loader
;这样两个loader
就不会互相影响了;
这样配合着使用oneOf
可以提升打包构建的速度;
使用webpack
进行文件打包时,并不是每次打包所有的文件都不一样,如果可以将每次打包都不变的文件缓存起来,下次直接复用,就可以大幅度提升打包的速度。这就涉及到了webpack
中的缓存机制;
五.缓存
1.babel缓存
比如处理了100
份文件,我们希望在第一次处理的时候可以将这100
份文件的处理结果缓存起来,以后只重新处理其中发生变化的文件,其余文件直接使用缓存。可通过cacheDirectory
开启缓存,配置如下:
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {version: 3},
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
//开启babel缓存
//第二次构建时,会读取之前的缓存
cacheDirectory: true
}
},
2.文件基本缓存
src
的文件结构如下:
首先在根目录下创建一个服务器文件server.js
:
/**
* 服务器
* 启动服务器指令;
* 方式一:
* 首先需要全局安装:npm i nodemon -g
* nodemon server.js
*
* 方式二:
* node server.js
*/
const express = require('express')
const app = express();
app.use(express.static('build', {maxAge: 1000 * 3600}));
app.listen(3000)
执行该文件:
node server.js
由于该文件监听的是3000
端口,所以访问地址为:http://localhost:3000
打开调试工具的network
选项,可以看到第一次打开网页,资源都需要请求:
刷新一次后,再次查看:
可以看到,浏览器直接从缓存读取了这两个文件。
点开其中一个文件,可以看到在HTTP
响应头中的max-age
字段正是server.js
中设置的3600s
:
也就是这两个资源需要被强制缓存两个小时
读取缓存的速度非常之快,所以缓存能够大大提高加载速度。
存在的问题:
当被缓存的文件发生变化时,就不能使用这个过期的缓存了,需要重新请求该最新版的该文件。
解决方案:
hash
每次打包都给文件拼接上一个hash
值当做是版本号,这样缓存的文件与发生更新后的文件由于版本号不一样,名字也就不同,所以会重新加载更新后的文件;具体配置为在每个文件的output
对象中为filename
属性拼接上hash
值:
output: {
filename: 'js/built.[hash:10].js',
path: resolve(__dirname, 'build')
},
//...
plugins: [
new MiniCssExtractPlugin({
//设置输出路径
filename: 'css/built.[hash:10].css',
})
]
再次执行webpack
打包,此时打包出来的文件就会拼接上10
位的hash
值了:
这个hash
值取的是每次执行webpack
时生成hash
值的前10
位:
这样就保证了,每次打包都会生成不同的打包文件。
随后,执行服务器代码:
node server.js
打开地址http://localhost:3000
,这次请求的资源都带上了hash
值:
当我们修改了源代码之后,再进行打包,打包出来的文件就拼接上了另外10
位hash
值了:
此时刷新网页,浏览器就会重新请求,经过再次打包的更新后的文件了:
文件资源缓存存在的问题
由于所有文件都共享同一次webpack
打包时生成的hash
值,所以只要有一个文件发生了变化,重新打包后,所有文件的缓存都会失效。
chunkhash
webpack
引入了另外一个hash
值:chunkhash
。只需要将webpack.config.js
配置中的hash
替换为chunkhash
即可。根据chunk
生成哈希值,如果打包来源于同一个chunk
哈希值一样。
此时,再进行打包,发现打包后的css
和js
文件仍然共有一个hash
值:
这是因为:css
文件是在js
文件中引入的,所以同属于一个chunk
。
chunk
:入口文件index.js
会引入很多文件,比如css
、less
等,这样一个整体被称为chunk
,即代码块;
contenthash
使用contenthash
才是最优解,这是根据文件的内容生成的hash
值,不同文件的hash
值一定不一样;将webpack.config.js
中的chunkhash
改为contenthash
,再次打包:
这次打包出来的css
文件与js
文件的hash
值就不一样了;这样的话如果只改变了css
文件,那么浏览器第一次缓存的js
文件仍然有效,只需要重新请求css
文件即可:
第一次打开页面,两文件都需要请求:
修改css
文件并重新打包后,再一次刷新浏览器。只需要重新请求css
文件即可:
所以一共有两种缓存策略:
babel
缓存:直接在webpack.config.js
中配置即可;- 优点:让第二次打包构建速度更快;
- 文件资源缓存:给文件添加
hash
值,有三种选择,最优解为添加contenthash
值。- 优点:让上线运行的代码更好地使用缓存;
六.Tree shaking
1.简介
在应用程序中引入的源代码和一些库可以理解为树上的一个绿色的活的树叶,哪些应用程序中没有引用的代码就是灰色的,枯萎的树叶;为了去掉这些灰色的树叶,我们需要摇晃这棵树,这就是树摇(tree shaking
)的概念;树摇是为了去除应用程序中没有使用的代码,这样能让代码的体积变得更小。
tree shaking
的作用为去除无用的代码;但是有前提:
- 必须使用
ES6
模块化; - 将
mode
设置为production
环境;
2.作用
减少代码体积。例如:
// app.js
export function A(a, b) {
return a + b
}
export function B(a, b) {
return a + b
}
// index.js
import {A} from '/app.js'
A(1, 2)
当index.js
引用了app.js
的A
函数,如果tree-shaking
起了作用,B
函数是不会被打包进最后的bundle
的。因为,index.js
中并没有用到B
函数;
3.配置
在webpack.josn
中配置sideEffects
:
"sideEffects": false
它表示所有的代码都是没有副作用的,都可进行tree shaking
。
问题:可能会把css
或@babel/polyfill
等文件删除(副作用);
解决方法:将sideEffects
的值设置为["*.css"]
:
"sideEffects": ["*.css"]
这样写在该数组内的元素,就不会执行tree shaking
;
默认情况下会将入口文件index.js
与其引入的js
文件一起打包,最后输出一个js
文件。如图所示,源码文件夹src
中的test.js
和index.js
最后都被打包成了一个js
文件:
如果我们需要将代码分门别类地打包呢?这时候就需要用到代码分割了;
七.代码分割
1.直接使用多入口
要进行抽离,可以先从entry
指定的入口文件路径下手,配置如下:
entry: {
index: './src/js/index.js',
test: './src/js/test.js'
},
entry
这样的配置称为多入口;对于多入口:有一个入口,最终输出就有一个bundle
。配置后,执行webpack
进行打包:
由于配置里有两个入口,所以打包后输出的js
文件也有两个:
为了方便区分打包后输出的内容,修改output
配置:
output: {
//[name]:取文件名
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
再次打包:此时打包后的文件与打包前的文件的对应关系就一目了然了:
一般,单页面应用对应的是单入口,多页面的对应的是多入口;
2.使用optimization
单入口时
在webpack.config.js
中添加:
entry: './src/js/index.js',
//...
optimization: {
splitChunks: {
chunks: 'all',
}
},
然后在入口文件index.js
中引入jquery
:
import $ from 'jquery'
执行webpack
进行打包,可以看到输出了两个文件,另外一个文件用于存储jquery
:
这种方式的好处是:可以将node_modules
中代码单独打包一个chunk
最终输出;
多入口时
配置如下:
entry: {
index: './src/js/index.js',
test: './src/js/test.js'
},
//...
optimization: {
splitChunks: {
chunks: 'all',
}
},
再次打包过后生成了三个chunk
:
如果没有在配置中添加optimization
属性,那么在多入口的情况下,如果两个js
文件都引入了jquery
,那么打包出来的两个js
文件,大小是相近的:
这是因为两个js
文件各自打包一份jquery
,这种重复打包比较不好;
解决方法为添加optimization
属性,它的好处为:它就会分析多入口chunk
中有没有公共的文件,如果有就会打包成单独的chunk
总结:optimization
的作用:
-
单入口时:可以将
node_modules
中代码单独打包一个chunk
最终输出; -
多入口时:会分析多入口
chunk
中有没有公共的文件,如果有就会打包成单独的chunk
;
也就是说多入口时一般配合着optimization
使用;
3.单入口动态引入(常用)
当若想要只使用单入口就能实现多入口配合optimization
使用时达到的效果的话,可以采用这种方法:
通过js
代码,让某个文件被单独打包成一个chunk
。import
动态导入语法,能将某个文件单独打包。比如在入口文件index.js
中动态引入test.js
:
test.js
代码:
export function mul(x, y) {
return x * y;
}
export function count(x, y) {
return x - y;
}
随后在index.js
中动态引入:
import('./test')
.then(({mul, count}) => {
//文件加载成功
// eslint-disable-next-line
console.log(mul(2, 4))
})
.catch(() => {
// eslint-disable-next-line
console.log('文件加载失败')
})
import
返回的是一个Promise
对象,then
方法代表加载成功,catch
方法代表加载失败;
随后打包,输出了两个js
文件,其中的1.js
代表test.js
:
打包出来的js
文件名字是webpack
打包过程生成的随机id
,不太美观,我们通过给import
增加参数设置打包后输出的js
文件的名字:
//设置输出文件名为test
import(/* webpackChunkName: 'test' */'./test')
.then(({mul, count}) => {
// eslint-disable-next-line
console.log(mul(2, 4))
})
.catch(() => {
// eslint-disable-next-line
console.log('文件加载失败')
})
再次打包:
可见,自定义了test.js
打包后输出文件的名字;
八.懒加载和预加载
1.懒加载
指的是触发了某些条件才加载,而不是一开始就加载;这里讲的是js
文件的懒加载;
可以通过import().then
的方式实现js
文件的懒加载,比如在入口文件index.js
中这样写:
//懒加载
document.getElementById('btn').onclick = function(){
import((/* webpackChunkName: 'test' */ './test').then((mul) => {
console.log(mul(3, 5));
})
}
一般将import
语句,放在一些判断语句中,当满足一定的条件时才执行import
动态引入;如上面的代码,将引入代码放在了按钮btn
的点击事件中,只有点击按钮btn
才会引入test.js
文件;
首先,源文件目录src
结构如下:
test.js
代码如下:
console.log('test.js文件被加载了');
export function mul(x, y) {
return x * y;
}
index.js
的代码如下:
console.log('index.js文件被加载了');
document.getElementById('btn').onclick = function(){
//懒加载
import(/* webpackChunkName: 'test'*/ './test').then(() => {
console.log('test.js被执行了');
})
}
index.html
代码如下,设置了一个按钮:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>hello lazy-loading</h1>
<button id="btn">按钮</button>
</body>
</html>
打包后,打开经过打包的index.html
文件:
可以看到,此时只加载了index.js
,当点击了按钮后,才会加载test.js
文件:
当我们多次点击按钮时,发现只会再次执行test.js
而不会再次加载。因为第一次加载之后该文件就被缓存起来了,之后使用的时候就可以直接从缓存中读取了:
2.预加载
为入口文件index.js
添加webpackPrefetch
:
console.log('index.js文件被加载了');
document.getElementById('btn').onclick = function(){
//懒加载
//预加载prefetch
import(/* webpackChunkName: 'test', webpackPrefetch: true*/ './test').then(() => {
console.log('test.js被执行了');
})
}
打包后,打开经过打包的index.html
文件:可以看到test.js
文件已经提前加载好了:
但是还没有输出:
当我们点击按钮后,会从缓存中直接读取,所以会大大提升test.js
的加载速度:
3.区别
-
懒加载:当文件需要使用时才加载;
-
预加载:在使用之前,提前加载
js
文件;正常加载可以认为是并行加载:同一时间加载多个文件;这些文件没有优先级,只是按代码顺序加载,这样就会导致提前加载还不需要用到的文件;
而预加载
prefetch
:是等其他资源加载完毕,浏览器空闲了,才会偷偷地加载资源;不会阻碍重要资源的优先加载;
总的来说:懒加载用的比较多,但是预加载存在兼容性问题,需要慎用;
九.PWA
1.简介
PWA
:渐进式网络开发应用程序,简而言之就是离线访问技术;
实现它需要用到插件:workbox-webpack-plugin
;
首先全局下载该插件:
npm i workbox-webpack-plugin -D
然后在webpack.config.js
中引入该插件:
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
然后直接在plugins
属性中使用就可以了:
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
clientsClaim: true,
skipWaiting: true
})
],
GenerateSW
中的两个参数作用为:
- 帮助
Service Worker
快速启动; - 删除旧的
Service Worker
;
最终会生成一个Service Worker
的配置文件;
2.配置
需要在入口文件index.js
中注册Service Worker
:
// 注册Service Worker
// 处理兼容性问题
if('serviceworker' in navigator){//如果有这个属性,才在页面全局资源加载完后进行注册
window.addEventListener('load', () => {
navigator.serviceW
orker.register('/service-worker.js')
//成功
.then(() => {
console.log('sw注册成功了');
})
//失败
.catch(() => {
console.log('sw注册失败了');
})
})
}
3.打包
此时执行打包会报eslint
的错误:
原因:eslint
不认识window
、navigator
这些全局变量;
解决方法:需要修改webpack.json
中eslint
的配置。
"eslintConfig": {
"extends": "airbnb-base",
"env": {
"browser": true
}
},
其中的"browser": true
表示:支持浏览器端全局变量;
再次执行webpack
构建就不会报错了,成功输出了service-worker.js
等文件:
4.验证Server Worker
sw
代码必须运行在服务器上,所以需要在服务器文件中启动构建后生成的sw
文件;有多种方式可以选择:
-
nodejs
-
通过
npm i serve -g
全局安装serve
包,安装之后可以直接通过serve -s build
启动服务器,将build
目录下的所有资源作为静态资源暴露出去:
打开这个链接:
可以看到,成功注册了serviceWorker
,随后就可以在Application
选项中的Service Workers
中看到注册的文件了:
并且可以在Cache
中看到存储的资源:
随后在Network
中将网络设置为离线Offline
:
随后刷新网页,网页还是可以显示。这是因为,这些文件都是直接从Service Worker
中直接读取的:
这就是PWA
:离线访问技术;
十.多进程打包
1.安装
我们知道js
引擎是单线程的,同一时间只能干一件事。所以可以通过多进程来优化打包速度;
首先需要下载thread-loader
:
npm i thread-loader -D
2.配置
一般应用在babel
中,一旦需要需要使用多个loader
时,就要放在use
中:
//4.js兼容性处理
{
test: /\.js$/,
exclude: /node_modules/,
use: [
//开启多进程打包
'thread-loader',
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {version: 3},
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
//开启babel缓存
cacheDirectory: true
}
}
]
},
缺点:进程启动需要时间:600ms
,进程通信也需要时间开销;所以只有在工作消耗时间比较长时,才需要多进程打包;由于开发中绝大多数都是js
文件,babel
除了语法检查外还有进行兼容性处理,生成大量兼容性代码,所以应该给babel
开启多进程打包;
3.打包
不加thread-loader
的打包时间:
加了thread-loader
后的打包时间:
需要花费更多的时间。这是因为当前源文件中的js
代码量比较少,而开启多进程需要时间,所以代码量小的时候不建议使用多进程打包;只有实际开发中,代码量上去了它的加速效果才会凸显出来。
如果想调整的话,可以将 'thread-loader'
改成对象形式:
{
loader: 'thread-loader',
options: {
workers: 2//指定使用2个进程
}
}
开启多进程会增大开销,千万不能乱用!
十一.Externals和Dll
1.Externals
在webpack.config.js
中添加externals
属性,该属性与五大基本属性同级:
externals: {
//拒绝jquery被打包进来
jquery: 'jQuery'
}
作用为:可以让webpack
不对某些库或包进行打包;不过忽略了之后,比如删除的jquery
,就需要在html
文件中通过srcipt
标签手动引入。
注意写法:字符串内指定包名,不能写错;
2.Dll
表示动态链接库。可以将多个功能(包)打包成一个chunk
。比如之前我们都是将node_modules
目录下的所有文件打包成一个chunk
,而使用dll
可以将它分为多个不同的部分,分别打包出多个chunk
。
也就是对某些库进行单独打包;
生成dll
文件
使用dll
需要在根目录下添加一个webpack.dll.js
配置文件;配置如下:
/**
* 使用dll技术,对某些库(vue、jquery、react等)进行单独打包
*/
const { resolve } = require('path')
//引入webpack插件
const webpack = require('webpack')
module.exports = {
entry: {
//最终打包生成的[name] --> jquery
//['jquery] --> 要打包的库是jquery
jquery: ['jquery']
},
output: {
//这里的[name]就是上面的jquery
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'
}
总结以下就是:entry
和output
中的配置作用为:单独打包jquery
文件,并将打包输出的文件命名为jquery_hash
。插件的作用为,生成一个mainfest.json
文件,管理与jquery
的映射关系;
当运行webpack
时,默认查找的是 webpack.config.js
配置文件;需求:需要运行webpack.dll.js
文件。这就需要:执行以下指令手动指定执行的配置文件:
webpack --config webpack.dll.js
执行该命令后,打包生成一个dll
目录,里面有两个文件:jquery.js
和manifest.json
:
并且这两个文件内都会拼接上一个hash
值,改值与webpack
打包时生成的hash
一致:
第一个文件:为指定的库单独打包出来的文件;
第二个文件:manifest.json
文件记录了哪些库不用经过了单独打包了,不需要重复打包的映射关系;
改变webpack
配置
同时,相应的也要修改webpack.config.js
的配置,新增两个插件:webpack
和add-asset-html-webpack-plugin
(记住要先全局下载插件):
const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
//1.引入webpack插件
const webpack = require('webpack')
//3.引入add-asset-html-webpack-plugin插件
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
module.exports = {
entry : './src/index.js',
output : {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
plugins: [
//plugins的配置
new HtmlWebpackPlugin({
template: './src/index.html'
}),
//2.使用webpack插件告诉webpack哪些库不参与打包,同时使用时的名称也得改变
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
//4.将该插件指定的文件打包输出,并在html中自动引入该资源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
],
mode: 'development'
}
具体新增配置如代码中的1~4
:
-
第一个插件的作用为:根据第一步中生成的
manifest.json
告知webpack
不用打包该文件指定的库(这里是jquery
),这样打包出来的built.js
也就不含有这些库(jquery
)的内容了。注意:由于
jquery
被指定忽略了,即使入口文件index.js
中引入了jquery
,打包时也就不会打包jquery
-
第二个插件的作用为:将第一步被单独打包生成的库文件(这里是
jquery
)自动引入html
文件中。
配置后之后,再次执行webpack
打包,可以发现,在第一步中单独打包的jquery
文件被再次打包了出来:
并且html
文件中自动引入了新增的打包文件jquery
,这是第二个插件的作用:
使用了webpack
插件,打包时就会忽略插件指定的jquery
文件。由于打包出来的html
只自动引入了打包后的built.js
文件,而built.js
因为插件的原因并没有打包jquery
;这时候就需要在html
文件中手动引入jquery
文件了。此时有两种方法:
- 第一:在
html
文件中直接通过script
标签引入; - 第二:使用上述的
add-asset-html-webpack-plugin
插件打包引入;
3.总结
经过以上两步操作之后,源码发生改变只需要再次执行webpack.config.js
即可,不需要执行webpack.dll.js
,也就是说不需要再重复打包jquery
了,达到了性能优化的效果;今后项目中会有很多的第三方库,我们都可以使用dll
的方式对它们进行单独打包。在使用webpack
重新打包的时候就不用再打包这些第三方的库了,这样能让webpack
第二次打包的速度快很多;
externals
:只忽略某些指定的库,不对它们进行打包,使用时需要通过script
标签引入;需要依赖外链引入。dll
:需要打包一次,将来就不需要重复打包了。如果想要将一些第三方库整合在一起,不依赖外链,就使用这种方式。