splitChunks精细控制代码分割降低包大小

目录
  • 背景
  • 问题测验
  • 代码分割的三种方式
  • splitChunks 代码拆分
    • splitChunks 默认配置
    • 理解 chunks
    • 理解 maxInitialRequests
    • 理解 maxAsyncRequests
    • 理解 minChunks
    • 理解 cache groups
  • 总结

背景

前端小伙伴都知道,为了降低包大小,经常会把依赖的前端模块独立打包,比如把 vuevue-router 打到一个单独的包 vendor 中。另外,常会将存在多个路由的复杂页面的每个页面都单独打一个包,只有访问某个页面的时候,再去下载该页面的js包,以此来加快首页的渲染。

无论是 react 还是 vue 都提供了完善的工具,帮我们屏蔽了繁琐的配置工作。当我们对代码进行构建时,已经自动帮我们完成了代码的拆分工作。

所以,很多小伙伴并不知道背后到底发生了什么事。至于为什么这么拆分,到底如何控制代码的拆分,更是一头雾水了。

问题测验

讲解开始之前,大家先看一个问题。如果你已经知道问题的答案,而且明白为什么,就不必往下阅读了。如果不知道答案或者知道答案,但不知道原因。那么,强烈建议阅读本文。

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  entry: { app: "./src/index.js" },
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist")
  },
  optimization: {
    splitChunks: {
      chunks: "all"
    }
  },
  plugins: [
    new HtmlWebpackPlugin()
  ]
};
// index.js
import "vue"
import(/*webpackChunkName: 'a' */ "./a");
import(/*webpackChunkName: 'b' */ "./b");
// a.js
import "vue-router";
import "./someModule"; // 模块大小大于30kb
// b.js
import "vuex";
import "./someModule"; // 模块大小大于30kb
// someModule.js
// 该模块大小超过30kb
// ...

代码分割的三种方式

webpack 中以下三种常见的代码分割方式:

  • 入口起点:使用 entry 配置手动地分离代码。
  • 动态导入:通过模块的内联函数调用来分离代码。
  • 防止重复:使用 splitChunks 去重和分离 chunk。 第一种方式,很简单,只需要在 entry 里配置多个入口即可:
entry: { app: "./index.js", app1: "./index1.js" }

第二种方式,就是在代码中自动将使用 import() 加载的模块分离成独立的包:

//...
import("./a");
//...

第三种方式,是使用 splitChunks 插件,配置分离规则,然后 webpack 自动将满足规则的 chunk 分离。一切都是自动完成的。

前两种拆分方式,很容易理解。本文主要针对第三种方式进行讨论。

splitChunks 代码拆分

splitChunks 默认配置

splitChunks: {
    // 表示选择哪些 chunks 进行分割,可选值有:async,initial和all
    chunks: "async",
    // 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。
    minSize: 30000,
    // 表示一个模块至少应被minChunks个chunk所包含才能分割。默认为1。
    minChunks: 1,
    // 表示按需加载文件时,并行请求的最大数目。默认为5。
    maxAsyncRequests: 5,
    // 表示加载入口文件时,并行请求的最大数目。默认为3。
    maxInitialRequests: 3,
    // 表示拆分出的chunk的名称连接符。默认为~。如chunk~vendors.js
    automaticNameDelimiter: '~',
    // 设置chunk的文件名。默认为true。当为true时,splitChunks基于chunk和cacheGroups的key自动命名。
    name: true,
    // cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块,就分配到该组。模块可以被多个组引用,但最终会根据priority来决定打包到哪个组中。默认将所有来自 node_modules目录的模块打包至vendors组,将两个以上的chunk所共享的模块打包至default组。
    cacheGroups: {
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        },
        //
    default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
        }
    }
}

以上配置,概括如下4个条件:

  • 模块在代码中被复用或者来自 node_modules 文件夹
  • 模块的体积大于等于30kb(压缩之前)
  • 当按需加载 chunks 时,并行请求的最大数量不能超过5
  • 初始页面加载时,并行请求的最大数量不能超过将3
