webpack学习教程之前端性能优化总结


Posted in Javascript onDecember 05, 2017

前言

webpack学习教程之前端性能优化总结

曾几何时,我们是如上图的方式引入JS资源的,相信现在很少遇见了。近年来Web前端开发领域朝着规范开发的方向演进。体现在以下两点:

1、MVC研发构架。多多益处(逻辑清晰,程序注重数据与表现分离,可读性强,利于规避和排查问题...)

2、构建工具层出不穷。多多益处(提升团队协作,以及工程运维,避免人工处理琐碎而重复的工作)

  • 模块化开发
  • 将前端性能优化理论落地,代码压缩,合并,缓存控制,提取公共代码等
  • 其他的还包括比如你可以用ES 6 或CoffeeScript写源码,然后构建出浏览器支持的ES5

所以,前端这么好玩,如果还有项目没有前后端分离的话,真的是守旧过头了。

主流构建工具

市面上有许多构建工具,包括Grunt、Gulp、browserify等,这些和WebPack都是打包工具。但WebPack同时也具备以下特点:

相比Grunt,WebPack除了具备丰富的插件外,同时带有一套加载(Loader)系统。使它支持多种规范的加载方式,包括ES6、CommonJS、AMD等方式,这是Grunt、Gulp所不具备的。

从代码混淆的角度来看,WebPack更加的极致

代码分片为处理单元(而不是文件),使得文件的分片更为灵活。

P.S.此处只做简单的比较,不论孰优孰劣。其实工具都能满足需求,关键是看怎么用,工具的使用背后是对前端性能优化的理解程度。

引言

最近在用webpack优化首屏加载性能,通过几种插件之后我们上线前后的速度快了一倍,在此就简单的分享下吧,先上个优化前后首屏渲染的对比图。

webpack学习教程之前端性能优化总结

可以看到总下载时间从3800ms缩短到1600ms。

我们在用webpack时一般都会选择多入口文件吧,为的就是将自己的源码跟第三方库代码分离。这是之前的代码,

entry: {
 entry: './src/main.js',
 vendor: ['vue', 'vue-router', 'vuex', 'element-ui','echarts']
},
output: {
 path: config.build.assetsRoot,
 filename: utils.assetsPath('js/[name].[chunkhash].js'),
 chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
}

echarts非常大,所以打包时的vendor.js大概为1.2MB(经过gzip压缩之后),而且首页没有用到echarts,所以我之后使用了externals将第三方库以cdn的方式去引入,下面是优化过的代码

entry: {
 entry: './src/main.js',
 vendor: ['vue', 'vue-router', 'vuex', 'element-ui']
 },
 // 这里的output为base中的output,不是生产的output
 output: {
 path: config.build.assetsRoot,
 filename: '[name].js',
 libraryTarget: "umd",
 publicPath: process.env.NODE_ENV === 'production' ?
  config.build.assetsPublicPath : config.dev.assetsPublicPath
 },
 externals: {
 echarts: 'echarts',
 _: 'lodash'
 },

webpack学习教程之前端性能优化总结

webpack学习教程之前端性能优化总结

这就是优化前后的对比。

