webpack以前端性能优化

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

能够看到总下载时间从3800ms缩短到1600ms。vue

咱们在用webpack时通常都会选择多入口文件吧,为的就是将本身的源码跟第三方库代码分离。这是以前的代码,jquery

复制代码
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的方式去引入,下面是优化过的代码webpack

复制代码
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'
    },
复制代码

 这就是优化先后的对比。git

而后咱们要到html中以script标签的形式去引externals中的cdn。以后就能够在相应的文件中import了,他的好处是无论你在多少vue文件中引用多少次,他都不会打包到全部的trunk(这里的trunk'指的是按需加载,一会详细说明)中,这是用webpack-bundle-analyzer插件展现的效果。github

复制代码
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一万次他都不会打包一次,厉害吧~~)。web

对于externals再说两点——ajax

1.externals中的key是import中使用的vue-router

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

 

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

而后咱们再来聊聊为何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才会解析并打包。

相关文章
相关标签/搜索