// index.js
import("./a");
// ...
// a.js
import "vue";
// ...

以上代码,在默认配置下的构建结果如下:

原因分析

  • index.js 作为入口文件,属于入口起点手动配置分割代码的情况,因此会独立打包。(app.js)
  • a.js 通过 import() 进行加载,属于动态导入的情况,因此会独立打出一个包。(1.js)
  • vue 来自 node_modules 目录,并且大于30kb;将其从 a.js 拆出后,与 a.js 并行加载,并行加载的请求数为2,未超过默认的5;vue 拆分后,并行加载的入口文件并无增加,未超过默认的3。vue 也符合 splitChunks 的拆分条件,单独打了一个包(2.js)

理解 chunks

chunks 用以告诉 splitChunks 的作用对象,其可选值有 asyncinitialall。默认值是 async,也就是默认只选取异步加载的chunk进行代码拆分。这个我们在开头的例子里已经验证。这里我们通过两个例子来看一下当chunks的值为 initialall 时,打包结果如何。 首先将chunks值改为 initial

chunks: "initial"

构建结果如下:

原因分析:

chunks 值为 initial 时,splitChunks 的作用范围变成了非异步加载的初始 chunk,例如我们的 index.js 就是初始化的时候就存在的chunk。而 vue 模块是在异步加载的chunk a.js 中引入的,所以并不会被分离出来。

chunks 仍使用 initial, 我们对 index.jsa.js 稍作修改:

// index.js
import 'vue'
import('./a')
// a.js
console.log('a')

构建结果如下:

原因分析:

vueindex.js 直接被引入,而 index.js 是初始chunk,所以分离出来打到了 vendors~app.js 中。

能不能让 splitChunks 既处理初始chunk也处理异步chunk呢?答案是可以,只需要将 chunks 改为 all

chunks: "all"

index.jsa.js 稍作修改:

// index.js
import 'vue-router'
import('./a')
// a.js
import 'vue'
console.log('a')

构建结果如下:

原因分析:

chunks 值为 all 时,splitChunks 的处理范围包括了初始chunk和异步chunk两种场景,因此 index.js 中的 vue-router 被分拆到了 vendors~app.js 中,而异步加载的chunk a.js 中的 vue 被分拆到了 3.js 中。推荐在开发中将 chunks 设置为 all

理解 maxInitialRequests

maxIntialRequests 表示 splitChunks 在拆分chunk后,页面中需要请求的初始chunk数量不超过指定的值。所谓初始chunk,指的是页面渲染时,一开始就需要下载的js,区别于在页面加载完成后,通过异步加载的js。

splitChunks 做以下修改,其他使用默认配置:

chunks: 'initial',
maxInitialRequests: 1

对 index.js 稍作修改:

// index.js
import 'vue'

构建结果如下:

原因分析:

因为 maxInitialRequests 为1,如果 vueindex.js 中拆出的话,新创建的chunk作为初始chunk index.js 的前置依赖,是需要在页面初始化的时候就先请求的。那么初始化时的请求数变成了2,因此不满足拆分条件,所以 splitChunks 没有对 index.js 进行拆分。

理解 maxAsyncRequests

maxInitialRequests 相对,maxAsyncRequests 表示 splitChunks 在拆分chunk后,并行加载的异步 chunk 数不超过指定的值。

splitChunks 做以下修改,其他使用默认配置:

maxAsyncRequests: 1

index.js 稍作修改:

// index.js
import('./a')
// a.js
import 'vue'
console.log('a')

构建结果如下:

原因分析: 因为 maxAsyncRequests 为1,由于 a.js 是通过 import() 异步加载的,此时并行的异步请求数是1。如果将 vuea.js 中拆出的话,拆出的包也将成为一个异步请求chunk。这样的话,当异步请求 a.js 的时候,并行请求数有2个。因此,不满足拆分条件,所以 splitChunks 没有对 a.js 进行拆分。

