欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

首頁綜合 正文
目錄

柚子快報邀請碼778899分享:如何實現(xiàn)一個微前端框架

柚子快報邀請碼778899分享:如何實現(xiàn)一個微前端框架

http://yzkb.51969.com/

名詞:

架構(gòu)層級:系統(tǒng)架構(gòu) 應(yīng)用級 模塊 代碼

微前端:子應(yīng)用調(diào)度(耦合) 共享 (巨石應(yīng)用)單實例/多實例

架構(gòu)質(zhì)量:穩(wěn)定性(回歸) 健壯性(容錯) 拓展性 維護(hù)性

實際業(yè)務(wù)場景:

目前我們已經(jīng)有多個成熟項目,各項目分別用不同的技術(shù)棧實現(xiàn),react15、react16、react17 、vue2、vue3、JQuery/js原生 等。我們希望通過構(gòu)建一個項目作為門戶/平臺將現(xiàn)有的多個項目融合起來?;诖?,我們采用微前端框架來實現(xiàn),將門戶平臺作為主應(yīng)用,其他被融合的項目作為子應(yīng)用嵌入其中,同時不影響各子應(yīng)用的獨立運行和維護(hù)迭代。

微前端實現(xiàn)方式:

Iframe -- 多域名鑒權(quán)問題 web component -- 不成熟 自研框架 -- 定制化 獨立通信機制 沙箱環(huán)境 首次加載資源大 實現(xiàn):路由分發(fā)? 主應(yīng)用控制路由匹配和子應(yīng)用加載? 子應(yīng)用實現(xiàn)功能 主應(yīng)用-- 注冊子應(yīng)用、加載渲染子應(yīng)用、路由匹配、獲取數(shù)據(jù)、通信 子應(yīng)用-- 渲染、監(jiān)聽通信傳遞過來的數(shù)據(jù)

Micro-web微前端框架實現(xiàn)方式

實現(xiàn)一個主應(yīng)用作為中央控制器: VUE3

在主應(yīng)用中注冊子應(yīng)用:通過路由匹配設(shè)置需要展示的當(dāng)前子應(yīng)用

main-nav.vue

import { ref, nextTick, watch } from 'vue'

import { useRouter, useRoute } from 'vue-router'

import { NAV_LIST } from '../../const'

import { headerState } from '../../store'

export default {

name: 'nav',

setup() {

const router = useRouter();

const route = useRoute();

watch(route, (val) => {

for (let i = 0, len = NAV_LIST.length; i < len; i++) {

if (

NAV_LIST[i].url &&

val.fullPath.indexOf(NAV_LIST[i].url) !== -1

) {

headerState.setCurrentIndex(i)

return

}

}

}, { deep: true })

const setCurrentIndex = (item) => {

router.push(`${item.url}`)

}

return {

NAV_LIST,

currentIndex: headerState.currentIndex,

setCurrentIndex,

}

}

};

/const/nav.js

export const NAV_LIST = [

{

name: '首頁',

status: true,

value: 0,

url: '/vue3/#/index',

hash: '',

},

{

name: '資訊',

status: false,

value: 1,

url: '/react15#/information',

},

{

name: '視頻',

status: false,

value: 2,

url: '/react17#/video',

hash: '',

},

{

name: '選車',

status: false,

value: 3,

url: '/vue3/#/select',

hash: '',

},

{

name: '新能源',

status: false,

value: 4,

url: '/vue2#/energy',

hash: '',

},

{

name: '新車',

status: false,

value: 5,

url: '/react16#/new-car',

hash: '',

},

{

name: '排行',

status: false,

value: 6,

url: '/react16#/rank',

hash: '',

},

]

/store/headerState

import { ref } from 'vue'

// 當(dāng)前導(dǎo)航所在位置

export const currentIndex = ref(0)

// 修改導(dǎo)航選擇

export const setCurrentIndex = (key, cb) => {

currentIndex.value = key

cb && cb()

}

/store/leftNav

import * as loading from './loading'

import * as appInfo from '../store'

export const navList = [

{

name: 'react15',// 唯一

entry: '//localhost:9002/',

loading,

container: '#micro-container',

activeRule: '/react15',

appInfo,

},

{

name: 'react16',

entry: '//localhost:9003/',

loading,

container: '#micro-container',

activeRule: '/react16',

appInfo,

},

{

name: 'vue2',

entry: '//localhost:9004/',

loading,

container: '#micro-container',

activeRule: '/vue2',

appInfo,

},

{

name: 'vue3',

entry: '//localhost:9005/',

loading,

container: '#micro-container',

activeRule: '/vue3',

appInfo,

},

];

