欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

webpack4配置之——24:细说webpack4打包及拆分代码

程序员文章站 2022-05-30 17:44:18
...

如果你喜欢这系列的文章,欢迎star我的项目,源码地址,点击这里

webpack4.x打包拆分

在第14章开发、生产环境的拆分代码中,其实已经对webpack4.x的打包拆分,做过简单的介绍,但是在当时我对这一块其实也是一脸懵逼状态,只知道是这样配置的,但是具体该如何操作也是不清楚的,这里会略微细说一下,当然也只是个人了解到的了

在上一章中,说到过对第三方插件库的打包问题,说到了打包速度和打包后包文件体积的问题,打包的文件体积不宜过大,现在webpack打包时包文件体积大于200kb(好像是~忘记了)会有提示警告,要让我们拆分,通常来说我们项目依赖的第三方插件库,我们是不会去修改它们的源码的,这些文件是不会有改动的,我们不用打包它们,或者说,不用每次都打包它们,

单独打包

在第14章开发、生产环境的拆分代码中,介绍过将这些第三方插件包单独打包到一个文件中,这里我们先拿测试环境打包做实验,测试环境的配置是未做压缩的打包配置。

首先修改webpack.test.conf.jsentry如下:

entry:{
    app:[path.resolve(__dirname, 'src/index.js')],
    vendor: ["jquery","vue",'vue-router']
},

先将jquery,vue,vue-router这三个项目依赖打包到一个文件中去,先注释掉optimization配置中的runtimeChunk配置项。

执行yarn test,这里我配置了BundleAnalyzerPlugin,终端中会输出这样一句话Webpack Bundle Analyzer is started at http://127.0.0.1:9528 在浏览器中打开这个链接地址,就可以看到,打包的项目各js文件中的依赖关系,可以看到在vendor.jsapp.js中都打包了jquery,这说明我们的打包是正确的,app.js中没有vue 是因为在页面中并没有引入vue,实际项目是肯定会引入的。

我们需要实现的是,vendor.js中存放所有我们引入的第三方库,而其他页面中的js文件不再再次打包这些文件,这样才是正确的打包

其实也简单,我们需要修改optimization的配置:

optimization: {
    splitChunks: {
        cacheGroups: {
            a: {
                chunks:'initial',
                test: /[\\/]node_modules[\\/]/,
                name: 'vendor',
                priority: 10,
                enforce: true,
            },
        }
    },
    // runtimeChunk: {
    //     name: 'manifest'
    // },
},

这里我们只是为cacheGroups.vendor新增了配置chunks:'initial',这里是只针对初始化模块,默认是all,具体配置项,可看第14章。

改完之后,再次运行yarn test 控制台可以看到前后两次打包的app.jsvendor.js的体积大小,打开http://127.0.0.1:9528看文件依赖关系,一目了然,app.js已经没有了jquery

但是这依然有一个问题,从控制台看两次打包文件后缀的hash值可以发现,两次打包的hash值都发生了变化,但是其实并没有修改任何js文件,hash值的变化,说明webpack每次都重新打包了这些js文件,但是我们没有做修改的文件,再次打包,那就是在浪费时间,项目大的话,打包速度可能会非常非常慢,这里我们要实现每次只打包有修改的文件,未做修改的文件,依然沿用上次打包的文件。

这个也非常简单,我们取消对optimization.runtimeChunk的注释,先运行一遍yarn test,可以看到,这时是多了一个叫manifest.js的文件,然后我们修改src/index.js文件,随便添加点什么东西~有修改就行,然后再次执行yarn test

比较这两次打包的文件hash值变化,会发现,vendor.jshash值并未发生变化,这说明我们的目的达到了。

那么到此我们就初步优化了打包速度,以及打包后文件体积大小的问题了

再配合expose-loader,个人认为,已经算是比较完美的方法了,经非常大程度上对引入及打包拆分做了优化。