然后我们要到html中以script标签的形式去引externals中的cdn。之后就可以在相应的文件中import了,他的好处是不管你在多少vue文件中引用多少次,他都不会打包到所有的trunk(这里的trunk'指的是按需加载,一会详细说明)中,这是用webpack-bundle-analyzer插件展示的效果。

var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
new BundleAnalyzerPlugin({
  // 可以是`server`,`static`或`disabled`。
  // 在`server`模式下,分析器将启动HTTP服务器来显示软件包报告。
  // 在“静态”模式下,会生成带有报告的单个HTML文件。
  // 在`disabled`模式下,你可以使用这个插件来将`generateStatsFile`设置为`true`来生成Webpack Stats JSON文件。
  analyzerMode: 'server',
  // 将在“服务器”模式下使用的主机启动HTTP服务器。
  analyzerHost: '127.0.0.1',
  // 将在“服务器”模式下使用的端口启动HTTP服务器。
  analyzerPort: 8888, 
  // 路径捆绑,将在`static`模式下生成的报告文件。
  // 相对于捆绑输出目录。
  reportFilename: 'report.html',
  // 模块大小默认显示在报告中。
  // 应该是`stat`,`parsed`或者`gzip`中的一个。
  // 有关更多信息,请参见“定义”一节。
  defaultSizes: 'parsed',
  // 在默认浏览器中自动打开报告
  openAnalyzer: true,
  // 如果为true,则Webpack Stats JSON文件将在bundle输出目录中生成
  generateStatsFile: false, 
  // 如果`generateStatsFile`为`true`,将会生成Webpack Stats JSON文件的名字。
  // 相对于捆绑输出目录。
  statsFilename: 'stats.json',
  // stats.toJson()方法的选项。
  // 例如,您可以使用`source:false`选项排除统计文件中模块的来源。
  // 在这里查看更多选项:https: //github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21
  statsOptions: null,
  logLevel: 'info' //日志级别。可以是'信息','警告','错误'或'沉默'。
 })

webpack学习教程之前端性能优化总结

webpack学习教程之前端性能优化总结

我们会看到,没用externals和用了externals后所有的js中都不会出现类似echarts和lodash的库出现(就算你import一万次他都不会打包一次,厉害吧~~)。

对于externals再说两点——

1.externals中的key是import中使用的

import lodash from "_";
import echarts from "echarts";

2.externals中的value是window下调用的

webpack学习教程之前端性能优化总结

然后我们再来聊聊为什么output使用trunkhash不用trunk,这是为了持久化缓存。简单说下两者的区别——

trunk:每次build之后的版本,就是说所有的build之后的文件hash值一致,比如我只改了一个文件,最后所有的文件hash都会变,这样所有的文件都不会走cache,这样缓存就失去了意义。

trunkhash:根据每个文件生成不同的hash值,当文件变化时hash会改变且只会改变相应的文件

然后我们肯定是要用到CommonsChunkPlugin,这个插件是用来抽取公共代码的,基本上99%的配置都是长这样子或者类似这样子用两个不同的commonschunkPlugin,但这从某方面来说并没有实现真正意义上的持久化缓存,这个一会我会通过webpack打包原理来详细解释其中的原因。。。。。。

new webpack.optimize.CommonsChunkPlugin({
 names: ['vendor','manifest']
})

在没用这个插件之前,我们的main.js和vendor.js会是这样子。。。

webpack学习教程之前端性能优化总结

大家会看到我们这两个文件会有公共的部分,比如vue和element-ui,所以我们要抽取公共代码到vendor中,所以我们可以先这样配置

new webpack.optimize.CommonsChunkPlugin({
 name: 'vendor',
}),

但这样的话虽然可以提取公共代码,但我们会把runtime(webpack运行时的代码,一会在打包原理中会再次提到)也放到vendor中,这里面会维护一个trunk的文件列表,类似于这样,就是说我们改任意的代码,这个table里面的hash会变,所以vendor的hash也会变

,所以这没有实现真正的持久化缓存。这个hash table是按需缓存的打包出来的trunk包,一般都是通过require.ensure(就是vue-router中配置的page对应页面,按需加载)

webpack学习教程之前端性能优化总结

所以我们就把name改为names,就是上面那个配置。因为使用这个插件,我们会把公共代码抽到第一个name中,把runtime放到最后一个name中,也就是我们所谓的“manifest”文件。

并且这个文件会比较小,通常都是2kb左右,所以build后会生成一个script标签,但这样的话就多了一个http请求,所以我们可以用另外一个插件(InlineManifestWebpackPlugin)将manifest.js内联进去。就会长这样子

webpack学习教程之前端性能优化总结

再回到我们的CommonsChunkPlugin,现在我们随便改任何已存在的文件,vendor.js的hash都不会变,是的,貌似这就实现了持久化缓存。但是当我们新增一个模块,并且在入口文件中import一下,我们的vendor就会跟main一起变。很奇怪对吧,我们明明已经做了自己的源码跟第三方库分离,为什么vendor还会变(到现在应该没有任何一篇博客对此进行详细的说明)。下面我就详细的给大家解释下我的看法,如果大家发现有不对的地方还请指正。

再解释为什么之前,我们先简单了解下webpack的打包规则。

webpack一个entry对应一个bundle,这个bundle包括入口文件和其依赖的模块。其他按需加载的则打包成其他的bundle。还有一个比较重要的文件时manifest,它是最先加载的,负责打包其他的bundle并按需加载和执行。

manifest是一个自执行函数,熟悉angular的同学看第一行应该很了解,因为anguar1.3版本的源码中启动就是angular.bootstrap,对,这里也是一样。里面的modules变量就是对应模块函数,它是webpack处理的基本单位,就是说对应打包前的一个文件

webpack学习教程之前端性能优化总结

这是js源文件,

webpack学习教程之前端性能优化总结

webpack学习教程之前端性能优化总结

这是打包后的文件,

webpack学习教程之前端性能优化总结

所有的模块函数索引都是连续的(每个js文件生成一个trunkid!!!!!),像这种 /* 4 */ 对应的就是js文件,他通过打包就变成了一个个trunkid,仔细看会看到咱们打包前js文件里的export和require依赖都会统一转换成webpack模块。咱们说的webpackJsonp就是除manifest之外打包其他的文件的函数体。

简单说下main吧,这个图的trunkid是连续的,为了在一张图上显示,我截掉了trunk3-7.

webpack学习教程之前端性能优化总结

这里面一共有三个参数,第一个是我当前文件的trunkid,它是唯一标识符,就是指main的trunkid,第二个就是打包的所有文件的模块函数,第三个是我要立即执行的trunkid模块函数。

ok,介绍这些就足够了。

然后我们再回过头来看看为什么我们所谓的commonschunkPlugin会变。刚才说过,有几个js就有几个trunkid。

所以当我们新加一个js并引入到main入口时,webpack再次打包,我的main文件会多一个模块函数,刚刚说过trunkid是依次递增的而且不会重复。所以对应的vendor的id会+1,就是这么细微的变化导致hash变了。

webpack学习教程之前端性能优化总结

webpack学习教程之前端性能优化总结

webpack学习教程之前端性能优化总结

大家仔细看,这两个vendor都是10272行,唯一的不同就是我要自执行这个vendor库,这里我引用的jquery,所以这个文件只有jquery,自执行肯定要有模块函数,trunkid+1,所以hash会变。我们再好好回忆一下,其实这也说明了这个插件的意义,我就是要抽出公共的库,OK,这个插件做到了,但是因为webpack打包机制,不同文件生成不同turnkid,所以这是美中不足的一点。再回想一下,我们一般是不会随便修改main.js的,所以从另一角度上来说这就是实现了持久化缓存。但我如果就是想保持vendor的hash不变要怎么办呢?

webpack学习教程之前端性能优化总结

这段代码就可以实现,没错,如果你对vue-cli了如指掌,这就是vue-cli的官方demo,至于为什么可以,这个我后续会跟大家解释(实在是写不动了。。。)。

最后再给大家介绍一个超级好用的东西,就是cdn。我们现在的需求是想让图片走cdn,让js走线上路径,但官方的解释是通过修改config文件做cdn变化,这样做的话我的所有输出都会走cdn,那所有的ajax请求就跨域了呀。

webpack学习教程之前端性能优化总结

一开始我的解决方案是,在源文件中挨个替换,这样会比较慢,更重要的是,cdn图片也是有hash值的,当我以后替换图片时,还得重新改相应的hash。有什么方法能让他自动去获取hash呢。

没错,我们需要在url-loader中单独配置cdn,做到js访问线上路径,静态资源使用cdn,两者互不影响。

webpack学习教程之前端性能优化总结

简单提醒一下,url-loader不能检测到js中的background,所以我们凡是在js中引用的地址,必须在外面先import这张图片,url-loader才会解析并打包。

今天就先到这里吧,改天继续。。。。。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
神奇的7个jQuery 3D插件整理
Jan 06 Javascript
用Juery网页选项卡实现代码
Jun 13 Javascript
jquery创建一个新的节点对象(自定义结构/内容)的好方法
Jan 21 Javascript
js跨浏览器实现将字符串转化为xml对象的方法
Sep 25 Javascript
jquery实现多屏多图焦点图切换特效的方法
May 04 Javascript
jQuery+AJAX实现遮罩层登录验证界面(附源码)
Sep 13 Javascript
基于jQuery插件实现点击小图显示大图效果
May 11 Javascript
javascript获取select标签选中的值
Jun 04 Javascript
JS根据生日月份和日期计算星座的简单实现方法
Nov 24 Javascript
JS出现失效的情况总结
Jan 20 Javascript
Nuxt.js实战详解
Jan 18 Javascript
关于javascript中的promise的用法和注意事项(推荐)
Jan 15 Javascript
vue实现消息的无缝滚动效果的示例代码
Dec 05 #Javascript
微信小程序基于slider组件动态修改标签透明度的方法示例
Dec 04 #Javascript
JS实现的计数排序与基数排序算法示例
Dec 04 #Javascript
JS 中document.write()的用法和清空的原因浅析
Dec 04 #Javascript
JavaScript实现省市联动过程中bug的解决方法
Dec 04 #Javascript
浅谈 vue 中的 watcher
Dec 04 #Javascript
vue中的计算属性的使用和vue实例的方法示例
Dec 04 #Javascript
You might like
PHP实现单例模式最安全的做法
2014/06/13 PHP
ThinkPHP实现跨模块调用操作方法概述
2014/06/20 PHP
php计算年龄精准到年月日
2015/11/17 PHP
Yii中实现处理前后台登录的新方法
2015/12/28 PHP
WordPress中用于获取搜索表单的PHP函数使用解析
2016/01/05 PHP
laravel入门知识点整理
2020/09/15 PHP
基于jQuery的简单九宫格实现代码
2012/08/09 Javascript
如何设置iframe高度自适应在跨域情况下的可用方法
2013/09/06 Javascript
php读取sqlite数据库入门实例代码
2014/06/25 Javascript
JavaScript 学习笔记之语句
2015/01/14 Javascript
jQuery zTree树插件动态加载实例代码
2017/05/11 jQuery
详解vue-resource promise兼容性问题
2017/06/20 Javascript
ES6新增数据结构WeakSet的用法详解
2017/08/07 Javascript
全选复选框JavaScript编写小结(附代码)
2017/08/16 Javascript
vue实现商城购物车功能
2017/11/27 Javascript
angular4 共享服务在多个组件中数据通信的示例
2018/03/30 Javascript
详解微信小程序开发用户授权登陆
2019/04/24 Javascript
如何使用JavaScript实现栈与队列
2019/06/24 Javascript
javascript实现异形滚动轮播
2019/11/28 Javascript
Vue的自定义组件不能使用click方法的解决
2020/07/28 Javascript
python获取糗百图片代码实例
2013/12/18 Python
python数据结构之二叉树的遍历实例
2014/04/29 Python
Python格式化输出%s和%d
2018/05/07 Python
Python实现使用卷积提取图片轮廓功能示例
2018/05/12 Python
Python实现的生产者、消费者问题完整实例
2018/05/30 Python
python实现判断一个字符串是否是合法IP地址的示例
2018/06/04 Python
Python发送邮件功能示例【使用QQ邮箱】
2018/12/04 Python
django框架模板语言使用方法详解
2019/07/18 Python
CSS3使用transition实现的鼠标悬停淡入淡出
2015/01/09 HTML / CSS
详解html5 canvas常用api总结(二)--绘图API
2016/12/14 HTML / CSS
纽约家具、家居装饰和地毯店:ABC Carpet & Home
2017/06/21 全球购物
Skyscanner加拿大:全球旅行搜索平台
2018/11/19 全球购物
文明班级建设方案
2014/05/15 职场文书
党校毕业个人总结
2015/02/28 职场文书
2015年综治宣传月活动总结
2015/03/25 职场文书
2019年员工晋升管理制度范本!
2019/07/08 职场文书