跳到主要内容

Systemjs

Systemjs

  • what

    • systemjs 是一个动态模块加载器
    • systemjs 是一个用于实现模块化的 JavaScript 库,有属于自己的模块化规范
    • systemjs 是现阶段下(浏览器尚未正式支持importMap)原生 ES Module 的替代品,ES Module 被编译成 System.register 格式之后能够跑在旧版本的浏览器当中
  • why 在微前端架构中,微应用会被打包为模块,但浏览器对模块化的支持还不够好,此时需要使用 systemjs 实现浏览器中的模块化

  • how 在开发阶段我们可以使用 ES 模块规范,然后使用 webpack 将其转换为 systemjs 支持的模块

特性

Import Maps

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

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

如果有了 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 的浏览器还很少。

如果想要使用这个特性的话,需要引入 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 是一个将多个单页面应用聚合为一个整体应用的 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,实现子模块的引入。

基本使用

🌰 DEMO

通过 webpack 将 react 应用打包为 systemjs 模块,再通过 systemjs 在浏览器中加载模块

1. 搭建开发环境

一、安装依赖

npm install webpack@5.17.0 webpack-cli@4.4.0 webpack-dev-server@3.11.2 html-webpack-plugin@4.5.1 @babel/core@7.12.10 @babel/cli@7.12.10 @babel/preset-env@7.12.11 @babel/preset-react@7.12.10 babel-loader@8.2.2

二、创建文件

1. 创建入口文件 src/index.js
2. 创建html模板文件 src/index.html
// src/index.js
import React from "react"
import ReactDOM from "react-dom"
import App from "./App.js"

/** 从这一步开始就是react 方面的内容 */
ReactDOM.render(<App />, document.getElementById("root"))

// App.js
import React from "react"

export default function App() {
return <div>Hello World</div>
}
<!--src/index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>systemjs-react</title>
<script type="systemjs-importmap">
// 在微前端架构中 react、react-dom 这些属于公共框架,我们不希望在每一个微应用中都去下载、打包
{
"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",
"react-router-dom": "https://cdn.jsdelivr.net/npm/react-router-dom@5.2.0/umd/react-router-dom.min.js"
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.0/dist/system.min.js"></script>
</head>
<body>
<div id="root"></div>
<script>
// 表示可以动态加载模块
// 加载模块的时候会提示加载react和react-dom 会自动在上边加载systemjs-importmap 中配置的要加载的模块
// 可以加载远程连接
// 类似AMD的前置依赖 引入index.js的时候需要先加载 react和 react-dom
System.import("./index.js")
</script>
</body>
</html>

2. 配置 webpack

微前端的公共模块 必须采用 cdn的方式

// webpack.config.js
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
filename: "index.js",
path: path.join(__dirname, "build"),
libraryTarget: "system" // !!! 1. 将 es-module 转换成 system module
},
devtool: "source-map",
devServer: {
port: 9000, // 应用运行的端口
contentBase: path.join(__dirname, "build"), // 静态资源文件夹
historyApiFallback: true
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"]
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html", // 在默认情况下会帮我们将 ./src/index.html 文件打包到 build 中,并且会把我们打包的JS 文件通过 script 标签引入到 html 文件中,但是在微前端架构中引 JS 文件的行为是不需要的,因为我们需要通过 systemjs 去加载该模块
inject: false // !!! 3.阻止 JS 文件的引入
})
],
externals: ["react", "react-dom", "react-router-dom"] // !!! 2. 哪些模块是不需要打包的
}

3. 添加启动命令

"scripts": {
"start": "webpack serve"
},

测试: npm start

完整版Demo Link