前言

最近入职的一家公司采用single-spa这个微前端框架,所以自学了此框架。

single-spa这个微前端框架虽然有中文文档,但是有些零散和晦涩。

所以我想在学习之余,写篇博客拉平一下这个学习曲线。

什么是微前端?

微前端的灵感来源于服务端微服务的理念。

可以简单理解为,在开发一个复杂前端应用时,将其划分为一系列更小更简单的前端应用。

这些前端应用可以单独开发、测试、部署,松耦合,可维护性强,还可以让前端代码实现增量升级和使用不同的框架。

它的懒加载还能让整个复杂应用加载速度变快。

常用微前端玩法和single-spa

在我之前的公司是使用iframe来实现微前端的,但是各个子应用间的通信往往比较麻烦,而且很不灵活。

而最新的微前端理念,是由webpack5中模块联合特性实现的,这里就不多讲了。

single-spa是一个比较流行的微前端框架,它并不是使用iframe来实现微前端,也不是通过模块联合,而是通过路由路径来在dom上加载不同的子应用。

Import maps和SystemJS

在具体讲解single-spa前,我们得先了解一个东西:Import maps

这个功能是Chrome 89才支持的。

顾名思义,它是对import的一个映射处理,让你控制在js中使用import时,到底从哪个url获取这些库。

比如通常我们会在js中,以下面这种方式引入模块:

  1. import moment from "moment"

正常情况下肯定是node_modules中引入,但是现在我们在html中加入下面的代码:

  1. <script type="importmap">
  2. {
  3. "imports": {
  4. "moment": "/moment/src/moment.js"
  5. }
  6. }
  7. </script>

这里/moment/src/moment.js这个地址换成一个cdn资源也是可以的。最终达到的效果就是:

  1. import moment from "/moment/src/moment.js"

有了Import maps,import的语法就可以直接在浏览器中使用,而不再需要webpack来帮我们进行处理,不需要从node_modules中去加载库。

Import maps甚至还有一个兜底的玩法:

  1. "imports": {
  2. "jquery": [
  3. "https://某CDN/jquery.min.js",
  4. "/node_modules/jquery/dist/jquery.js"
  5. ]
  6. }

当cdn无效时,再从本地库中获取内容。

它的功能还有很多,我就不一一列举了,只需要对这个有一定的了解即可。

尽管Import maps非常强大,但是毕竟浏览器兼容性还并不是很好,所以就有了我们的polifill方案:SystemJS

SystemJS同样是一个模块加载器,可兼容到IE11,同样支持import映射,但是它的语法稍有不同:

  1. <script src="system.js"></script>
  2. <script type="systemjs-importmap">
  3. {
  4. "imports": {
  5. "lodash": "https://unpkg.com/lodash@4.17.10/lodash.js"
  6. }
  7. }
  8. </script>

在浏览器中引入system.js后,会去解析type为systemjs-importmap的script下的import映射。

它和Import maps最终达到的效果是一致的。

single-spa

之所以我们要先讲Import mapsSystemJS,就是因为single-spa的微前端往往需要结合SystemJS来实现。

single-spa框架中,基座会检测浏览器url的变化,在变化时往往通过SystemJS的import映射,来加载不同的子应用js。

但是需要注意,single-spa并不是必须依赖SystemJS

刚刚我们提到了一个概念:基座,现在讲下single-spa的两个概念:基座应用

你可以简单理解应用是一个个的单页面应用,而基座是一个应用管理器,用来根据路由加载不同的应用

一般在基座中我们需要像下面这样注册一个应用:

  1. import { registerApplication, start } from 'single-spa';
  2. // 注册应用1
  3. registerApplication({
  4. name:'app1',
  5. app:() => import('./app1.js'),
  6. activeWhen: '/app1',
  7. customProps: { myTitle: "传递给应用的自定义参数的值" }
  8. });
  9. // 注册应用2
  10. registerApplication({
  11. name:'app2',
  12. app:() => import('./app2.js'),
  13. activeWhen: '/app2'
  14. });
  15. start();

在上面的代码中,我们注册了app1和app2两个应用,分别匹配路由/app1和/app2。

也就是说当路由是/app1或者/app1/home时,会直接加载app1这个应用。

注册应用后,需要start()来开始挂载应用,否则只会下载应用,而不会挂载应用。

那么应用应该如何设置呢?

