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

2019 年 2 月 16 日 前端大全

(给前端大全加星标,提升前端技能


作者:Oliveryoung

https://segmentfault.com/a/1190000017893334


话说之前也是对 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(=> {

        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 这个插件

参考官方文档(https://webpack.js.org/plugins/split-chunks-plugin/#optimization-splitchunks)

直接上代码:

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(https://babeljs.io/docs/en/babel-plugin-syntax-dynamic-import/#installation),在浏览器中为了保证兼容性,还需要安装 promise 的 polyfill,用法大同小异,可直接观摩 webpack 的官方文档(https://webpack.js.org/guides/code-splitting/#dynamic-imports)

修改我们的代码:

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 定时器完全可以换成其他诸如按钮点击之类的事件来触发。


推荐阅读

(点击标题可跳转阅读)

webpack 启动代码源码解读

深入理解 webpack 文件打包机制

手摸手,带你用合理的姿势使用webpack4(下)



觉得本文对你有帮助?请分享给更多人

关注「前端大全」加星标,提升前端技能

喜欢就点一下「好看」呗~

登录查看更多
1

相关内容

Bundle tool for the front-end
【实用书】Python技术手册,第三版767页pdf
专知会员服务
234+阅读 · 2020年5月21日
【干货书】流畅Python,766页pdf,中英文版
专知会员服务
224+阅读 · 2020年3月22日
100+篇《自监督学习(Self-Supervised Learning)》论文最新合集
专知会员服务
164+阅读 · 2020年3月18日
《代码整洁之道》:5大基本要点
专知会员服务
49+阅读 · 2020年3月3日
【新书】Java企业微服务,Enterprise Java Microservices,272页pdf
【电子书】Flutter实战305页PDF免费下载
专知会员服务
22+阅读 · 2019年11月7日
微信小程序支持webP的WebAssembly方案
前端之巅
19+阅读 · 2019年8月14日
GitHub超2.7万星,最全Python入门算法来了
新智元
5+阅读 · 2019年4月28日
超强干货!TensorFlow易用代码大集合...
机器学习算法与Python学习
6+阅读 · 2019年2月20日
React Native 分包哪家强?看这文就够了!
程序人生
13+阅读 · 2019年1月16日
WebAssembly在QQ邮箱中的一次实践
IMWeb前端社区
13+阅读 · 2018年12月19日
实战 | 用Python做图像处理(三)
七月在线实验室
15+阅读 · 2018年5月29日
Python3.7中一种懒加载的方式
Python程序员
3+阅读 · 2018年4月27日
【干货】--基于Python的文本情感分类
R语言中文社区
5+阅读 · 2018年1月5日
教你用Python爬虫股票评论,简单分析股民用户情绪
数据派THU
10+阅读 · 2017年12月12日
A Sketch-Based System for Semantic Parsing
Arxiv
4+阅读 · 2019年9月12日
Arxiv
8+阅读 · 2019年5月20日
Arxiv
4+阅读 · 2018年5月24日
Arxiv
4+阅读 · 2018年1月29日
VIP会员
相关VIP内容
【实用书】Python技术手册,第三版767页pdf
专知会员服务
234+阅读 · 2020年5月21日
【干货书】流畅Python,766页pdf,中英文版
专知会员服务
224+阅读 · 2020年3月22日
100+篇《自监督学习(Self-Supervised Learning)》论文最新合集
专知会员服务
164+阅读 · 2020年3月18日
《代码整洁之道》:5大基本要点
专知会员服务
49+阅读 · 2020年3月3日
【新书】Java企业微服务,Enterprise Java Microservices,272页pdf
【电子书】Flutter实战305页PDF免费下载
专知会员服务
22+阅读 · 2019年11月7日
相关资讯
微信小程序支持webP的WebAssembly方案
前端之巅
19+阅读 · 2019年8月14日
GitHub超2.7万星,最全Python入门算法来了
新智元
5+阅读 · 2019年4月28日
超强干货!TensorFlow易用代码大集合...
机器学习算法与Python学习
6+阅读 · 2019年2月20日
React Native 分包哪家强?看这文就够了!
程序人生
13+阅读 · 2019年1月16日
WebAssembly在QQ邮箱中的一次实践
IMWeb前端社区
13+阅读 · 2018年12月19日
实战 | 用Python做图像处理(三)
七月在线实验室
15+阅读 · 2018年5月29日
Python3.7中一种懒加载的方式
Python程序员
3+阅读 · 2018年4月27日
【干货】--基于Python的文本情感分类
R语言中文社区
5+阅读 · 2018年1月5日
教你用Python爬虫股票评论,简单分析股民用户情绪
数据派THU
10+阅读 · 2017年12月12日
Top
微信扫码咨询专知VIP会员