理解 minChunks

minChunks 表示一个模块至少应被指定个数的 chunk 所共享才能分割。默认为1。

splitChunks 做以下修改,其他使用默认配置:

chunks: 'all',
minChunks: 2

index.js 稍作修改:

// index.js
import 'vue'

构建结果如下:

原因分析:

因为 minChunks 为 2,所以只有当 vue 至少被2个 chunk 所共享时,才会被拆分出来。

思考题

请问如下代码,构建结果是什么?

chunks: 'all',
minChunks: 2
// index.js
import 'vue'
import './a'
// a.js
import 'vue'
console.log('a')

理解 cache groups

cacheGroups 继承 splitChunks 里的所有属性的值,如 chunksminSizeminChunksmaxAsyncRequestsmaxInitialRequestsautomaticNameDelimitername ,我们还可以在 cacheGroups 中重新赋值,覆盖 splitChunks 的值。另外,还有一些属性只能在 cacheGroups 中使用:testpriorityreuseExistingChunk

通过 cacheGroups,我们可以定义自定义 chunk 组,通过 test 条件对模块进行过滤,符合条件的模块分配到相同的组。

cacheGroups 有两个默认的组,一个是 vendors,将所有来自 node_modules 目录的模块;一个 default,包含了由两个以上的 chunk 所共享的模块。

前面的例子中,你可能注意到了怎么有的拆分出的chunk名字这么奇怪,例如 vendors~app(默认由 cacheGroups 中组的 key + 源chunk名组成)。我们看一下如何自定义拆分出的chunk名。

首先找到该chunk所属的分组,该例为 vendors 分组,作如下修改,其他使用默认配置:

chunks:'all',
cacheGroups: {
    vendors: {
      test: /[\\/]node_modules[\\/]/,
      name: "customName",
      priority: -10
    }
}

对 index.js 稍作修改:

// index.js
import 'vue'

构建结果如下:

原因分析:

vue 来自 node_modules 目录,被分配到了默认的 vendors 组中,如果不指定 name 的话,会使用默认的chunk名,这里我们指定了 name,因此最终的chunk名为customName

模块还可以分配到多个不同的组,但最终会根据 priority 优先级决定打包到哪个 chunk。

新增一个分组:

chunks:'all',
cacheGroups: {
    vendors: {
      test: /[\\/]node_modules[\\/]/,
      name: "customName",
      priority: -10
    },
    customGroup: {
      test: /[\\/]node_modules[\\/]/,
      name: "customName1",
      priority: 0
    }
}

构建结果:

原因分析:

虽然 vendorscustomGroup 这个两个组的条件都符合,但由于后者的优先级更高,所以最终将 vue 打包到了 customName1.js 中。

总结

讲解到这里,想必你对 webpack 如何进行代码分割有了深刻地理解了。对于文章开头的问题,可以给出你的答案了吧?更多关于splitChunks代码分割的资料请关注我们其它相关文章!

(0)