主應(yīng)用生命周期:

在主應(yīng)用中傳入子應(yīng)用的導(dǎo)航(第一個參數(shù))和生命周期(第二個參數(shù)),beforeLoad 是開始加載,mounted 是渲染完成,destoryed 是卸載完成

import { leftNav, headerState, footerState } from '../store';

import { registerMicroApps, start } from 'test-micro-web';

export const starMicroApp = () => {

// 注冊子應(yīng)用

registerMicroApps(

leftNav.navList,

// 生命周期

{

beforeLoad: [

app => {

app.loading.openLoading()

// 每次改動,都將頭部和底部顯示出來,不需要頭部和底部的頁面需要子應(yīng)用自己處理

headerState.changeHeader(true)

footerState.changeFooter(true)

console.log('開始加載 -- ', app.name);

},

],

mounted: [

app => {

console.log('加載完成 -- ', app.name);

setTimeout(() => {

app.loading.closeLoading()

}, 200)

},

],

destoryed: [

app => {

console.log('卸載完成 -- ', app.name);

},

],

},

{

}

);

// 如果當(dāng)前是根路由,且沒有子應(yīng)用,默認(rèn)進(jìn)入到 主應(yīng)用vue3的根路由

if (window.location.pathname === '/') {

window.history.pushState(null, null, '/vue3#/index');

}

// 啟動

start();

};

框架實現(xiàn):

registerMicroApps 接收兩個參數(shù),apps 子應(yīng)用參數(shù)列表和 mainLifecycle 生命周期,然后通過 setMainLifecycle 緩存主應(yīng)用的生命周期

//start.js

import { setList} from "./const/subApps"

// 注冊子應(yīng)用列表

export const registerMicroApps = (apps, mainLifecycle) => {

// apps.forEach(app => window.appList.push(app));

// 注冊子應(yīng)用

setList(apps)

// 保留主應(yīng)用的生命周期

setMainLifecycle(mainLifecycle)

}

setList 方法

// 子應(yīng)用列表

let list = []

export const setList = (data) => {

if (Array.isArray(data)) {

list = data;

return

}

list.push(data)

}

setMainLifecycle 是緩存主應(yīng)用的生命周期

export const setMainLifecycle = (data) => {

mainLifecycle = data

}

findAppByRoute 查找上一個和下一個 app 里面的內(nèi)容,isTurnChild 判斷子應(yīng)用是否做了切換,通過 window.__CURRENT_SUB_APP__ 判斷當(dāng)前路由已改變,修改當(dāng)前路由,通過 window.__CURRENT_HASH__ 判斷當(dāng)前 hash 值是否改變

// 根據(jù) 路由 查找子應(yīng)用

export const findAppByRoute = (router) => {

return filterApp('activeRule', router);

};

// 根據(jù) name 查找子應(yīng)用

export const findAppByName = (name) => {

return filterApp('name', name);

};

export const filterApp = (key, rule) => {

const currentApp = getList().filter(app => app[key] === rule);

return currentApp.length ? currentApp[0] : false;

};

跳轉(zhuǎn)時先將上一個子應(yīng)用生命周期卸載,然后啟動當(dāng)前子應(yīng)用的生命周期

// 改變了路由,重新裝載新的子應(yīng)用

export const lifecycle = async () => {

const prevApp = findAppByRoute(window.__ORIGIN_APP__); // 獲取上一個子應(yīng)用

const nextApp = findAppByRoute(window.__CURRENT_SUB_APP__); // 獲取跳轉(zhuǎn)后的子應(yīng)用

if (!nextApp) {

return

}

if (prevApp) {

// 卸載上一個應(yīng)用

await unmount(prevApp);

}

// 還原 prevApp 快照。

// prevApp.sandBox.active()

await boostrap(nextApp);

await mount(nextApp);

}

