zoukankan      html  css  js  c++  java
  • 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才会解析并打包。

    冰冻三尺非一日之寒,滴水石穿非一日之功
  • 相关阅读:
    mybatis动态查询,模糊查询
    Python 列表
    异步加载实现点赞功能($.load 出现post不支持)
    shiro多账号登录(用户名,手机号,邮箱)
    spring data jpa 级联操作的时候注意事项
    semantic UI 点击按钮自动提交表单原因
    上线日期
    有关弱类型意识、DOM、动态语言与函数式编程
    视频项目时长的存储和程序设计方案---单例模式节省内存空间以及自定义注解
    项目中整合第三方插件与SpringMVC数据格式化关于ip地址
  • 原文地址:https://www.cnblogs.com/Mr--Li/p/7954551.html
Copyright © 2011-2022 走看看