首页IT科技微前端作用(微前端(无界))

微前端作用(微前端(无界))

时间2025-06-16 10:14:32分类IT科技浏览4286
导读:前言:微前端已经是一个非常成熟的领域了,但开发者不管采用哪个现有方案,在适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等用户核心诉求都或存在问题,或无法提供支持。本文提供一种基于 iframe 的全新微前端方案,完善的解决了这些核心诉求。...

前言:微前端已经是一个非常成熟的领域了             ,但开发者不管采用哪个现有方案                   ,在适配成本             、样式隔离                   、运行性能      、页面白屏       、子应用通信                   、子应用保活            、多应用激活       、vite 框架支持                    、应用共享等用户核心诉求都或存在问题      ,或无法提供支持             。本文提供一种基于 iframe 的全新微前端方案       ,完善的解决了这些核心诉求                   。

微前端概念

微前端是借鉴了微服务的理念                   ,将一个庞大的应用拆分成多个独立灵活的小型应用            ,每个应用都可以独立开发       ,独立运行                    ,独立部署            ,还可以随意组合,这样就降低了耦合度                    ,从而更加灵活      。

微前端特性

技术栈无关 主框架不限制接入应用的技术栈                   ,子应用可自主选择技术栈(vue,react             ,jq                   ,ng等) 独立开发/部署 各个团队之间仓库独立      ,单独部署             ,互不依赖 增量升级 当一个应用庞大之后                   ,技术升级或重构相当麻烦      ,而微应用具备渐进式升级的特性 独立运行时 微应用之间运行时互不依赖       ,有独立的状态管理

场景演示

后台管理系统

最外面一层可以当主应用                   ,里面可以放不同的子应用子应用不受技术的限制             。

web商店(未来趋势)

例如一些导航网站,可以提供微前端的接入            ,我们的网站也可以入驻该网站       ,并且还可以提供一些API增加交互                    ,有点类似于小程序                   。小程序可以调用微信的一些能力例如支付            ,扫码等,导航类型的网站也可以提供一些API                    ,我们的网站接入之后提供API调用                   ,可以实现更多有趣的玩法      。

微前端方案

iframe 方案

特点

接入比较简单 隔离非常稳完美

不足

dom割裂感严重,弹框只能在iframe             ,而且有滚动条 通讯非常麻烦                   ,而且刷新iframe url状态丢失 前进后退按钮无效

qiankun 方案

qiankun 方案是基于 single-spa 的微前端方案       。

特点

html entry 的方式引入子应用      ,相比 js entry 极大的降低了应用改造的成本; 完备的沙箱方案             ,js 沙箱做了 SnapshotSandbox            、LegacySandbox、ProxySandbox 三套渐进增强方案                   ,css 沙箱做了 strictStyleIsolation                    、experimentalStyleIsolation 两套适用不同场景的方案; 做了静态资源预加载能力;

不足

适配成本比较高      ,工程化                   、生命周期、静态资源路径             、路由等都要做一系列的适配工作; css 沙箱采用严格隔离会有各种问题       ,js 沙箱在某些场景下执行性能下降严重; 无法同时激活多个子应用                   ,也不支持子应用保活; 无法支持 vite 等 esmodule 脚本运行;

底层原理 js沙箱使用的是proxy进行快照然后用用 with(window){} 包裹起来 with内的window其实就是proxy.window 我们声明变量 var name = 小满 实际这个变量挂到了proxy.window 并不是真正的window css沙箱原理 第一个就是shadowDom隔离 第二个类似于Vue的scoped [data-qiankun-426732]

micro-app 方案

micro-app 是基于 webcomponent + qiankun sandbox 的微前端方案                   。

特点

使用 webcomponet 加载子应用相比 single-spa 这种注册监听方案更加优雅; 复用经过大量项目验证过 qiankun 的沙箱机制也使得框架更加可靠; 组件式的 api 更加符合使用习惯            ,支持子应用保活; 降低子应用改造的成本       ,提供静态资源预加载能力;

