前端模块化发展

189 阅读5分钟

CommonJS 规范

CommonJS是业界最早正式提出的JavaScript模块规范,主要用于服务端,随着Node.js越来越普及,这个规范也被业界广泛应用。对于模块规范而言,一般会包含2方面内容:

  • 统一的模块化代码规范
  • 实现自动加载模块的加载器
// a.js
const data='hello world'
function getData(){
    return data
}
module.exports={
    getData
}
// index.js
const { getData } =require('./a.js')
console.log(getData())

代码中使用require来导入一个模块,用module.exports来导出一个模块。实际上Node.js内部会用响应的laoder转译模块代码,最后模块代码会被处理成下面这样:

(function (exports, require, module, __filename, __dirname) { 
// 执行模块代码 
// 返回 exports 对象 
});

对于CommonJs而言,一方面它定义了一套完整的模块发代码规范,另一方面Node.js为之实现了自动加载模块的loader,看上去是一个很不错的模块规范,但也存在一些问题:

  • 模块加载器由 Node.js 提供,依赖了 Node.js 本身的功能实现,比如文件系统,如果 CommonJS 模块直接放到浏览器中是无法执行的。当然, 业界也产生了 browserify 这种打包工具来支持打包 CommonJS 模块,从而顺利在浏览器中执行,相当于社区实现了一个第三方的 loader。
  • CommonJS 本身约定以同步的方式进行模块加载,这种加载机制放在服务端是没问题的,一来模块都在本地,不需要进行网络 IO,二来只有服务启动时才会加载模块,而服务通常启动后会一直运行,所以对服务的性能并没有太大的影响。但如果这种加载机制放到浏览器端,会带来明显的性能问题。它会产生大量同步的模块请求,浏览器要等待响应返回后才能继续解析模块。也就是说,模块请求会造成浏览器 JS 解析过程的阻塞,导致页面加载速度缓慢。 总之,CommonJS 是一个不太适合在浏览器中运行的模块规范。因此,业界也设计出了全新的规范来作为浏览器端的模块标准,最知名的要数 AMD 了。

AMD规范

AMD全称为Asynchronous Module Definition,即异步模块定义规范。模块根据这个规范,在浏览器环境中会被异步加载,而不会像 CommonJS 规范进行同步加载,也就不会产生同步请求导致的浏览器解析过程阻塞的问题了。我们先来看看这个模块规范是如何来使用的:

// main.js 
define(["./print"], function (printModule) {
    printModule.print("main");
});
// print.js 
define(function () {
    return {
        print: function (msg) {
            console.log("print " + msg);
        },
    };
});

在 AMD 规范当中,我们可以通过 define 去定义或加载一个模块,比如上面的 main 模块和print模块,如果模块需要导出一些成员需要通过在定义模块的函数中 return 出去(参考 print 模块),如果当前模块依赖了一些其它的模块则可以通过 define 的第一个参数来声明依赖(参考main模块),这样模块的代码执行之前浏览器会先加载依赖模块

当然,你也可以使用 require 关键字来加载一个模块,如:

// module-a.js 
require(["./print.js"], function (printModule) { 
printModule.print("module-a"); 
});

不过 require 与 define 的区别在于前者只能加载模块,而不能定义一个模块

由于没有得到浏览器的原生支持,AMD 规范需要由第三方的 loader 来实现,最经典的就是 requireJS 库了,它完整实现了 AMD 规范,至今仍然有不少项目在使用。

不过 AMD 规范使用起来稍显复杂,代码阅读和书写都比较困难。因此,这个规范并不能成为前端模块化的终极解决方案,仅仅是社区中提出的一个妥协性的方案。

同期出现的规范当中也有 CMD 规范,这个规范是由淘宝出品的SeaJS实现的,解决的问题和 AMD 一样。不过随着社区的不断发展,SeaJS 已经被requireJS兼容了。

当然,你可能也听说过 UMD (Universal Module Definition)规范,其实它并不算一个新的规范,只是兼容 AMD 和 CommonJS 的一个模块化方案,可以同时运行在浏览器和 Node.js 环境。

ES6 Module

ES6 Module 也被称作 ES Module(或 ESM), 是由 ECMAScript 官方提出的模块化规范,作为一个官方提出的规范,ES Module 已经得到了现代浏览器的内置支持。在现代浏览器中,如果在 HTML 中加入含有type="module"属性的 script 标签,那么浏览器会按照 ES Module 规范来进行依赖加载和模块解析,这也是 Vite 在开发阶段实现 no-bundle 的原因,由于模块加载的任务交给了浏览器,即使不打包也可以顺利运行模块代码。

不仅如此,一直以 CommonJS 作为模块标准的 Node.js 也紧跟 ES Module 的发展步伐,从 12.20 版本开始正式支持原生 ES Module。也就是说,如今 ES Module 能够同时在浏览器与 Node.js 环境中执行,拥有天然的跨平台能力。

// main.js
import { methodA } from "./module-a.js";
methodA();

//module-a.js
const methodA = () => {
  console.log("a");
};

export { methodA };

在 Node.js 中,即使是在 CommonJS 模块里面,也可以通过 import 方法顺利加载 ES 模块,如下所示:

async function func() { 
// 加载一个 ES 模块 
// 文件名后缀需要是 mjs 
const { a } = await import("./module-a.mjs"); 
console.log(a); 
} 
func();
module.exports = { func, };