乐趣区

关于前端:前端性能优化webpack

一、vueCli 查看打包后文件的大小占比

⚠️vue-cli2 应用 webpack-bundle-analyzer

// 用 vue-cli2 构建的我的项目 中里曾经集成了 
应用 npm run build --report 命令即可

⚠️上面实用于:vue-cli3

1.1 装置依赖

$ npm install webpack-bundle-analyzer --save-dev

1.2 配置 vue.config.js

chainWebpack: config => {
    // 查看打包文件体积大小
    config
      .plugin('webpack-bundle-analyzer')
      .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin,[
          {analyzerMode: 'server'}
      ])
  }
  /**
  analyzerMode?: 'server' | 'static' | 'json' | 'disabled';
         * Can be "server", "static" or "disabled".
         * Defaults to "server".
         * In "server" mode analyzer will start HTTP server to show bundle report.
         * In "static" mode single HTML file with bundle report will be generated.
         * In "json" mode single JSON file with bundle report will be generated
         * In "disabled" mode you can use this plugin to just generate Webpack Stats JSON file by setting "generateStatsFile" to true.
         */

1.3 配置打包脚本
package.jsonscripts 中配置

$ "build": "vue-cli-service build --report"

执行命令:

$ npm run build

关上浏览器:http://127.0.0.1:8888 之后 就会看到一个【可视化】的文件占比

❗️扩大:终端如果报出正告: (资产大小限度 244KIB, 可能回影响网络性能)。
![[图片上传中 …(20200428150718890.png-e3528-1624502443756-0)]
](https://upload-images.jianshu.io/upload_images/11846892-a979dcca61ef8edd.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

🧠解决办法:在 vue.config.js 中配置

module.exports = {
    //webpack 配置
    configureWebpack: {
        // 敞开 webpack 的性能提醒
        performance: {hints:false}
        // 或者
        // 正告 webpack 的性能提醒
        performance: {
            hints:'warning',
            // 入口终点的最大体积
            maxEntrypointSize: 50000000,
            // 生成文件的最大体积
            maxAssetSize: 30000000,
            // 只给出 js 文件的性能提醒
            assetFilter: function(assetFilename) {return assetFilename.endsWith('.js');
            }
        }
    },
    // vue.config.js
    //     configureWebpack: config => {
    //         config.performance = {
    //            hints: 'warning',
    //           maxEntrypointSize: 50000000,
    //           maxAssetSize: 30000000,
    //           assetFilter: function(assetFilename) {//              return assetFilename.endsWith('.js');
    //          }
    //      }
    //    }
}

更多细节可参考:webpack 中文文档 - 性能(performance)

二、移除 console

如果你应用的是 webpack v5 或以上版本,你不须要装置这个插件。
webpack v5 自带最新的 terser-webpack-plugin
如果应用 webpack v4,则必须装置 terser-webpack-plugin v4 的版本。

2.1 装置依赖

$ npm install terser-webpack-plugin -D

2.2 配置 vue.config.js

const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
    chainWebpack: config => {if (process.env.NODE_ENV === 'production') {
          config.optimization.minimizer([
            new TerserPlugin({test: /\.js(\?.*)?$/i,
              terserOptions: {
                compress: {
                  drop_console: true,
                  pure_funcs: ['console.log']
                }
              }
            })
          ])
        } else {
          // disable optimization during tests to speed things up
          config.optimization.minimize(false)
        }
      }
}

更多细节可参考:webpack 中文文档 -TerserWebpackPlugin

❓❓❓如果报错:

