Single-spa 简介

Flying
2020-08-25 / 0 评论 / 114 阅读 / 正在检测是否收录...

最近,我负责建立一个门户,用于整合公司的内部工具并为用户提供统一的界面。在这个过程中,我发现了微前端的世界。本文旨在记录我对微前端的学习以及如何使用一种流行的微前端框架 single-spa 来实现这样一个应用程序。

single-spa.svg

什么是微前端应用程序

微前端应用程序是由多个前端框架构成的应用程序。为什么要锁定在一个框架上,当你可以充分利用它们的优势呢?你可以在微前端应用程序中使用 React 编写登录、头部和侧边栏,使用 Angular 编写另一个功能,使用 Vue 编写另一个功能。微前端应用程序是与语言无关的。应用程序中的每个组件应该最小化彼此之间的通信。

为什么要使用微前端

当你需要构建一个整合了多个已有应用程序的统一界面时,微前端就能发挥其优势。与其重新构建每个应用程序(这将耗费大量时间和金钱),我们可以利用微前端快速集成以独立开发的应用程序。

微前端还可用于处理遗留代码。与其雇佣开发人员维护一个遗留应用程序,团队可以使用现代化的框架编写新功能,并通过微前端将其与旧应用程序集成。

怎样使用微前端

有许多实现微前端的方法。你可以使用 WebComponents,使用带有事件总线消息系统的 iframe(就像 Spotify 一样——你可能不知道他们的应用程序是一个微前端),或者使用诸如 single-spa 之类的微前端框架。这篇文章将展示如何使用 single-spa 实现一个基本的微前端应用程序。

single-spa 的工作原理

single-spa 的思想非常简单:

  1. single-spa 根配置 是一个应用程序,它整合了所有的子应用程序并将它们呈现给用户。
  2. single-spa 应用程序 是由 single-spa 根配置提供的应用程序。
  3. single-spa 根配置 会在请求时提供 single-spa 应用程序 的 JavaScript 捆绑包。

因此,我们应该做的是:

  1. 创建一个 single-spa 根配置
  2. 以某种方式告诉我们现有的 single-spa 应用程序 中的框架提供一个 JavaScript 捆绑包,而不是提供一个 HTML 应用程序。

因为更多的使用场景是将现有的 SPA 应用迁移到 single-spa 框架中,所以我将详细介绍使用 single-spa 制作微前端应用程序的步骤,还将把一个 React CRA 应用程序转换为用于我们的微前端应用程序。

转换 React CRA 应用程序

实际上,当你考虑采用微前端架构时,你很可能是将现有的单体应用程序转换或将几个现有应用程序合并在一起。我假设你已经知道如何创建一个 React CRA 应用程序。如果不知道,请[点击此处]https://create-react-app.dev/docs/getting-started/)查看 React 的教程。

如果你已经知道如何创建一个 CRA 应用程序,那么下面的文件夹结构应该很熟悉:

cra.jpg
CRA 文件夹结构

CRA 是如何工作的呢?它使用 Webpack 和 Babel 将我们的代码编译成 index.htmlindex.js 文件,并提供这些文件。CRA 还从我们那里抽象出了 Webpack 配置和 Babel 配置,防止我们修改其开箱即用的解决方案。然而,我们的目标是让 React 只提供一个 index.js 文件 ,这样它就可以被我们的 single-spa 根配置下载并注入到根配置的 index.html 文件中,并为此应用程序实现 生命周期方法 ,以便 single-spa 根配置在需要时调用引导、挂载和卸载应用程序。因此,我们需要对 React 的 webpack 进行一些更改。

虽然我们可以弹出 React,但有一种更好的方法可以做到这一点。幸运的是,Canopy Tax 的开发人员制作了一个 single-spa CLI 工具,可以帮助我们将现有的 React CRA 应用程序转换为 single-spa 应用程序

在你的 package.json 文件中, 删除 以下包:

"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.3"

以及以下脚本:

"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"

single-spa CLI 将稍微修改我们的 package.json,因此删除测试库以及 reactreact-dom 只是为了避免冲突。对于我们的新的 single-spa 应用程序,我们将不再使用 react-scripts,而是直接使用 webpack。

现在,在项目根目录中运行 npx create-single-spa。提示非常直观。记得允许 CLI 工具覆盖你的 package.json.gitignore 文件。

? Directory for new project .
? Select type to generate single-spa application / parcel
? Which framework do you want to use? react
? Which package manager do you want to use? npm
? Will this project use Typescript? No
? Organization name (can use letters, numbers, dash or underscore) abc
? Project name (can use letters, numbers, dash or underscore) demo
 conflict package.json
