webpack基础篇(五):代码分离(Code Splitting)

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大缩减加载时间。

webpack基础篇(三):管理资源(image、css、fonts、csv、json5)我们我们讲了使用 mini-css-extract-plugin 将 CSS 从主应用程序中分离,今天我们来看 JS 代码如何分离。

常用的代码分离方法有三种:

  • 入口起点:使用 entry 配置手动地分离代码。

  • 防止重复:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离chunk

  • 动态导入:通过模块的内联函数 import 调用来分离代码。


1. 入口起点

入口起点(entry points)

这是迄今为止最简单直观的分离代码的方式。不过,这种方式手动配置较多,并有一些隐患,我们将会解决这些问题。先来看看如何从 main bundle 中分离 another module(另一个模块):

src 目录下创建 another-module.js 文件:

src/index.js

console.log('Hello world!');

src/another-module.js

import _ from 'lodash'

console.log(_.join(['another', 'module', 'chunk'], ' '));

这个模块依赖了 lodash ,需要安装一下:

npm install lodash

webpack.config.js

module.exports = {
  mode: 'development',
  entry: { // 配置多入口文件
    index: './src/index.js',
    another: './src/another_module.js'
  },
   output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, './dist'),
    },
}

执行webpack命令,可以看到报错了 ̄□ ̄||
在这里插入图片描述

这个错误表明发生了冲突,多个入口文件打包后出现了相同的filename,所以我们要对多个入口文件设置多个出口不同文件名文件

webpack.config.js

module.exports = {
 mode: 'development',
  entry: {
    index: './src/index.js',
    another: './src/another_module.js'
  },
  output: {
    filename: '[name].bundle.js', // 对应多个出口文件名
    path: path.resolve(__dirname, './dist'),
  },
}

执行webpack命令,可以看到不报错了,并且dist输出了两个js文件
在这里插入图片描述
在这里插入图片描述

文件another.bundle.js来源于entry.another,即src/another.js,文件大小为554kb,因为被lodash被打包进去了

文件index.bundle.js来源于entry.index,即src/index.js,文件大小为1.21kb

查看dist/app.html可以看到,两个js文件已经被写入script中
在这里插入图片描述

执行npx webpack-dev-server可以看到,js文件加载也是正常的,控制台也能打印出another module chunk

但是,如果我们的其他入口也需要使用lodash呢?

src/index.js

import _ from 'lodash'

console.log(_.join(['index', 'module', 'chunk'], ' '));

执行webpack命令,可以看到打包成功
在这里插入图片描述

但是index.bundle.js明显变大了很多,这是因为它也将lodash打包进去了

执行npx webpack-dev-server,可以看到控制台打印输出了another module chunk index module chunk
在这里插入图片描述

问题

我们发现,lodash在两个引用文件中都被打包了,我们期望lodash应该是公用的,但是使用这种方式造成了重复打包问题


2. 防止重复

2.1 配置 entry 提取公用依赖

配置 dependOn option 选项,这样可以在多个 chunk 之间共享模块:

webpack.config.js

module.exports = {
 mode: 'development',
  entry: {
    index: {
      import: './src/index.js', // 启动时需加载的模块
      dependOn: 'common_chunk', // 当前入口所依赖的入口
    },
    another: {
      import: './src/another_module.js',
      dependOn: 'common_chunk',
    },

    common_chunk: 'lodash' // 当上面两个模块有lodash这个模块时,就提取出来并命名为shared chunk
  },
  output: {
    filename: '[name].bundle.js', // 对应多个出口文件名
    path: path.resolve(__dirname, './dist'),
  },
}

执行webpack命令,可以看到打包结果
在这里插入图片描述

已经提取出来common_chunk.bundle.js,即为提取打包了lodash公用模块

index.bundle.js another.bundle.js体积也变小

查看dist/index.html可以看到三个文件都被加载了

执行npx webpack-dev-server可以看到页面加载正常,打开控制台可以看到打印结果也正常输出了


2.2 SplitChunksPlugin

SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。让我们使用这个插件,将之前的示例中重复的 lodash 模块去除:

webpack.config.js

module.exports = {
  entry: { // 多入口
    index: './src/index.js',
    another: './src/another_module.js',
  },
  output: {
    filename: '[name].bundle.js', // 对应多个出口文件名
    path: path.resolve(__dirname, './dist'),
  },
  optimization: {
    splitChunks: { // 代码分割
      // include all types of chunks
      chunks: 'all' 
    }
  },
}

执行webpack可以看到打包结果,文件已经被分割为chunks
在这里插入图片描述

执行npx webpack-dev-server可以看到代码也是正常加载的


关于splitChunks 你可以看这篇更细致的实验文章 splitChunks.chunks 中的 async、initial 和 all


3. 动态导入

3.1 import() 动态导入

