介绍
SystemJS 是由 Guy Bedford 创建的一个模块加载器,它诞生于 2014 年左右,正值 JavaScript 模块化标准尚未完全统一,开发者需要在 AMD、CommonJS、ES6 模块等多种模块格式之间切换的时期。SystemJS 的目标是提供一个高度灵活的模块加载解决方案,能够无痛地支持多种模块格式,并且能适应从旧的浏览器环境到最新的 JavaScript 标准的过渡。
使用示例
在 HTML 中使用 systemjs-importmap 导入依赖
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>SystemJS AMD Modules from CDN Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="systemjs-importmap">
{
"imports": {
"react": "https://cdn.jsdelivr.net/npm/react/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom/umd/react-dom.production.min.js"
}
}
</script>
<script src="../dist/system.js"></script>
</head>
<body>
<div id="react-root"></div>
<script type="systemjs-module" src="../dist/hello-world.js"></script>
</body>
</html>
核心 API
System.import(id [, parentURL]) -> Promise(Module)
<script type="systemjs-module" src="/js/main.js"></script>
// System.import('/js/main.js');
System.addImportMap(map [, base])
<script type="systemjs-importmap">
{
"imports": {
"lodash": "https://unpkg.com/lodash@4.17.10/lodash.js"
}
}
</script>
<!-- <script type="systemjs-importmap" src="path/to/map.json" crossorigin="anonymous"</script>-->
// System.addImportMap({
// imports: {
// lodash: 'https://unpkg.com/lodash@4.17.10/lodash.js',
// },
// });
System.set(id, module) -> Module
System.set('http://site.com/normalized/module/name.js');
System.register 一般结构如下
System.register([...deps...], function (_export, _context) {
return {
setters: [...setters...],
execute: function () {
}
};
}, [...metas...],);
deps: String[]: 依赖项setters: Function[]: 每当依赖项更新时要调用的函数数组,其索引顺序与deps相同。对于没有导出的依赖项,setter 函数可以未定义。execute: Function | AsyncFunction: 这是在代码执行的精确点调用的,而外部包装器则在早期调用,从而允许包装器在执行之前导出函数声明;如果使用异步函数进行执行,则按照规范的变体提供顶级等待执行支持语义。_export: ({ [name: String]: any } | (name: String, value: any)) => value: 可以使用导出函数的直接形式来导出绑定_export('exportName', value)它返回设置的值,以便在表达式中使用;还可以批量导出函数允许设置导出对象。通过减少使用时的设置函数调用来提高性能,因此在实现中应尽可能使用批量导出函数,而不是直接导出。_context.meta: Objectimport.meta:这是一个表示模块执行值的对象。默认情况下,它将import.meta.url存在于 SystemJS 中。_context.import: (id: String) => Promise<Module>:这是可供模块替代的上下文动态导入功能import()。metas: Object[]:附加到模块依赖项的元数据,按与 相同的顺序进行索引deps。这是一个可选参数。
hello-world.js
System.register(["react", "react-dom"], function (_export, _context) {
"use strict";
var React, ReactDOM;
return {
setters: [function (_react) {
React = _react.default;
}, function (_reactDom) {
ReactDOM = _reactDom.default;
}],
execute: function () {
ReactDOM.render(React.createElement("button", null, "A button created by React"), document.getElementById('react-root'));
}
};
});
加载流程
加载流程

...
envGlobal.System = new SystemJS();
...
if (hasDocument) {
window.addEventListener('DOMContentLoaded', processScripts);
}
...
function processScripts () {
[].forEach.call(document.querySelectorAll('script'), function (script) {
...
if (script.type === 'systemjs-module') {
...
System.import(script.src.slice(0, 7) === 'import:' ? script.src.slice(7) : resolveUrl(script.src, baseUrl));
...
}
else if (script.type === 'systemjs-importmap') {
...
var fetchPromise = script.src ? (System.fetch || fetch)(script.src) : script.innerHTML;
...
importMapPromise = importMapPromise.then(function () {
return fetchPromise;
}).then(function (text) {
extendImportMap(importMap, text, script.src || baseUrl);
});
}
});
}
import 机制
systemJSPrototype.import = function (id, parentUrl, meta) {
var loader = this;
(parentUrl && typeof parentUrl === 'object') && (meta = parentUrl, parentUrl = undefined);
return Promise.resolve(loader.prepareImport())
.then(function() {
return loader.resolve(id, parentUrl, meta);
})
.then(function (id) {
var load = getOrCreateLoad(loader, id, undefined, meta);
return load.C || topLevelLoad(loader, load);
});
};
systemJSPrototype.import 方法是 SystemJS 中用于异步导入模块的核心函数。它接受三个参数:id(模块标识符),parentUrl(可选的父模块URL,用于确定模块的相对路径),以及meta(元数据,如模块的类型或其他信息)
- 参数处理:如果传入的
parentUrl实际上是一个对象,那么它的值被赋给meta,而parentUrl被重置为undefined。这允许你以一个对象的形式传递meta数据,其中可能包括parentUrl。 - 准备导入:使用当前的加载器实例(
loader)调用prepareImport方法,这是一个异步操作,内部调用processScripts方法做开始导入前的再次确认。结果是一个 Promise,当prepareImport完成时会被解析。 - 解析模块路径:一旦
prepareImport成功完成,下一个then方法会调用loader.resolve来解析模块标识符id到一个绝对的模块路径。resolve方法会考虑parentUrl和meta来正确解析模块路径。 - 获取或创建加载实例:解析成功后,得到的模块路径被用来调用
getOrCreateLoad方法。这个方法会查找或创建一个代表模块加载过程的load对象。如果模块已经被加载过,load将直接返回先前的加载实例;否则,会创建一个新的加载实例。 - 执行加载:最后,如果
load实例已经有一个表示完成状态的C属性(即模块已经完成加载),那么直接返回这个C值。如果没有,调用topLevelLoad函数,这个函数负责实际执行模块的加载和执行流程。
总结
按上述的思路,那么微前端是不是也是一个远程模块?
<script type="systemjs-importmap">
{
"imports":
{
"react": "https://cdn.jsdelivr.net/npm/react/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom/umd/react-dom.production.min.js"
}
}
</script>