各类模块规范以及AMD/UMD原理
各类模块规范以及AMD/UMD原理
几种模块化规范
- CommonJS:Nodejs,运行在服务端环境
CMD(Common Module Definition - 通用模块定义):主要通过
sea.js流行起来,是国内大佬的作品。写法模仿CommonJS规范,不过是运行在浏览器环境
- AMD(Asynchronous Module Definition - 异步模块定义):采用异步加载,运行在浏览器环境,使用广泛。常用的库是
require.js - UMD(Universal Module Definition - 通用模块定义):该模式主要用来解决CommonJS模式和AMD模式代码不能通用的问题。 在开发中,产物模块规范变成umd后,可以在commonjs、AMD、CMD等多种环境下使用,应用广泛,功能强大,是线上产物常用规范。
- ESM(ES Modules):ES6 规范,目前也可以运行在高版本的浏览器和Nodejs中
参考文章
IIFE
CMD、AMD、UMD的实现,本质上就是利用函数立即执行表达式(immediately invoked function expression),在不同环境下的全局对象上,挂入模块。
Sea.js 实现原理(不推荐使用)
魔法在哪里?
1
2
3
define('a', function (require,exports,modules){
var b = require('b')
})
和 require.js 显式声明依赖不一样,写法类似 CommonJS 规范。
由于没有去提前声明/配置(回调函数只有定义,没有运行),那么如何在「加载期」知道依赖哪个模块呢?
实现原理
核心是利用 Function.toString 方法,拿到「函数的字符串定义」。
「加载期」:
- 通过回调函数的Function.toString函数,使用正则表达式来捕捉内部的require字段,找到require(‘jquery’)内部依赖的模块jquery
- 根据配置文件,找到jquery的js文件的实际路径;如果有多个依赖,也会根据依赖树确定先后顺序
- 在dom中插入script标签,载入模块指定的js,绑定加载完成的事件,使得加载完成后将js文件绑定到require模块指定的id(这里就是jquery这个字符串)上
「运行期」:
- 回调函数内部依赖的js全部加载(暂不调用)完后,调用回调函数
- 当回调函数调用require(‘jquery’),即执行绑定在’jquery’这个id上的js文件,即刻执行,并将返回值传给var b
参考文章
UMD
魔法在哪里?
支持AMD、CMD规范,也支持原生的直接在window上定义的做法,还支持CommonJS。
实现原理
利用 IIFE 的写法,内部区别处理即可。
下面是考虑到「有前置依赖」的UMD的实现思路:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(function(root, factory) {
if (typeof module === 'object' && typeof module.exports === 'object') {
console.log('是commonjs模块规范,nodejs环境')
var depModule = require('./umd-module-depended')
module.exports = factory(depModule);
} else if (typeof define === 'function' && define.amd) {
console.log('是AMD模块规范,如require.js')
define(['depModule'], factory)
} else if (typeof define === 'function' && define.cmd) {
console.log('是CMD模块规范,如sea.js')
define(function(require, exports, module) {
var depModule = require('depModule')
module.exports = factory(depModule)
})
} else {
console.log('没有模块环境,直接挂载在全局对象上')
root.umdModule = factory(root.depModule);
}
}(this, function(depModule) {
console.log('我调用了依赖模块', depModule)
// ...省略了一些代码,去代码仓库看吧
return {
name: '我自己是一个umd模块'
}
}))
在线案例演示了在sea.js上下文中,引入了一个UMD规范的文件,其可以被正常加载:
参考文档
配套工具
NPM 入口
在monorepo / SDK开发中,经常会对不同的运行环境,输出不同的产物。这些产物的「入口文件」在 package.json 中可以声明,如下所示:
目前支持3种字段:
- browser:在 browser 下的入口文件
- module:在 browser & node 下的入口文件,符合 ESM 规范
- main:在 browser & node 下的入口文件
同时声明这3个字段时,在不同的环境下,解析优先级不一样:
- 在非browser下:module > main
- 在browser下:browser > module > main
Webpack
- 环境:可以通过
target字段来声明产物运行环境。 每个环境,解析依赖包的入口文件时,优先级都不同。 - 解析顺序:通过
mainFields字段可以自定义解析顺序,比如[main, module]
参考文档
用法示例
下面文章都挺好的,没必要重复拷贝,直接看原文吧。
AMD & require.js
NodeJS
本文由作者按照 CC BY 4.0 进行授权