? Overwrite package.json? overwrite
    force package.json
   create jest.config.js
   create babel.config.json
   create .eslintrc
 conflict .gitignore
? Overwrite .gitignore? overwrite
    force .gitignore
   create .prettierignore
   create webpack.config.js
   create src\root.component.js
   create src\root.component.test.js
   create src\abc-demo.js

现在让我们看看 single-spa CLI 为我们创建的新文件:

webpack.config.js
babel.config.json
jest.config.js
.prettierignore
.eslintrc
src\root.component.js
src\root.component.test.js
src\abc-demo.js

正如你所看到的,我们现在有一个 webpack.config.js 文件和一个 babel.config.json 文件!它们还自动配置为提供单个捆绑的 JavaScript 文件,这正是我们想要的。不过,还有一些工作要做。

对于我们新配置的应用程序,我们的入口点现在是 abc-demo.js 文件,而不是 index.js 文件。让我们将重要的内容(如 react-router-domBrowserRouter 或 Redux 的 Provider)从 index.js 移动到我们的 App.js 文件,并删除 index.jsserviceWorker.js

如果你点击我们的 abc-demo.js 文件,你会看到这些代码:

import React from "react";
import ReactDOM from "react-dom";
import singleSpaReact from "single-spa-react";
import Root from "./root.component";

const lifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: Root,
  errorBoundary(err, info, props) {
    // Customize the root error boundary for your microfrontend here.
    return null;
  },
});

export const { bootstrap, mount, unmount } = lifecycles;

正如你所看到的, 生命周期方法 已经为我们创建好了!这个入口文件调用了一个叫做 root.component.js 的组件。然而,我们的第一个组件很可能是 App.js,而不是这个。将导入更改为导入我们的 App.js 并将我们的 App 放在 rootComponent 中。此时,我们还可以删除 root.component.jsindex.csssetupTests.jsroot.component.test.js

entry.jpg
修改 abc-demo.js

到这一步,你可以尝试使用 npm-start 运行应用程序。为了设置你想要的端口,你可以将启动脚本修改为 webpack-dev-server --port 3000

你可能会遇到一些编译错误。很多开发者经常遇到的一个常见错误是处理图像文件。

ERROR in ./src/logo.svg 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type...

为了解决这个问题,使用 webpack 的 file-loader

  1. 安装 file-loader
npm install file-loader --save-dev
  1. webpack.config.js 添加 file-loader
// modify the webpack config however you'd like to by adding to this object
module: {
  rules: [
    {
      test: /\.(png|jpe?g|gif|svg)$/i,
      use: [
        {
          loader: 'file-loader'
        }
      ]
    }
  ]
}

解决错误后,当一切正常时,你的应用程序应该是这样的:

3000.jpg
single-spa 欢迎界面

恭喜!你的应用程序现在正在使用 webpack 的文件。我们的应用程序中的 public 文件夹现在不再需要,因为我们不再需要 index.html 文件。如果你还没有将所需的所有资源移动到 src 中,请将它们移动到 src 中,并删除该文件夹。现在,如果你转到 localhost:port/abc-demo.js,你应该能看到 webpack 打包文件:

System.register(["react","react-dom"], function(__WEBPACK_DYNAMIC_EXPORT__) {
  var __WEBPACK_EXTERNAL_MODULE_react__, __WEBPACK_EXTERNAL_MODULE_react_dom__;
  return {
    //...
  }
})

这样可以检验你当前做得对不对。这是我们应用程序的打包后的 JavaScript 文件,准备使用 single-spa 根配置 作为服务。这就引出了我下一个要讲的内容。

创建 Single-Spa 根配置

正如文章中提到的,我们需要一个 single-spa 根配置 来下载我们的 single-spa应用程序的 JS 打包文件并提供服务。也就是说,需要新建一个项目来消费我们的 *single-spa应用程序,我们将再次使用 single-spa CLI

在一个新的文件夹中运行 npx create-single-spa 命令。它将帮助我们创建一个全新的 single-spa 根配置 项目,我们可以直接使用。选择如下:

? Directory for new project .
? Select type to generate single-spa root config
? Which package manager do you want to use? npm
? Will this project use Typescript? No
? Would you like to use single-spa Layout Engine No
? Organization name (can use letters, numbers, dash or underscore) abc

  create package.json
  create babel.config.json
  create .gitignore
  create .eslintrc
  create .prettierignore
  create webpack.config.js
  create src\abc-root-config.js
  create src\index.ejs