不足

接入成本较 qiankun 有所降低                    ,但是路由依然存在依赖; (虚拟路由已解决) 多应用激活后无法保持各子应用的路由状态            ,刷新后全部丢失; (虚拟路由已解决) css 沙箱依然无法绝对的隔离,js 沙箱做全局变量查找缓存                    ,性能有所优化; 支持 vite 运行                   ,但必须使用 plugin 改造子应用,且 js 代码没办法做沙箱隔离; 对于不支持 webcompnent 的浏览器没有做降级处理;

底层原理 js隔离跟qiankun类似也是使用proxy + with             ,css隔离自定义前缀类似于scoped

const prefix = `micro-app[name=${appName}]` 复制代码

EMP 方案

EMP 方案是基于 webpack 5 module federation 的微前端方案             。

特点

webpack 联邦编译可以保证所有子应用依赖解耦; 应用间去中心化的调用                   、共享模块; 模块远程 ts 支持;

不足

对 webpack 强依赖                   ,老旧项目不友好; 没有有效的 css 沙箱和 js 沙箱      ,需要靠用户自觉; 子应用保活      、多应用激活无法实现; 主             、子应用的路由可能发生冲突;

底层原理 这个东西有点类似于拆包             ,也可以叫模块共享                   ,例如React有个模块可以共享给Vue项目用Vue2的组件可以共享给Vue3用       。

无界微前端 方案

预览demo wujie-micro.github.io/demo-main-v…**

特点

接入简单只需要四五行代码 不需要针对vite额外处理 预加载 应用保活机制

不足

隔离js使用一个空的iframe进行隔离 子应用axios需要自行适配 iframe沙箱的src设置了主应用的host      ,初始化iframe的时候需要等待iframe的location.orign从about:blank初始化为主应用的host       ,这个采用的计时器去等待的不是很悠亚                   。

底层原理 使用shadowDom 隔离css                   ,js使用空的iframe隔离            ,通讯使用的是proxy

前置知识了解webComponents

演示webComponents的 传参 样式隔离 以及写法

