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

前言

上篇从多线程打包缩小打包作用域两个方面入手,对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文件,以此来节约服务器资源。
下一篇介绍打包优化最后一个环节:死代码检测与去除。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352