资讯专栏INFORMATION COLUMN

Webpack 的 Bundle Split 和 Code Split 区别和应用

jimhs / 2690人阅读

摘要:当然,上面的定时器完全可以换成其他诸如按钮点击之类的事件来触发。

Webpack Bundle Split 和 Code Split
话说之前也是对 chunk 这个概念有些模糊,并且很多时候网上的文章大部分在将代码分离动态加载之类的。写这篇文章的目的也是想让其他那些跟我一样曾经对这个概念不是很清楚的童鞋有个清晰的认识。废话不多说,撸起袖子直接干!

Let"s Dive in!

Webpack 文件分离包括两个部分,一个是 Bundle 的分离,一个是 Code 代码的分离:

Bundle splitting: 实际上就是创建多个更小的文件,并行加载,以获得更好的缓存效果;主要的作用就是使浏览器并行下载,提高下载速度。并且运用浏览器缓存,只有代码被修改,文件名中的哈希值改变了才会去再次加载。

Code splitting: 只加载用户最需要的部分,其余的代码都遵从懒加载的策略;主要的作用就是加快页面加载速度,不加载不必要加载的东西。

准备工作

在进行文件分离之前的准备工作,我们先写一些代码:

入口文件 src/index.js:

const { getData } = require("./main")
const { findMaxIndex } = require("./math")

let arr = [1,2,123,21,3,21,321,1]

findMaxIndex(arr)
getData("./index.html")

两个依赖模块:

src/main.js:

const axios = require("axios")

const getData = url => {
    axios.get(url).then(d => {
        console.log(d.status)
        console.log(d.data.length)
    })
}

module.exports = {
    getData
}

src/math.js:

const _ = require("lodash")

const findMaxIndex = arr => {
    let x = _.max(arr)
    let r = Array.prototype.indexOf.call(arr, x)
    console.log(r);
}

module.exports = {
    findMaxIndex
}

增加一个 webpack 配置文件 webpack.config.js:

const path = require("path")

module.exports = {
    mode: "development",
    entry: path.resolve(__dirname, "src/index.js"),
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "[name].[contenthash].js"
    },
}
文件分离之前打包效果

在 bundle split 和 code split 操作之前,我们先看一下当前默认打包的效果:

全部依赖都被打包到 main.xxx.js 中去,大小是 609k
开始分离操作 Bundle Split

Bundle Split 的主要任务是将多个引用的包和模块进行分离,避免全部依赖打包到一个文件下

基本用法

Webpack 4 中需要使用到 optimization.splitChunks 的配置:

const path = require("path")

module.exports = {
    mode: "development",
    entry: path.resolve(__dirname, "src/index.js"),
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "[name].[contenthash].js"
    },
    optimization: {
        splitChunks: {
            chunks: "all"
        }
    }
}
optimization.splitChunks 的意思是将所有来自 node_modules 中的依赖全部打包分离出来,这个时候我们再看打包的文件是个什么样子:

增加了 splitChunks 的配置,我们第三方模块都被打包到了 vendors~main.xxx.js 中去了,这个文件大小有 604k,而入口文件 main.xxx.js 则只有 7k

虽然说这样将第三方模块多带带打包出去能够减小入口文件的大小,但这样仍然是个不小的文件;这个小的测试项目中我们使用到了 axios 和 lodash 这两个第三方模块,因此我们希望的应该是将这两个模块多带带分离出来两个文件,而不是全部放到一个 vendors 中去,那么我们继续配置 webpack.config.js:

将每个 npm 包多带带分离出来

这里我们需要使用到 webpack.HashedModuleIdsPlugin 这个插件

参考官方文档

直接上代码:

const path = require("path")
const webpack = require("webpack")

module.exports = {
    mode: "development",
    entry: path.resolve(__dirname, "src/index.js"),
    plugins: [
        new webpack.HashedModuleIdsPlugin() // 根据模块的相对路径生成 HASH 作为模块 ID
    ],
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "[name].[contenthash].js"
    },
    optimization: {
        runtimeChunk: "single",
        splitChunks: {
            chunks: "all", // 默认 async 可选值 all 和 initial
            maxInitialRequests: Infinity, // 一个入口最大的并行请求数
            minSize: 0, // 避免模块体积过小而被忽略
            minChunks: 1, // 默认也是一表示最小引用次数
            cacheGroups: {
                vendor: {
                    test: /[/]node_modules[/]/, // 如果需要的依赖特别小,可以直接设置成需要打包的依赖名称
                    name(module, chunks, chcheGroupKey) { // 可提供布尔值、字符串和函数,如果是函数,可编写自定义返回值
                        const packageName = module.context.match(/[/]node_modules[/](.*?)([/]|$)/)[1] // 获取模块名称
                        return `npm.${packageName.replace("@", "")}` // 可选,一般情况下不需要将模块名称 @ 符号去除
                    }
                }
            }
        }
    }
}

这里我们主要做了几件事:

为了避免每次打包的文件哈希变化,我们可以使用 webpack 内置的 HashedModuleIdsPlugin,这样可以避免每次打包的文件哈希值变化

首先增加 maxInitialRequests 并设置成 Infinity,指定这个入口文件最大并行请求数

然后将 minSize 和 minChunks 分别设置成 0 和 1,即使模块非常小也将其提取出来,并且这个模块的引用次数只有 1 也要提取

最后配置匹配的依赖以及分离出的文件名格式

另外,我们还将运行时代码分离出来,这块代码还可以配合 InlineManifestWebpackPlugin 直接插入到 HTML 文件中。这里我们将这个配置设置成 single,即将所有chunk的运行代码打包到一个文件中

