webpack多页应用架构系列(十一):预打包Dll,实现webpack音速编译

本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。
原文地址:https://segmentfault.com/a/1190000007104372
如果您对本系列文章感兴趣,欢迎关注订阅这里:https://segmentfault.com/blog/array_huang

前言

书承上文《webpack多页应用架构系列(十):如何打造一个自定义的bootstrap》。

上文说到我们利用webpack来打包一个可配置的bootstrap,但文末留下一个问题:由于bootstrap十分庞大,因此每次编译都要耗费大部分的时间在打包bootstrap这一块,而换来的仅仅是配置的便利,十分不划算。

我也并非是故意卖关子,这的确是我自己开发中碰到的问题,而在撰写完该文后,我立即着手探索解决之道。终于,发现了webpack这一大杀器:DllPlugin&DllReferencePlugin,打包时间过长的问题得到完美解决。

解决方案的机制和原理

DllPlugin&DllReferencePlugin这一方案,实际上也是属于代码分割的范畴,但与CommonsChunkPlugin不一样的是,它不仅仅是把公用代码提取出来放到一个独立的文件供不同的页面来使用,它更重要的一点是:把公用代码和它的使用者(业务代码)从编译这一步就分离出来,换句话说,我们可以分别来编译公用代码和业务代码了。这有什么好处呢?很简单,业务代码常改,而公用代码不常改,那么,我们在日常修改业务代码的过程中,就可以省出编译公用代码那一部分所耗费的时间了(是不是马上就联想到坑爹的bootstrap了呢)。

整个过程大概是这样的:

  1. 利用DllPlugin把公用代码打包成一个“Dll文件”(其实本质上还是js,只是套用概念而已);除了Dll文件外,DllPlugin还会生成一个manifest.json文件作为公用代码的索引供DllReferencePlugin使用。
  2. 在业务代码的webpack配置文件中配置好DllReferencePlugin并进行编译,达到利用DllReferencePlugin让业务代码和Dll文件实现关联的目的。
  3. 在各个页面<head>中,先加载Dll文件,再加载业务代码文件。

适用范围

Dll文件里只适合放置不常改动的代码,比如说第三方库(谁也不会有事无事就升级一下第三方库吧),尤其是本身就庞大或者依赖众多的库。如果你自己整理了一套成熟的框架,开发项目时只需要在上面添砖加瓦的,那么也可以把这套框架也打包进Dll文件里,甚至可以做到多个项目共用这一份Dll文件。

如何配置哪些代码需要打包进Dll文件?

我们需要专门为Dll文件建一份webpack配置文件,不能与业务代码共用同一份配置:

const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const dirVars = require('./webpack-config/base/dir-vars.config.js'); // 与业务代码共用同一份路径的配置表

module.exports = {
  output: {
    path: dirVars.dllDir,
    filename: '[name].js',
    library: '[name]', // 当前Dll的所有内容都会存放在这个参数指定变量名的一个全局变量下,注意与DllPlugin的name参数保持一致
  },
  entry: {
    /*
      指定需要打包的js模块
      或是css/less/图片/字体文件等资源,但注意要在module参数配置好相应的loader
    */
    dll: [
      'jquery', '!!bootstrap-webpack!bootstrapConfig',
      'metisMenu/metisMenu.min', 'metisMenu/metisMenu.min.css',
    ],
  },
  plugins: [
    new webpack.DllPlugin({
      path: 'manifest.json', // 本Dll文件中各模块的索引,供DllReferencePlugin读取使用
      name: '[name]',  // 当前Dll的所有内容都会存放在这个参数指定变量名的一个全局变量下,注意与参数output.library保持一致
      context: dirVars.staticRootDir, // 指定一个路径作为上下文环境,需要与DllReferencePlugin的context参数保持一致,建议统一设置为项目根目录
    }),
    /* 跟业务代码一样,该兼容的还是得兼容 */
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
      'window.jQuery': 'jquery',
      'window.$': 'jquery',
    }),
    new ExtractTextPlugin('[name].css'), // 打包css/less的时候会用到ExtractTextPlugin
  ],
  module: require('./webpack-config/module.config.js'), // 沿用业务代码的module配置
  resolve: require('./webpack-config/resolve.config.js'), // 沿用业务代码的resolve配置
};

如何编译Dll文件?

编译Dll文件的代码实际上跟编译业务代码是一样的,记得利用--config指定上述专供Dll使用的webpack配置文件就好了:

$ webpack --progress --colors --config ./webpack-dll.config.js

另外,建议可以把该语句写到npm scripts里,好记一点哈。

如何让业务代码关联Dll文件?

我们需要在供编译业务代码的webpack配置文件里设好DllReferencePlugin的配置项:

new webpack.DllReferencePlugin({
  context: dirVars.staticRootDir, // 指定一个路径作为上下文环境,需要与DllPlugin的context参数保持一致,建议统一设置为项目根目录
  manifest: require('../../manifest.json'), // 指定manifest.json
  name: 'dll',  // 当前Dll的所有内容都会存放在这个参数指定变量名的一个全局变量下,注意与DllPlugin的name参数保持一致
});

配置好DllReferencePlugin了以后,正常编译业务代码即可。不过要注意,必须要先编译Dll并生成manifest.json后再编译业务代码;而以后每次修改Dll并重新编译后,也要重新编译一下业务代码。

如何在业务代码里使用Dll文件打包的module/资源?

不需要刻意做些什么,该怎么require就怎么require,webpack都会帮你处理好的了。

如何整合Dll?

在每个页面里,都要按这个顺序来加载js文件:Dll文件 => CommonsChunkPlugin生成的公用chunk文件(如果没用CommonsChunkPlugin那就忽略啦) => 页面本身的入口文件。

有两个注意事项:

  • 如果你是像我一样利用HtmlWebpackPlugin来生成HTML并自动加载chunk的话,请务必在<head>里手写<script>来加载Dll文件。
  • 为了完全分离源文件和编译后生成的文件,也为了方便在编译前可以清空build目录,不应直接把Dll文件编译生成到build目录里,我建议可以先生成到源文件src目录里,再用file-loader给原封不动搬运过去。

光说不练假把式,来个跑分啊大兄弟!

下面以我的脚手架项目Array-Huang/webpack-seed为例,测试一下(使用开发环境的webpack配置文件webpack.dev.config.js)使用这套Dll方案前后的webpack编译时间:

  • 使用Dll方案前的编译时间为:10秒17
  • 使用Dll方案后的编译时间为:4秒29

由于该项目只是一个脚手架,涉及到的第三方库并不多,我只把jQuery、bootstrap、metisMenu给打包进Dll文件里了,尽管如此,还是差了将近6秒了,相信在实际项目中,这套DllPlugin&DllReferencePlugin的方案能为你省下更多的时间来找女朋友(大误)。

示例代码

诸位看本系列文章,搭配我在Github上的脚手架项目食用更佳哦(笑):Array-Huang/webpack-seedhttps://github.com/Array-Huang/webpack-seed)。

附系列文章目录(同步更新)

本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。
原文地址:https://segmentfault.com/a/1190000007104372
如果您对本系列文章感兴趣,欢迎关注订阅这里:https://segmentfault.com/blog/array_huang

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

推荐阅读更多精彩内容