window.onload = () => { class WuJie extends HTMLElement { constructor() { super() this.init() this.getAttr(url) } init() { const shadow = this.attachShadow({ mode: "open" }) //开启影子dom 也就是样式隔离 const template = document.querySelector(#wu-jie) as HTMLTemplateElement console.log(template); shadow.appendChild(template.content.cloneNode(true)) } getAttr (str:string) { console.log(获取参数,this.getAttribute(str)); } //生命周期自动触发有东西插入 connectedCallback () { console.log(类似于vue 的mounted); } //生命周期卸载 disconnectedCallback () { console.log(类似于vue 的destory); } //跟watch类似 attributeChangedCallback (name:any, oldVal:any, newVal:any) { console.log(跟vue 的watch 类似 有属性发生变化自动触发); } } window.customElements.define(wu-jie, WuJie) } 复制代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./index.js"></script> </head> <body> <!--外层写一个div测试隔离--> <div>我是div</div> <wu-jie url="xxxxxx"></wu-jie> <template id="wu-jie"> <!--div的样式是作用于全局的--> <style> div { background: red; } </style> <div> 小满zs(测试样式隔离) </div> </template> </body> </html> 复制代码

可以完美隔离css样式

前置知识2 monorepo架构

我们采用的是微前端一个主应用       ,和多个子应用                    ,我们肯定不会一个一个去install安装依赖            ,太傻了,我们采用monorepo 架构 一次install 即可安装完成             。

第一步需要安装pnpm

pnpm内置了对单个代码仓库包含多个软件包的支持                    ,是monorepo架构模式的不二速选

npm i pnpm -g 复制代码

最外层建一个main充当主应用然后新建一个web文件夹里面放两个子应用分别是vue和react

配置monorepo

在根目录新建一个 pnpm-workspace.yaml 配置依赖项

packages: # all packages in direct subdirs of packages/ - main # all packages in subdirs of components/ - web/** 复制代码

配置完成后install一次就行

他会把所有的公共依赖项抽到外层                   ,而里层的依赖项都是一些最核心的

无界入门

我们使用Vue3来充当主应用 首先需要安装依赖

vue2 npm i wujie-vue2 -S vue3 npm i wujie-vue3 -S react npm i wujie-react -S

主应用的main.ts

import { createApp } from vue import App from ./App.vue import Wujie from wujie-vue3 createApp(App).use(Wujie).mount(#app)

主应用hellowWord url填写子应用的url 子应用通过npm run dev启动

<template> <div> <WujieVue width="100%" height="100%" name="xxx" :url="url" ></WujieVue> </div> </template> <script setup lang=ts> import { ref, reactive } from vue const url = http://127.0.0.1:5174/ </script> <style scoped lang=less></style>

只需要简单的几行代码就可以实现微前端应用,接入成本很低

wujie-vue3 原理

这个包其实是作者根据wujie 自行封装的我们也可以自己去封装一下

文档地址

设置子应用​

非必须             ,由于preloadApp和startApp参数重复                   ,为了避免重复输入      ,可以通过setupApp来设置默认参数。

javascript

setupApp({ name: "唯一id", url: "子应用地址", exec: true, el: "容器", sync: true })

预加载​

javascript

preloadApp({ name: "唯一id"});

启动子应用​

javascript

startApp({ name: "唯一id" });

知道以上几个API的用法就可以简单封装一个无界的组件我们使用vue3 + webpack + swc 封装

依赖

"@swc/core": "^1.3.42", "swc-loader": "^0.2.3", "ts-loader": "^9.4.2", "typescript": "^5.0.2", "vue": "^3.2.47", "webpack": "^5.77.0", "webpack-cli": "^5.0.1", "wujie": "^1.0.13"

1.webpack 配置

const { Configuration } = require(webpack) const path = require(path) /** * @type {Configuration} //配置智能提示 */ const config = { entry: "./src/index.ts", output: { filename: "wujie.js", path:path.resolve(__dirname, ./lib) , library:"Wujie", libraryTarget:"umd", umdNamedDefine:true }, externals:{ vue:vue, wujie:"wujie" }, mode:"none", cache:true, module: { rules: [ { test: /\.ts$/, //解析ts loader: "swc-loader", //使用新技术swc-loader } ] }, } module.exports = config

这个就是差距 为什么使用新技术swc swc是rust写的性能是原生的好几倍             ,他官网也说了他是babel 的20倍

2.编写组件

import type { plugin } from wujie type lifecycle = (appWindow: Window) => any; interface Props { /** 唯一性用户必须保证 */ name: string; /** 需要渲染的url */ url: string; /** 需要渲染的html, 如果用户已有则无需从url请求 */ html?: string; /** 渲染的容器 */ loading?: HTMLElement; /** 路由同步开关                   , false刷新无效      ,但是前进后退依然有效 */ sync?: boolean; /** 子应用短路径替换       ,路由同步时生效 */ prefix?: { [key: string]: string }; /** 子应用保活模式                   ,state不会丢失 */ alive?: boolean; /** 注入给子应用的数据 */ props?: { [key: string]: any }; /** js采用fiber模式执行 */ fiber?: boolean; /** 子应用采用降级iframe方案 */ degrade?: boolean; /** 自定义运行iframe的属性 */ attrs?: { [key: string]: any }; /** 自定义降级渲染iframe的属性 */ degradeAttrs?: { [key: string]: any }; /** 代码替换钩子 */ replace?: (codeText: string) => string; /** 自定义fetch            ,资源和接口 */ fetch?: (input: RequestInfo, init?: RequestInit) => Promise<Response>; /** 子应插件 */ plugins: Array<plugin>; /** 子应用生命周期 */ beforeLoad?: lifecycle; /** 没有做生命周期改造的子应用不会调用 */ beforeMount?: lifecycle; afterMount?: lifecycle; beforeUnmount?: lifecycle; afterUnmount?: lifecycle; /** 非保活应用不会调用 */ activated?: lifecycle; deactivated?: lifecycle; }; export { Props } import { startApp, bus } from wujie import { h, defineComponent, onMounted, getCurrentInstance, onBeforeUnmount } from vue import type { App, PropType } from vue import { Props } from ./type const WuJie = defineComponent({ props: { width: { type: String, default: "" }, height: { type: String, default: "" }, name: { type: String, default: "", required: true }, loading: { type: HTMLElement, default: undefined }, url: { type: String, default: "", required: true }, sync: { type: Boolean, default: undefined }, prefix: { type: Object, default: undefined }, alive: { type: Boolean, default: undefined }, props: { type: Object, default: undefined }, attrs: { type: Object, default: undefined }, replace: { type: Function as PropType<Props[replace]>, default: undefined }, fetch: { type: Function as PropType<Props[fetch]>, default: undefined }, fiber: { type: Boolean, default: undefined }, degrade: { type: Boolean, default: undefined }, plugins: { type: Array as PropType<Props[plugins]>, default: null }, beforeLoad: { type: Function as PropType<Props[beforeLoad]>, default: null }, beforeMount: { type: Function as PropType<Props[beforeMount]>, default: null }, afterMount: { type: Function as PropType<Props[afterMount]>, default: null }, beforeUnmount: { type: Function as PropType<Props[beforeUnmount]>, default: null }, afterUnmount: { type: Function as PropType<Props[afterUnmount]>, default: null }, activated: { type: Function as PropType<Props[activated]>, default: null }, deactivated: { type: Function as PropType<Props[deactivated]>, default: null }, }, setup(props: Props, { emit }) { const instance = getCurrentInstance() const handlerEmit = (event: string, ...args: any[]) => { emit(event, ...args) } onMounted(() => { bus.$onAll(handlerEmit) //添加事件订阅 //初始化无界 startApp({ name: props.name, url: props.url, el: instance?.refs.wujie as HTMLElement, loading: props.loading, alive: props.alive, fetch: props.fetch, props: props.props, attrs: props.attrs, replace: props.replace, sync: props.sync, prefix: props.prefix, fiber: props.fiber, degrade: props.degrade, plugins: props.plugins, beforeLoad: props.beforeLoad, beforeMount: props.beforeMount, afterMount: props.afterMount, beforeUnmount: props.beforeUnmount, afterUnmount: props.afterUnmount, activated: props.activated, deactivated: props.deactivated, }) }) onBeforeUnmount(() => { bus.$offAll(handlerEmit) //取消事件订阅 }) return () => h(div, { style: { width: 200, height: 200 }, ref: "wujie" }, ) } }) WuJie.install = (app: App) => { app.component(wujie, WuJie) } export default WuJie

编写声明文件

import { bus, preloadApp, destroyApp, setupApp } from "wujie"; import type { App } from vue; declare const WujieVue: { bus: typeof bus; setupApp: typeof setupApp; preloadApp: typeof preloadApp; destroyApp: typeof destroyApp; install: (app: App) => void }; export default WujieVue;

编写package json

{ "name": "wujie-vue-setup", "version": "0.0.4", "description": "", "main": "lib/index.js", "module": "esm/index.js", "files": [ "esm", "lib", "index.d.ts" ], "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "wujie": "^1.0.13" }, "devDependencies": { "@swc/core": "^1.3.42", "swc-loader": "^0.2.3", "ts-loader": "^9.4.2", "typescript": "^5.0.2", "vue": "^3.2.47", "webpack": "^5.77.0", "webpack-cli": "^5.0.1" } }

编写完成之后就是npm adduser 创建账号 npm login 登录 npm publish 发布到npm 发布的时候记住看看源是否是npm 的

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
为什么word打印不出图片(Word文档打印时没有打印图片) 网站关键词优化的方法(如何提高网站关键词排名的有效方法)