前端模块化类型
在看 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 导入,这应该是我平时接触的最多的场景了。
最后
介绍的内容都是作为粗略了解,大部分还没遇到使用场景,先记一下。