考虑到一些第三方依赖库,在短时间内不会发生频繁变更,不必每次都打包,只须要经过js引入便可。javascript
eject暴露webpack配置以后,module.exports下原本是没有externals配置项的,须要手动新增该配置项。php
webpack.config.prod.js
css
externals: {
jquery: 'jQuery',
axios: 'axios',
moment: 'moment'
}复制代码
该配置是将代码中的一部分第三方依赖库,如jquery、axios、moment不进行打包,而是手动在html中引入相应的cdn js,以下:html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Gearbest Influencer Program</title>
<link rel="stylesheet" href="//at.alicdn.com/t/font_975949_zge2u562xp.css">
<script src="//at.alicdn.com/t/font_975949_zge2u562xp.js"></script>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.bootcss.com/moment.js/2.24.0/moment.min.js"></script>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="main"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>复制代码
其中前端
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.bootcss.com/moment.js/2.24.0/moment.min.js"></script>复制代码
这三行代码,分别引入了jquery、axios、moment三个cdn js。像这些第三方依赖库的cdn js的连接地址,能够从www.bootcdn.cn/上获取。可是,也不是全部第三方依赖库能够采用cdn js的方式引入成功的,须要经过实践去验证到底有哪些第三方依赖库是能够经过cdn js的方式引入的。java
未采用externals配置的打包图(原始)node
采用externals配置的打包图react
从图中很明显地发现,引入externals配置打包后,jquery没有被打包进去,且main包大小从902.68KB减少到822.95KB,瘦身幅度达到8.83%
。jquery
考虑到一些第三方依赖库,在短时间内不会发生频繁变更,不必每次都打包,能够经过dll(相似于C/C++语言中的动态连接库的概念)的方式单独引入。该方式为externals的另外一种实现方式。
webpack
在目录config下新建webpack.dll.config.js文件,具体内容以下:
// webpack_dll.config.js
const paths = require('./paths');
const DllPlugin = require('webpack/lib/DllPlugin');
module.exports = {
entry: {
// 如下基本是全量dll优化的第三方包
// react: ['react', 'react-dom', 'react-redux', 'redux-saga', 'react-router', 'video-react', 'jquery', 'axios', 'bizcharts'],
react: ['react', 'react-dom', 'redux-saga', 'video-react'],
// polyfill: ['core-js/fn/promise', 'whatwg-fetch']
},
output: {
filename: '[name].dll.js',// 该配置上面entry配置项key为react,那么生成filename就是react.dll.js
path: paths.appPublic,// 生成dll文件的目标路径,本项目保存在public目录下
library: '_dll_[name]',// dll的全局变量名
},
plugins: [
new DllPlugin({
name: '_dll_[name]', // dll的全局变量名
path: paths.appPublic + '/[name].manifest.json',// 描述生成的manifest文件,一样保存在public目录下
})
]
};复制代码
以上配置能够将生成的dll文件(react.dll.js)和对应的manifest.json文件(react.manifest.json)保存到public目录下,这样在对整个项目build时能够将该文件直接打包到html文件中。
在package.json中新增dll命令
"scripts": {
"start": "set PORT=3001 && node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js --env=jsdom",
"lint": "eslint --ext .jsx --ext .js src --fix",
"dll": "webpack --config config/webpack.dll.config.js"// dll打包命令,--config后的参数为dll配置文件的路径
}复制代码
在控制台输入yarn dll,生成dll文件(react.dll.js)
将dll文件(react.dll.js)手动引入到对应html文件body标签末尾
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Gearbest Influencer Program</title>
<link rel="stylesheet" href="//at.alicdn.com/t/font_975949_zge2u562xp.css">
<script src="//at.alicdn.com/t/font_975949_zge2u562xp.js"></script>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script src="%PUBLIC_URL%/react.dll.js"></script>
</body>
</html>复制代码
其中,PUBLIC_URL即public目录。
yarn build以后,打包后的文件目录结构以下:
build
├─ asset-manifest.json
├─ favicon.ico
├─ index.html
├─ login.html
├─ main.html
├─ manifest.json
├─ react.dll.js
├─ react.manifest.json
├─ register.html
├─ service-worker.js
└─ static
├─ css
│ ├─ index.7dc833cb.css
│ ├─ index.7dc833cb.css.map
│ ├─ login.2ff64c8e.css
│ ├─ login.2ff64c8e.css.map
│ ├─ main.c8e56e47.css
│ ├─ main.c8e56e47.css.map
│ ├─ register.e54b83db.css
│ └─ register.e54b83db.css.map
├─ js
│ ├─ index.e6968086.js
│ ├─ index.e6968086.js.map
│ ├─ login.67bdfb3a.js
│ ├─ login.67bdfb3a.js.map
│ ├─ main.4a5c410c.js
│ ├─ main.4a5c410c.js.map
│ ├─ register.5d9dd95a.js
│ └─ register.5d9dd95a.js.map
└─ media
├─ GB_bg.c4c2913b.png
├─ iconfont.26b4e298.eot
├─ iconfont.3e355800.svg
├─ iconfont.5cd1f541.ttf
├─ img07.c2e13bab.png
├─ img08.22d05748.png
└─ login_bg.165cc596.jpg复制代码
能够发现,打包后的build文件夹中已经有了react.dll.js和react.manifest.json文件,这两个文件是没有通过打包,直接从public文件夹中copy过来的。
采用dll配置的打包图
从打包图中,能够明显地看出来原先两个第三方依赖库react-dom和video-react,并无打包进来,且index包大小从460.75KB减少到408.67KB,瘦身幅度达到11.3%
。
采用externals和dll进行webpack优化的初衷都是提取一些公共的第三方依赖包,单独由cdn引入或以动态连接库的方式引入项目中来,使其不用每次都被打包进主包(build/dist文件夹)。可是,仍然须要手动在html文件中引入,在页面加载渲染的时候不只要下载主css、主js,还要下载对应的cdn js或对应的dll js,这无疑可能会使得页面的白屏时间更长。
能够发现,上述两种方法均是从客户端(前端第三方依赖库的分离)的角度出发对页面首屏性能优化的尝试,不一样的是,gzip(以及接下来要介绍的cdn方法)是从服务端的角度出发对页面首屏进行优化的一种方案。gzip的重点在于对nginx进行配置优化,如下将以本人的nginx为例对该方案进行详细介绍。
nginx.conf
个人前端项目nginx配置文件node_react.conf在/etc/nginx/vhost目录下,能够看到,咱们经过include的方式引入个人前端项目nginx配置文件,这种写法更加便于管理和维护,可以下降配置污染。进入该目录。
该目录下,主要关注node_react.conf和gzip.conf,其中前者为个人前端项目nginx配置文件,后者为gzip配置文件。
node_react.conf
进一步查看gzip配置文件
gzip.conf
#开启gzip
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
#gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php;
gzip_vary off;
gzip_disable "MSIE [1-6]\.";
#针对不一样格式文件过滤进行gzip缓存
location ~* ^.+\.(ico|gif|jpg|jpeg|png|webp)$ {
access_log off;
expires 30d;
}
location ~* ^.+\.(css|js|txt|xml|swf|wav)$ {
access_log off;
expires 24h;
}
location ~* ^.+\.(html|htm)$ {
expires 1h;
}
#开启gzip复制代码
打开customize control DevTools,切换到Network,找到Content-Encoding列。
若是找不到Content-Encoding列,可按照下图中的方法操做
下图为“未开启gzip”的DevTools截图
下图为“开启gzip”的DevTools截图
从图中能够看出,在DevTools->Network中Content-Encoding列显示gzip字样,便可肯定服务端开启了gzip。
也许此时读者会有疑问啦!咱们能够经过什么手段准确地获得页面首屏时间和页面白屏时间的数据呢?怎么判断何时是页面首屏加载结束的时间点呢?其实刚开始我也有这些疑问,后来通过查阅资料和对谷歌浏览器customize control DevTools的一番研究以后,得出了一种简单可行的方法推荐给你们。
注意记录页面首屏时间和页面白屏时间前必定要清缓存
),点击F12,进入customize control DevTools界面,切换Performance面盘,点击已提早保存的书签打开网页或输入网址,同时点击DevTools界面左上方的灰色圆形按钮,随即灰色原型按钮变为红色闪亮,开始记录页面加载时间,此时网页页面title处会出现loading一直转圈。到此,一波页面首(白)屏时间之旅正式宣告结束,不知道您学会没呢?固然,咱们也能够结合特定工具对咱们的网页进行性能分析,如下推荐给你们一些谷歌插件:
为了节省篇幅,在此就不对各个插件进行详细介绍了,你们能够自行研究。
通过测试,咱们发现开启gzip后,在客户端请求服务器前端静态资源(css、js、图片)时能够对其进行高质量压缩,大大下降带宽压力,从而能够大幅度减少页面白屏时间。如下将从一组数据对该结论进行验证。在给出测试数据和图表以前,须要对下文说起的方案一、方案二、方案三、方案四、方案五、方案六、方案7-一、方案7-二、方案8进行简要描述。
方案1:原始webpack(eject后,未作任何处理)+未开启gzip
方案2:原始webpack(eject后,未作任何处理)+开启gzip
方案3:原始webpack(eject后,未作任何处理)+未开启gzip+对GB_bg.png/img07.png/img08.png进行cdn地址替换+【对打包后的index.js进行cdn地址替换】
方案4:原始webpack(eject后,未作任何处理)+开启gzip+对GB_bg.png/img07.png/img08.png进行cdn地址替换+【对打包后的index.js进行cdn地址替换】
方案5:原始webpack(eject后,未作任何处理)+开启gzip+对GB_bg.png/img07.png/img08.png进行cdn地址替换+【不对打包后的index.js进行cdn地址替换】
方案6:原始webpack(eject后,未作任何处理)+开启gzip+img07/img08图片懒加载
方案7-1:原始webpack(eject后,未作任何处理)+开启gzip+img07/img08图片懒加载+GB_bg.png->GB_bg.jpg
方案7-2:原始webpack(eject后,未作任何处理)+开启gzip+img07/img08图片懒加载+GB_bg.png->GB_bg.webp
方案8:原始webpack(eject后,未作任何处理)+开启gzip+【不对打包后的index.js进行cdn地址替换】+img07/img08图片懒加载+GB_bg.png->GB_bg.webp后替换cdn地址
未开启gzip
开启gzip
以上这组数据对个人前端项目中的两张图片和主css文件进行了gzip性能对比测试,设备为本人笔记本电脑,网络为住所WIFI。
从数据中能够看出,开启gzip较之未开启gzip,优化之处在于大幅度下降了下载静态资源的时间。其中,index.css的下载时间降低了87.88%
,index.js的下载时间降低了59.41%
,并且index.css的文件大小由175KB减少到33KB,瘦身了81.14%
,index.js的文件大小由1536KB减少到525KB,瘦身了65.82%
。
进一步地,咱们只关注本组数据的页面首屏时间和页面白屏时间,能够看到前者的指标数据从25.208秒降低到了18.253秒,降低幅度达到了27.59%
,后者的指标数据从14.454秒降低到了6.02秒,降低幅度达到了58.35%
,特别是页面白屏时间的降低幅度已是一个很不错的成绩了。
咱们已经知道,服务端gzip方案确实可以大幅下降页面白屏时间,大大优化首屏性能,那么为何能够达到这样的性能优化效果呢?在回答这个问题以前,咱们首先思考一下形成前端页面首屏加载慢的缘由究竟是什么呢?仔细琢磨一下,浏览器加载首屏以前,首先会去服务器下载主css、主js文件,这二者之中任何一个文件下载速度慢了都会致使页面阻塞,形成页面首屏加载慢。而服务端gzip恰好能够解决这个问题,它采用高效的压缩算法(应该也有一部分缓存做用,这部分暂不探究),可以大幅度下降服务端静态资源到达客户端的速度(或体量),起到加速(或瘦身)器的做用。
CDN的全称是Content Delivery Network,即 内容分发网络。CDN是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,经过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,下降网络拥塞,提升用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。---引自百度
CDN应用于前端领域,主要是能够起到加速下载静态资源的过程,从而达到首屏优化的效果。通常地,在CDN的加持下,静态资源的下载过程将被大幅度加速,从而达到优化首屏性能的目的。结合实际状况,咱们能够选择对整个前端项目进行全局CDN加速,也能够对部分js文件或图片进行局部CDN加速。
如下将给出一组测试数据,测试设备为本人笔记本电脑,测试网络为本人住所WIFI。
未开启gzip+cdn(保留异常数据)
未开启gzip+cdn(去除异常数据)
从以上测试数据能够看出,cdn方案虽然可以达到较理想的性能优化效果(去除异常数据后,相较于方案1,页面首屏时间下降了50.02%
,页面白屏时间下降了61.94%
),可是稳定性不足,12次测试就有两次数据异常,异常率高达16.67%
,甚至有一次3分钟还未加载出首屏。
为了达到最大化的性能优化效果,咱们能够将gzip与cdn结合运用到咱们的前端项目中,如下给出一组这种结合的测试数据。
开启gzip+cdn(保留异常数据)
开启gzip+cdn(去除异常数据)
该方案虽然能够达到较大的优化结果,可是稳定性仍然有待提升(异常率高达23.08%
),这取决于cdn网络的稳定性,若是cdn网络的稳定性达到理想程度,首屏优化效果至少还能够再提高50%以上。此处为了折中稳定性和优化性能,给出了方案5,即“原始webpack(eject后,未作任何处理)+开启gzip+对GB_bg.png/img07.png/img08.png进行cdn地址替换+【不对打包后的index.js进行cdn地址替换】”的局部cdn方案,如下为该方案的测试数据。
开启gzip+局部cdn
综合上述五种方案,给出第一轮性能对比。
从柱状图中能够看出,方案4虽然性能提高最大,可是较不稳定(取决于cdn网络的稳定性),方案5的性能提高幅度仅次于方案4,且较稳定,很好地折中了稳定性和性能提高两方面,对于cdn网络待提高的现实应用场景下,不失为一个理想的解决方案。
若是首屏有些图片不须要在可视区当即展现,咱们能够采用图片懒加载的方式减小首屏加载的时间。在本项目中,有两张图片img07和img08不须要在首屏可视区中当即展现,能够对其进行懒加载,即鼠标滑动进入图片所在区域才加载。参考延迟加载图像和视频,对本项目两张图片img0七、img08进行懒加载,并对首屏性能进行了一组测试,数据以下:
从数据中能够看出,该方案相较于方案2,页面首屏时间减小1.033秒,降低幅度达5.66%
,这段减小的时间即为图片懒加载省下来的时间。
经过将图片转化为编码性能更高、体积更小的格式,也可以有效减小首屏加载时间,如PNG格式转化为jpeg格式或webP格式后,文件将变得更小。咱们经过一些手段将PNG格式图片转化为jpeg格式或webP格式,这里我推荐一款谷歌扩展工具Convertio,它能够帮助咱们完成多种图片格式的转换,很是方便。利用该工具,我特意将本项目中首屏页面中的一张最大的背景图GB_bg.png转换为了jpeg格式和webP格式,以下图
从图中能够看出,相比于png格式的原图,jpeg格式转换图的压缩率达到了39.22%
,webP格式转换图的压缩率更是达到了惊人的88.12%
。
进一步地,咱们在对img07和img08采起懒加载处理的基础上,将jpeg转换图和webP转换图分别应用于项目中,获得了一组测试数据。
jpeg转换图
webP转换图
能够看出,将转换后的jpeg格式图片和webP格式图片应用到项目中,与方案2相比,相同内容的图片下载时间分别减小了49.12%
和92.65%
,最终表如今页面首屏时间上的提高则是14.1%
和42.5%
,这也有力地证明了将图片转换成编码性能更高、体积更小的格式,可以有效减小首屏加载时间的结论。
咱们不妨继续折腾一下,将上述转换的webP图片用cdn地址替换,对页面首屏性能进行了一组测试。
很不幸,咱们看到,将转换的webP格式图片用cdn地址替换后,页面首屏时间并无降低,而是有了小幅上升,可见不是全部的资源都适合cdn。仔细分析一波,转换的webP格式图片只有84.8KB,直接从本地服务器下载的速度可能并不比从cdn服务器下载的速度慢,这才致使了页面首屏时间不降反升的现象。
好了,说了这么多,给出了这么多方案,为了可以更加宏观地分析方案的总体性能,咱们对以上八种方案进行了终极性能对比。
从终极性能对比图中,咱们能够看到方案4的页面首屏性能提高最为明显,平均只须要9.656秒便可完成首屏加载,可是该方案的稳定性不足,可是若是cdn网络足够稳定的话,页面首屏性能仍有较大的提高空间。方案7-2的页面首屏性能提高仅次于方案4,且具备较强的稳定性,该方案在cdn网络性能受限的应用场景下具有较强的竞争力。固然,具体选择什么方案,须要针对应用场景和现状具体问题具体分析,仁者见仁智者见智,永远没有最好的方案,只有更好的方案。