CommonsChunkPlugin学习小结

Chunk

首先弄明白chunk是什么东西:webpack将多个模块打包之后的代码集合称为chunk。

webpack里, chunk有三种类型:

  1. entry chunk: 含有webpack runtime代码的模块代码集合。
  2. normal chunk:不含runtime代码的模块集合。
  3. initial chunk:文档里讲是一种特殊的normal chunk。 在加载的时候顺序会在normal chunk前面(这个有兴趣的同学可以深入了解一下)。

另外有几点需要注意的:

  1. entry chunk是必须要先于normal chunk加载的,因为里面包含的runtime代码定义了一些列webpack要用到的函数,不事先加载好,后面的代码webpack就没法玩了。
  2. 每一个entry point都会对应生成一个entry chunk。
  3. 每一个用import()懒加载的模块会对应生成一个normal chunk,这个chunk会依赖于调用import()的entry chunk,成为其child.

CommonsChunkPlugin

先贴一段官网自己的介绍:

The CommonsChunkPlugin is an opt-in feature that creates a separate file (known as a chunk), consisting of common modules shared between multiple entry points. By separating common modules from bundles, the resulting chunked file can be loaded once initially, and stored in cache for later use. This results in page speed optimizations as the browser can quickly serve the shared code from cache, rather than being forced to load a larger bundle whenever a new page is visited.

理解下来大概就是说:webpack打包的代码都是以chunk的形式存储的。但是呢,不同chunk里可能存在相同的模块,CommonsChunkplugin呢,就是把这些不同chunk里重复的模块提取出来放到一个公共chunk里。这个公共chunk只需要下载一次,就可以让所有的chunk都使用了。而且这部分代码可以放到缓存里,这样以后就不用再下载了(另外有写关于用webpack做缓存的文章,有兴趣可以看看)。而且这么做每个chunk的代码也少了,所以每次加载的速度也更快。

那CommonsChunkplugin怎么用呢?

常用参数:

  1. 决定生成chunk的参数: name, names, async
    name: string: 公共chunk的名字。如果传入一个已经存在的chunk名,那这个chunk就作为公共chunk存放提取出来的公共代码.否则webpack会新建一个公共chunk。
    names: string[]: 和name一样,不过传入的是一个数组。相当于对数组中的每个元素做一次代码切割。
    async: boolean|string: 把公共代码提取到一个懒加载的chunk,在被使用到时才进行下载,当传入值为string的时候,该值会被用来当做懒加载chunk的名字。目前来看一般都是配合children使用(entry chunk在app初始化的时候就会被加载,增加async标签没什么意义)。

  2. 决定被提取的chunk: chunks, children, deepChildren
    chunks: string[]: webpack会从传入的chunk里面提取公共代码,如果不传则从所有的entry chunk中提取。
    children: boolean : 当不设置children(deepChildren)的时候,webpack会从entry chunk中根据条件提取公共代码。 当设置children为true时,webpack会从entry chunk的直接子chunk中提取代码.
    deepChildren: boolean: 和children一样,不过选取公共chunk的所有下属节点。

  3. 决定提取条件: minChunks
    minChunks: number|infinity|function(module,count)->boolean: 如果传入数字或infinity(默认值为3),就是告诉webpack,只有当模块重复的次数大于等于该数字时,这个模块才会被提取出来。当传入为函数时,所有符合条件的chunk中的模块都会被传入该函数做计算,返回true的模块会被提取到目标chunk。

总结一下:

  • webpack里面就entry chunk, normal chunk两种,在没有手动设置chunks的情况下,如果要提取normal chunk里的公共代码,那就把children(deepChildren)设为true。否则提取的就是entry chunk。如果对webpack这两种分类不满意,那就用chunks手动指定要选取的chunk。
  • 如果你希望对打包出来的公共chunk做一个懒加载,把async设成true。
  • 通过minChunks来决定要把哪些模块提取到公共chunk

再看几个样例:

case 1
两个entry App 和 page1 都使用了 react, react-dom 和 classnames, 我们要把重复出现2次以上的module都提取到一个公共chunk vendor里:
App:

import * as React from 'react';
import * as ReactDOM from 'react-dom';

import * as axios from 'axios';
import * as classnames from 'classnames';

Page1:

import * as React from 'react';
import * as ReactDOM from 'react-dom';

import * as classnames from 'classnames';

webpack配置

        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            minChunks: 2,
        }),