prevApp 是獲取到上一個子應(yīng)用,卸載掉上一個子應(yīng)用,nextApp 是獲取到要跳轉(zhuǎn)到的子應(yīng)用,執(zhí)行對應(yīng)的子應(yīng)用生命周期。若沒有獲取到下一個子應(yīng)用,直接退出,后面的就不用執(zhí)行。獲取到上一個子應(yīng)用,并且有它自己的生命周期,由其它子應(yīng)用切換過來的,卸載掉上一個子應(yīng)用。beforeLoad、mounted 和 destoryed 是主應(yīng)用的生命周期,runMainLifeCycle 是運行主應(yīng)用生命周期,等待所有的方法執(zhí)行完成,subApp 是獲取的是子應(yīng)用的內(nèi)容

獲取子應(yīng)用的html內(nèi)容 子應(yīng)用入口解析html并掛載到根元素進(jìn)行渲染 解析其它dom/script

htmlLoader 是加載 html 的方法,container 是第一個子應(yīng)用需要顯示在哪里,#id 內(nèi)容,entry 是子應(yīng)用的入口。

// 加載和渲染html

export const htmlLoader = async (app) => {

const {

container: cantainerName, entry, name

} = app

let [dom, scriptsArray] = await parseHtml(entry, name);

let container = document.querySelector(cantainerName);

if (!container) {

throw Error(` ${name} 的容器不存在,請查看是否正確指定`);

}

container.innerHTML = dom;

scriptsArray.map((item) => {

sandbox(item, name);

});

}

// 解析html

export const parseHtml = async (url, appName) => {

const div = document.createElement('div');

let scriptsArray = [];

div.innerHTML = await fetchUrl(url);

const [scriptUrls, scripts, elements] = getResources(div, findAppByName(appName));

const fetchedScript = await Promise.all(scriptUrls.map(url => fetchUrl(url)));

scriptsArray = scripts.concat(fetchedScript);

cache[appName] = [elements, scriptsArray];

return [elements, scriptsArray];

}

parseHtml 是解析 html,在 fetchUrl 后解析、加載、執(zhí)行流程。通過 script.concat 處理 fetchedScripts,將得到且攜帶標(biāo)簽里面的內(nèi)容、外部鏈接引入的內(nèi)容結(jié)合到一起,構(gòu)成子應(yīng)用所有 script 的集合,在其它方法中將這些集合運行起來,得到完整的子應(yīng)用渲染,最后將 dom、allScript 直接緩存,后面可以直接使用 cache,獲取到對應(yīng)的子應(yīng)用。

// 解析 js 內(nèi)容

export const getResources = (root, app) => {

const scriptUrls = [];

const scripts = [];

function deepParse(element) {

const children = element.children;

const parent = element.parentNode;

// 處理位于 link 標(biāo)簽中的 js 文件

if (element.nodeName.toLowerCase() === 'script') {

const src = element.getAttribute('src');

if (!src) {

// 直接在 script 標(biāo)簽中書寫的內(nèi)容

let script = element.outerHTML;

scripts.push(script);

} else {

if (src.startsWith('http')) {

scriptUrls.push(src);

} else {

// fetch 時 添加 publicPath

scriptUrls.push(`http:${app.entry}/${src}`);

}

}

if (parent) {

let comment = document.createComment('此 js 文件已被微前端替換');

// 在 dom 結(jié)構(gòu)中刪除此文件引用

parent.replaceChild(comment, element);

}

}

// 處理位于 link 標(biāo)簽中的 js 文件

if (element.nodeName.toLowerCase() === 'link') {

const href = element.getAttribute('href');

if (href.endsWith('.js')) {

if (href.startsWith('http')) {

scriptUrls.push(href);

} else {

// fetch 時 添加 publicPath

scriptUrls.push(`http:${app.entry}/${href}`);

}

}

}

for (let i = 0; i < children.length; i++) {

deepParse(children[i]);

}

}

deepParse(root);

return [scriptUrls, scripts, root.outerHTML];

}

在 getResources 中,scriptUrl是 鏈接/src /href,script是寫在 script 中的 js 腳本內(nèi)容,dom 是需要展示到子應(yīng)用上的 DOM 結(jié)構(gòu)。deepParse 是深度解析,獲取到所有 script 和 link 的鏈接、內(nèi)容,先處理位于 script 中的內(nèi)容,link 也會有 js 的內(nèi)容,遞歸遍歷進(jìn)行處理

子應(yīng)用生命周期改造

bootstrap mount unmount

import React from 'react'

import "./index.scss"

import ReactDOM from 'react-dom'

import BasicMap from './src/router';

import { setMain } from './src/utils/global'

export const render = () => {

ReactDOM.render(, document.getElementById('app-react'))

}

if (!window.__MICRO_WEB__) {

render()

}

