Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何最大程度的优化webpack打包速度? #27

Open
JCHappytime opened this issue Mar 4, 2021 · 0 comments
Open

如何最大程度的优化webpack打包速度? #27

JCHappytime opened this issue Mar 4, 2021 · 0 comments
Labels
webpack webpack工程化常见问题

Comments

@JCHappytime
Copy link
Owner

JCHappytime commented Mar 4, 2021

参考文章

1. lodash在webpack上的各种优化尝试

前言

想象一个场景,两个前端项目组 A 和 B 同时上线2个项目,比如 A 上线的项目叫做”牛云聊“,B 上线的项目叫做”牛外卖“。结果上线当天,A 只用了1个小时实现打包上线,而 B 却用了一天时间打包上线。这时候我们可以想象一下B项目经理脸上的表情。

实际上,在上线前我们是需要对webpack打包进行一定优化的,那么我们该从哪些方向来进行优化呢?请继续往下看。

如果你们项目组正在使用 webpack4.x 的版本,那你应该或多或少了解到 4.x 版本的webpack在生产环境下会对代码做自动的tree shaking。但是,可能当你读完这篇文章你的Tree-Shaking并没什么卵用之后,你会有不一样的看法,你的Tree-Shaking不一定真的有用!

Tree-Shaking的原理

总结一下tree-shaking的原理就是:

  • ES6的模块引入是静态分析的,所以可以在编译时正确判断到底加载了哪些代码;
  • 分析程序流,可以判断哪些变量未被引用、使用,从而可以删除这些未被使用的代码。
    更多的原理请参考:Tree-Shaking性能优化实践-百度外卖大前端
    那为什么说tree shaking不一定真的有用呢?这都是函数副作用引起的。

函数副作用

我们都知道,函数式编程的副作用就是,一个函数可能会对函数外部的变量产生影响,而这个影响就是函数的副作用。举个例子吧:

function goBack() {
  window.location.href = '/home';
}

可以看到,goBack()修改了全局变量,结果时为了让浏览器进行跳转,而修改全局变量的这个行为就可能会引发一些副作用。
所以,针对这个问题,国内外各路大神提出了很多解决办法,比如说Webpack 中的 sideEffects 到底该怎么用?"sideEffects": false没有使打包后的bundle减少

1. 优化代码重复打包

比如说,一个项目中有个lib目录下放着自己编写的函数,分析之后发现它被重复打包到了业务代码的js文件中。这种情况该如何优化呢?

  1. 将node_module目录下的依赖统一打包成一个vendor依赖;
  2. 将lib和其他表示公用的文件夹(比如common)下编写的函数库单独打包成一个common;
  3. 将依赖的第三方组件库按需打包,如果使用了组件库中体积比较大的组件,比如:moment。如果只使用一次就打包进入自己引用页面的js文件中,如果被多个页面都引用就打包进入 common 中。
    那拆包该如何在webpack中配置呢?
splitChunks: {
    chunks: 'all',
    automaticNameDelimiter: '.',
    name: undefined,
    cacheGroups: {
        default: false,
        vendors: false,
        common: {
            test: function (module, chunks) {
                // 这里通过配置规则只将 common lib  moment公共依赖打包进入common中
                if (/src\/common\//.test(module.context) ||
                    /src\/lib/.test(module.context) ||
                    /cube-ui/.test(module.context) ||
                    /better-scroll/.test(module.context)) {
                    return true;
                }
            },
            chunks: 'all',
            name: 'common',
            // 这里的minchunks 非常重要,控制moment使用的组件被超过几个chunk引用之后才打包进入common中,否则不打包进去
            minChunks: 2,
            priority: 20
        },
        vendor: {
            chunks: 'all',
            test: (module, chunks) => {
                // 将node_modules 目录下的依赖统一打包进入vendor中
                if (/node_modules/.test(module.context)) {
                    return true;
                }
            },
            name: 'vendor',
            minChunks: 2,
            // 配置chunk的打包优先级,这里的数值决定了node_modules下的 moment不会被打包到 vendor 中
            priority: 10,
            enforce: true
        }
    }
}

为了避免影响打包速度,在项目代码中就是不要将使用频率低,体积大的组件引入到这个文件中。类似于datepicker这种大型的组件,就在对应需要使用的页面中引入即可。然后再webpack配置中,通过设置minChunk来指定当这些较大的组件引用超过多少次之后才能打包到common中,否则就单独打包到对应页面的js中。

因此,优化对于第三方依赖组件的加载方式,以减少不必要的加载和执行时间的损耗。

