前端模块化类型

在看 Babel 的时候,里面介绍了四种模块化的插件,AMD、Commonjs、SystemJS、UMD,还有 CMD、EMS 没有涉及,在这里总结一下调查的内容。

目前使用的模块化协议有 AMD、CMD、CommonJS、UMD、SystemJS、ESM

AMD、CMD

AMD(Asynchronous Moudle Definition)异步模块定义;CMD(Common Module Definition)通用模块定义;它们都是一种在浏览器端模块化开发的规范,并没有被JS原生支持。

在使用AMD 规范进行页面开发,需要使用到对应的 RequireJS 库函数,实际上 AMD 也是 RequireJS 在推广过程中对模块定义的规范化的产出。
Require.js 官网 https://requirejs.org/

在使用CMD 规范进行开发,用到的是 SeaJS 库函数,它是国内发展出来的。
Sea.js 官网 https://seajs.github.io/seajs/docs/

看了两个官网样式,都有历史的味道。

Require.js

例子🌰:

// 引入 require.js
// 定义 myModule.js 模块
define(['dependency'], function() {
  var name = "Byron";
  function printName() {
    console.log(name);
  }
  return {
    printName: printName
  }
});

//加载模块
require(['myModule'], function(my) {
  my.printName();
})

语法:
Require.js 定义全局函数 define 用来定义模块,require 加载模块

define(id?: string, dependencies?: string[], factory: Function)
  • id 定义模块标识符,默认是脚本文件名(去除扩展名)
  • dependencied 当前模块依赖的模块名称数组
  • factory 模块初始化执行的函数或对象,函数之被执行一次,对象是为模块的输出值
require(dependencies: string[], callback: Function)
  • dependencies 依赖的模块
  • callback 回调函数,在指定模块加载完成后被调用

require() 函数在加载以来的函数是异步加载,指定的回调函数,在前面模块都加载完成后才会运行。

Sea.js

例子🌰:

// 引入 Sea.Sea
// 定义模块 myModule.js
define(function(require, exports, module) {
  var math = require('math');
  exports.add = function(left, right) {
    return math.add(left, right);
  }
});

// 加载模块
require(['myModule'], function(my) {
  console.log(my.add(1, 1));
})
define(id?: string, dependencies?: string[], factory: (require: Function, exports: Object, module: Object) => {})

CMD 推崇的是依赖就近,所以一般不在 define 的参参数写依赖

  • id 定义模块标识符,默认是脚本文件名(去除扩展名)
  • dependencied 当前模块依赖的模块名称数组

factory 回调函数的参数有三个:

  • require 接受模块标识,用来获取其他模块提供的接口
  • exports 用来向外提供模块的对象
  • 存储当前模块信息的对象

AMD 和 CMD 的区别

在浏览器中使用

明显的区别在于模块定义时候依赖的处理方式:

  • AMD 推崇依赖前置,在定义模块的时候就要声明依赖的模块
  • CMD 推崇就近依赖,只有在用到某个模块的时候再通过 require 引入

这种区别只是在语法上的差距。实现库 Require.js 和 Sea.js 都支持对方的写法。

CommonJS

在服务器使用

这个规范在 Node 中被原生支持
协议 https://www.commonjs.org/

协议规范,一个单独的文件就是一个模块,每个模块都是一个单独的作用域。达到的效果就是咋模块内部定义的变量是无法被其他模块读取的,除非是定义为 global 对象的作用域。

通过 exports 和 module.exports 来暴露模块内容
通过 require 来加载模块

例子🌰:

通过 exports 暴露

// 模块定义
var hello = function () {
    console.log('hello studygd.com.');
}
exports.hello = hello;

// 模块加载
const studygd = require('./study');
studygd.hello();

通过 module.exports 暴露

module.exports = {
  add: function(left, right) {
    return left + right;
  },
  subtract: function(left, right) {
    return left - right;
  }
}

UMD

上面已经介绍的协议实现上 AMD、CMD 被限制在浏览器中使用,CommonJS 被限制在服务端使用,两种协议的不同必然带来复用问题,UMD 解决了这个问题。

根据官网介绍,它是使用了 AMD 作为基础,添加了包裹函数来处理 CommonJS 的兼容性。

Github 地址 https://github.com/umdjs/umd

例子🌰:

(function (global, factory) {
  if (typeof define === "function" && define.amd) {
    // amd
    define(["exports"], factory);
  } else if (typeof exports !== "undefined") {
    factory(exports);
  } else {
    // commonjs
    var mod = {
      exports: {}
    };
    factory(mod.exports);
    global.test_umd = mod.exports;
  }
})(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports) {
  "use strict";

  Object.defineProperty(_exports, "__esModule", {
    value: true
  });
  _exports.default = void 0;
  var _default = 42;
  _exports.default = _default;
});

SystemJS

诞生于 2015 年,那时候 ES Module 还没有成熟,已经开始使用 require.js 和 sea.js,system.js 为了做一个通用的模块加载器应运而生。

SystemJS 是目前浏览器(浏览器尚未正式支持 importMap)原生 ES Module 的替代品,把 ES Module 编译成 System.register 格式然后运行在旧版本的浏览器中。

引入模块方式,例子🌰:

// 通过标签引入 moment 和 lodash 模块
<script type="systemjs-importmap">
  {
    "imports": {
      "moment": "https://cdn.jsdelivr.net/npm/moment/dist/moment.js",
      "lodash": "https://cdn.jsdelivr.net/npm/lodash/dist/lodash.js"
    }
  }
</script>
// 因为大部分浏览器不支持(也就 chrome 支持) importmap 需引入额外的库做兼容
<script src="https://cdn.jsdelivr.net/npm/systemjs/dist/system.js"></script>

内部解析,例子🌰:

// ESM 方式
export  default 42;
// Babel 中给出的转换成 SystemJS 协议的产物
System.register([], function(_export, _context) {
  return {
    setters: [],
    execute: function() {
      _export("default", 42);
    },
  };
});

ESM

是 ECMAScript 标准化的模块化协议,目前已经被浏览器和 Node 6+ 中支持

通过 export 导出,import 导入,这应该是我平时接触的最多的场景了。

最后

介绍的内容都是作为粗略了解,大部分还没遇到使用场景,先记一下。