Error: optimization.minimizer() no longer supports being passed an array. Either switch to the new syntax (https://github.com/neutrinojs/webpack-chain#config-optimization-minimizers-adding) or downgrade to webpack-chain 4. If using Vue this likely means a Vue plugin has not yet been updated to support Vue CLI 4+.

🧠可重新配置

if (process.env.NODE_ENV === 'production') {config.optimization.minimizer('js')
                .use(require.resolve('terser-webpack-plugin'), [{ terserOptions: {
                    // 打包删掉正文
                    comments: true,
                    compress: {
                        drop_console: true,
                        drop_debugger: true
                        // pure_funcs: ["console.log"]

                    }
                } }])
        } else {
            // disable optimization during tests to speed things up
            config.optimization.minimize(false)
        }

❗️扩大:为什么删除生产环境的 console?

console.log:向 web 开发控制台打印一条音讯,罕用来在开发时调试剖析。有时在开发时,须要打印一些对象信息,但公布时却遗记去掉 console.log 语句,这可能造成内存泄露。

在传递给 console.log 的对象是不能被垃圾回收 ♻️,因为在代码运行之后须要在开发工具能查看对象信息。所以最好不要在生产环境中 console.log 任何对象。

实例代码:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Leaker</title>
</head>

<body>
  <input type="button" value="click">
  <script>
    !function () {function Leaker() {this.init();
      };
      Leaker.prototype = {init: function () {this.name = '*'.repeat(1e5);
          console.log("Leaking an object %o: %o", (new Date()), this); // this 对象不能被回收
        }
      };
      document.querySelector('input').addEventListener('click', function () {new Leaker();
      }, false);
    }()
  </script>
</body>

</html>

这里联合 Chrome 的 Devtools–>Performance 做一些剖析,操作步骤如下:

  1. 开启 Performance 的记录
  2. 执行 CG 按钮,创立基准参考线
  3. 屡次点击【click】按钮,新建 Leaker 对象
  4. 执行 CG 按钮
  5. 进行记录


能够看出 【JS Heap】 线最初没有降回到基准参考线的地位,显然存在没有被回收的内存。如果将代码批改为

// console.log("Leaking an object %o: %o", (new Date()), this);


反复上述的操作步骤,剖析后果如下:

从比照剖析后果可知,console.log 打印的对象是不会被垃圾回收器回收的。
因而最好不要在页面中 console.log 任何对象,包含 warn、error 等兄弟,这样可能会影响页面的整体性能,特地在生产环境中,这些细节须要特地的关注。

三、压缩图片

3.1 装置依赖

$ npm install terser-webpack-plugin -D

3.2 配置 vue.config.js

config.module
            .rule('images')
            .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
            .use('image-webpack-loader')
            .loader('image-webpack-loader')
            .options({
                bypassOnDebug: true,
                disable: process.env.NODE_ENV !== 'production'
            });

四、UI 库按需加载

对于大多数零碎而言,都会应用一些一些 UI 组件库,例如 Ant Design 或者是 Element UI,这些组件都是反对按需引入,咱们在应用这些组件时,如果只用到了其中一部分组件,能够配置按需加载,在 main.js 中批改代码:

import {
    Pagination,
    Icon,
    Tabs,
} from 'ant-design-vue'
// import 'ant-design-vue/dist/antd.css'  曾经通过 babel 引入 这里就不全局引入了

Vue.use(Pagination)
    .use(Icon)
    .use(Tabs)

而后批改 babel.config.js,如下:

 "plugins": [["import", { "libraryName": "ant-design-vue", "libraryDirectory": "es", "style": "css"}], // `style: true` 会加载 less 文件

  ]

这样,组件对应的 js 和 css 文件就能够实现按需加载.

五、路由懒加载

对于个别比拟大型的 B 端管理系统我的项目,基本上都会应用 Vue Router 来治理路由,这些我的项目波及的页面都比拟多,所以为了避免首屏资源过大,须要采纳路由懒加载资源即 Code Splitting,将每个页面的资源进行拆散,这个只需在 router.js 里配置即可:

// 采纳箭头函数和 import 进行懒加载解决
$ component: () => import('./index.vue')

六、moment 优化

6.1 问题形容

依据打包剖析图来看,次要是 locale 下 moment 的其余语言包占用体积较大。默认是 en 的语言包,所以在无需其余语言的状况下,能够间接疏忽掉 locale 下的文件不打包。

疏忽之前:

疏忽之后:

6.2 解决方案
用 webpack 自带的 IgnorePlugin 插件

// vue.config.js
var webpack = require('webpack')

module.exports = {
 // ... 此处省略其余配置
    
 chainWebpack: config => {config.plugin('ignore')
      .use(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)); // 疏忽 /moment/locale 下的所有文件
  }
  
  // ... 此处省略其余配置
}

6.3 解决方案 - 原理
在 webpack 编译阶段, 如果引入的文件门路匹配 /^./locale$/,则会疏忽这个文件,也就不会被打包进去。

  • 搜寻 moment 包编译后的文件并未找到齐全匹配 /^./locale$/ 这个正则的引入语句,只有 aliasedRequire(‘./locale/’ + name)这条语句和 locale 相干, 却又和正则匹配不上,倒是在 moment 的 src 源文件中有 import … from ‘./locale’。然而在 moment 的 package.json 中 main 是指向编译后的文件并不是 src 文件,这就奇了怪了, 于是 debug IgnorePlugin 看了一下。

  • 图中 request 真是./locale,眼瞎了还是 webpack 的问题?依照 dependencies 的地位 1853 行查看 moment 编译后的文件,定位到了的确是 aliasedRequire(‘./locale/’ + name),怎么回事?

  • 原来 webpack 在编译时,遇到 require(‘./locale/’ + name)此类表达式时,webpack 会查找目录 ‘./locale/’ 下合乎正则表达式 /^.*.$/ 的文件。因为 name 在编译时还是未知的,webpack 会将每个文件都作为模块引入到 bundle 中,这就是为什么引入 moment 之后,编译完的文件为什么会那么大的起因。

6.4 增加 IgnorePlugin 后, 须要设置 locale 怎么办?

  1. 在增加 webpack.IgnorePlugin 之后,文件大小是减小了,然而在设置 moment.locale(‘zh-cn’)之后,format 之后的日期依然是英文的,语言没有切换过去。

增加之前:打包之后蕴含 momen.js 的文件大小