这样 Bundle Split 的操作基本就完成了,让我们看看效果如何:

所依赖的几个模块都被分离出去了

使用 HtmlWebpackPlugin 这个插件将 js 代码注入 html 文件中

npm i -D html-webpack-plugin

修改 webpack.config.js 文件:

// 配置文件引入这个插件

var HtmlWebpackPlugin = require("html-webpack-plugin");
// ...
module.exports = {
    // ...
    plugins: [
        new HtmlWebpackPlugin(),
        new webpack.HashedModuleIdsPlugin() // 根据模块的相对路径生成 HASH 作为模块 ID
    ],
    // ...
}

安装 http-server 或使用 vscode 的插件 Live Server 将代码放入一个本地服务器中,打开浏览器的调试窗口进入到 Network 面板:

可以看到我们将模块多带带分离出来并行加载,这样比多带带加载一个庞大的包要快不少,接下来我们还要进行代码分离,将不必要加载的模块延迟加载
Code Split

代码分离实际上就是只加载用户需要使用到的部分代码,不是必须的就暂时不加载。

这里我们要用到 require.ensure 这个方法去获取依赖,这样 webpack 打包之后将会增加运行时代码,在设定好的条件下才会触发获取这个依赖。

在 ES6 中我们可以用 Dynamic Imports 来替代上述方案,如果使用 ES6 语法那么需要使用到 babel 以及 babel 的插件 plugin-syntax-dynamic-import,在浏览器中为了保证兼容性,还需要安装 promise 的 polyfill,用法大同小异,可直接观摩 webpack 的官方文档

修改我们的代码:

const { getData } = require("./main")

let arr = [1,2,123,21,3,21,321,1]

getData("./index.html")

setTimeout(() => {
    require.ensure(["./math"], function(require) {
        const { findMaxIndex } = require("./math")
        console.log(findMaxIndex(arr))
    })
}, 3000)
我们设定了一个定时器,只有在 3000 毫秒以后,浏览器才会去请求这个 math 模块

编译之后,打开调试面板刷新浏览器:

在页面刚加载完毕后,浏览器会尝试获取上述这么几个模块,因为模块都很小很快就加载完成了

在 3500ms 左右,浏览器才会去获取 requie.ensure 方法定义的 math 模块,因 math 模块又包含依赖 lodash,因此这个 lodash 第三方模块也会被按需加载。

这样我们就完成了代码分离的操作,这样做的优势就是不需要第一时间加载的模块,可以推迟加载,以页面的加载速度。当然,上面的 timeout 定时器完全可以换成其他诸如按钮点击之类的事件来触发。

END

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101027.html

相关文章

  • Webpack 构建后文件变得很大?

    摘要:的和的是同步加载的,通过这些方式引入的依赖会被打包在一起,文件因而变大。而或的是按需加载异步的,对于一些可以延迟加载的模块依赖,应该用这种方式,从而避免文件太大。 上一篇回顾使用 Webpack 的动机,比较理论,本篇侧重实用。 这几篇文章的前提是 Webpack 已经入门。若无,请自行到 Webpack 官方网站的 getting start 按指引一步步实操。 啃先生(MrKenn...

    Barry_Ng 评论0 收藏0
  • 深入理解 Webpack 打包分块(上)

    摘要:而一个哈希字符串就是根据文件内容产生的签名,每当文件内容发生更改时,哈希串也就发生了更改,文件名也就随之更改。很显然这不是我们需要的,如果文件内容发生了更改,的打包文件的哈希应该发生变化,但是不应该。前言 随着前端代码需要处理的业务越来越繁重,我们不得不面临的一个问题是前端的代码体积也变得越来越庞大。这造成无论是在调式还是在上线时都需要花长时间等待编译完成,并且用户也不得不花额外的时间和带宽...

    Rocko 评论0 收藏0
  • webpack学习(四)— code splitting

    摘要:支持定义分割点,通过进行按需加载。若按照中做,则会造成通用模块重复打包。下文将详细说明。同样是利用和来处理的。如下在中添加入口其中模块为通用功能模块在中对应和这样则会打包出和两个文件。为通用功能模块。希望有更好方案的同学能够不吝赐教。 什么是code splitting 首先说,code splitting指什么。我们打包时通常会生成一个大的bundle.js(或者index,看你如...

    lsxiao 评论0 收藏0
  • webpack搭建多页面系统(三) 理解webpack.config.js四个核心概念

    摘要:关于模板的有好几种。一次安装所有的大家可以了解一些的用法把编译成。安装参考文档功能将源文件迁移到指定的目录,返回新的文件路径。安装用法它会将所有的入口中引用的移动到和页面对应的独立分离的文件。 webpack是需要自己编写自己需要的一个配置对象,取决你如何使用webpack,下面指定了所有的可用的配置选项。参考文档:https://doc.webpack-china.org... we...

    邹强 评论0 收藏0
  • webpack Code Splitting浅析

    摘要:不知大家是不是跟大雄一样之前从未看过编译产出的代码。前文大雄给了一个粗陋的动态加载的方法说白了就是动态创建标签。大雄看完至少大概知道了原来编出来的代码是那样执行的原来可以那么灵活的使用。 Code Splitting是webpack的一个重要特性,他允许你将代码打包生成多个bundle。对多页应用来说,它是必须的,因为必须要配置多个入口生成多个bundle;对于单页应用来说,如果只打包...

    Amos 评论0 收藏0

发表评论

0条评论

jimhs

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<