!出现2次的react,react-dom,classnames都被打包到vendor里](http://upload-images.jianshu.io/upload_images/10204068-1809ef63506b15b9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

case 2
继续case1的例子,我们可以看到axios这个包依然和app的业务代码混在一起。再提取一下把他单独拉出来:

new webpack.optimize.CommonsChunkPlugin({
            name: 'axios',
            chunks: ['app'],
            minChunks: function(module) {
                return /axios/.test(module.context);
            }
        }),
axios被单独提取到axios.js

当然这里是为了写chunks的使用样例,实际操作中大可不必这样提取两次。直接在第一次提取的时候把node_modules里面的库都打到vendor里就好了(minChunks: 1也行)

case 3
子chunk存在的情况,这里我选择把子chunk提取到一个新的懒加载chunk里:
App异步引用Home,Topics,About:

import * as React from 'react';
import * as ReactDOM from 'react-dom';

import * as moment from 'axios';
import * as classnames from 'classnames';

const Home = () => import('./Home');
const Topics = () => import('./Topics');
const About = () => import('./About');

Home,Topic,About都引用mobx和moment

import * as React from 'react';
import * as moment from 'moment';
import * as mobx from 'mobx';
打包结果
new webpack.optimize.CommonsChunkPlugin({
            name: 'app',
            async: 'vendor',
            children: true,
            minChunks: 2,
        }),

如图,相当于告诉webpack,扫描app(entry chunk)直接子chunk(Home, Topics, About)里的模块,把出现次数不少于2次的提取出来放到一个叫vendor的懒加载模块中去。


打包结果

case 4
其实除了提取公共模块之外,用CommonsChunkPlugin做前端工程的代码切割也非常好用。
为了更好的利用缓存,假设我们有如下需求:

  1. webpack runtime(entry chunk): 上面提到了,runtime的代码必须先于其他代码执行。并且由于runtime代码随着module和chunk ID的变化会经常变动,所以建议单独打包出来

  2. lib(normal chunk): lib里放一些如react, react-dom, react-router等基本不会改变的基础库。

  3. vendor(normal chunk): vendor里放一些如 axios,moment等偶尔变化的工具库

  4. 业务代码(normal chunk): 随时都在变,单独放一个chunk

直接上配置:

        new webpack.optimize.CommonsChunkPlugin({
            deepChildren: true,
            async: 'async-vendor',
            minChunks: function (module) {
                return /node_modules/.test(module.context);
            }
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor',
            minChunks: function (module) {
                return /node_modules/.test(module.context);
            }
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: "lib",
            minChunks: function (module) {
                return /react/.test(module.context);
            }
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'manifest',
            minChunks: Infinity
        }),

第一次打包:从所有entry chunk(app, page1)的直接子chunk(Home,Topics,About)中提取出公共模块(mobx, moment)放入懒加载chunk async-vendor中。

第二次打包: 从所有entry chunk(app, page1)中提取出node_modules里的模块放入chunk vendor中。app, page1此时变为normal chunk。

第三次打包: 从所有entry chunk(vendor)中提取出路径含有react的模块,放入chunk lib.

第四次打包: 新建一个manifest chunk,不放入任何模块(minChunks:infinity)。由于manifest是此时唯一的entry chunk,则runtime代码放入manifest。

打包结果

如图,业务代码和lib代码,vendor工具代码等都完全分离。

参考文献

CommonsChunkPlugin

Code Splitting

Vendor and code splitting in webpack 2

webpack: Unraveling CommonsChunkPlugin

webpack bits: Getting the most out of the CommonsChunkPlugin()

Added deepChildren support from ArcEglos' pull request

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

推荐阅读更多精彩内容

  • GitChat技术杂谈 前言 本文较长,为了节省你的阅读时间,在文前列写作思路如下: 什么是 webpack,它要...
    萧玄辞阅读 12,609评论 7 110
  • 本文来自尚妆前端团队南洋发表于尚妆github博客,欢迎订阅。 分割webpack配置文件的多种方法 (一) 将你...
    尚妆产品技术刊读阅读 4,776评论 1 15
  • 前端将大型项目分成一个个单独的模块,一般封装好的每个模块都会实现一个目的明确的完成的功能。如何处理这些模块以及模块...
    pixels阅读 3,347评论 1 14
  • 目录第1章 webpack简介 11.1 webpack是什么? 11.2 官网地址 21.3 为什么使用 web...
    lemonzoey阅读 1,703评论 0 1
  • webpack 介绍 webpack 是什么 为什么引入新的打包工具 webpack 核心思想 webpack 安...
    yxsGert阅读 6,325评论 2 71