splitChunks详解

webpack4.x会根据以下条件自动分割代码块:

  • 新代码块可以被共享引用/这些代码块都是来自node_modules文件夹里面;
  • 新代码块大于30kb (min+gziped之前的体积);
  • 按需加载的代码块,最大数量应该小于或者等于5;
  • 初始加载的代码块,最大数量应该小于或者等于3.
splitChunks: {
  // 默认:用于异步chunk,值为all
  // initial模式下会分开优化打包异步和非异步模块。all会把异步和非异步模块同时进行优化,也就是意味着module1在index1中
  // 异步引入,index2中同步引入,initial下module1会出现在两个打包块中,而all只会出现一个。
  // all所有chunk代码(同步加载和异步加载模块都可以使用)的公共部分分离出来成为一个单独的文件
  // async将异步模块代码公共部分抽离出来成为一个单独的文件
  chunks: async,
  minSize: 30000,   // 默认值是30kb,当文件体积>=minSize时将会被拆分为2个文件,否则不生成新的chunk
  minChunks: 1,   // 共享该module的最小chunk数(当>=minChunks时才会被拆分为新的chunk)
  maxAsyncRequests: 5,   // 最多有5个异步加载请求该module
  maxInitialRequests: 3,   // 初始会话时最多有3个请求该module
  automaticNameDelimiter: '~',   // 名字中间的间隔符
  name: true,   // 打包后的名称,如果设置为true默认时chunk的名字通过分隔符(默认时~)分隔开,如vendor~也可以自己手动 
                       // 指定。
  cacheGroups: {   // 设置缓存组用来抽取满足不同规则的chunk,切割成的每一个新的chunk就是一个cache group
    common: {
      name: 'common',    // 抽取的chunk名字
      chunks: 'all',    // 同外层的参数配置,覆盖外层的chunks,以chunk为维度进行抽取
      // 1. 可以为字符串,正则表达式,函数,以module为维度进行抽取;
      // 2. 只要是满足条件的module都会被抽取到该common的chunk下,为函数的第一个参数;
      // 3. 遍历到的每一个模块,第二个参数是每一个引用到该模块的chunks数组
      test(module, chunks) {
        // module.context: 当前文件模块所属的目录,该目录下包含多个文件
        // module.resource: 当前模块文件的绝对路径
      if (/datepicker/.test(module.context)) {
        let chunkName = ''; // 引用该chunk的模块名字
        chunks.forEach(item => {
          chunkName += item.name + ',';
         });
        console.log(`module-datePicker`, module.context, chunkName, chunks.length);
        }
      },
    // 优先级,一个chunk很可能满足多个缓存组,会被抽取到优先级高的缓存组中,数值高的优先被选择
    priority: 10,
    minChunks: 2,    // 最少被几个chunk引用
    reuseExistingChunk: true,    // 如果该chunk中引用了已经被抽取的chunk,直接引用该chunk,不会重复打包代码(当module未 
                                                // 发生变化时是否使用之前的mudule)
    enforce: true,    // 如果cacheGroup中没有设置minSize,据此判断是否使用上层的minSize,当为true时则使用0;为false时则使 
                             // 用上层的minSize
    },
  },
},

2. 尽可能的去掉非必要的import

这种情况一般会因为检查遗漏导致引入。举个例子:最开始写代码的时候,由于业务上的需要,导致要引入datepicker,但是设计突然更改了原型图,不需要选择日期了,这时候你注释掉了js里面对日期的各种操作,但是却遗漏了在import部分的引入。在webpack打包的时候,仍然会将datepicker打进去。

好的一点是,现在大部分的编辑器都会将这种不需要的引入进行提示,一般是颜色变灰。

3. lodash优化

lodash这个js库是我们日常开发中非常依赖的一个前端库,非常好用,但是好用的同时也存在一定的缺陷,就是全量引入后打包的体积较大。那我们能不能按需引入lodash呢?

答案是肯定的。我们可以在npm库上搜索lodash-es这个模块,通过阅读它的文档,我们可以将lodash导出为es6 modules,然后就可以通过import的方式单独导入某个函数来使用。

到底有没有必要优化lodash,其实这个存在一定的争议,可以参考lodash在webpack中的各项优化的尝试。其实优化就是根据自身的业务需求做出各种权衡后的妥协。

@JCHappytime JCHappytime added the webpack webpack工程化常见问题 label Mar 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
webpack webpack工程化常见问题
Projects
None yet
Development

No branches or pull requests

1 participant