我们上面代码引用的./app1.js并没有导出一个真的单页面应用,而一般是如下:

  1. console.info('第一步 下载应用阶段')
  2. export function bootstrap(props) {
  3. const {
  4. name, // 应用名称
  5. singleSpa, // singleSpa实例
  6. mountParcel, // 手动挂载的函数
  7. myTitle // 我们之前在注册应用时传递给customProps的属性
  8. } = props; // Props 会传给每个生命周期函数
  9. // 这个生命周期函数会在应用第一次挂载前执行一次。
  10. return Promise.resolve().then(() => {
  11. console.info('第二步 初始化', myTitle)
  12. })
  13. }
  14. export function mount(props) {
  15. // 每当应用路由匹配成功,但该应用处于未挂载状态时,挂载的生命周期函数就会被调用。调用时,函数会根据URL来确定当前被激活的路由,创建DOM元素、监听DOM事件等以向用户呈现渲染的内容。任何子路由的改变(如hashchange或popstate等)不会再次触发mount,需要各应用自行处理。
  16. return Promise.resolve().then(() => {
  17. console.info('第三步 挂载应用', props.name)
  18. document.getElementById('root').innerHTML = "我是app1啊"
  19. })
  20. }
  21. export function unmount(props) {
  22. // 每当应用路由匹配不成功,但该应用已挂载时,卸载的生命周期函数就会被调用。卸载函数被调用时,会清理在挂载应用时被创建的DOM元素、事件监听、内存、全局变量和消息订阅等。
  23. return Promise.resolve().then(() => {
  24. console.info('第四步 卸载应用', props.name)
  25. document.getElementById('root').innerHTML = ""
  26. })
  27. }
  28. export function unload(props) {
  29. // 移除”生命周期函数的实现是可选的,它只有在unloadApplication被调用时才会触发。如果一个已注册的应用没有实现这个生命周期函数,则假设这个应用无需被移除。
  30. // 移除的目的是各应用在移除之前执行部分逻辑,一旦应用被移除,它的状态将会变成NOT_LOADED,下次激活时会被重新初始化。
  31. // 移除函数的设计动机是对所有注册的应用实现“热下载”,不过在其他场景中也非常有用,比如想要重新初始化一个应用,且在重新初始化之前执行一些逻辑操作时。
  32. return Promise.resolve().then(() => {
  33. console.info('第五步 移除应用', props.name)
  34. })
  35. }

可以看到我们的app1.js这个应用中的代码,并没有导出一个单页面应用组件,而是导出了几个生命周期函数,然后通过这几个生命周期函数来控制组件的初始化,加载和卸载。

它这个操作是从现在我们的react或者vue这些框架的组件生命周期中获得灵感,将生命周期应用于整个应用程序。

single-spa 与 SystemJS实现微前端

看了上面的代码之后你可能有点疑惑,你这个东西也没什么用嘛,不就是个懒加载吗?

哪来的微前端?

我用个React.lazy(() => import('./app1.js'))来个懒加载怎么了,你不要说一大堆把我绕晕了。

上面这些实际上还真的没有实现微前端,但是,你可以结合我们之前讲的微前端,想象一下:

如果./app1.js不是基座这个项目内的代码,而是另一个项目呢?

我们将这个app1.js放在一个单独的项目中,它用react来写了一个单页面应用。

再将app2.js放在另一个单独的项目中,它用vue来写了一个单页面应用。

通过我们现在的这个基座项目再来处理这两个应用呢?

我们的基座项目是不是就可以写成下面这样:

  1. import { registerApplication, start } from 'single-spa';
  2. // 注册应用1
  3. registerApplication({
  4. name:'app1',
  5. app:() => import('@晓组织/app1'),
  6. activeWhen: '/app1',
  7. customProps: { myTitle: "传递给应用的自定义参数的值" }
  8. });
  9. // 注册应用2
  10. registerApplication({
  11. name:'app2',
  12. app:() => import('@晓组织/app2'),
  13. activeWhen: '/app2'
  14. });
  15. start();

然后再在基座项目的模板页中来个引入映射:

  1. <script type="systemjs-importmap">
  2. {
  3. "imports": {
  4. "@晓组织/app1": "//某网站/app1.js"
  5. "@晓组织/app2": "//另一个网站/app2.js"
  6. }
  7. }
  8. </script>

以后我们要做app1模块的部分,只需要在app1这个项目中维护就可以了,不会干扰到其他的应用。

以后React20,React30出来,或者部分项目升级webpack,或者给一个项目大调整,我们可以一个个小应用尝试升级修改,不用所有项目同时调整。不仅风险变小了很多,也更加可控。

