详解利用webpack的splitChunk拆分打包文件

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

在 webpack 中,总共提供了三种方式来实现代码拆分(Code Splitting):

这里我们主要讲如何通过splitChunks抽取公共代码。在讲解之前,再来复习下 webpack 中三个重要的概念:module、chunks、bundle。

splitChunks 默认配置

由于 Webpack 做到了开箱即用,所以splitChunks是有默认配置的:

module.exports = {
    // ...
    optimization: {
        splitChunks: {
            chunks: 'async',
            minSize: 30000, 
            maxSize: 0, 
            minChunks: 1,
            name: true, 
            cacheGroups: {
                vendors: {
                    // // 正则规则,如果符合就提取 chunk
                    test: /[\/]node_modules[\/]/,
                    // 缓存组优先级,当一个模块可能属于多个 chunkGroup,这里是优先级
                    priority: -10
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    // 如果该chunk包含的modules都已经另一个被分割的chunk中存在,那么直接引用已存在的chunk,不会再重新产生一个
                    reuseExistingChunk: true
                }
            }
        }
    }
};

splitChunks默认配置对应的就是 chunk 生成的第二种情况:通过写代码时主动使用import()来动态加载。下面来看下使用import()来写代码,在 webpack 打包的时候有什么不同。

创建index.js,使用import()动态加载react模块,同时为了方便跟踪产出物,在这里使用了 webpack 的魔法注释,保证输出的 bundle 名称,后面也使用这种方式。内容如下:

// index.js
import(/* webpackChunkName: "react" */ 'react');

添加webpack.config.js,内容如下:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
    mode: 'production',
    entry: {
        main: './src/index.js'
    },
    plugins: [new BundleAnalyzerPlugin()]
};

在这里使用了webpack-bundle-analyzer插件来查看 webpack 打包情况。

执行webpack --config webpack.config.js命令

可以发现,index.js 打包出来了两个文件 react.js 和 main.js,参考下图可知:

图片[1]-详解利用webpack的splitChunk拆分打包文件-JieYingAI捷鹰AI

index.js打包出来了两个文件react.js和main.js;react.js是被拆分出来的,内容实际是 react;react.js被拆分出来是因为splitChunks默认配置chunks='async'。理解splitChunks.chunks三个值

splitChunks中的chunks是一个很重要的配置项,表示从哪些 chunks 里面抽取代码,chunks的三个值有:"initial"、 "all"、 "async", 默认就是是async。

为了理解splitChunks.chunks三个值的差异,下面通过实例来帮助我们理解。首先创建两个文件a.js和b.js:

// a.js
import react from 'react'
import $ from 'jquery'
const a = 'I am a.js'
export default a
// b.js
import $ from 'jquery'
import(/* webpackChunkName: "b-react" */ 'react')
const b = 'I am b.js'
export default b

这两个文件的特点是:

react模块被两个文件都引入了,不同的是a.js是同步引入,b.js是动态引入;jquery模块在两个文件中都被引入,并且都是同步引入;

下面是我们的webpack.config.js文件内容,我们主要修改是chunks的三个值:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
    mode: 'development',
    entry: {
        a: './src/a.js',
        b: './src/b.js'
    },
    plugins: [new BundleAnalyzerPlugin()],
    optimization: {
        splitChunks: {
            cacheGroups: {
                vendors: {
                    chunks: 'async', // 这里是我们修改的地方,async|initial|all
                    test: /[\/]node_modules[\/]/
                }
            }
        }
    }
};

chunks='async'

执行打包命令后,dist文件如下:

图片[2]-详解利用webpack的splitChunk拆分打包文件-JieYingAI捷鹰AI

图片[3]-详解利用webpack的splitChunk拆分打包文件-JieYingAI捷鹰AI

通过实践观察,在这种模式下:

在a.js和b.js都同步引入的jquery被打包进了各自的 bundle 中没有拆分出来共用,说明在这种配置下只会针对动态引入的的代码进行拆分;

react在a.js和b.js表现不同:

在a.js因为是同步引入的,设置的chunks='async',所以不被拆分出去;在b.js是动态引入的,符合chunks='async'的设置,所以被单独拆到vendors-node_modules_react_index_js;chunks='initial'

initial 即原始的最初的意思,原则就是有共用的情况即发生拆分。首先,动态引入的模块不受影响,它是无论如何都会被拆分出去的。而对于同步引入的代码,如果有多处都在使用,则拆分出来共用,至于共同引用多次会被拆分,是通过minChunks单独配置的,针对这个原则,我们再来看下上面的代码拆分的结果:

图片[4]-详解利用webpack的splitChunk拆分打包文件-JieYingAI捷鹰AI

图片[5]-详解利用webpack的splitChunk拆分打包文件-JieYingAI捷鹰AI

因为jquery模块是a.js和b.js共用的代码,所以单独拆除来放到vendors-node_modules_jquery_dist_jquery_js.js中;react在b.js因为用的是动态引入,所以被拆成了b-react.js(名字来自于设置的魔法注释);a.js的react则被拆到了vendors-node_modules_react_index_js.js;chunks='all'

在chunks='initial'配置下,虽然a.js和b.js都引入了react,但是因为引入方式不同,而没有拆分在一起,而是各自单独拆封成一个 chunk,要想把react放到一个文件中,就要使用chunks='all'了。下面是chunks='all'的配置结果:

图片[6]-详解利用webpack的splitChunk拆分打包文件-JieYingAI捷鹰AI

图片[7]-详解利用webpack的splitChunk拆分打包文件-JieYingAI捷鹰AI

通过执行打包结果,跟我们的预期一致,chunks='all'的配置下能够最大程度的生成复用代码,所以一般来说chunks='all'是推荐的方式,但是async和initial也有其存在的必要,理解三者差异,根据项目实际代码拆分需求来配置即可。

使用 cacheGroups

cacheGroups(缓存组)是 webpack splitChunks 最核心的配置,splitChunks的配置项都是作用于cacheGroup上的,也就是cacheGroups缓存组可以继承和覆盖来自 splitChunks.* 的任何选项。

为什么取名叫缓存组呢?

比如 index.js 引入了两个库,一个是 lodash,一个是 jquery,当 webpack 打包的时候发现这两个库都满足上面的拆分要求,于是先打包 lodash,打包完后放在缓存组当中缓存起来,接着继续打包 jquery,打包完后也放在缓存组中,这样就可以在一个打包文件中同时容纳了 lodash 和jquery,如果没有缓存组的概念,就需要打包成两个文件,现在只需要打包成一个文件即可。

我们可以做一个实验来验证下:

// a.js
import react from 'react'
import $ from 'jquery'
const a = 'I am a.js'
export default a

webpack.config.js配置:

optimization: {
  splitChunks: {
    cacheGroups: {
      vendors: {
        chunks: 'all',
        test: /[\/]node_modules[\/]/
      }
    }
  }
},

打包后的文件为vendors-node_modules_jquery_dist_jquery_js-node_modules_react_index_js.js,从下面图中可以看到这个文件包含了 jquery 和 react。

图片[8]-详解利用webpack的splitChunk拆分打包文件-JieYingAI捷鹰AI

如果想要分开打包呢?

只需要给每个库分别取一个名字,这里我们没有把库的名字写死,而是通过在程序打包过程中动态获取满足打包条件的库的名字,作为打包之后的文件名,webpack 是支持这种写法的,这也是在实际使用中常用方式。具体配置如下:

cacheGroups: {
  vendors: {
    chunks: 'all',
    test: /[\/]node_modules[\/]/,
    name(module) {
      const packageName = module.context.match(
        /[\/]node_modules[\/](.*?)([\/]|$)/
      )[1]
      return `${packageName.replace('@', '')}`
    }
  }
}

从下图可以看到把 react 和 jquery 进行分开打包:

图片[9]-详解利用webpack的splitChunk拆分打包文件-JieYingAI捷鹰AI

注意:cacheGroups不会对异步代码生效,因为异步代码肯定会分割的。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
来说点什么吧!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容