当然项目还能继续优化拆分,对本项目来说,我们用到了superSlide这个插件,也想让它拆分出来,单独打包,我们需要做的就是:

  1. 新增入口配置:
entry:{
    app:[path.resolve(__dirname, 'src/index.js')],
    superSlide: [path.resolve(__dirname, 'src/assets/js/jquery.SuperSlide.2.1.1.js')],
    vendor: ["jquery","vue",'vue-router']
},
  1. 新增缓存组:
cacheGroups: {
    a: {
    chunks:'initial',
    test: /[\\/]node_modules[\\/]/,
    name: 'vendor',
    priority: 10,
    enforce: true,
    },
    b: {
    chunks:'initial',
    test: /[\\/]src[\\/]assets[\\/]js[\\/]/,
    name: 'superSlide',
    priority: 10,
    enforce: true,
    },
}

需要注意这里test需要匹配到该插件的目录所在位置,否则拆分可不会成功~

到此,我们再次执行yarn test,然后运行http://127.0.0.1:9528,可以看到,app.js中已经不存在superSlide的依赖了,而是被单独打包成一个独立文件,app.js体积再次缩小~

对于webpack打包优化的方法,网上有一种使用 DllPluginDllReferencePlugin 配合来提取那些我们不需要经常更新,并且每个页面都有引用的第三方库的方法,并且做到不让其他模块的变化污染dll库的hash缓存,在webpack4中我们不需要使用者两个插件,也能实现这种效果,我们只需要配置好optimization的配置即可。

首先说一个项目我们往往都会抽离的几个chunk包:

  1. common:将被多个页面同时引用的依赖包打到一个包中,一般都是引入2次以上,即打入该包中。也可以根据自己项目的页面数量来调整
  2. dll:这个就是网上使用 DllPluginDllReferencePlugin 配合打出的包。
  3. manifest: webpack运行时代码,每当依赖包变化,webpack运行时代码也会变化,这个需要单独抽离,以减少common包的hash值变化的可能性
  4. 页面入口文件对应的app.js

我们要实现的效果是,项目的每次迭代发布,尽量减少 chunk hash值得改变,那么以上这些要求,在webpack4我们都可以通过optimization的配置项来实现。

runtimeChunk

webpack4中,抽离manifest,只需要配置runtimeChunk即可:

optimization: {
    runtimeChunk: {
        name: 'manifest'
    },
},

splitChunks

这个配置,可以让我们以一定的规则抽离想要的包,其中的cacheGroups字段,每增加一个key值,就相当于多一个抽包规则。

该配置的maxInitialRequests字段,表示在一个入口中,最大初始请求chunk数(不包含按需加载的,也就是页面中通过script引入的chunk),默认是3个,但是我们抽离的包common,dll,manifest,app.js,一个页面最少引入4个js 所以这个也需要重新配置,修改我们现有的配置,修改后如下:

entry:{
    app:[path.resolve(__dirname, 'src/index.js')],
    superSlide: [path.resolve(__dirname, 'src/assets/js/jquery.SuperSlide.2.1.1.js')],
},

optimization: {
    splitChunks: {
        maxInitialRequests: 6,
        cacheGroups: {
            dll: {
                chunks:'all',
                test: /[\\/]node_modules[\\/](jquery|vue|vue-router)[\\/]/,
                name: 'dll',
                priority: 2,
                enforce: true,
                reuseExistingChunk: true
            },
            superSlide: {
                chunks:'all',
                test: /[\\/]src[\\/]assets[\\/]js[\\/]/,
                name: 'superSlide',
                priority: 1,
                enforce: true,
                reuseExistingChunk: true
            },
            commons: {
                name: 'commons',
                minChunks: 2,//Math.ceil(pages.length / 3), 当你有多个页面时,获取pages.length,至少被1/3页面的引入才打入common包
                chunks:'all',
                reuseExistingChunk: true
            }
        }
    },
    runtimeChunk: {
        name: 'manifest'
    },
},

