webpack|webpack基础(8).代码分离

代码分离是 Webpack 最引人注目的特性之一。
(1)这个特性可以将代码分离到不同的bundle中,然后按需加载或并行加载这些文件
(2)所谓bundle,就是我们打包出来的文件。代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,可以极大影响加载时间。从而提高首屏加载速度。
(3)常用的代码分离方法:
webpack|webpack基础(8).代码分离
文章图片

一、入口起点 —— entry 1、在src下面建一个another-module.js,这个模块里面引入loadash

npm install loadash --save-dev

import _ from 'lodash' console.log(_.join(['Another', 'module', 'loaded!'], ' '))

webpack.config.js:
module.exports = { entry: { // *定义两个入口文件,分别key值为index和another index: './src/index.js', another: './src/another-module.js' }, output: { filename: '[name].bundle.js' // *这里的[name]就会去取entry的key值。例如, another.bundle.js } }

执行 npx webpack 可以看到 another.bundle.js 的大小有1.38MB,引入的 loadash.js 也打包在了 another.bundle.js 中。打开 app.html ,可以发现 script 里面引入了这两个 bundle.js.
2、这时候,修改 index.js,即另一个入口文件,也加上 another.bundle.js 的以下代码:
import _ from 'lodash' console.log(_.join(['Another', 'module', 'loaded!'], ' '))

打包后发现 index.js 也变大了,同时也打包了 loadash.js。
3、因此,entry 入口起点这种方式存在一些问题:
(1)如果入口 chunk 之间包含重复的模块,这些重复模块会被引入各自的 bundle中;
(2)这个方法不够灵活,不能动态地将核心应用程序逻辑中的代码拆分出来。
二、防止重复 1、入口依赖entry的方式
webpack.config.js
module.exports = { entry: { index: { // *不采用字符串,采用对象的方式 import: './src/index.js', dependOn: 'shared' }, another: { import: './src/another-module.js', dependOn: 'shared' }, shared: 'loadash' // *表示当上面两个模块中有loadash这个模块的时候,就会把他抽离出来,并且把它取名为shared这样一个chunk }, output: { filename: '[name].bundle.js' } }

打包后可以看到 sahred.bundle.js 被打包出来了,说明把 loadash.js 打包到这个chunk 里面了;其他两个 js 的大小也减小了。并且在 app.html 里面也引入了这三个 js.
webpack|webpack基础(8).代码分离
文章图片

2、split-chunk-plugin
除了上面 entry 的方式,还可以使用 split-chunks-plugin 这个插件,这个插件也可以把模块中依赖的公共文件给抽离成单独的 chunk。
webpack.config.js:
module.exports = { entry: { index: './src/index.js', // *采用字符串 another: './src/another-module.js' }, output: { filename: '[name].bundle.js' }, optimization: { splitChunks: { // *使用split-chunks-plugin插件 chunks: 'all' } } }

箭头指向的两个文件就是公共部分的chunk
webpack|webpack基础(8).代码分离
文章图片

三、动态导入 当涉及到动态代码拆分时,webpack 提供了两个类似的技术。
第一种,也是推荐选择的方式是,使用符合 ECMAScript 提案的import()语法来实现动态导入。
第二种,则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure
【webpack|webpack基础(8).代码分离】1、通过import() 语法动态引入
(1)创建 async-module.js 文件
webpack|webpack基础(8).代码分离
文章图片

function getComponent() { // *采用import的方式引入 return import('lodash') .then(({default: _}) => { const element = document.createElement('div')element.innerHTML = _.join(['Hello', 'webpack'], ' ')return element }) }getComponent().then((element) => { document.body.appendChild(element) })

(2)导入模块
在入口文件 index.js 中 引入刚刚建的async-module.js
index.js:
import ./async-module.js

可以看到公共模块单独打包在了一个文件。
四、动态导入的应用 1. 懒加载 懒加载或者按需加载,是一种很好的优化网页或应用的方式。
这种方式实际上是,先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立引用或即将引用另外一些新的代码块。
这样加快了应用的初始加载速度,减轻了它的总体体积,因为有些代码可能永远不会加载。
创建一个math.js,主页面里面点击按钮调用其中的函数。
math.js
export const add = (x, y) => { return x + y }export const minus = (x, y) => { return x - y }

index.js
const button = document.createElement('button') button.textContent = '点击执行加法运算' button.addEventListener('click', () => { // *使用import动态加载的方式实现懒加载。在这里就是点击了按钮才加载 math.js 文件 // *import里面有一个魔法注释/**/,表示 webpack 打包后的文件名为 math.js import(/* webpackChunkName: 'math' */'./math.js').then(({ add }) => { console.log(add(4, 5)) }) }) document.body.appendChild(button)

上例中,采用 import 动态导入的方式,引入了 math.js。打包后发现生成了一个 src_math_js.bundle.js,且其他打包文件大小没有变大。刷新 html 页面,没有加载src_math_js.bundle.js,点击按钮后才加载了这个文件。说明我们懒加载成功了。
import里面有一个注释/**/,我们称它为魔法注释,表示 webpack 打包后的文件名为 math.js。再次执行打包之后发现 src_math_js.bundle.js 变成了 math.js。
2. 预获取、预加载 Webpack v4.6.0+ 增加了对预获取和预加载的支持。
在声明 import 时,使用下面这些内置指令,可以让 webpack 输出"resource hint(资源提示)",来告知浏览器:
prefetch: 预获取。在将来某些导航下可能需要的资源;
preload: 预加载。在当前导航下可能需要的资源。
(1)prefetch
编辑 index.js 文件,在上一个例子的魔法注释中加第二句webpackPrefetch: true
const button = document.createElement('button') button.textContent = '点击执行加法运算' button.addEventListener('click', () => { // *添加预获取的魔法注释 import(/* webpackChunkName: 'math', webpackPrefetch: true */'./math.js').then(({ add }) => { console.log(add(4, 5)) }) }) document.body.appendChild(button)

打包之后发现,浏览器中 html 页面头部追加了一个 ,这表示浏览器会在闲置时间预获取 main.js 文件。且在浏览器中可以观察到,还没有点击按钮就加载好了 math.js。
这种方式比刚刚的懒加载还要优秀一点。
(2) preload
也是改一哈魔法注释。效果和懒加载类似,点击按钮之后才加载文件。是并行加载。
/* webpackChunkName: 'math', webpackPreload: true */

prefetch 和 preload 的区别:
  • preload chunk 会在父 chunk 加载时,以并行的方式加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
  • preload chunk 具有中等优先级,会立即下载。prefetch chunk 在浏览器闲置时下载。
  • preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来某个时刻。
  • 浏览器支持成都不同。
五、总结
  • 代码分离:可以把多个模块共享的代码抽离出去,减少入口文件的大小,从而提高首屏的加载速度。
  • 分离代码有三种方法:
    (1)入口起点(entry):在 entry 中配置多个入口实现代码的分离。但是可能会有重复的代码。
    (2)防止重复:也是在 entry 里面配置,但是可以把共享的代码单独抽离成一个文件,从而防止重复的代码打包。
    (3)动态导入:通过 ES6的 import 方法实现代码分离。
  • 动态导入有两个应用:
    (1)懒加载:在编译的时候把模块打包好,什么时候需要才加载。实现了按需加载。
    (2)预获取和预加载:
  • prefetch: 在网络空闲的时候加载。既不影响首屏加载速度,又省去了将来模块的加载延迟。
  • preload: 需要的时候加载。实现了页面模块的并行加载。

    推荐阅读