webpack实战——打包优化【中】

作者: 流眸Tel | 来源:发表于2020-10-19 20:39 被阅读0次

    前言

    上篇从多线程打包缩小打包作用域两个方面入手,对webpack打包层面做出优化。本篇描述从动态链接库思想方面继续深入探究打包层面的深度优化。

    动态链接库与DLLPlugin

    动态链接库(Dynamic Link Library 或者 Dynamic-link Library,缩写为 DLL),是微软公司在微软Windows操作系统中,实现共享函数库概念的一种方式。这些库函数的扩展名是 ”.dll"、".ocx"(包含ActiveX控制的库)或者 ".drv"(旧式的系统驱动程序)。

    当一段相同的子程序被多个程序调用时,为了减少内存消耗,可以将这段子程序存储为一个可执行文件,当被多个程序调用时只在内存中生成和使用同一个实例。

    今天要介绍的主角“DLLPlugin”则借鉴了动态链接库的思路,对于第三方模块或者一些不常变化的模块预先进行编译和打包,然后再项目实际构建过程中直接取用。不过区别还是有的,DLLPlugin实际生成的文件是JS文件而不是动态链接库。在打包vendor的时候还会附加生成一份vendor的模块清单,这份清单将会在工程业务模块打包时起到链接和索引的作用。

    1 vendor配置

    首先需要为动态链接库单独创建一个Webpack配置文件,例如:webpack.vendor.config.js,注意要与webpack.config.js区分开来。

    例:

    // webpack.vendor.config.js
    const path = require('path');
    const webpack = require('webpack');
    const dllAssetPath = path.join(__dirname, 'dll');
    const dllLibraryName = 'dllExample';
    
    module.exports = {
        entry: ['react'],
        output: {
            path: dllAssetPath,
            filename: 'vendor.js',
            library: dllLibraryName
        },
        plugins: [
            new webpack.DllPlugin({
                name: dllLibraryName,
                path: path.join(dllAssetPath, 'manifest.json')
            })
        ]
    }
    

    其中,entry指定了将哪些模块打包为vendor,plugins的部分引入了DLLPlugin,并有如下配置:

    • name: 导出的dll library的名字,需要与output.library的值对应;
    • path: 资源清单的绝对路径,业务打包时将会使用这个清单进行模块索引;

    2 vendor打包

    接下来就要打包vendor并且生成资源清单。为后续方便操作,可以在package.json中配置一条运行指令:

    // pachage.json
    {
        ...
        "scripts": {
            ...
            "dll": "webpack --config webpack.vendor.config.js"
        }
    }
    

    然后执行npm run dll,会发现生成了一个dll目录,里面对应有两个文件:

    • vendor.js: 库的代码
    • manifest.json: 资源清单

    感兴趣的可以打开这两个文件阅读一下。

    3 链接到业务代码

    试过之后,我们就要考虑将vendor链接到项目中去了。这里推荐与DLLPlugin配套的插件“DLLReferencePlugin”,它起到索引和链接作用。在工程的webpack配置文件中(注意是webpack.config.js,不是vendor的配置文件),通过DLLReferencePlugin来获取刚才打包好的资源清单,然后在页面中添加vendor.js就可以引用。

    // webpack.cinfig.js
    const path = require('path');
    const webpack = require('webpack');
    module.exports = {
        ...
        plugins: [
            new webpack.DllReferencePlugin({
                manifest: require(path.join(__dirname, 'dll/manifest.json')
            })
        ]
    }
    

    那么 index.html 引入会即可:

    ...
    
    <body>
    ...
    
    <script src="dll/vendor.js"></script>
    <script src="dist/app.js"></script>
    </body>
    

    设置完毕后,当页面执行到vendor.js时,会声明全局变量dllExample,而manifest相当于注入app.js的资源地图,app.js会通过name字段找到名为DLLExample的library,再进一步获取其内部模块。

    4 潜在问题

    细心的小伙伴或许已经发现了,在当前配置中会存在一个问题:当打开manifest.json文件后,可以发现每个模块都会有一个id,其值是按照数字顺序递增的,而业务代码在引用vendor中模块时也是引用这个数字id,当我们更vendor时这个数字id也会随之发生改变。

    现假设我们工程目录中有如下资源文件,并每个资源都加上了chunk hash:

    • vendor@[hasn].js
    • pageUser@[hasn].js
    • pageIndex@[hasn].js
    • util@[hasn].js

    现在vendor中you一些模块,例如包含了react,其id为5.当尝试添加更多模块到vendor中的时候,那么重新进行Dll构建时,moment.js可能出现在react之前,此时react的id会变为6.而pageUser和pageIndex是通过id进行引用的,因此他们的文件内容也发生了改变。此时我们会面临如下情况:

    1. 两个页面的chunk hash均发生了改变。这是我们不希望看到的,因为他们本身并无变化,但是vendor的改变却驱使用户不得不重新下载所有资源。
    2. 两个页面chunk hash没有改变,但是这种情况更为糟糕:vendor中的模块id改变了,但是用户没有更新缓存,使用的还是旧版本的内容,而引用不到新的vendor模块,导致页面发生错误。并且对于开发者而言,这个错误却难以排查,因为开发环境下一切正常!

    针对上述的问题2,解决方法是在打包vendor时添加上HashedModuleIdsPlugin,如下:

    // webpack.vendor.config.js
    module.exports = {
        ...
        plugins: [
            new webpack.DllPlugin({
                name: dllLibraryName,
                path: path.join(dllAssetPath, 'manifest.json')
            }),
            // 添加HashedModuleIdsPlugin
            new webpack.HashedModuleIdsPlugin();
        ]
    }
    

    HashedModuleIdsPlugin是webpack3中被引入进来的,主要就是为了解决数字id的问题。HashedModuleIdsPlugin可以把id的生成算法修改为根据模块的引用路径生成一个字符串hash。

    注:从webpack3开始,模块id不仅可以是数字,也可以是字符串。

    小结

    本篇从动态链接库思想着手,介绍了DLLPlugin与其配套插件DLLReferencePlugin使用,将第三方库与一些不常改动的模块编译打包,处理为类似于动态链接库的JS文件,以此来节约服务器资源。
    下一篇介绍打包优化最后一个环节:死代码检测与去除。

    相关文章

      网友评论

        本文标题:webpack实战——打包优化【中】

        本文链接:https://www.haomeiwen.com/subject/aeulmktx.html