热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

require动态加载_RingCentralTech丨深入理解webpack的require.context

前言require.context其实是一个非常实用的api。但是3-4年过去了,却依旧还有很多人不知道如何使用。而这个api主要为我们做什么样的事情?
e1fdd6becd8cc89f645a3777dc06e327.png

前言

require.context 其实是一个非常实用的 api。但是 3-4 年过去了,却依旧还有很多人不知道如何使用。

而这个 api 主要为我们做什么样的事情?它可以帮助我们动态加载我们想要的文件,非常灵活和强大(可递归目录)。可以做 import 做不到的事情。今天就带大家一起来分析一下,webpack 的 require.context是如何实现的。

准备工作

在分析这个 api 之前呢,我们需要先了解一下一个最简单的文件,webpack 会编译成啥样。

-- src-- index.ts
// index.ts
console.log(123)

编译之后,我们可以看见 webpack 会编译成如下代码

// 源码 https://github.com/MeCKodo/require-context-sourece/blob/master/simple-dist/bundle-only-index.js(function(modules) { // webpackBootstrap// The module cachevar installedModules = {};// The require functionfunction __webpack_require__(moduleId) {// Check if module is in cacheif(installedModules[moduleId]) {return installedModules[moduleId].exports;}// Create a new module (and put it into the cache)var module = installedModules[moduleId] = {i: moduleId,l: false,exports: {}};// Execute the module functionmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);// Flag the module as loadedmodule.l = true;// Return the exports of the modulereturn module.exports;}// expose the modules object (__webpack_modules__)__webpack_require__.m = modules;// expose the module cache__webpack_require__.c = installedModules;// define getter function for harmony exports__webpack_require__.d = function(exports, name, getter) {if(!__webpack_require__.o(exports, name)) {Object.defineProperty(exports, name, {configurable: false,enumerable: true,get: getter});}};// define __esModule on exports__webpack_require__.r = function(exports) {Object.defineProperty(exports, '__esModule', { value: true });};// getDefaultExport function for compatibility with non-harmony modules__webpack_require__.n = function(module) {var getter = module && module.__esModule ?function getDefault() { return module['default']; } :function getModuleExports() { return module; };__webpack_require__.d(getter, 'a', getter);return getter;};// Object.prototype.hasOwnProperty.call__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };// __webpack_public_path____webpack_require__.p = "";// Load entry module and return exportsreturn __webpack_require__(__webpack_require__.s = "./src/index.ts");})({"./src/index.ts": (function(module, exports) {console.log('123');})});

初次一看是很乱的,所以为了梳理结构,我帮大家去除一些跟本文无关紧要的。其实主要结构就是这样而已,代码不多为了之后的理解,一定要仔细看下每一行

// 源码地址 https://github.com/MeCKodo/require-context-sourece/blob/master/simple-dist/webpack-main.js(function(modules) {// 缓存所有被加载过的模块(文件)var installedModules = {};// 模块(文件)加载器 moduleId 一般就是文件路径function __webpack_require__(moduleId) {// 走 cacheif (installedModules[moduleId]) {return installedModules[moduleId].exports;}// Create a new module (and put it into the cache) 解释比我清楚var module = (installedModules[moduleId] = {i: moduleId,l: false,exports: {}});// 执行我们的模块(文件) 目前就是 ./src/index.ts 并且传入 3 个参数modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);// Flag the module as loaded 解释比我清楚module.l = true;// Return the exports of the module 解释比我清楚return module.exports;}// 开始加载入口文件return __webpack_require__((__webpack_require__.s = './src/index.ts'));
})({'./src/index.ts': function(module, exports, __webpack_require__) {console.log('123');}
});

__webpack_require__ 就是一个模块加载器,而我们所有的模块都会以对象的形式被读取加载

modules = {'./src/index.ts': function(module, exports, __webpack_require__) {console.log('123');}
}

我们把这样的结构先暂时称之为 模块结构对象

正片

了解了主体结构之后我们就可以写一段require.context来看看效果。我们先新增 2 个 ts 文件并且修改一下我们的 index.ts,以便于测试我们的动态加载。

--- src--- demos--- demo1.ts--- demo2.tsindex.ts
// index.ts
// 稍后我们通过源码分析为什么这样写
function importAll(contextLoader: __WebpackModuleApi.RequireContext) {contextLoader.keys().forEach(id => console.log(contextLoader(id)));
}const contextLoader = require.context('./demos', true, /.ts/);
importAll(contextLoader);

查看我们编译后的源码,发现多了这样一块的 模块结构对象

