2021-01-31 03:46:36
关于 JS Module 与 Bundle
JS Module(模块)
JS 模块系统是进行复杂项目开发、扩展生态所必需的功能。它经历了从全局作用域下的模块系统,到 Node.js 中的 CommonJS,再到浏览器下的 AMD(Asynchronous Module Definition)标准及其实现如 RequireJS,最终发展到 JS 标准中的 ES Module(ESM)的演变过程。
ES Module(ESM)的设计与工作原理:
分阶段执行:ESM 分成 loading(加载)、instantiation(实例化)、evaluation(求值)三个阶段执行。在 loading 阶段,运行时决定如何进行模块解析、文件获取,并解析文件为模块记录,最后构造模块依赖图。在 instantiation 阶段,根据依赖图的导入导出记录在内存中开辟空间,实现 live binding(实时绑定),即导入导出的变量指向同一块内存空间。在 evaluation 阶段,执行模块代码。
异步与同步:ESM 的处理是异步的,因为 loading 阶段与 instantiation 阶段不是同步进行的。而 CommonJS 对应每个请求的模块都会立刻同步执行这三个步骤。
循环依赖处理:由于 live binding 在 evaluation 之前进行,所以在被引用模块没有被执行时变量已经存在,因此 ESM 能更好地处理循环依赖问题。
require 与 import 的区别:
规范所属:require 属于 CommonJS,import 属于 ESM。
执行时机:require 是代码执行过程中动态同步加载的,可以在代码任意位置调用,称为动态导入。而 import 要求在执行之前加载所有文件构造依赖图并实例化,称为静态加载,因此必须在文件顶层使用,不能用在条件语句中。
导入导出关系:ESM 的导入导出模块指向的是同一块内存地址,而 CommonJS 中导入导出模块通过 module.exports 构建连接,导出模块将导出的值赋值给 module.exports,module.exports 只能导出一个对象,而 ESM 支持默认导出和命名导出等多种形式。
Bundle(打包)
背景与需求:
浏览器下模块系统工作时,请求模块特别耗时,因为需要通过网络进行。如果代码运行时再请求模块,会导致同步运行的代码必须等待模块的网络请求完成。
ES Module 在实现时采取分阶段处理全部模块的方式,虽然运行代码时无需再等待网络加载,但加载阶段仍然需要请求所有模块,特别当依赖模块深度较大时,会有多个 RTT(往返时间)的开销。
Bundle 的作用:
把所有模块打包成一个大的 JS 文件,以减少请求的次数,提高加载效率。
解决很多 JS 模块包基于 Node.js 环境开发,而浏览器环境缺少对应内置对象(如 process)的问题,通过 polyfill 解决兼容性问题。
Bundle 的扩展应用:
Webpack 等工具对 module 的概念进行了一般化,将 CSS、图片等也视为可以被导入的模块。
支持以一个 entry(入口)为抓手去构建 module 之间的依赖,并输出到一个或多个 bundle 到 output(输出)目录下。
Bundle 可以被作为一个整体被复用,提高了代码的可维护性和复用性。
综上所述,JS Module 提供了模块化的编程方式,使得代码更加清晰、易于维护;而 Bundle 则通过打包优化,提高了代码的加载效率和运行性能。两者共同构成了现代 JavaScript 开发中的重要组成部分。