注意: single-spa CLI 项目类型选择为:single-spa root config

这个根配置可以直接使用,所以我们不需要额外的配置步骤。但是,让我们深入了解一下这个项目中发生了什么。项目目录如下所示:

src\abc-root-config.js
src\index.ejs

非常简单!只涉及到两个文件:abc-demo.js 文件和 index.ejs 文件。

让我们来看看 abc-root-config.js文件:

import { registerApplication, start } from "single-spa";

registerApplication({
  name: "@single-spa/welcome",
  app: () =>
    System.import(
      "https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"
    ),
  activeWhen: ["/"],
});

// registerApplication({
//   name: "@abc/navbar",
//   app: () => System.import("@abc/navbar"),
//   activeWhen: ["/"]
// });

start({
  urlRerouteOnly: true,
});

这个文件的结构非常简单:[注册所有要显示的应用程序 => 启动应用程序]

毋庸置疑,我们需要使用 registerApplication 方法来注册我们的 single-spa 应用程序registerApplication 方法有 3 个参数:

  1. name:我们正在加载的 single-spa 应用程序的名称。格式应为“@organization/projectname”。这个名称应该与你之前使用 CLI 在你的 single-spa 应用程序中初始化的组织名称和项目名称相同(例如,本示例中,名称为“@abc/demo”)。
  2. app:这将是一个异步方法,用于加载我们的应用程序。我们的 single-spa 根配置 应用程序使用了 SystemJS 进行外部导入。
  3. activeWhen:这是一个触发加载我们的 single-spa 应用程序 的路由数组。“/” 表示我们的 single-spa 应用程序将始终被加载。

如上所述,single-spa 根配置使用 SystemJS 来导入外部文件。这意味着我们的根配置应用程序将能够从外部 URL 导入一个 JavaScript 文件,而 Webpack 无法做到这一点。

为了导入外部应用程序,我们首先需要在 index.ejs 文件中声明它的导入映射。我们当前的 index.ejs 文件如下所示:

index.jpg
index.ejs 文件

如果你还记得 abc-demo.js 文件中的内容,我们在其中声明了 import {} from 'single-spa'。这是因为我们已经在上面的 SystemJS 导入映射中声明了它。因此,为了能够导入我们的 single-spa 应用程序的打包 JavaScript 文件,我们也不得不在导入映射中进行相应的声明:

<script type="systemjs-importmap">
  {
    "imports": {
      "single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.5.0/lib/system/single-spa.min.js"
      "@abc/demo": "http://localhost:3000/abc-demo.js"
    }
  }
</script>

然后在我们的 abc-root-config.js 文件中注册它:

registerApplication({
  name: "@abc/demo",
  app: () => System.import("@abc/demo"),
  activeWhen: ["/"],
});

接下来在根配置中注册我们的 single-spa 应用

如果你现在运行 npm start,应用程序将被打包。但是在打开时,你将面临一个令人讨厌的问题 Unable to resolve bare specifier 'React'。这是因为我们的 single-spa 应用程序的打包 JavaScript 正在尝试导入 reactreact-dom,但它们在我们的导入映射中没有声明。为了解决这个问题,在我们的导入映射中包含 reactreact-dom 的 CDN:

<script type="systemjs-importmap">
  {
    "imports": {
      "single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.5.0/lib/system/single-spa.min.js"
      "react": "https://cdn.jsdelivr.net/npm/react@16.3.1/umd/react.production.min.js"
      "react-dom": "https://cdn.jsdelivr.net/npm/react-dom@16.3.1/umd/react-dom.production.min.js"
      "@abc/demo": "http://localhost:3000/abc-demo.js"
    }
  }
</script>

最后在我们的导入映射中声明 react 和 react-dom

你可以在此处查看原始问题。在同一 GitHub 问题中,你还可以获取到 react 和 react-dom 的 CDN 链接。

如果你已经完成了这些步骤,恭喜你!你应该有一个可工作的微前端应用程序!

总结

在本文中,我们使用 single-spa CLI 和 React CRA 构建了一个微前端应用程序。因为我们更多时候是将现有的 SPA 应用迁移到 single-spa 框架中,为了使教程更加实际,我将现有的 React CRA 应用程序进行了转换,而不是从头开始。如果你从头开始,建议你使用 npx create-single-spa 而不是 React CRA 开始。

1

评论 (0)

取消