SystemJS揭秘

1,764 阅读4分钟

背景

SystemJS 诞生于 2015 年,那个时候 ES Module 还未成为标准,在浏览器端只能通过 requirejs、seajs 等方案实现模块加载,随着 npm 在前端界的流行,一个项目中可能存在多种模块规范,所以我认为 SystemJS 最初诞生的目的是为了做一个通用的模块加载器,在浏览器端实现对 CommonJS、AMD、UMD 等各种模块的加载。

后来,随着 ES6 的普及,越来越多的浏览器开始支持 ES Module, SystemJS 是现阶段下(浏览器尚未正式支持importMap)原生 ES Module 的替代品,ES Module 被编译成 System.register 格式之后能够跑在旧版本的浏览器当中。

特性

Import Maps

<script type="module">
    import moment from "moment";
    import { partition } from "lodash";
</script>

这样写会报错,原因是在浏览器中,import 必须给出相对或绝对的 URL 路径。没有任何路径的模块被称为“裸(bare)”模块。在 import 中不允许这种模块。 某些环境,像 Node.js 或者打包工具允许没有任何路径的裸模块,因为它们有自己查找模块的方法。但是浏览器尚不支持裸模块。

但是如果有了 Import Maps

<script type="importmap">
{
  "imports": {
    "moment": "/node_modules/moment/src/moment.js",
    "lodash": "/node_modules/lodash-es/lodash.js"
  }
}
</script>

上面的写法就能被解析为:

<script type="module">
    import moment from "/node_modules/moment/src/moment.js";
    import { partition } from "/node_modules/lodash-es/lodash.js";
</script>

Import maps 在 Chrome 74 中可以通过实验性质开启,本质上是一个配置文件,可以让开发者将模块标识符映射到一到多个其他标识符的机制,这个机制非常强大,它赋予了开发者在运行时针对指定的模块动态修改浏览器实际获取模块的能力。该配置文件描述了依赖的解析方式,某种程度上,Import maps 给浏览器端带来了包管理,但是目前支持 Import Maps 的浏览器还很少

image.png

如果想要使用这个特性的话,需要引入 SystemJS

<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>
<script src="https://cdn.jsdelivr.net/npm/systemjs/dist/system.js"></script>

应用

single-spa

single-spa 是一个将多个单页面应用聚合为一个整体应用的 JavaScript 微前端框架

在 single-spa 的使用过程中,我们需要用 import-map 在根项目中引入所有的模块文件和子项目,从而在其余项目中可以进行模块的引用,就像上面说的那样,可以把 moment 想象成一个子项目。

<script type="systemjs-importmap">
  {
    "imports": {
      "module": "https://[cdn-link].js",
    }
  }
</script>
<script src="https://cdn.jsdelivr.net/npm/systemjs/dist/system.js"></script>

把 import-map 里的文件放到一个 json 当中,就变成了

<script type="systemjs-importmap" src="https://[bucket]/import-map.json"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs/dist/system.js"></script>

我们开发者需要做的,就是把模块文件打包成单一的 cdn 文件,然后引入 import-map.json,实现子模块的引入。

Bundless

ESModule

使用 type="module" 开启 ESModule

<script type="importmap">
  {
    "imports": {
      "react": "https://airpack.alibaba-inc.com/react",
      "react-dom": "https://airpack.alibaba-inc.com/react-dom"
    }
  }
</script>

<div id="app"></div>

<script type="module">
  import React from 'react'
  import ReactDOM from 'react-dom'

  ReactDOM.render('Hello React', document.getElementById('root'))
</script>

这种方式的缺点是需要所有模块都导出成 ESModule,当前社区当中的很多模块都没有导出成 ESModule,有些模块甚至没有经过编译,所以目前使用 ESModule 实现 bundless 仍然有一定困难。

SystemJS

StackBlitz 是社区非常著名的一个在线 WebIDE,它就是基于 SystemJS 和 Unpkg 实现 bundless。

Our architecture is based entirely on SystemJS & Unpkg and uses no code from CodeSandbox or the Webpack bundling service ya’ll made. From the start, our goal was to port VS Code, NPM, and Webpack loaders to run _entirely in your browser and still work offline. _With StackBlitz, the browser is installing, bundling, and serving everything — our servers don’t do any of that. More info on this can be found here.

下面是使用 SystemJS 实现类似 ESModule 的示例,区别是不需要模块导出成 ESModule,可以使用 UMD 模块。

<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="https://cdn.jsdelivr.net/npm/systemjs/dist/system.min.js"></script>

<div id="app"></div>

<script type="systemjs-module">
  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('Hello React', document.getElementById('root'));
      }
    };
  });
</script>

总结

SystemJS 是时代的产物,现阶段下也仍然是原生 ESModule 的替代品,但是不可否认,未来一定是 ESModule 的世界。

es-module-shims和systemjs区别

es-module-shimsSystemJS 都是用于在非原生 ES Modules 环境下加载和运行 ES Modules 的工具。虽然它们的目标相同,但它们的实现方式和使用方法有所不同。

es-module-shims

es-module-shims 是一个 JavaScript 库,它提供了一种在不支持原生 ES Modules 的浏览器中加载和运行 ES Modules 的方法。它通过将 ES Modules 转换为 CommonJS 模块并在浏览器中加载来实现这一点。在转换过程中,es-module-shims 还会模拟一些 ES Modules 的特性,例如模块的生命周期和动态导入。es-module-shims 的优点在于它的体积很小,并且易于使用。只需在页面上引入它的 JavaScript 文件,然后使用标准的 import 语句加载和使用 ES Modules 即可。

SystemJS

SystemJS 是一个 JavaScript 模块加载器,它可以在所有环境下加载和运行 ES Modules。它使用一组自定义加载器来解析和加载模块,并在运行时创建模块之间的依赖关系。SystemJS 支持动态导入和插件化,因此它可以扩展到许多不同的用例和场景中。SystemJS 的缺点在于它的体积较大,并且配置和使用比 es-module-shims 更加复杂。需要配置 SystemJS 的加载器和插件,并使用 System.import 方法来加载模块。

因此,es-module-shimsSystemJS 的主要区别在于它们的实现方式和使用方法。如果您只需要在浏览器中加载和运行 ES Modules,则可以使用 es-module-shims。如果您需要在不同环境中加载和运行 ES Modules,并且需要更高级的特性和定制选项,则应该考虑使用 SystemJS