探索 single-SPA 框架,构建可扩展且可维护的微前端架构。了解其优势、实现方法以及面向全球团队的最佳实践。
Single-SPA 框架:微前端编排综合指南
在当今快速发展的 Web 开发领域,单体前端应用越来越难以跟上不断增长的应用和分布式团队的需求。微前端架构作为应对这些挑战的强大解决方案应运而生,它使开发人员能够将复杂的用户界面构建为一组独立、可部署且可维护的组件集合。这种方法促进了团队的自主性,提高了代码的可复用性,并简化了整体开发流程。在众多用于微前端编排的框架中,single-SPA 以其通用性和稳健性脱颖而出。
什么是微前端?
微前端是一种架构风格,它将一个前端应用分解为多个更小、独立且自包含的单元(即微前端)。每个微前端都可以由不同的团队独立开发、部署和维护。可以将其看作是多个迷你应用共同协作,构成一个统一的用户体验。
微前端的主要特点包括:
- 技术无关性: 每个微前端可以使用不同的框架和技术(如 React、Angular、Vue.js 等)来构建。
- 独立部署: 微前端可以独立部署,而不会影响应用的其他部分。
- 团队自治: 不同的团队可以拥有和维护不同的微前端,从而促进自主性并加快开发周期。
- 代码复用: 公共组件和库可以在微前端之间共享。
- 提升可扩展性和可维护性: 与大型单体应用相比,更小、独立的单元更易于扩展、维护和更新。
为什么选择 Single-SPA?
Single-SPA 是一个 JavaScript 框架,它有助于在单个浏览器页面内编排多个 JavaScript 应用(微前端)。它本身不规定微前端使用任何特定的技术栈,允许团队选择最适合其需求的工具。该框架充当一个元框架,为加载、卸载和管理不同微前端的生命周期提供基础设施。
以下是 single-SPA 成为微前端编排热门选择的原因:
- 框架无关性: single-SPA 几乎可以与任何 JavaScript 框架一起使用,包括 React、Angular、Vue.js、Svelte 等。这种灵活性允许团队逐步采用微前端,而无需重写现有应用。
- 渐进式采用: 您可以从小型、独立的功能开始,逐步将单体应用迁移到微前端架构。
- 代码共享: single-SPA 允许您在微前端之间共享代码和依赖项,从而减少冗余并提高一致性。
- 懒加载: 微前端按需加载,从而改善了初始页面加载时间和整体性能。
- 简化部署: 微前端的独立部署可以加快发布周期并降低风险。
- 稳健的生命周期管理: single-SPA 为每个微前端提供了明确定义的生命周期,确保它们被正确地初始化、挂载、卸载和销毁。
Single-SPA 的核心概念
为了有效地使用 single-SPA,理解其核心概念至关重要:
- Single-SPA 配置: 启动 single-SPA 应用的主 JavaScript 文件。它负责注册微前端并定义路由逻辑。这通常包括管理一切的根组件。
- 微前端: 在 single-SPA 配置中注册的独立 JavaScript 应用。每个微前端负责渲染用户界面的特定部分。
- Parcels: 可以在微前端之间共享的可复用组件。Parcels 对于创建应用中多个部分所需的通用 UI 元素或业务逻辑非常有用。
- 根配置: 加载和编排微前端的主应用外壳。它负责处理路由、全局状态管理以及微前端之间的通信。
- 活动函数 (Activity Functions): 用于确定微前端何时应处于活动状态(已挂载)或非活动状态(已卸载)的 JavaScript 函数。这些函数通常基于 URL 路由或其他应用状态。
实现 Single-SPA:分步指南
让我们通过一个基本示例来演示如何设置一个包含两个微前端的 single-SPA 应用:一个用 React 构建,另一个用 Vue.js 构建。
步骤 1:设置 Single-SPA 配置
首先,为您的 single-SPA 应用创建一个新目录并初始化一个 Node.js 项目:
mkdir single-spa-example
cd single-spa-example
npm init -y
接下来,安装必要的依赖项:
npm install single-spa import-map-overrides
在根目录中创建一个 `index.html` 文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Single-SPA Example</title>
<meta name="importmap-type" content="systemjs-importmap">
<script type="systemjs-importmap">
{
"imports": {
"single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/single-spa.min.js",
"react": "https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.production.min.js",
"vue": "https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js"
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/import-map-overrides@2.2.0/dist/import-map-overrides.js"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/system.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/extras/named-exports.js"></script>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<script>
System.import('single-spa-config');
</script>
<import-map-overrides-full show-when-local-storage="devtools"></import-map-overrides-full>
</body>
</html>
这个 `index.html` 文件设置了 SystemJS 模块加载器、导入映射 (import maps) 和 single-SPA 配置。导入映射定义了微前端所使用的依赖项的 URL。
创建一个 `single-spa-config.js` 文件:
import * as singleSpa from 'single-spa';
singleSpa.registerApplication(
'react-app',
() => System.import('react-app'),
location => location.pathname.startsWith('/react')
);
singleSpa.registerApplication(
'vue-app',
() => System.import('vue-app'),
location => location.pathname.startsWith('/vue')
);
singleSpa.start();
该文件注册了两个微前端:`react-app` 和 `vue-app`。`activityFunction` 根据 URL 决定每个微前端何时应该处于活动状态。
步骤 2:创建 React 微前端
为 React 微前端创建一个新目录:
mkdir react-app
cd react-app
npx create-react-app .
npm install single-spa-react
修改 `src/index.js` 文件以使用 `single-spa-react`:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import singleSpaReact from 'single-spa-react';
const lifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: App,
errorBoundary(err, info, props) {
// Customize the root error boundary for your microfrontend here.
return (<h1>Error</h1>);
},
});
export const { bootstrap, mount, unmount } = lifecycles;
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
创建一个 `public/index.html` 文件(如果不存在),并确保存在 `root` div:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
修改 `App.js` 以显示一些自定义文本,方便我们验证工作:
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
This is the <b>React Micro-Frontend</b>!
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
构建 React 微前端:
npm run build
将 `build` 目录重命名为 `react-app` 并将其放置在 single-SPA 应用的根目录中。然后,在 `react-app` 目录内创建一个 `react-app.js` 文件,并将 `build/static/js` 目录中的文件内容复制进去。如果 `static/js` 目录中有多个 js 文件,也应将它们一并包含进来。
更新 `index.html` 中的导入映射,使其指向 React 微前端:
{
"imports": {
"single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/single-spa.min.js",
"react": "https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.production.min.js",
"vue": "https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js",
"react-app": "/react-app/react-app.js"
}
}
步骤 3:创建 Vue.js 微前端
为 Vue.js 微前端创建一个新目录:
mkdir vue-app
cd vue-app
npx @vue/cli create .
npm install single-spa-vue --save
在 Vue CLI 设置过程中,选择默认预设或根据需要进行自定义。
修改 `src/main.js` 文件以使用 `single-spa-vue`:
import Vue from 'vue'
import App from './App.vue'
import singleSpaVue from 'single-spa-vue';
Vue.config.productionTip = false
const vueLifecycles = singleSpaVue({
Vue,
appOptions: {
el: '#vue-app',
render: h => h(App)
}
});
export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;
修改 `App.vue` 以显示一些自定义文本,方便我们验证工作:
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<p>This is the <b>Vue Micro-Frontend</b>!</p>
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
</style>
构建 Vue.js 微前端:
npm run build
将 `dist` 目录重命名为 `vue-app` 并将其放置在 single-SPA 应用的根目录中。然后,在 `vue-app` 目录内创建一个 `vue-app.js` 文件,并将 `dist/js/app.js` 文件的内容复制进去。如果 `dist/js` 目录中有多个 js 文件,也应将它们一并包含进来。
更新 `index.html` 中的导入映射,使其指向 Vue.js 微前端:
{
"imports": {
"single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/single-spa.min.js",
"react": "https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.production.min.js",
"vue": "https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js",
"react-app": "/react-app/react-app.js",
"vue-app": "/vue-app/vue-app.js"
}
}
步骤 4:运行应用
使用一个简单的 HTTP 服务器来运行 `index.html` 文件。您可以使用像 `http-server` 这样的工具:
npm install -g http-server
http-server -c-1
访问 `http://localhost:8080/react` 可以看到 React 微前端,访问 `http://localhost:8080/vue` 可以看到 Vue.js 微前端。
重要注意事项:
- 此示例使用基于 URL 前缀的简单路由。对于更复杂的路由场景,请考虑使用专门的路由库,如 `single-spa-router`。
- 在生产环境中,您通常会从 CDN 或其他静态资源托管服务提供微前端。
- 此示例使用导入映射进行依赖管理。请考虑使用 Webpack 或 Parcel 等构建工具来打包您的生产环境微前端。
高级 Single-SPA 技术
当您设置好基本的 single-SPA 应用后,可以探索更高级的技术来提高架构的可扩展性和可维护性。
使用 Parcels 共享代码
Parcels 允许您在微前端之间共享可复用的组件和逻辑。这有助于减少代码重复并提高整个应用的一致性。
要创建一个 parcel,您可以使用 `singleSpa.mountRootParcel` 函数:
import * as singleSpa from 'single-spa';
import React from 'react';
import ReactDOM from 'react-dom';
function MyParcel(props) {
return (<div>Hello from Parcel! {props.name}</div>);
}
const parcel = singleSpa.mountRootParcel(() => {
return Promise.resolve({
bootstrap: () => Promise.resolve(),
mount: (props) => {
ReactDOM.render(<MyParcel name={props.name} />, document.getElementById('parcel-container'));
return Promise.resolve();
},
unmount: () => {
ReactDOM.unmountComponentAtNode(document.getElementById('parcel-container'));
return Promise.resolve();
},
});
});
// To mount the parcel:
parcel.mount({ name: 'Example' });
微前端之间的通信
微前端通常需要相互通信以共享数据或触发操作。有几种方法可以实现这一点:
- 共享全局状态: 使用像 Redux 或 Vuex 这样的全局状态管理库在微前端之间共享数据。
- 自定义事件: 使用自定义 DOM 事件在微前端之间广播消息。
- 直接函数调用: 从一个微前端导出函数,并在另一个微前端中导入它们。这种方法需要仔细协调以避免依赖和循环引用。
- 消息代理: 使用像 RabbitMQ 或 Kafka 这样的库实现消息代理模式,以解耦微前端并实现异步通信。
认证和授权
在微前端架构中实现认证和授权可能具有挑战性。以下是一些常见的方法:
- 集中式认证: 使用中央认证服务来处理用户登录和认证。认证服务可以颁发令牌,用于向微前端的请求进行身份验证。
- 共享认证模块: 创建一个由所有微前端使用的共享认证模块。该模块可以处理令牌管理和用户会话。
- API 网关: 使用 API 网关处理对所有微前端请求的认证和授权。API 网关可以验证令牌并强制执行访问控制策略。
使用 Single-SPA 的微前端架构的优势
- 增强团队自主性: 独立的团队可以开发和部署微前端,而不会影响其他团队。这促进了自主性并加快了开发周期。
- 提高可扩展性: 微前端可以独立扩展,使您能够优化资源分配并处理增加的流量。
- 增强可维护性: 与大型单体应用相比,更小、独立的单元更易于维护和更新。
- 技术多样性: 团队可以为其微前端选择最佳技术栈,从而实现更大的灵活性和创新。
- 降低风险: 微前端的独立部署降低了部署变更的风险,并简化了回滚程序。
- 渐进式迁移: 您可以逐步将单体应用迁移到微前端架构,而无需进行完全重写。
微前端架构的挑战
虽然微前端提供了许多好处,但它们也带来了一些挑战:
- 增加复杂性: 管理多个微前端可能比管理单个单体应用更复杂。
- 通信开销: 协调微前端之间的通信可能具有挑战性。
- 部署复杂性: 部署多个微前端可能比部署单个应用更复杂。
- 一致性: 在所有微前端中保持一致的用户体验可能很困难。
- 代码重复: 如果没有仔细规划,代码和依赖项可能会在微前端之间重复。
- 运营开销: 为多个微前端设置和管理基础设施可能会增加运营开销。
使用 Single-SPA 构建微前端的最佳实践
要成功地使用 single-SPA 实现微前端架构,请遵循以下最佳实践:
- 定义清晰的边界: 明确定义微前端之间的边界,以最大限度地减少依赖和通信开销。
- 建立共享样式指南: 创建共享样式指南,以确保所有微前端的用户体验一致。
- 自动化部署: 自动化部署过程,以简化微前端的部署。
- 监控性能: 监控每个微前端的性能,以识别和解决问题。
- 使用集中式日志系统: 使用集中式日志系统聚合所有微前端的日志,简化故障排除。
- 实现稳健的错误处理: 实现稳健的错误处理,以防止一个微前端中的错误影响其他微前端。
- 记录您的架构: 记录您的微前端架构,以确保团队中的每个人都了解其工作原理。
- 选择正确的通信策略: 根据应用的需求选择适当的通信策略。
- 优先考虑性能: 优化每个微前端的性能,以确保快速响应的用户体验。
- 考虑安全性: 实施安全最佳实践,保护您的微前端架构免受漏洞攻击。
- 采纳 DevOps 文化: 培养 DevOps 文化,促进开发和运维团队之间的协作。
Single-SPA 和微前端的用例
Single-SPA 和微前端非常适合各种用例,包括:
- 大型复杂应用: 微前端有助于将大型复杂应用分解为更小、更易于管理的单元。
- 拥有多个团队的组织: 微前端可以使不同团队独立地开发应用的不同部分。例如,在一个全球性的电子商务公司中,一个团队(例如,位于德国)可以专注于产品目录,另一个团队(例如,位于印度)处理购物车,第三个团队(例如,位于美国)管理用户账户。
- 迁移旧版应用: 微前端可用于将旧版应用逐步迁移到更现代的架构。
- 构建平台即服务(PaaS)解决方案: 微前端可用于构建 PaaS 解决方案,允许开发人员创建和部署自己的应用。
- 个性化用户体验: 可以使用不同的微前端,根据用户角色、偏好或位置提供个性化的用户体验。想象一个新闻网站,根据用户的兴趣和阅读历史动态加载不同的内容模块。
微前端的未来
微前端架构在不断发展,新的工具和技术不断涌现,以应对构建和管理分布式前端应用的挑战。一些值得关注的关键趋势包括:
- Web Components: Web Components 是创建可复用 UI 元素的标准,可用于任何 Web 应用。Web Components 可用于构建与框架无关且易于集成到不同应用中的微前端。
- 模块联邦 (Module Federation): 模块联邦是 Webpack 的一项功能,允许您在不同的 Webpack 构建之间共享代码和依赖项。模块联邦可用于构建松散耦合且可独立部署的微前端。
- 服务器端渲染 (SSR): 服务器端渲染可以提高微前端应用的性能和 SEO。SSR 可用于在服务器上渲染微前端的初始 HTML,从而减少需要在客户端下载和执行的 JavaScript 量。
- 边缘计算: 边缘计算可用于将微前端部署到更靠近用户的位置,从而减少延迟并提高性能。边缘计算还可以为微前端带来新的用例,例如离线访问和实时数据处理。
结论
Single-SPA 是一个用于构建可扩展、可维护且灵活的微前端架构的强大框架。通过遵循微前端的原则并利用 single-SPA 的功能,组织可以赋能其团队,加快开发周期,并提供卓越的用户体验。虽然微前端引入了复杂性,但采纳最佳实践、仔细规划和选择正确的工具对于成功至关重要。随着微前端领域的不断发展,了解新技术和新方法对于构建现代化和有弹性的 Web 应用将至关重要。