相关推荐

  • 浅谈webpack SplitChunksPlugin实用指南

    提到前端打包工具,毫无疑问想先到的是webpack.但是前端发展地很快,时不时会有新东西出现,打包工具这边之前也出现parcel和rollup.各种工具的碰撞,相互汲取优点,促进技术的发展. webpack4中支持了零配置的特性,同时对块打包也做了优化, CommonsChunkPlugin 已经被移除了,现在是使用 optimization.splitChunks 代替. 下面就开始介绍splitChunks的内容. 默认情况 首先webpack会根据下述条件自动进行代码块分割: 新代码块可以

  • Webpack中SplitChunksPlugin 配置参数详解

    代码分割本身和 webpack 没有什么关系,但是由于使用 webpack 可以非常轻松地实现代码分割,所以提到代码分割首先就会想到使用 webopack 实现. 在 webpack 中是使用 SplitChunksPlugin 来实现的,由于 SplitChunksPlugin 配置参数众多,接下来就来梳理一下这些配置参数. 官网上的默认配置参数如下: module.exports = { //... optimization: { splitChunks: { chunks: 'async'

  • webpack4 SplitChunks实现代码分隔详解

    代码均放在 git仓库 Webpack 4给我们带来了一些改变.包括更快的打包速度,引入了SplitChunksPlugin插件来取代(之前版本里的)CommonsChunksPlugin插件.在这篇文章中,你将学习如何分割你的输出代码,从而提升我们应用的性能. SplitChunks插件( webpack 4.x以前使用CommonsChunkPlugin )允许我们将公共依赖项提取到现有的 entry chunk 或全新的代码块中. 代码分割的理念 首先搞明白: webpack里的代码分割是

  • 详解webpack4之splitchunksPlugin代码包分拆

    本文讲解的是最近在做的一个多页面项目,结合webpack4的splitChunks进行代码包分拆的过程 项目框架 项目有home和topic两个入口文件,主要包括: react.mobx.antd作为项目的基本框架, echarts和d3(画图)是项目中部分页面用到比较大的组件库 src下的工作的组件和代码 其他的非公共代码. 两个入口文件都用react-loadable做了异步加载 import Loadable from 'react-loadable'; ... const Loadabl

  • webpack4之SplitChunksPlugin使用指南

    写在前面 前面写了一篇有关webpack4的不完全升级指南以及在webpack3.x迁移的时候遇到的问题,有兴许可以看一下. 0. 参数介绍 先对参数有一个大概的认识,虽然撸了很多遍官方的更新文档,但是还是去参看了一下新的wbepack源码,下面是各种参数及含义: chunks: 表示显示块的范围,有三个可选值:initial(初始块).async(按需加载块).all(全部块),默认为all; minSize: 表示在压缩前的最小模块大小,默认为0: minChunks: 表示被引用次数,默认

  • splitChunks精细控制代码分割降低包大小

    目录 背景 问题测验 代码分割的三种方式 splitChunks 代码拆分 splitChunks 默认配置 理解 chunks 理解 maxInitialRequests 理解 maxAsyncRequests 理解 minChunks 理解 cache groups 总结 背景 前端小伙伴都知道,为了降低包大小,经常会把依赖的前端模块独立打包,比如把 vue.vue-router 打到一个单独的包 vendor 中.另外,常会将存在多个路由的复杂页面的每个页面都单独打一个包,只有访问某个页面

  • webpack优化之代码分割与公共代码提取详解

    前言 开发多页应用的时候,如果不对webpack打包进行优化,当某个模块被多个入口模块引用时,它就会被打包多次(在最终打包出来的某几个文件里,它们都会有一份相同的代码).当项目业务越来越复杂,打包出来的代码会非常冗余,文件体积会非常庞大.大体积文件会增加编译时间,影响开发效率:如果直接上线,还会拉长请求和加载时长,影响网站体验.作为一个追求极致体验的攻城狮,是不能忍的.所以在多页应用中优化打包尤为必要.那么如何优化webpack打包呢? 一.概念 在一切开始前,有必要先理清一下这三个概念: mo

  • Vue包大小优化的实现(从1.72M到94K)

    一.背景 最近做了一个网站,uidea,是用来辅助独立开发者做一些 UI 设计的,当时只管开发,等部署完以后,发现访问速度堪忧 毕竟是个小水管服务器,相比提高带宽,还是先看看代码上能不能优化一下,性价比更高 这个是优化前的包大小,这家伙都上 1.72 M 了,小水管加载时间直接往 3s 以上走了,臣妾扛不住啊 二.目标 这必须得优化一下,优化前得大致定一下目标,目标又需要指标来衡量,所以定了两个指标: 页面加载时间不多说,至少得 1s 以内,越快越好 包大小控制在 200k 以内 为什么定这两个

  • Webpack3+React16代码分割的实现

    项目背景 最近项目里有个webpack版本较老的项目,由于升级和换框架暂时不被leader层接受o(╥﹏╥)o,只能在现有条件进行优化. webpack3 + react16 webpack v3配置检查 很明显项目的配置是从v1继承过来的,v1->v3的升级较为简单,参考官网https://webpack.js.org/migrate/3/即可. loaders变为rules 不再支持链式写法的loader,json-loader不需要配置 UglifyJsPlugin插件需要自己开启mini

  • webpack学习笔记之代码分割和按需加载的实例详解

    本文介绍了webpack学习笔记之代码分割和按需加载的实例详解,分享给大家,也给自己留个笔记 为什么需要代码分割和按需加载 代码分割就是我们根据实际业务需求将代码进行分割,然后在合适的时候在将其加载进入文档中. 举个简单的例子: 1.一个HTML中存在一个按钮 2.点击按钮出现一个包着图片的div 3.点击关闭按钮图片消失 Demo目录: 一.当未点击按钮时浏览器只加载了对入口文件打包后的js 二.点击按钮会对组件进行异步加载 这个clichunk就是我们打包好的click组件,包括相应的JS逻

  • Asp.NET控制文件上传的大小方法(超简单)

    在web.config中的system.web 节点下添加如下代码: 第2行的maxRequestLength="8192",这里限制最大为8MB,可以自行设置.executionTimeout="800",executionTimeout预设(即默认)是 90 秒 <system.web> <httpRuntime maxRequestLength="8192" executionTimeout="800"

  • Java编程访问权限的控制代码详解

    本文研究的主要是Java编程访问权限的控制的相关内容,具体介绍如下. 之前没去注意的修饰符,一般变量前面没添加,一个是不知道有什么用,一个是懒,后面遇到项目的时候就会发现私有和公有区别还是很大的. (1)首先是包名 使用一个类的时候,例如集合类,就需要引入这个包,然后再使用该包下面的类.如: package com.myown.iaiti; public class Print { static void print(String s){ System.out.println(s); } } 自

  • vue代码分割的实现(codesplit)

    在vue单页应用中,若不做任何处理,所有vue文件会打包为一个文件,这个文件非常的大,造成网页在首次进入时比较缓慢.做了代码分割后,会将代码分离到不同的bundle中,然后进行按需加载这些文件,能够提高页面首次进入的速度,网站性能也能够得到提升. 一.未分割时浏览器加载js的情况 可以看到,只有一个app.js,大小为595kb,若在实际的大型项目中,这个大小会更大 二.做了代码分割后浏览器加载js情况 发现多了一个js文件,且app.js大小也变小了,下面看看点击到其他页面时加载情况 点到其他

  • R语言入门使用RStudio制作包含Rcpp代码的R包

    目录 1. 创建项目 2. 修改一些文件 3. 打包 4. 使用Eigen或其它依赖库会出现的问题 前面博客中有提及,当我们进行模拟想要再次进行提速时,通常都会使用Rcpp将我们的R代码改成C++代码.具体Rcpp的使用可参考博客:Rcpp入门R代码提速方法过程,R语言学习RcppEigen进行矩阵运算. 平时在我们使用的时候,直接使用Rcpp::sourceCpp()就可以直接将我们的C++代码中的函数进行导入,这不会遇到什么问题,但如果我们想要使用snowfall进行并行时就不能再这样做了.

  • React中代码分割的4种实现方式

    目录 前言 import() React.lazy import() + React Loadable UmiJS 按需加载 总结 前言 在 React 应用中,我们通常的做法是直接将某个模块导入到页面中,这样做导致的结果是打包出来的包体积过大.尤其是在引入了体积巨大的第三个库的情况下,打包后的包体积会十分巨大.因此,我们需要关注我们的应用中所包含的代码,以避免因体积过大而导致加载时间过长. 对代码进行分割能够“懒加载”当前用户所需要的内容,能够显著提高应用的性能.尽管并没有减少应用的整体代码体

随机推荐