export async function bootstrap() {

console.log('react bootstrap')

}

export async function mount(app) {

setMain(app)

console.log('react mount')

render()

}

export async function unmount(ctx) {

console.log('react unmout')

const { container } = ctx

if (container) {

document.querySelector(container).innerHTML = ''

}

}

微前端環(huán)境變量window.__MICRO_WEB__ == true,獲取生命周期掛載到app上, 以sandBox暴露出去

// 創(chuàng)建沙箱環(huán)境

export const sandbox = (script, app) => {

// 創(chuàng)建沙箱環(huán)境

const global = new ProxySandBox();

// 設(shè)置微前端環(huán)境

window.__MICRO_WEB__ = true;

// 獲取子應(yīng)用生命周期

const lifeCycles = performScriptForEval(script, app, global.proxy);

const performScriptForEval = (script, appName, global) => {

const globalWindow = (0, eval)(window)

globalWindow.proxy = global;

const scriptText = `

((window) => {

try{

${script}

} catch (e) {

console.error('run script error: ' + e)

}

return window['${appName}']

}).bind(window.proxy)(window.proxy)

`

return eval(scriptText)// app module mount

}

app.sandBox = global;

app.bootstrap = lifeCycles.bootstrap;

app.mount = lifeCycles.mount;

app.unmount = lifeCycles.unmount;

}

快照沙箱(單實例)--應(yīng)用于老版本瀏覽器中,代理window(共兩層)->激活沙箱->銷毀沙箱

代理沙箱(多實例) --激活沙箱->new Proxy(window,handler) 設(shè)置兩個攔截方法 get() set() 獲取到屬性指向window -- (function) window.defaultValue[key] / window.target[key] -> 銷毀沙箱

css樣式隔離-- 三種方法 css modules / shadow dom / minicss

css modules:webpack打包命名空間(略)

shadow dom:開啟shadow dom模式(API):box1.attachShadow(init:{mode:’open’}) , appendChild添加到div容器, 新版本瀏覽器中支持

minicss: webpack插件loader打包輸出不同的子應(yīng)用單獨css文件,子應(yīng)用切換時會清空css的引入link,后再加載新的

父子組件通信-- 兩種 props / customevent

props:依賴注入 - 主應(yīng)用信息在組件中暴露出來(store) 生命周期mounted中傳遞app實例的參數(shù)對象,registerApp方法注入到子應(yīng)用中,然后方法調(diào)用

customevent:事件監(jiān)聽on() window.addEventListener ,事件觸發(fā)emit() new CustomEvent() window.dispatchEvent() detail可傳遞參數(shù)

子應(yīng)用之間通信-- props通過父組件傳遞 / customevent

customevent : window.custom 先在一個子應(yīng)用監(jiān)聽test2 再在另一個子應(yīng)用監(jiān)聽到之后觸發(fā)

全局狀態(tài)管理-- 全局方法createSrore = (()=>{})() 接收初始數(shù)據(jù)initData為store ,其中定義一個方法getStore用于獲取store , 定義第二個方法update 用于將store更新再通知到所有訂閱者observers.forEach(async item=> await item(store,oldstore)),定義第三個方法 subscribe用于添加訂閱者,暴露createStore攜帶三個方法。 主應(yīng)用中獲取 createStore() 掛到window.store= store, 子應(yīng)用中獲取 const storeData=window.store.getStore(), 然后可在子應(yīng)用中進(jìn)行store更新 window.store.update(value:{...storeData, a:1})

提高加載性能:兩步緩存預(yù)加載

應(yīng)用緩存-- 根據(jù)子應(yīng)用name做緩存 psrseHtml=async (entry,name)=>{ 獲取所有script資源 cache[name]=[dom,script]}

預(yù)加載-- 定義prefetch()方法獲取其它子應(yīng)用列表然后使用await Promise.all() ,在start.js中start()方法尾部執(zhí)行prefetch(),這樣會將其它未展示的子應(yīng)用中的js資源保存到了當(dāng)前的緩存對象中cache{}。

柚子快報邀請碼778899分享:如何實現(xiàn)一個微前端框架

http://yzkb.51969.com/

好文閱讀

評論可見,查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。

轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://gantiao.com.cn/post/19539486.html

發(fā)布評論

您暫未設(shè)置收款碼

請在主題配置——文章設(shè)置里上傳

掃描二維碼手機訪問

文章目錄