上面是结合SystemJS实现的微前端,其实还有使用npm包和单项目的玩法,但是不推荐,有兴趣的可以参考官网的这篇文章:拆分应用

single-spa-react

看到这里的朋友一定会想,这个东西好是好,怎么把它和react的单页面应用结合起来?

让我自己写加载和卸载react的单页面应用?这也太挫了吧。

当然不可能,single-spa的生态可是很好的。

single-spa-react就是一个辅助库,它可以帮助React应用程序实现single-spa 需要的生命周期函数(bootstrap、mount 和 unmount)。

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import rootComponent from './path-to-root-component.js';
  4. import singleSpaReact from 'single-spa-react';
  5. const reactLifecycles = singleSpaReact({
  6. React,
  7. ReactDOM,
  8. rootComponent,
  9. errorBoundary(err, info, props) {
  10. // https://reactjs.org/docs/error-boundaries.html
  11. return (
  12. <div>This renders when a catastrophic error occurs</div>
  13. );
  14. },
  15. });
  16. export const bootstrap = reactLifecycles.bootstrap;
  17. export const mount = reactLifecycles.mount;
  18. export const unmount = reactLifecycles.unmount;

这是官网提供的示例代码,那个rootComponent就是我们的顶层React组件。

其它的就不用多说了,毕竟都很容易理解,想要了解详情可以看下:详情

其它的一些语言比如vue和Angular都有自己对应的辅助库,具体可以查阅:辅助库列表

single-spa的Parcels

Parcels是single-spa的一个高级特性,是一个与框架无关的组件,与应用的不同之处在于parcel组件需要手动挂载,而不是通过匹配路由的activity方法被激活。

官网说:只有在涉及到跨框架的应用之间进行组件调用时,我们才需要考虑parcel的使用。

一想到我们公司只用react,那么打扰了,再见。

CLI工具:create-single-spa和可定制化的webpack配置

上面很多都是在讲原理,现在到了实际应用的时候了。

single-spa的相关配置有些繁琐,所以我推荐依赖现有的CLI去新建项目,可以去改造,而不是自己从零开始去搭建。

  1. npx create-single-spa

运行上面这行命令后,就会让你做一系列选择,那些选择就不多说,只说最关键的。

create-single-spa 可以让你创建三种项目,分别是:

  • single-spa application/parcel :应用和parcel。
  • single-spa root config:基座。
  • in-browser utility module: 通用组件,工具函数,样式指引。

你可以根据需要去创建不同的项目。

single-spa还提供了一些推荐的webpack配置库,不用自己操心去设置webpack配置。

不过我建议最后输出得到webpack配置你还是稍微打印出来看一下,做到心中有数,然后才可以再根据它的配置去做相应修改。

Demo分享

光看不做假把式,下面是我自己根据single-spa的CLI工具搭建的两个简易Demo:

同时运行起来即可,命令都是:

  1. yarn start

如果确实想入门的话,对比一下我写的和官方CLI工具初始化时的一些差异,可以了解到更多的一些小细节。

总结

总的来说,single-spa是一个非常优秀的微前端框架。

微前端领域最近的趋势是用webpack5中模块联合特性来实现,这与single-spa并不冲突,single-spa也有结合模块联合特性实现的例子。

不过这就不在本篇文章的涉及范围内了,也许以后会写下这块的内容。

本篇博客到此结束。

希望这篇文章能给您带来一些帮助,如有疏漏,也请不吝赐教。

