title | date | tags | categories | ||||||
---|---|---|---|---|---|---|---|---|---|
think of JsModule |
2022-05-22 17:48:24 -0700 |
|
javascript module |
JsModule 的演化经历
下面这张图可以清晰的看出,javascript module 演化的历史,由最初的 commonjs 到最终方案 esm,而现在正处于 umd -> esm 阶段.
commonjs
commonjs 的特性就是其导入不是在编译器编译时执行的,而是在代码执行时才实行的.其特性导致了两个特点: 动态导入和赋值复制.
- 动态导入.
下面这段 js 代码完美诠释了此含义,在此判断为 true 的情况下的 commonjs,才会导入 selectivizr,并实行 selectivizr 中的脚本.
if(browser.desktop && browser.msie && browser.versionNumber < 9){
require('selectivizr');
}
- 赋值复制.
// module.js
let count = 4;
function add() {
count++;
}
module.exports = {
count,
add
};
// index.js
const {count, add} = require('./module.js');
console.log('count:', count);
add();
console.log('count:', count);
上面这两段 js 代码完美诠释了此含义,其结果为:
count: 4
count: 4
可以看出 commonjs 对于导出的变量以及函数都是代码执行时直接复制其值,而不是连同引用一起导出,导致通过模块内部修改变量的值之后,在外部导入模块变量并没有发现其值发生变化.
-
优势和劣势.
-
优势:
- 导入比较灵活;
- NodeJS 模块导入导出完全采用 commonjs 模式,npm 上绝大部分的依赖库都会兼容 commonjs 模块导入导出,适用范围很广泛;
- 同步模块加载;
- 良好的团队维护,完备的社区/论坛;
-
劣势:
- 不支持静态分析,静态分析所带来的一系列福利不能在 commonjs 模块导入导出模式下实行;
- 不能实行异步模块加载;
-
amd(cmd)
amd(cmd) 的适用范围很窄,受众面也远远没有 commonjs 和 esm 广泛,因为受限于第三方库的环境依赖(无论是 SeaJS,还是 RequireJS 都需要事先下载依赖).
-
优势和劣势.
-
优势:
- 支持同步/异步模块加载,amd 近似于同步模块导入导出(与 commonjs 同步模块加载有着本质的不同),cmd 异步模块导入导出(与 esm 异步模块加载也有着本质的不同);
-
劣势:
- 不支持静态分析,静态分析所带来的一系列福利不能在 amd(cmd) 模块导入导出模式下实行;
- 受限于第三方库的环境依赖;
- 写法上很不友好;
- 适用范围很窄,没有类 NodeJS、npm 以及 ECMAScript 标准这种受众面很广泛的'推手'推动;
- 社区/论坛不成熟,维护一般;
-
umd
umd,全称: Universal Module Definition,其标准为: commonjsStrictGlobal以及returnExportsGlobal.
- commonjsStrictGlobal.
// Uses CommonJS, AMD or browser globals to create a module. This example
// creates a global even when AMD is used. This is useful if you have some
// scripts that are loaded by an AMD loader, but they still want access to
// globals. If you do not need to export a global for the AMD case, see
// commonjsStrict.js.
// If you just want to support Node, or other CommonJS-like environments that
// support module.exports, and you are not creating a module that has a
// circular dependency, then see returnExportsGlobal.js instead. It will allow
// you to export a function as the module value.
// Defines a module "commonJsStrictGlobal" that depends another module called
// "b". Note that the name of the module is implied by the file name. It is
// best if the file name and the exported global have matching names.
// If the 'b' module also uses this type of boilerplate, then
// in the browser, it will create a global .b that is used below.
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'b'], function (exports, b) {
factory((root.commonJsStrictGlobal = exports), b);
});
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(exports, require('b'));
} else {
// Browser globals
factory((root.commonJsStrictGlobal = {}), root.b);
}
}(typeof self !== 'undefined' ? self : this, function (exports, b) {
// Use b in some fashion.
// attach properties to the exports object to define
// the exported module properties.
exports.action = function () {};
}));
- returnExportsGlobal.
// Uses CommonJS, AMD or browser globals to create a module.
// If you just want to support Node, or other CommonJS-like environments that
// support module.exports, and you are not creating a module that has a
// circular dependency, then see returnExports.js instead. It will allow
// you to export a function as the module value.
// Defines a module "commonJsStrict" that depends another module called "b".
// Note that the name of the module is implied by the file name. It is best
// if the file name and the exported global have matching names.
// If the 'b' module also uses this type of boilerplate, then
// in the browser, it will create a global .b that is used below.
// If you do not want to support the browser global path, then you
// can remove the `root` use and the passing `this` as the first arg to
// the top function.
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'b'], factory);
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(exports, require('b'));
} else {
// Browser globals
factory((root.commonJsStrict = {}), root.b);
}
}(typeof self !== 'undefined' ? self : this, function (exports, b) {
// Use b in some fashion.
// attach properties to the exports object to define
// the exported module properties.
exports.action = function () {};
}));
从源码中可以看出 umd 是对于 commonjs、Node(webpack中值枚举为commonjs2)、amd 以及 Browser globals 的并集,是实行兼容的一种模块导入导出模式.
-
优势和劣势.
-
优势:
- 兼容的这几种模块导入导出都支持同步模块加载;
- 导入比较灵活;
- 导出的类型更丰富;
-
劣势:
- 不支持静态分析,静态分析所带来的一系列福利也不能在 umd 模块导入导出模式下实行;
- 不能实行异步模块加载;
-
esm(ecmascript module)
模块导入导出的最终方案模式,也是现在 NodeJS、npm 以及 ECMAScript 标准这些受众面很广泛的'推手'主要推动的模块导入导出模式. 其导入是在编译器编译阶段,由此特性也导致了两个特点: 静态分析和赋值引用,与 commonjs 的特性与特点完全相反.
- NodeJS ESM.
混用阶段,也就是 import 配合 module.exports,require 配合 export.
注意在 import 配合 module.export 这部分,import 必须导入 module.exports 导出的模块,不能导入 exports 导出的模块,由于还是以commonjs模块导出,那么esm导入就不能实行静态分析,只能整体导入.
//module.js
const count = 4;
//NodeJS ESM 必须使用 module.exports 导出
module.exports = {
count
};
//index.js
//以 commonjs 模块导出,esm 导入不能实行静态分析,只能整体导入.
//import {count} from './module.js';
import module from './module.js';
console.log(module.count);
- 静态分析.
其导入是在编译器编译阶段,所以需要将所使用的模块都在所要导入文件的头部进行导入.可对其引入的值、函数或者模块可进行静态分析.
//module.js
export const count = 4;
//index.js
//对其引入的值、函数或者模块可进行静态分析
import {count} from './module.js';
console.log(count);
- 赋值引用.
//module.js
export let count = 4;
export function add() {
count++;
}
//index.js
import {count, add} from './module.js';
console.log('count:', count);
add();
console.log('count:', count);
上面这两段 js 代码与 commonjs 赋值复制部分是同一个🌰,但是执行结果却是不相同的,其结果为:
count: 4
count: 5
可以看出 esm 对于导出的变量以及函数都是编译器编译时连同引用一起导出,导致通过模块内部修改变量的值之后,在外部导入模块变量的值也发生了改变.
-
优势和劣势.
-
优势:
- 支持静态分析,静态分析所带来的一系列福利都可接收;
- 可实行异步模块加载;支持动态导入 import();
- 良好的团队维护,完备的社区/论坛;
-
劣势:
- 现阶段 NodeJS、npm 以及 ECMAScript 标准这些受众面很广泛的'推手'因历史、兼容、底层改动大等问题,实现的都不成熟,还需要 Webpack/Babel 等工具进行转译;
-