目录
[2、Web Components](#2、Web Components)
背景
1.首先我们可以先来想一下,日常工作中我们是如何进行代码复用的?
- 复制粘贴 Ctrl cv
- 抽离封装模块
- 打npm包
2.但是,以上方法,会存在哪些问题呢?
--复制粘贴
- 增加重复的代码
- 复用代码逻辑发生变动时需要处处修改
- 违反 Don't Repeat Yourself 原则
--抽离封装模块
- 仅适用于当前项目,无法兼容多个项目要使用同一个模块的情景
--打npm包或库
- 还蛮通用的...但是也有其他问题:
- 发布效率低下
- 当迭代npm包内的逻辑业务:发布npm包 -> 告诉其他使用的伙伴更新npm ->再各自构建发布一次 (繁琐极了)
于是微前端诞生了,用来将大型项目进行拆分和解耦。
一、微前端是什么?
微前端概念是从微服务概念扩展而来的,摒弃大型单体方式,将前端整体分解为小而简单的块。这些块可以独立开发、测试和部署,同时仍然聚合为一个产品出现在客户面前。
微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用后,随之而来产生的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。
二、三大特性
- 无技术栈限制
- 应用单独开发、测试和部署、交付
- 多应用整合
三、现有微前端解决方案
1、iframe
iframe是html提供的标签,能加载其他web应用的内容,并且能兼容所有的浏览器。
不足:
- 不是单页应用,会导致浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
- 弹框类的功能无法应用到整个大应用中,只能在对应的窗口内展示。 由于可能应用间不是在相同的域内,主应用的 cookie,要透传到根域名都不同的子应用中才能实现免登录效果。
- 每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程,占用大量资源的同时也在极大地消耗资源。
经过以上思考,拓展总结: iframe的特性导致搜索引擎无法获取到其中的内容,进而无法实现应用的seo。
2、Web Components
Web Component 是一套不同的技术,允许你创建可重用的定制元素(它们的功能封装在你的代码之外),并且在你的 web 应用中使用它们。Web Component包括Custom Element、Shadow DOM、HTML templates 三种技术规范。
Web Component具备微前端的几个特性:
- **技术栈无关:**Web Components是浏览器原生组件,在任何框架中都可以使用。
- **样式隔离:**使用 Shadow DOM 可以实现样式的完全隔离,防止不同组件之间的样式冲突。
- **可重用性:**创建的 Web Component 可以在多个项目中重复使用,提高了代码的复用率。
不足:
- 对于不熟悉 Web Components 的开发者来说,学习和使用这些 API 可能有一定的学习曲线,需要理解 Shadow DOM、Custom Elements 和 HTML Templates 等概念。
- 缺少生态和社区支持,社区支持和文档资源相对较少,解决问题和获取帮助不如其他框架方便。
3、ESM
ESM是ES Module的缩写,是Ecma script 2015中提出的一种前端模块化手段
也是符合以下几点:
- **无技术栈限制:**ESM加载的只是js内容,无论哪个框架,最终都要编译成js。因此,无论哪种框架,ESM都能加载。
- 应用单独开发: ESM只是js的一种规范,不会影响应用的开发模式。
- 多应用整合: 只要将微应用以ESM的方式暴露出来,就能正常加载。
- 远程加载模块: ESM能够直接请求cdn资源,这是它与生俱来的能力。
不足: 兼容性问题
4、EMP
EMP是由欢聚时代业务中台自主研发的最年轻的单页微前端解决方案,除了具备微前端的能力外,还实现了跨应用状态共享、跨框架组件调用的能力。
具备以下几大特点:
- 每个微应用独立部署运行 跨技术栈组件式调用。
- 应用间通信,每一个应用都可以进行状态共享 ,就像在使用npm模块进行开发一样便捷。
- 每个微应用独立部署运行 。
不足 :EMP的社区支持和文档可能不如其他成熟的微前端框架丰富。虽然EMP支持跨技术栈组件调用,但它主要基于Vue.js和Element UI。如果项目中需要使用其他框架或库,可能会遇到兼容性问题。
5、Fronts
Fronts 是一个基于 Webpack 的 Module Federation API 设计的渐进式微前端框架。它强调颗粒间的去中心化依赖管理,并支持多种运行模式来满足不同的微前端架构需求。
- 支持非 Module Federation - 虽然 Fronts 基于 Module Federation 概念,但它依然支持任何传统且不支持 Module Federation 的前端应用。
- 去中心化配置 - 只需要在每个 Fronts 应用中设置 site.json,就像设置一个 package.json 一样简单,Fronts 支持多层嵌套的微前端。
- 跨框架 - 没有任何现代前端框架限定。
- 代码分割/懒加载 - 支持在 Fronts 应用内进行代码拆分和导出共享模块,它可以被其他 Fronts 应用作为依赖模块进行懒加载。
- CSS 隔离 - 可选的 CSS 隔离设定,并根据不同的渲染方式,有宽松隔离和严格隔离的可选项。
- 生命周期 - 每个 Fronts 应用的 Entry 支持简洁的生命周期接口。
- Web Components 和 iFrame - 支持多种前端运行时容器用于不同隔离环境的要求。
- 多种构建模式 - 同时支持在微前端模式和非微前端模式构建,兼容动态化的运行时集成和静态化的构建时集成。
- Monorepo 和 TypeScript - 良好支持 Monorepo 和 TypeScript,它们和 Fronts 是非常适合架构在一起的技术栈。
- 版本控制 - Fronts 提供的版本控制可用于高效和动态的即时交付应用,当然也包括支持灰度发布。
- 零劫持 - Fronts 不做任何执行容器上的环境全局公共 API 劫持,保持运行环境的原始性,避免可能带来的性能损失和安全问题。
- 通用化消息通讯 - Fronts 提供简洁通用的响应式 API,它支持前端绝大部分原生 API。
**不足:**学习曲线较陡峭、构建时间可能较长。
6、无界( 文档)
基于 webcomponent 容器 + iframe 沙箱,能够完善的解决适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等用户的核心诉求。
1.成本低 :无界微前端的成本非常低,主要体现在主应用的使用成本、子应用的适配成本两个方面。
- 主应用使用成本低 :主应用使用无界不需要学习额外的知识,无界提供基于 vue 封装的 wujie-vue 和基于 react 封装的 wujie-react,用户可以当成普通组件一样加载子应用。
- 子应用适配成本低 :当运行的方式是重建模式时,子应用可以不做任何改造就在无界框架中运行,其他模式需要适当的做一些生命周期改造工作。
2.速度快 :无界微前端非常快,主要体现在首屏打开快、运行速度快两个方面
3.原生隔离 :无界微前端实现了 css 沙箱和 js 沙箱的原生隔离,子应用不用担心污染问题。
4.功能强大: 无界微前端的功能非常强大,支持子应用保活、子应用内嵌、多应用激活、去中心化通信、生命周期、插件系统、vite 框架支持、兼容 IE9、应用共享。
不足 :iframe沙箱可能带来性能开销和通信复杂性,生命周期管理在复杂环境中可能变得更复杂。
7、 qiankun
在微前端界,qiankun算得上是最早成型且知名度最广的框架了,它是真正意义上的单页微前端框架
具备以下几大特点:
- 基于single-spa封装,提供了更加开箱即用的 API 。
- 技术栈无关 ,任意技术栈的应用均可使用/接入,不论是 React/Vue/Angular/JQuery 还是其他的框架。
- HTML Entry 接入方式 ,让你接入微应用像使用 iframe 一样简单。
- 样式隔离 ,确保微应用之间样式互相不干扰。
- JS 沙箱 ,确保微应用之间全局变量/事件不冲突。
- 资源预加载 ,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
- umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统 。
- 活跃的社区/文档支持。
不足 :qiankun是基于single-spa封装的,因此它的功能和限制也受到single-spa的影响。虽然提供了开箱即用的API,但对于复杂的微前端架构,配置和管理可能会变得复杂。
四、我们选择的方案
结合公司产品和现有项目的技术框架(vue2),我们采取的是qianku方案,首先我们的路由模式是hash模式,要将hash改成history。
为什么我们要把原本的hash改成history?
qiankun接入外部服务时,父容器需要适应路由方式history(当前开发平台为hash,即/#/)
-- hash路由:子应用也必须是hash路由,且需继承主应用的基础路径。
-- history路由:主应用如果是history,则子应用可以是history也可以是hash。
如何将路由从 hash改成history? 这个会再出一篇文章详细讲解,敬请期待。
引入qiankun并使用(src外层作为主应用)
1.安装qiankun依赖
yarn add qiankun # 或者 npm i qiankun -S
主应用使用qiankun
a.承载微应用(我们选择了创建容器的方式,可以根据实际情况来传递一些参数给微应用)
<template>`
`<div class="info">`
`<div @click="changeChildState">改变子路由状态(测试)</div>`
`<!-- 目标容器 -->`
`<div id="subapp-viewport"></div>`
`</div>`
`</template>`
`<script>`
`import { registerMicroApps, start, initGlobalState }` `from 'qiankun'`
`export default` `{`
`// 对应微应用的name`
` name: 'apply-manage',`
`data()` `{`
`return` `{`
`// 全局状态`
` actions:` `null`
`}`
`},`
`mounted()` `{`
`// 传递给微应用的值`
`const initialState =` `{`
` router: '/test'`
`}`
`// 初始化全局状态(可选)`
`this.actions =` `initGlobalState(initialState)`
`// 注册微应用`
`registerMicroApps([`
`{`
`// 微应用名字,需要和微应用的package.json的name一致/微应用全局导出的name一致`
` name: 'apply-manage',`
`// 微应用入口`
`// 1、内部子模块直接指向对应的入口,到时候需要区分生产/本地`
`// 2、外部应用直接指向其入口`
` entry: 'http://localhost:8081/apply',`
`// 触发加载微应用的路由`
` activeRule: '/temApply',`
`// 容器id`
` container: '#subapp-viewport',` `// 子应用挂载的div,`
`// 可选,需要传递给微应用的参数`
` props:` `{`
` test1: 'testtt'`
`}`
`}`
`],` `{`
`// 下面是微应用加载时候的一些钩子函数`
` beforeLoad: app =>` `{`
` console.log('before load app.name====>>', app.name)`
`},`
`beforeMount:` `[`
` app =>` `{`
` console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name)`
`}`
`],`
`afterMount:` `[`
` app =>` `{`
` console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name)`
`}`
`],`
`afterUnmount:` `[`
` app =>` `{`
` console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name)`
`}`
`]`
`})`
`// 改变传递给微应用时候会触发的函数`
`this.actions.onGlobalStateChange((newState, prev)` `=>` `{`
`// state: 变更后的状态; prev 变更前的状态`
` console.log('main change', JSON.stringify(newState), JSON.stringify(prev))`
`})`
`// 启动 qiankun`
`start({`
`// 沙箱隔离`
` sandbox:` `true,`
`// 可选,有第三方js引入时使用`
` excludeAssetFilter: assetUrl =>` `{`
`// 白名单协议:子应用下如需要放行动态加载的css/js资源,可以在链接上带上参数 _custom-exclude_=MAIN`
`const whiteWords =` `['_custom-exclude_=MAIN']`
`return whiteWords.some(w =>` `{`
`return assetUrl.includes(w)`
`})`
`}`
`})`
`},`
`methods:` `{`
`// 测试改变全局状态`
`changeChildState()` `{`
`// 按一级属性设置全局状态,微应用中只能修改已存在的一级属性,会同步到微应用`
`this.actions.setGlobalState({`
` router: 'routerNameMap[name]'`
`})`
`}`
`},`
`beforeDestroy()` `{`
`// 移除当前应用的状态监听,微应用 umount 时会默认调用`
`this.actions.offGlobalStateChange()`
`}`
`}`
`</script>`
`<style lang="less" scoped>`
`</style>`
`
b.设置容器对应的路由
路由后面的(.*)*需要携带,不然会微应用子路由会出现404
微应用工程要做什么
c.引入public-path.js
- public-path.js
/* eslint-disable camelcase */`
`if` `(window.__POWERED_BY_QIANKUN__)` `{`
` __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__`
`}
- main.js
// 第一行`
`import './public-path'`
`xxxx`
`
d.导出相应的生命周期钩子
main.js
`
`// 第一行`
`import './public-path'`
`let instance =` `null`
`function` `render(props)` `{`
`// 作为独立项目`
`if` `(!props)` `{`
`/* eslint-disable no-new */`
`new` `Vue({`
` router,`
` store,`
`render: h =>` `h(App)`
`}).$mount('#app')`
`return`
`}`
`// 作为微应用时`
`const` `{ container, usePicker }` `=` `props`
` instance =` `new` `Vue({`
` router,`
` store,`
`data()` `{`
`return` `{`
` usePicker`
`}`
`},`
`render: h =>` `h(App)`
`}).$mount(container ? container.querySelector('#app')` `: '#app')`
`// if (props.router) {`
`// router.push({`
`// name: props.router`
`// })`
`// }`
`}`
`// 独立运行时`
`if` `(!window.__POWERED_BY_QIANKUN__)` `{`
`render()`
`}`
`(global` `=>` `{`
`// apply-manage跟主应用加载微应用时候的name一致`
`global['apply-manage']` `=` `{`
` bootstrap:` `()` `=>` `{`
`return Promise.resolve()`
`},`
`mount:` `(props)` `=>` `{`
`// 全局状态更改时候触发`
` props.onGlobalStateChange(state =>` `{`
`// setTimeout(() => {`
`// router.push({`
`// name: state.router`
`// })`
`// }, 500)`
`},` `true)`
`return` `render(props)`
`},`
`unmount:` `()` `=>` `{`
` instance.$destroy()`
`return Promise.resolve()`
`}`
`}`
`})(window)`
`
e.如果是history模式的话,要设置routerbase为主应用那边的触发路由
主应用容器
微应用
f.需要区分好独立运行和作为微应用运行时候的各项功能配置
例子:独立运行要登录,作为微应用时候直接获取主应用的登录状态等等
主应用&微应用通讯
g.思路
主应用采用原生方式实现订阅机制(以下称为eventBus),并挂载在window上,使主&子应用共用一份eventBus机制。
h.实现
这个会再出一篇文章详细讲解,敬请期待。
FAQ
官网常见文集文档:常见问题 - qiankun
1.使用第三方链接报跨域
· 解决方案:手动加载
2.父子组件中加载两个monaco编辑器,其中实例放在data中会页面卡死
· 方案:将editor属性从data移除
3.主应用&微应用通讯规范
五、总结
对于低代码平台,推荐优先考虑 qiankun ,因为它提供了技术栈无关性、样式隔离、JS沙箱、跨应用通信、性能优化以及活跃的社区支持。这些特性使得qiankun非常适合需要支持多种技术栈和高度可扩展性的低代码平台。
如果你的项目需要更细粒度的控制和更复杂的配置,可以考虑 fronts ,它提供了更多的灵活性和高级功能,但需要更多的配置和学习成本。
如果你特别关注低成本和快速集成,并且对iframe沙箱带来的限制能够接受,那么 无界 也是一个不错的选择。
最终的选择应该基于你的具体需求、团队的技术栈和经验,以及项目的长期发展规划。
点点关注,下期精彩继续!
道一云七巧-与你在技术领域共同成长
更多技术知识分享: https://bbs.qiqiao668.com/