cjs (commonjs)

commonjs 是 Node 中的模块规范,通过 require 及 exports 进行导入导出 (进一步延伸的话,module.exports 属于 commonjs2)

同时,webpack 也对 cjs 模块和 esm 模块都得以解析,因此 cjs 模块可以运行在 node 环境及 webpack 环境下的,但不能在浏览器中直接使用。但如果你写前端项目在 webpack 中,也可以理解为它在浏览器和 Node 都支持。

比如,著名的全球下载量前十 10 的模块 ms 只支持 commonjs,但并不影响它在前端项目中使用(通过 webpack),但是你想通过 cdn 的方式直接在浏览器中引入,估计就会出问题了

// sum.js
exports.sum = (x, y) => x + y;
 
// index.js
const { sum } = require("./sum.js");

由于 cjs 为动态加载,可直接 require 一个变量

require(`./${a}`);

esm (es module)

esm 是 tc39 对于 ESMAScript 的模块话规范,正因是语言层规范,因此在 Node 及 浏览器中均会支持

它使用 import/export(export default) 进行模块导入导出.

// sum.js
export const sum = (x, y) => x + y;
 
// index.js
import { sum } from "./sum";

esm 为静态导入,正因如此,可在编译期进行 Tree Shaking,减少 js 体积。

如果需要动态导入,tc39 为动态加载模块定义了 API: import(module) 。可将以下代码粘贴到控制台执行

const ms = await import("https://cdn.skypack.dev/ms@latest");
 
ms.default(1000);

esm 是未来的趋势,目前一些 CDN 厂商,前端构建工具均致力于 cjs 模块向 esm 的转化,比如 skypack、 snowpack、vite 等。

目前,在浏览器与 node.js 中均原生支持 esm。

两者不同点在于:

  • cjs 模块输出的是一个值的拷贝,esm 输出的是值的引用

  • cjs 模块是运行时加载,esm 是编译时加载

对于 cjs 模块来说,一旦值被导出,它不会在模块外部发生变化,但如果是可变类型的值(对象、数组),其内部的属性或元素是可以被修改的。esm 模块中导出了一个变量,其他模块引入这个模块后得到的是一个绑定到该变量的引用。如果在导入模块的过程中,该变量的值发生了改变,那么其他模块中的引用也会随之改变。注意模块的独立性,例如在模块内部修改了变量的值,也不会影响到导出的值(假如再a.js中导出变量n,函数fn改变了n的值,后面b.js引用a.js,并调用fn。此时在b.js中n是不会有变化的)。


amd (Asynchronous Module Definition)

AMD是一种JavaScript模块化的概念和规范。它提出了一种异步加载和定义模块的方式,旨在解决前端开发中模块依赖管理和加载顺序的问题。

AMD的核心观念:

  • 异步加载:AMD鼓励使用异步方式加载模块。在传统的同步加载方式中,所有脚本文件都需要先加载完毕,才能执行后续的代码逻辑。而使用异步加载可以在不阻塞页面加载的情况下,按需加载模块,提高应用的性能和用户体验。

  • 模块定义:AMD规范中,模块的定义通过define函数来实现。define函数接受三个参数:模块名称、依赖数组和工厂函数。模块名称是一个可选参数,可以用于在其他地方引用该模块。依赖数组包含了当前模块所依赖的其他模块。工厂函数则用于定义模块的具体功能,并返回一个模块实例。

  • 依赖管理:AMD规范明确了模块之间的依赖关系,并通过依赖数组来声明这些依赖关系。在加载模块时,AMD加载器(如Require.js)会自动解析模块的依赖关系,并按照正确的顺序进行加载和执行。

  • 模块加载:AMD提供了require函数用于加载和使用模块。require函数接受两个参数:依赖数组和回调函数。在所有依赖模块都加载完成后,AMD加载器会调用回调函数并将模块实例传递给该回调函数,供开发者使用。

<!DOCTYPE html>
<html>

<head>
  <title>require.js</title>
  <style>
  </style>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
  <script>
    // Configure RequireJS
    require.config({
      baseUrl: 'js', // Set the base URL for module loading
      paths: {
        // Define module paths
        'jquery': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min',
        'lodash': 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min'
      }
    });

    // Define and use modules
    define('totalModule', ['jquery', 'lodash'], function ($, _) {
      var moduleA = {
        $,
        _
      };
      return moduleA;
    });

    // Load and use modules
    require(['totalModule'], function (m) {
      m.$('#app').html('11');
    });
  </script>
</head>

<body>
  <div id="app"></div>
</body>
</html>

umd

一种兼容 cjs 与 amd 的模块,既可以在 node/webpack 环境中被 require 引用,也可以在浏览器中直接用 CDN 被 script.src 引入。

(function (root, factory) {
  if (typeof define === "function" && define.amd) {
    // AMD
    define(["jquery"], factory);
  } else if (typeof exports === "object") {
    // CommonJS
    module.exports = factory(require("jquery"));
  } else {
    // 全局变量
    root.returnExports = factory(root.jQuery);
  }
})(this, function ($) {
  // ...
});

这三种模块方案大致如此,部分 npm package 也会同时打包出 commonjs/esm/umd 三种模块化格式,供不同需求的业务使用,比如 antd。