不同網(wǎng)站對(duì)商家做o2o的政策阿里seo排名優(yōu)化軟件
目錄
什么是微前端
目前現(xiàn)有的微前端
好處
使用
?子應(yīng)用的頁(yè)面在主應(yīng)用里顯示?
什么是微前端
微前端是一種多個(gè)團(tuán)隊(duì)通過(guò)獨(dú)立發(fā)布功能的方式來(lái)共同構(gòu)建現(xiàn)代化 web 應(yīng)用的技術(shù)手段及方法策略。
?
我的理解就是將一個(gè)大型的前端應(yīng)用拆分成多個(gè)模塊,每個(gè)微前端模塊可以由不同的團(tuán)隊(duì)進(jìn)行管理,并可以自主選擇框架,并且有自己的倉(cāng)庫(kù),可以獨(dú)立部署上線。
目前現(xiàn)有的微前端方案
iframe
通過(guò)iframe實(shí)現(xiàn)的話就是每個(gè)子應(yīng)用通過(guò)iframe標(biāo)簽來(lái)嵌入到父應(yīng)用中,iframe具有天然的隔離屬性,各個(gè)子應(yīng)用之間以及子應(yīng)用和父應(yīng)用之間都可以做到互不影響。
但是iframe也有很多缺點(diǎn):
- url不同步,如果刷新頁(yè)面,iframe中的頁(yè)面的路由會(huì)丟失。
- 全局上下文完全隔離,內(nèi)存變量不共享。
- UI不同步,比如iframe中的頁(yè)面如果有帶遮罩層的彈窗組件,則遮罩就不能覆蓋整個(gè)瀏覽器,只能在iframe中生效。
- 慢。每次子應(yīng)用進(jìn)入都是一次瀏覽器上下文重建、資源重新加載的過(guò)程
single-spa
single-spa是最早的微前端框架,可以兼容很多技術(shù)棧。
single-spa首先在基座中注冊(cè)所有子應(yīng)用的路由,當(dāng)URL改變時(shí)就會(huì)去進(jìn)行匹配,匹配到哪個(gè)子應(yīng)用就會(huì)去加載對(duì)應(yīng)的那個(gè)子應(yīng)用。
相對(duì)于iframe的實(shí)現(xiàn)方案,single-spa中基座和各個(gè)子應(yīng)用之間共享著一個(gè)全局上下文,并且不存在URL不同步和UI不同步的情況,但是single-spa也有以下的缺點(diǎn):
- 沒(méi)有實(shí)現(xiàn)js隔離和css隔離
- 需要修改大量的配置,包括基座和子應(yīng)用的,不能開(kāi)箱即用
qiankun
基于single-spa二次開(kāi)發(fā),封裝了開(kāi)箱即用的api
資源預(yù)加載,在瀏覽器空閑時(shí)間預(yù)加載未打開(kāi)的微應(yīng)用資源,加速微應(yīng)用打開(kāi)速度。
實(shí)現(xiàn)了樣式隔離
基于qiankun的微前端實(shí)戰(zhàn)
準(zhǔn)備兩個(gè)空項(xiàng)目
- qiankun-base 主應(yīng)用
- qiankun-child vue 子應(yīng)用
創(chuàng)建基座項(xiàng)目qiankun-base和qiankun-child-vue
創(chuàng)建一個(gè)vue3+vite+tsx項(xiàng)目詳情見(jiàn)?創(chuàng)建一個(gè)vue3+vite+ts項(xiàng)目
vue子應(yīng)用 qiankun-child-vue
修改.env
VITE_APP_NAME = qiankun-child-vue
修改根節(jié)點(diǎn)掛載id
index.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><link rel="icon" type="image/svg+xml" href="/vite.svg" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>qiankun-child-vue</title></head><body><div id="qiankun-child-vue"></div><script type="module" src="/src/main.ts"></script></body>
</html>main.tsimport { createApp } from "vue";
import "./style.css";
import App from "./App.vue";
import router from "./router";
import antv from "ant-design-vue";
const app = createApp(App);
app.use(router).use(antv).mount("#qiankun-child-vue");
配置子應(yīng)用菜單?
/views/index.tsx
import { defineComponent, h, reactive, ref } from "vue";
import { Menu, SubMenu, MenuItem, ItemType } from "ant-design-vue";
import "./index.css";
import { RouterView, useRouter } from "vue-router";
// 展平數(shù)組
const flattenMenu = (list) => {const res: any = [];if (!list) return;list.forEach((item) => {res.push(item);if (item.children) res.push(...flattenMenu(item.children));});return res;
};
const getMenuKey = (menuList, key) => {const allList = flattenMenu(JSON.parse(JSON.stringify(menuList)));const cur = allList.find((item) => item.key == key);return cur ? cur : {};
};export default defineComponent({setup() {const router = useRouter();const menuList = ref([{key: "1",label: "子應(yīng)用菜單",url: "/qiankun-child-vue",children: [{ label: "設(shè)置", key: "2", url: "/qiankun-child-vue/setting" },],},]);// 找到點(diǎn)擊的菜單對(duì)象const handleMenuSelect = (params) => {const menu = getMenuKey(menuList.value, params.key);router.push(menu.url);};return () => (<a-layout class="layout"><a-layout-sider><MenuonSelect={handleMenuSelect}style="height:100%"mode="inline"items={menuList.value}></Menu></a-layout-sider><a-layout-content><RouterView></RouterView></a-layout-content></a-layout>);},
});
新建/views/setting.tsx
import { defineComponent, ref } from "vue";export default defineComponent({setup() {return () => <div>設(shè)置</div>;},
});
配置路由?/router/index.ts
import { createRouter, createWebHashHistory } from "vue-router";
import Index from "@/views/index";
const router = createRouter({history: createWebHashHistory(),routes: [{path: "/",component: Index,children: [{path: "/qiankun-child-vue/setting",name: "setting",component: () => import("@/views/setting"),},],},],
});export default router;
基本的頁(yè)面就搭建好了?
基座 qiankun-base
同樣在/views/index.tsx 寫(xiě)好基本的菜單
配置路由?/router/index.ts
import { createRouter, createWebHashHistory } from "vue-router";
import Index from "@/views";const router = createRouter({history: createWebHashHistory(),routes: [{path: "/:afterUser(.*)", // 正則匹配url 跳轉(zhuǎn)不會(huì)報(bào)錯(cuò)component: Index,},],
});export default router;
配置vite.config.js? ?根路徑base
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import { resolve } from "path";
export const pathResolve = (dir: string) => resolve(process.cwd(), ".", dir);export default defineConfig(({ mode }) => {return {base: "/qiankun-base/",plugins: [vue(), vueJsx()],server: {host: "0.0.0.0",port: 1000,},resolve: {alias: {"@": pathResolve("src"),},},};
});
修改dom根節(jié)點(diǎn) 和主應(yīng)用一樣改為子應(yīng)用的項(xiàng)目名稱 改這個(gè)是為了主應(yīng)用和子應(yīng)用的掛載在根節(jié)點(diǎn)的id不會(huì)重復(fù),如果一樣的話會(huì)導(dǎo)致主應(yīng)用頁(yè)面渲染不出來(lái)子應(yīng)用(這里不在展示細(xì)節(jié))
qiankun配置步驟(上面還沒(méi)開(kāi)始)
主應(yīng)用qiankun-base中下載qiankun
yarn add qiankun
在main.ts中開(kāi)啟?
注意:在掛載之前開(kāi)啟
import { createApp } from "vue";
import "./style.css";
import App from "./App.vue";
import { start } from "qiankun";
import router from "./router";
import antv from "ant-design-vue";
start({sandbox: {// strictStyleIsolation: true, // 開(kāi)啟嚴(yán)格的樣式隔離模式experimentalStyleIsolation: true, // 開(kāi)啟后所有樣式都會(huì)加上一個(gè)類名 .app-main {} ===> div[data-qiankun-react16] .app-main {}},singular: false, // 單一時(shí)間只渲染一個(gè)微應(yīng)用,默認(rèn)為true
});
createApp(App).use(router).use(antv).mount("#qiankun-base");
vue子應(yīng)用 qiankun-child-vue
下載vite-plugin-qiankun插件
yarn add vite-plugin-qiankun
配置vite.config.js 使用vite-plugin-qiankun
import { defineConfig, loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import qiankun from "vite-plugin-qiankun";
import { resolve } from "path";export const pathResolve = (dir: string) => resolve(process.cwd(), ".", dir);export default defineConfig(({ mode }) => {const env = loadEnv(mode, process.cwd());return {base: mode == "production" ? `/${env.VITE_APP_NAME}/` : "",plugins: [vue(),vueJsx(),qiankun(env.VITE_APP_NAME, { useDevMode: true }),],server: {host: "0.0.0.0",port: 2000,},resolve: {alias: {"@": pathResolve("src"),},},};
});
修改main.ts 判斷是在主應(yīng)用還是子應(yīng)用中
import "./style.css";
import App from "./App.vue";
import router from "./router";
import antv from "ant-design-vue";
import { render } from "@/hooks/microApp";render(App, "#qiankun-child-vue", (app, props) => {app.use(router).use(antv);
});
/hooks/microApp.ts
import renderWithQiankun, {QiankunProps,qiankunWindow,
} from "vite-plugin-qiankun/dist/helper";
import { App, Component, createApp } from "vue";const isMicroApp = qiankunWindow.__POWERED_BY_QIANKUN__;
export const render = (AppRoot: Component,domId,configApp: (app: App, props?: QiankunProps) => any
) => {let app: App;const _render = (props: QiankunProps = {}) => {const { container } = props;const root: string | Element = container? container.querySelector(domId)!: domId; // 避免 id 重復(fù)導(dǎo)致微應(yīng)用掛載失敗app = createApp(AppRoot);// 回調(diào)配置app的函數(shù) 讓調(diào)用的地方 可以使用appconfigApp(app, props);app.mount(root);};const initQiankun = () => {renderWithQiankun({bootstrap() {// console.log("微應(yīng)用:bootstrap");},mount(props) {// 獲取主應(yīng)用傳入數(shù)據(jù)// console.log("微應(yīng)用:mount", props);_render(props);},unmount(props) {// console.log("微應(yīng)用:unmount", props);app.unmount();},update(props) {// console.log("微應(yīng)用:update", props);},});};isMicroApp ? initQiankun() : _render();
};
在views/index.tsx 增加判斷邏輯 是在主應(yīng)用中還是在子應(yīng)用中
import { qiankunWindow } from "vite-plugin-qiankun/dist/helper";return () => {
// 判斷如果在主應(yīng)用中if (qiankunWindow.__POWERED_BY_QIANKUN__) {return <router-view></router-view>;}
// 在子應(yīng)用中return (<a-layout class="layout"><a-layout-sider><MenuonSelect={handleMenuSelect}style="height:100%"mode="inline"items={menuList.value}></Menu></a-layout-sider><a-layout-content><RouterView></RouterView></a-layout-content></a-layout>);};
易錯(cuò)點(diǎn)
1.主應(yīng)用和子應(yīng)用掛載在根節(jié)點(diǎn)的domid是同一個(gè)
2.主應(yīng)用配置路徑和子應(yīng)用路徑不一致