修改过后,执行yarn test 再次查看http://127.0.0.1:9528,发现core-js被多次打包,本项目在引用superSlide插件时,因为没有core-js而报错,我们下载了该插件,此时它也属于项目依赖包,我们也需要将其打包到dll中,修改dll缓存组配置:

dll: {
    chunks:'all',
    test: /[\\/]node_modules[\\/](jquery|core-js|vue|vue-router)[\\/]/,
    name: 'dll',
    priority: 2,
    enforce: true,
    reuseExistingChunk: true
},

再次执行yarn test,可以看到,core-js 已经被提取到dll包中去了,我们看包依赖发现,虽然我们配置了vue vue-router 但是在打包后,项目中并没有打包这两个库的任何文件,那是因为虽然我们配置了这两个库需要打包到dll中,但实际项目中,我们并没有做任何引用~,所以不会打包进项目。

那么到这里拆分打包就算比较完善了,但是实际上,它还会有其他问题。

module chunk moduleIds namedChunks

前端经常说到模块化,也就是module,而webpack打包又有chunk的概念,webpackxxxModuleIdsPlugin以及xxxChunksPlugin这些插件,那么在webpackmodulechunk到底是一种什么样的关系呢?

  • chunk: 是指代码中引用的文件(如:jscss、图片等)会根据配置合并为一个或多个包,我们称一个包为 chunk
  • module: 是指将代码按照功能拆分,分解成离散功能块。拆分后的代码块就叫做 module。可以简单的理解为一个 export/import 就是一个 module

每个chunk包可以包含多个module,比如我们打包的dll.xxxxxxxx.jschunkiddll,包含了jquery,core-js两个module

一个module 还能跨chunk引用另一个module,也就是跨js引用功能块。

webpack 内部维护了一个自增的 id,每个 module 都有一个 id。所以当增加或者删除 module 的时候,id 就会变化,导致其它文件虽然没有变化,但由于 id 被强占,只能自增或者自减,导致整个 id 的顺序都错乱了。

我们上面配置runtimeChunk时,保证了hash的稳定性,但是在chunk包内部的moduleid因为webpack的这个机制,在我们对包做增删操作时,其实会有所改变,进而可能影响所有 chunkcontent hash 值,这就会导致缓存失效,为了解决这个问题,我们不用它的自增id就行了,改为使用它的hashid,在webpack4中,我们只需要这样配置就行:

optimization: {
    moduleIds: 'hashed',
}

那么除了moduleId,每个分离出的chunk也有其chunkId,同样的,在webpack4中,我们只需配置如下参数即可:

optimization: {
    namedChunks: true,
}

至此,项目的打包拆分就算趋近于完美了~,到这里后的optimization配置如下:

optimization: {
    namedChunks: true,
    moduleIds: 'hashed',
    splitChunks: {
        maxInitialRequests: 6,
        cacheGroups: {
            dll: {
                chunks:'all',
                test: /[\\/]node_modules[\\/](jquery|core-js|vue|vue-router)[\\/]/,
                name: 'dll',
                priority: 2,
                enforce: true,
                reuseExistingChunk: true
            },
            superSlide: {
                chunks:'all',
                test: /[\\/]src[\\/]assets[\\/]js[\\/]/,
                name: 'superSlide',
                priority: 1,
                enforce: true,
                reuseExistingChunk: true
            },
            commons: {
                name: 'commons',
                minChunks: 2,//Math.ceil(pages.length / 3), 当你有多个页面时,获取pages.length,至少被1/3页面的引入才打入common包
                chunks:'all',
                reuseExistingChunk: true
            }
        }
    },
    runtimeChunk: {
        name: 'manifest'
    },
},

此时的webpack配置,已经可以说是非常完整了,剩下的除了eslint的支持,就是与各框架的搭配配置了~