// 编译后代码地址 https://github.com/MeCKodo/require-context-sourece/blob/master/simple-dist/contex-sync.js#L82-L113
{
'./src/demos sync recursive .ts': function(module, exports, __webpack_require__) {var map = {'./demo1.ts': './src/demos/demo1.ts','./demo2.ts': './src/demos/demo2.ts'};// context 加载器,通过之前的模块加载器 加载模块(文件) function webpackContext(req) {var id = webpackContextResolve(req);var module = __webpack_require__(id);return module;}// 通过 moduleId 查找模块(文件)真实路径// 个人在这不喜欢 webpack 内部的一些变量命名,moduleId 它都会编译为 requestfunction webpackContextResolve(req) {// id 就是真实文件路径var id = map[req];// 说实话这波操作没看懂,目前猜测是 webpack 会编译成 0.js 1.js 这样的文件 如果找不到误加载就出个 errorif (!(id + 1)) {// check for number or stringvar e = new Error('Cannot find module "' + req + '".');e.code = 'MODULE_NOT_FOUND';throw e;}return id;}// 遍历得到所有 moduleIdwebpackContext.keys = function webpackContextKeys() {return Object.keys(map);};// 获取文件真实路径方法webpackContext.resolve = webpackContextResolve;// 该模块就是返回一个 context 加载器module.exports = webpackContext;// 该模块的 moduleId 用于 __webpack_require__ 模块加载器webpackContext.id = './src/demos sync recursive .ts';
}

我在源码中写了很详细的注释。看完这段代码就不难理解文档中所说的require.context 会返回一个带有 3 个API的函数(webpackContext)了。

3cc1b25bd00ec0f76a168f29b9a83ac7.png

接着我们看看编译后 index.ts 的源码

'./src/index.ts': function(module, exports, __webpack_require__) {function importAll(contextLoader) {contextLoader.keys().forEach(function(id) {// 拿到所有 moduleId,在通过 context 加载器去加载每一个模块return console.log(contextLoader(id));});}var contextLoader = __webpack_require__('./src/demos sync recursive .ts');importAll(contextLoader);
}

很简单,可以发现 require.context 编译为了 __webpack_require__加载器并且加载了 id 为./src/demos sync recursive .ts 的模块,sync表明我们是同步加载这些模块(之后我们在介绍这个参数),recursive 表示需要递归目录查找。自此,我们就完全能明白 webpack 是如何构建所有模块并且动态加载的了。

进阶深入探究 webpack 源码

我们知道 webpack 在 2.6 版本后,在加载模块时,可以指定 webpackMode 模块加载模式,我们能使用几种方式来控制我们要加载的模块。常用的 mode一般为sync lazy lazy-once eager

所以在 require.context 是一样适用的,我们如果查看一下@types/webpack-env就不难发现它还有第四个参数。

0690b896c17f02ed2959660c6fbd1bbe.png

简要来说

  • sync 直接打包到当前文件,同步加载并执行
  • lazy 延迟加载会分离出单独的 chunk 文件
  • lazy-once 延迟加载会分离出单独的 chunk 文件,加载过下次再加载直接读取内存里的代码。
  • eager 不会分离出单独的 chunk 文件,但是会返回 promise,只有调用了 promise 才会执行代码,可以理解为先加载了代码,但是我们可以控制延迟执行这部分代码。
文档在这里 https://webpack.docschina.org/api/module-methods/#magic-comments。

这部分文档很隐晦,也可能是文档组没有跟上,所以如果我们去看 webpack 的源码的话,可以发现真正其实是有 6 种 mode。

mode类型定义 https://github.com/webpack/webpack/blob/master/lib/ContextModule.js#L13

那 webpack 到底是如何做到可递归获取我们的文件呢?在刚刚上面的源码地址里我们能发现这样一行代码。

45f1c977619d80c8e9c2765ba4145ffc.png

这一看就是去寻找我们所需要的模块。所以我们跟着这行查找具体的源码。

2317cbbc086f54b990bdd74c689fc591.png

这就是 require.context 是如何加载到我们文件的具体逻辑了。其实就是fs.readdir而已。最后获取到文件之后在通过 context 加载器来生成我们的模块结构对象。比如这样的代码就是负责生成我们sync类型的context加载器。大家可以具体在看别的5种类型。

95b0d163fa9449fe70c55a84ae5130cc.png
6种类型加载逻辑并且生成 context 加载器的模块结构对象 https://github.com/webpack/webpack/blob/master/lib/ContextModule.js

总结

1.学习了解 webpack 是如何组织加载一个模块的,webpack 的加载器如何运作,最后如何生成编译后的代码。

2.本来仅仅只是想了解 require.context 如何实现的,却发现了它第三个参数有 6 种mode,这部分却也是 webpack 文档上没有的。

3.从一个实用的 API 出发,探索了该 api 的实现原理,并且一起阅读了部分 webpack 源码。

4.探索本质远比你成为 API 的搬运工更重要。只有你不断地探寻本质,才可以发现世界的奥秘。

最后抛砖引玉,可以按照这样的思路再去学习另外 6 种 mode 编译后的代码。

文章里编译后的代码,都在这里 >>> https://github.com/MeCKodo/require-context-source

作者:Nello



推荐阅读
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • 本文介绍了机器学习手册中关于日期和时区操作的重要性以及其在实际应用中的作用。文章以一个故事为背景,描述了学童们面对老先生的教导时的反应,以及上官如在这个过程中的表现。同时,文章也提到了顾慎为对上官如的恨意以及他们之间的矛盾源于早年的结局。最后,文章强调了日期和时区操作在机器学习中的重要性,并指出了其在实际应用中的作用和意义。 ... [详细]
  • 本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 本文介绍了在使用vue和webpack进行异步组件按需加载时可能出现的报错问题,并提供了解决方法。同时还解答了关于局部注册组件和v-if指令的相关问题。 ... [详细]
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
  • 在编写业务代码时,常常会遇到复杂的业务逻辑导致代码冗长混乱的情况。为了解决这个问题,可以利用中间件模式来简化代码逻辑。中间件模式可以帮助我们更好地设计架构和代码,提高代码质量。本文介绍了中间件模式的基本概念和用法。 ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
author-avatar
501917112_0de975_837
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有