当涉及到动态代码拆分时,webpack 提供了两个类似的技术。第一种,也是推荐选择的方式是,使用符合 ECMAScript 提案 的 import() 语法 来实现动态导入。第二种,则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure 。

这里让我们尝试使用第一种方式

首先,我们将之前的代码注释一部分

webpack.config.js

module.exports = {
  entry: { // 多入口
    index: './src/index.js',
    // another: './src/another_module.js',
  },
  output: {
    filename: '[name].bundle.js', // 对应多个出口文件名
    path: path.resolve(__dirname, './dist'),
  },
  optimization: {
    // splitChunks: {
    //   // include all types of chunks
    //   chunks: 'all'
    // }
  },
}

src.index.js

// import _ from 'lodash'
//
// console.log(_.join(['index', 'module', 'chunk'], ' '));

在 src 下创建 async-module.js 文件:

Warning
注意当调用 ES6 模块的 import() 方法(引入模块)时,必须指向模块的 .default 值,因为它才是 promise 被处理后返回的实际的 module 对象。

function getComponent() {
  // import 返回 Promise
  // 加载一个模块
  return import('lodash').then(({ default: _ }) => {
    const element = document.createElement('div');
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    return element
  }).
  catch((error) =>'An error occurred while loading the component')
}
getComponent().then(component => {
  document.body.appendChild(component)
})

src/index.js

import './async-module';

执行webpack,可以看到公用模块也已经被抽离了
在这里插入图片描述

执行npx webpack-dev-server,可以看到页面上加载了一个Hello webpack

这表明动态导入能实现抽离模块

那么如果动态导入静态导入一起使用会发生什么呢

src/index.js将之前的注释解开

import _ from 'lodash'

console.log(_.join(['index', 'module', 'chunk'], ' '));

执行webpack命令成功,但是我们发现并没有实现代码分离
在这里插入图片描述

这说明一旦我们加入了静态资源时,我们需要开启optimization.splitChunks.chunks

module.exports = {
  // ...
  optimization: {
    splitChunks: {
      // include all types of chunks
      chunks: 'all'
    }
  },
}

执行webpack命令,可以看到打包成功,打包结果
在这里插入图片描述

可以看到已经抽离出了公用模块

执行npx webpack-dev-server可以看到资源加载成功

再次开启多入口entry

webpack.config.js

module.exports = {
  entry: { // 多入口
    index: './src/index.js',
    another: './src/another_module.js',
  },
  output: {
    filename: '[name].bundle.js', // 对应多个出口文件名
    path: path.resolve(__dirname, './dist'),
  },
  optimization: {
    splitChunks: { // 代码分割
      // include all types of chunks
      chunks: 'all' 
    }
  },
}

执行webpack可以看到打包结果,文件已经被分割为chunks
在这里插入图片描述

执行npx webpack-dev-server可以看到资源依旧加载成功


3.2 懒加载 / 按需加载

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。

我们增加一个交互,当用户点击按钮的时候做一些事情。但是会等到第一次交互的时候再加载那个代码块(math.js)

我们之前创建过src/math.js

export function add (x, y) {
  return x + y
}

export function reduce (x, y) {
  return x - y
}

src/index.js

const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click', () => {
 // 魔法注释 webpackChunkName 修改懒加载打包文件名
 // 即使不使用 webpackChunkName,webpack 5 也会自动在 development 模式下分配有意义的文件名。
 import(/* webpackChunkName: 'math' */ './math.js').then(({ add }) => {
  console.log(add(4, 5))
 })
}) 
document.body.appendChild(button)

执行webpack可以看到多出了一个新的js文件,打开可以看到这个文件里包含了我们写入的add、reduce函数。可见这个模块已经被单独的抽离了
在这里插入图片描述

执行npx webpack-dev-server,可以看到页面上已经有了一个按钮
在这里插入图片描述

上图可以看到,点击按钮后才加载math.bundle.js并执行了函数打印输出结果


3.3 prefetch 预获取

Webpack v4.6.0+ 增加了对预获取和预加载的支持。

在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 “resource hint(资源提示)”,来告知浏览器:

prefetch(预获取):将来某些导航下可能需要的资源(当页面所有内容都加载完毕后,在网络空闲的时候,加载资源)

src/index.js

const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click', () => {
 // webpackPrefetch: true 在动态引入时开始预获取
 import(/* webpackChunkName: 'math', webpackPrefetch: true */ './math.js').then(({ add }) => {
  console.log(add(4, 5))
 })
})
document.body.appendChild(button)

执行npx webpack-dev-server,可以看到math.bundle.js已经预先获取了
在这里插入图片描述

3.4 preload 预加载

preload(预加载):当前导航下可能需要资源

与 prefetch 指令相比,preload 指令有许多不同之处:

  • preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
  • preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
  • preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
  • 浏览器支持程度不同。


源码地址:https://gitee.com/yanhuakang/webpack-test

如果有用,就点个赞吧(\*^▽^\*)

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__畫戟__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值