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

  • 1. 入口起点
    • 问题
  • 2. 防止重复
    • 2.1 配置 entry 提取公用依赖
    • 2.2 SplitChunksPlugin
  • 3. 动态导入
    • 3.1 import() 动态导入
    • 3.2 懒加载 / 按需加载
    • 3.3 prefetch 预获取
    • 3.4 preload 预加载

代码分离是 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 chunkschunks: '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 chunkschunks: '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 chunkschunks: '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

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

webpack基础篇(五):代码分离(Code Splitting)相关推荐

  1. Webpack系列——代码分离(Code Splitting)

    代码分离(Code Splitting) // index.js import _ from 'lodash'; const ele = document.createElement('div'); ...

  2. webpack基础篇(三):管理资源(image、css、fonts、csv、json5)

    目录 1. 处理资源 - loader 1.1 加载css - css-loader 1.2 处理less - less-loader 2. 加载css 2.1 抽离css 自定义分类的css文件名 ...

  3. Webpack5学习笔记(基础篇五)—— mode之Development环境相关参数配置

    在Webpack5中,mode(模式)有三种: development(开发环境模式) production(生产环境模式) none或' '(空) 在不同模式中,我们可能对于webpack.conf ...

  4. vue实战入门基础篇五:从零开始仿门户网站实例-关于我们实现

    上一篇:vue实战入门基础篇四:从零开始仿门户网站实例-网站首页实现https://blog.csdn.net/m0_37631110/article/details/123045334 一.目录 第 ...

  5. Webpack进阶(二)代码分割 Code Splitting

    源代码index.js里包含2部分 ① 业务逻辑代码 1mb ② 引入(如lodash包)的代码 1mb 若更新了业务逻辑代码,但在浏览器运行时每次都下载2mb的index.js显然不合理,第三方包是 ...

  6. 【JavaScript笔记 · 基础篇(五)】Array全家桶(引用数据类型中的数组 / Array对象 / Array.prototype)

    文章目录 一. 引用数据类型中的数组 1.1 概述 1.2 初始化 1.2.1 字面量 1.2.2 构造函数模式 1.3 访问 1.4 length属性 1.5 数组遍历 1.6 类数组对象 1.6. ...

  7. shell脚本学习之基础篇五:函数

    shell脚本中的函数 函数的作用 函数的定义 函数的调用 函数的返回值 函数的传参 函数中的变量 函数的递归 函数的作用 在编写shell脚本的时候,经常会发现在多个地方使用了同一段代码,如果只是一 ...

  8. C#基础—不安全代码(unsafe code)

    1.为何要有unsafe 也许是为了实现CLR类型安全的目标吧,默认情况下,C#没有提供指针的使用算法,但是有些情况下也可能需要指针这样直接访问内存的东西(虽然目前我还没有用过),但是有时候程序员非常 ...

  9. window.addeventlistener 不能调用方法_方法入门(基础篇五)

    前边写运算符的时候,都是创建一个类和一个main方法,这样会有很多重复代码,为了减少重复代码,我们可以使用方法来实现. 什么是方法? Java的方法是语句的集合,它们在一起执行一个功能. 方法是解决一 ...

最新文章

  1. 计算机应用基础上机操作,计算机应用基础上机操作试题
  2. Excel如何快速清除单元格所有内容
  3. python2 x与python3 x_python2.x 与 python3.x的不同
  4. Acwing 271. 杨老师的照相排列
  5. activemq 内存_ActiveMQ:了解内存使用情况
  6. 电商小程序 -- 商品多规格选择弹框
  7. android 55
  8. mysql bit类型 查询_数据库中的bit类型
  9. DelphiWebMVC框架实现对Redis支持
  10. Golang 大杀器之性能剖析 PProf
  11. Facebook高管:文字分享将枯竭 5年后或许全是视频
  12. python文件自动化处理 -- 读写文件
  13. [NLP]高级词向量表达之Word2vec详解(知识点全覆盖)
  14. 数据挖掘背景知识2——数据挖掘可以做到什么 带给我们什么?
  15. 比較好的JAVA網站
  16. 软件测试简历自我评价范文,测试工程师求职简历自我评价范文
  17. python输入生日输出星座_python字典保存星座性格特点并输出
  18. android 闹钟运行原理,简单 闹钟 实现 原理
  19. c3p0和dbcp的使用和区别
  20. 对逻辑主键、业务主键和复合主键的思考

热门文章

  1. Python怎么读?这篇文章说的很明白
  2. ppt提示内存或系统资源不足_苹果iPhone手机内存不足怎么办?手机内存不足怎么处理?苹果手机提示内存不足怎么办?...
  3. 11. 系统限制和选项
  4. oracleclient和plsql安装注意事项
  5. 演进:如何在工作的前三年里快速成长
  6. 「3点钟区块链」连接资本创始人林嘉鹏:区块链商业变现的关键在于流量
  7. iPhone限制每天游戏时间,设置某些APP每天最长使用时间,未成年保护 - 《屏幕使用时间》密码设置
  8. 你为什么应该为软件付费
  9. 微信删除朋友圈多久会从服务器消失,6.6亿人消失在朋友圈,微信的社交属性正在降低?你多久没发了?...
  10. python是否高送转预测股票_用Python分析公开数据选出高送转预期股票