微前端框架single-spa初探的更多相关文章

  1. 极致简洁的微前端框架-京东MicroApp开源了

    前言 MicroApp是一款基于类WebComponent进行渲染的微前端框架,不同于目前流行的开源框架,它从组件化的思维实现微前端,旨在降低上手难度.提升工作效率.它是目前市面上接入微前端成本最低的 ...

  2. 微前端框架 single-spa 技术分析

    在理解微前端技术原理中我们介绍了微前端的概念和核心技术原理.本篇我们结合目前业内主流的微前端实现 single-spa 来说明在生产实践中是如何实现微前端的. single-spa 的文档略显凌乱,概 ...

  3. 微前端框架 qiankun 技术分析

    我们在single-spa 技术分析 基本实现了一个微前端框架需要具备的各种功能,但是又实现的不够彻底,遗留了很多问题需要解决.虽然官方提供了很多样例和最佳实践,但是总显得过于单薄,总给人一种&quo ...

  4. 微前端框架 之 qiankun 从入门到源码分析

    封面 简介 从 single-spa 的缺陷讲起 -> qiankun 是如何从框架层面解决 single-spa 存在的问题 -> qiankun 源码解读,带你全方位刨析 qianku ...

  5. 基于 iframe 的微前端框架 —— 擎天

    vivo 互联网前端团队- Jiang Zuohan 一.背景 VAPD是一款专为团队协作办公场景设计的项目管理工具,实践敏捷开发与持续交付,以「项目」为核心,融合需求.任务.缺陷等应用,使用敏捷迭代 ...

  6. 微前端框架 single-spa

    单体应用对比前端微服务化 普通的前端单体应用 微前端架构 1.基本概念 实现一套微前端架构,可以把其分成四部分(参考:https://alili.tech/archive/11052bf4/) 加载器 ...

  7. 微前端大赏二-singlespa实践

    微前端大赏二-singlespa实践 微前端大赏二-singlespa实践 序 介绍singleSpa singleSpa核心逻辑 搭建环境 vue main react child 生命周期 结论 ...

  8. 微前端 & 微前端实践 & 微前端教程

    微前端 & 微前端实践 & 微前端教程 微前端 micro frontends https://micro-frontends.org/ https://github.com/neul ...

  9. vue-qiankun公司微前端项稳定目落地后的总结(附github仓库demo,将会持续更新)

    ️本文为博客园社区首发文章,未获授权禁止转载 大家好,我是aehyok,一个住在深圳城市的佛系码农‍♀️,如果你喜欢我的文章,可以通过点赞帮我聚集灵力️. 个人github仓库地址: https:gi ...

随机推荐

  1. js学习笔记之日期倒计时DOM操作

    1.访问html元素 getElementById() 方法  返回对拥有指定 id 的第一个对象的引用,只有dom对象有效 getElementsByName() 方法  返回指定名称的对象集合 g ...

  2. rsync(873)未授权访问

    cd vulhub-master/rsync/common docker -composeup -d 检测 1.列出目标服务器的同步目录 rsync 192.168.244.129:: 2.查看模块文 ...

  3. 零基础涂鸦智能面板SDK开发记录(一)

    前言 本人基础背景:在学校学了点JS,在blbl上看过几节node.js视频,现在是一名Android开发工程师,因公司需要学习涂鸦面板SDK开发.说真的除了官方的一些文档外,我真的找不到其他的资料. ...

  4. SQL语句(四)联表查询

    目录 一.关联查询的分类 按年代分 按功能分 二.sql92语法的连接 语法 1. 简单应用 2. 为表起别名 3. 加入筛选 4. 加入分组 5. 三表连接 6. 非等值连接 7. 自连接 三.sq ...

  5. C++ //多态 //静态多态:函数重载 和 运算符重载 属于静态多态 ,复用函数名 //动态多态:派生类和虚函数实现运行时多态

    1 //多态 2 //静态多态:函数重载 和 运算符重载 属于静态多态 ,复用函数名 3 //动态多态:派生类和虚函数实现运行时多态 4 5 //静态多态和动态多态的区别 6 //静态多态的函数地址早 ...

  6. 解决Win10用户VS Code的C/C++更新到1.6.0后无法调试的问题

    今天突然遇到一个问题 Win10上 vscode C++突然无法正常调试 在运行调试后 编译成功后没有任何提示 直接就停止了 没有错误 不运行程序 尝试重新写一遍launch.json 自动生成lau ...

  7. 【动画消消乐|CSS】083.纯CSS实现卡通齿轮效果

    前言 Hello!小伙伴! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出-   自我介绍 ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿|C++选手|学生 简介:因C语言结识编程,随后转入计 ...

  8. Windows内核开发-6-内核机制 Kernel Mechanisms

    Windows内核开发-6-内核机制 Kernel Mechanisms 一部分Windows的内核机制对于驱动开发很有帮助,还有一部分对于内核理解和调试也很有帮助. Interrupt Reques ...

  9. Dockerfile 多阶段构建实践

    写在前面 在Docker Engine 17.05 中引入了多阶段构建,以此降低构建复杂度,同时使缩小镜像尺寸更为简单.这篇小作文我们来学习一下如何编写实现多阶段构建的Dockerfile 关于doc ...

  10. sentinel使用(结合OpenFeign)

    前提 需要先安装sentinel. 父项目POM pom.xml <?xml version="1.0" encoding="UTF-8"?> &l ...