增加之后:打包之后蕴含 momen.js 的文件大小

  1. 性能缺失必定是不能承受的,怎么办?怎么办?
  2. 在 moment 文档上也提供了解决方案, moment-locales-webpack-plugin

    $ npm install --save-dev moment-locales-webpack-plugin
    // vue.config.js
    new MomentLocalesPlugin({localesToKeep: ['zh-cn'],
    })
    
     // const MomentLocalesPlugin = require('moment-locales-webpack-plugin')
         // config.plugin('moment-locales-webpack-plugin').use(
         //     new MomentLocalesPlugin({//         localesToKeep: ['zh-cn']
         //     })
         // );
  3. moment 默认 locale 是 en,它必然会被打包进去, 如果须要配置其余语言,能够通过 localesToKeep 来配置,其余没用到的语言包也就不会被打包进去了。

vue.config.js 配置如下代码

config.plugin('moment-locales-webpack-plugin').use(
            new MomentLocalesPlugin({localesToKeep: ['es-us', 'ru', 'cs', 'hi', 'uk']
            })
        );

能够看到打包的时候都被打包删除掉了!
![[图片上传中 …(下载 (3).png-2486c4-1624615311186-0)]
](https://upload-images.jianshu.io/upload_images/11846892-f582e7cb6b75a6c6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

6.5 moment-locales-webpack-plugin 原理剖析

  1. 如果没有配置 option,用 IgnorePlugin 疏忽所有语言包(en 除外)
  2. 如果设置 option,用 ContextReplacementPlugin 插件设置 webpack 在编译阶段的查找规定,即查找指定的 locale。

    ...
    if (localesToKeep.length > 0) {var regExpPatterns = localesToKeep.map(function(localeName) {return localeName + '(\\.js)?';
     });
     return new ContextReplacementPlugin(/moment[\/\\]locale/,
         new RegExp('(' + regExpPatterns.join('|') + ')$') // 配置 webpack 编译阶段的查找规定,即指定语言包
     );
    } else {return new IgnorePlugin(/^\.\/locale$/, /moment$/);
    }
    ...
    

    七、webpack 反复打包同名依赖包

最近装置了 webpack-bundle-analyzer 插件来剖析打包形成,发现有一些包被反复的打包了屡次,这样会让构建进去的包分外的臃肿。这次要是因为咱们往往援用了很多的第三方包,而很多工具类的库也会被别的包间接的依赖,所以就导致了反复打包的景象,例如下图的 bn.js。

7.1 解决方案
在 webpack 的 resolve 上面增加如下配置:

// 第一种办法
module.exports = {
    resolve: {
        alias:{'bn.js': path.resolve(process.cwd(), 'node_modules', 'bn.js')
        }
    }
};
// 第二种办法
module.exports = {
    configureWebpack:{
        resolve:{
            alias:{'bn.js': path.resolve(process.cwd(), 'node_modules', 'bn.js')
            }
        }
    }
};

// 第三种办法
const path = require('path');// 引入 path 模块
function resolve(dir){return path.join(__dirname,dir)//path.join(__dirname)设置绝对路径
}
module.exports={
    chainWebpack: config =>{
        config.resolve.alias
        .set("@", resolve("src")) 
    }
}

resolve.alias 的作用其实就是增加包的别名并强行指定援用对立门路,配置完的成果如下,只能看到一个 bn.js 了。

优化之后

优化之前

八、有抉择的应用 prefetch 和 preload

prefetch

<link rel="prefetch" ></link>

这段代码通知浏览器,这段资源将会在将来某个导航或者性能要用到,然而本资源的下载程序权重比拟低。也就是说 prefetch 通常用于减速下一次导航,而不是本次的。
preload

<link rel="preload" ></link>

preload 通常用于本页面要用到的要害资源,包含要害 js、字体、css 文件。preload 将会把资源得下载程序权重进步,使得要害数据提前下载好,优化页面关上速度。

在应用 Vue Cli 生成的我的项目里,当咱们配置了路由懒加载后,默认状况下 webpack 在构建时会对所有的懒加载资源进行 prefetch 和 preload,所以当你关上首页时,会看到大量的 prefetch 和 preload 申请,如下图:

// 禁止 prefetch 和 preload
chainWebpack: (config) => {config.plugins.delete('prefetch')
  config.plugins.delete('preload')
}
// 有抉择的 prefetch 和 preload
config.plugin('prefetch').tap(options => {options[0].fileBlacklist = options[0].fileBlacklist || []
    options[0].fileBlacklist.push(/myasyncRoute(.)+?\.js$/)
    return options
})

下面代码批改 vue.config.js 的 chainWebpack 来增加配置。

总结:功败垂成✌️✌️✌️✌️✌️✌️✌️✌️✌️✌️✌️✌️✌️✌️✌️✌️✌️✌️✌️✌️

参考链接:
https://segmentfault.com/a/1190000012295395
https://zhuanlan.zhihu.com/p/362547907
https://www.jianshu.com/p/4f8f36944a46
https://juejin.cn/post/6844903987632685063
https://blog.csdn.net/u010352770/article/details/101538528

退出移动版