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‘
    },

这就是优化前后的对比。

然后我们要到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‘ //日志级别。可以是‘信息‘,‘警告‘,‘错误‘或‘沉默‘。
        })

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

对于externals再说两点——

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

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

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

然后我们再来聊聊为什么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会是这样子。。。

大家会看到我们这两个文件会有公共的部分,比如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对应页面,按需加载)

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

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

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

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

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

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

这是js源文件,

这是打包后的文件,

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

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

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

ok,介绍这些就足够了。

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

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

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

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

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

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

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

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

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

时间: 2024-10-03 22:51:56

webpack之前端性能优化(史上最全,不断更新中。。。)的相关文章

WebPack实例与前端性能优化

[前端构建]WebPack实例与前端性能优化 计划把微信的文章也搬一份上来. 这篇主要介绍一下我在玩Webpack过程中的心得.通过实例介绍WebPack的安装,插件使用及加载策略.感受构建工具给前端优化工作带来的便利. 壹 | Fisrt 曾几何时,我们是如上图的方式引入JS资源的,相信现在很少遇见了.近年来Web前端开发领域朝着规范开发的方向演进.体现在以下两点: MVC研发构架.多多益处(逻辑清晰,程序注重数据与表现分离,可读性强,利于规避和排查问题...) 构建工具层出不穷.多多益处(提

【前端构建】WebPack实例与前端性能优化

计划把微信的文章也搬一份上来. 这篇主要介绍一下我在玩Webpack过程中的心得.通过实例介绍WebPack的安装,插件使用及加载策略.感受构建工具给前端优化工作带来的便利. 壹 | Fisrt 曾几何时,我们是如上图的方式引入JS资源的,相信现在很少遇见了.近年来Web前端开发领域朝着规范开发的方向演进.体现在以下两点: MVC研发构架.多多益处(逻辑清晰,程序注重数据与表现分离,可读性强,利于规避和排查问题...) 构建工具层出不穷.多多益处(提升团队协作,以及工程运维,避免人工处理琐碎而重

基于大数据的用户行为预测在前端性能优化上的应用

首先,我得说,这篇文章有点标题党了,其实内容并没有标题看起来那么高大上.其次,本文只是做一个技术方案可能性的探讨,并没有提供完善的解决方案,至多给了一个Demo供参考. 目的 如需转载,请注明转自:http://www.cnblogs.com/silenttiger/p/4929841.html 前端性能优化,我觉得最主要的目的就两个:1.提升页面加载速度:2.节约服务器资源. 这里特别提一下节约服务器资源,很多人在做前端性能优化的时候,往往只考虑前端性能的问题,而完全忽视前端的性能优化对后端服

2017前端性能优化清单

https://github.com/Findow-team/Blog/issues/11?utm_source=tuicool&utm_medium=referral 2017前端性能优化清单 你开始使用渐进启动了么?是不是已经使用过React和Angular中tree-shaking和code-splitting两个工具?有没有用过Brotli.Zofli和HPACK这几种压缩技术,或者OCSP协议(在线证书状态协议)?知不知道资源提醒,客户端提醒和CSS containment一类的技术?

大话WEB前端性能优化基本套路

前言 前端性能优化这是一个老生常谈的话题,但是还是有很多人没有真正的重视起来,或者说还没有产生这种意识. 当用户打开页面,首屏加载速度越慢,流失用户的概率就越大,在体验产品的时候性能和交互对用户的影响是最直接的,推广拉新是一门艺术,用户的留存是一门技术,拉进来留住用户,产品体验很关键,这里我以美柚的页面为例子,用实例展开说明前端优化的基本套路(适合新手上车). WEB性能优化套路 基础套路1:减少资源体积 css 压缩 响应头GZIP js 压缩 响应头GZIP html 输出压缩 响应头GZI

前端性能优化的七大手段

前面的话 本文将详细介绍前端性能优化的七大手段,包括减小请求数量.减小资源大小.优化网络连接.优化资源加载.减少重绘回流.使用性能更好的API和构建优化 减小请求数量 [合并] 如果不进行文件合并,有如下3个隐患 1.文件与文件之间有插入的上行请求,增加了N-1个网络延迟 2.受丢包问题影响更严重 3.经过代理服务器时可能会被断开 但是,文件合并本身也有自己的问题 1.首屏渲染问题 2.缓存失效问题 所以,对于文件合并,有如下改进建议 1.公共库合并 2.不同页面单独合并 [图片处理] 1.雪碧

前端性能优化分类

介绍 本文将详细介绍前端性能优化的七大手段,包括减少请求数量.减小资源大小.优化网络连接.优化资源加载.减少重绘回流.使用性能更好的API和构建优化 减少请求数量 [合并] 如果不进行文件合并,有如下3个隐患 1.文件与文件之间有插入的上行请求,增加了N-1个网络延迟 2.受丢包问题影响更严重 3.经过代理服务器时可能会被断开 但是,文件合并本身也有自己的问题 1.首屏渲染问题 2.缓存失效问题 所以,对于文件合并,有如下改进建议 1.公共库合并 2.不同页面单独合并 [图片处理] 1.雪碧图

vue-小爱ADMIN系列文章(二):微信微博等分享,国际化,前端性能优化,nginx服务器部署

最近在做我的小爱ADMIN后台管理系统,结合当前市场后台管理系统对相关功能的需求,我又开始新增了一些新的功能和组件,如分享功能组件,项目国际化功能:项目完成后,部署在nginx服务器,发现首次访问的速度特别慢,严重的影响了用户体验,因此,我又开始进行了一系列的前端性能优化;以及将优化后的项目部署到nginx服务器二级子目录的注意细节. 效果演示地址 github地址 分享功能 背景说明 用微信,微博等做网站的第三方登录及用微信和支付宝进行支付,都需要注册开发者账号和添加网站应用,比较麻烦.另外,

【杂谈】BI系统的前端性能优化

近一年,由于笔者团队的一些变化,笔者开始承担一个BI系统的前端应用的维护和迭代,一年中,围绕着这个BI系统,发生了不少令人啼笑皆非和醍醐灌顶的故事.最近,终于有时间把它们一点点的沉淀写来,以文字的形式呈现出来. 首先,简单介绍下笔者维护的这个BI系统,和常规的BI(Business Intelligence)系统一样,笔者的BI系统同样可以分为三层: 数据层:这一层其实主要是ETL的过程,即将业务数据库的数据通过抽取(Extract).转换(transform).加载(Load)到新的数据库中,