0. 前言
在Tob項(xiàng)目如火如荼的今天,越來(lái)越多的前端項(xiàng)目規(guī)模開(kāi)始急劇擴(kuò)大,多團(tuán)隊(duì)分模塊開(kāi)發(fā)迭代是越來(lái)越不可以避免,微前端的形式也越來(lái)越有必要.
1.微前端的核心介紹
1.1 模塊服務(wù)
管理模塊和插件的核心服務(wù).
1.2 模塊
用戶(hù)開(kāi)發(fā)的統(tǒng)一模塊.對(duì)外提供統(tǒng)一方法.可以稱(chēng)為應(yīng)用
1.3 插件
平臺(tái)或生態(tài)開(kāi)發(fā)的插件機(jī)制,對(duì)模塊的加載和業(yè)務(wù)開(kāi)發(fā)起到輔助作用.
1.3 微前端服務(wù)核心
1.3.1 模塊如何載入?何時(shí)載入?
如何載入?
目前騰訊云NMC中采用Seajs統(tǒng)一管理組織所有的核心或應(yīng)用模塊.
新產(chǎn)品的接入都是打包成一個(gè)CMD的模塊通過(guò)script標(biāo)簽引入.SeaJs 使用起來(lái)較為方便.有明確的api.
我們這里采用何種的組織形勢(shì)呢?requirejs/systemjs/es6? 考慮到微前端的重要優(yōu)勢(shì)之一,就是大項(xiàng)目的可以分粒度,技術(shù)選型靈活.對(duì)于舊的模塊可以做到兼容.
新的技術(shù)項(xiàng)目使用ES6 import,老的模塊化項(xiàng)目進(jìn)行使用Seajs,RequireJs或Iframe的加載.
所以這里設(shè)計(jì)將模塊的載入方法和時(shí)機(jī)都交給模塊去定義.
服務(wù)核心僅調(diào)用用來(lái)注冊(cè)模塊名稱(chēng)載入方法和載入時(shí)機(jī).
關(guān)于載入時(shí)機(jī)的問(wèn)題?
事件點(diǎn)擊?hashchange?onpopstate....
等等都可以注冊(cè)到服務(wù)核心中去.當(dāng)捕捉到事件之后去遍歷所有已注冊(cè)的載入時(shí)機(jī),如果滿(mǎn)足模塊的載入時(shí)機(jī),則調(diào)用模塊的載入方法.不局限與路由切換,菜單點(diǎn)擊等情況.同時(shí)我們也提供一些核心插件用于一些機(jī)制的注冊(cè).
1.3.2 具體實(shí)現(xiàn)
class Services{
constructor(pluginService){
this.entrys=new Map();
this.plguinService=pluginService;
//監(jiān)聽(tīng)事件類(lèi)型的注冊(cè)
this.eventTypeMoudles=new Map();
}
/**
* 服務(wù)注冊(cè)入口
* @param {*} entry
* @memberof Services
*/
registerEntry(entry){
console.log(entry.getName())
this.entrys.set(entry.getName(),entry);
}
getPluginService(){
return this.plguinService;
}
registerPlugin(plugin){
this.plguinService.register(plugin)
}
/**
* 注冊(cè)全局加載時(shí)機(jī)
*/
registerWindowLoadChance(eventType,moudleName){
if(this.eventTypeMoudles.has(eventType)){
this.eventTypeMoudles.get(eventType).add(moudleName);
}else{
this.eventTypeMoudles.set(eventType,new Set([moudleName]));
}
//更新監(jiān)聽(tīng)方法
window[eventType]=(event)=>{
//觸發(fā)set中所有的moudle的checkload方法
[...this.eventTypeMoudles.get(eventType)].forEach((moudleName)=>{
this.entrys.get(moudleName) && this.entrys.get(moudleName).onEvent(event,this)
})
}
}
/**
* 直接開(kāi)啟某個(gè)模塊
*/
loadMoudle(moudleName){
this.entrys.get(moudleName) && this.entrys.get(moudleName).start(this)
}
/**
* 通知其他模塊.本模塊被加載了
*/
noticeOtherEntry(sendEntry,notice){
for(let [entryName,entry] of this.entrys){
if(entryName!==sendEntry.getName()){
entry.onEntryNotice(sendEntry,notice)
}
}
}
}
3.關(guān)于模塊
關(guān)于微前端,網(wǎng)上有許多的文章描述和見(jiàn)解,微前端中的核心就是子應(yīng)用被實(shí)現(xiàn)的方式.不外有以下三種方式
- 三大框架(React,Vue,Angular)
- iframe
- web-component
原生(好像沒(méi)看到)
所以如何設(shè)計(jì)出一個(gè)好的模塊設(shè)計(jì),可以去適應(yīng)不同的框架,不同的開(kāi)發(fā)方式.
3.1 模塊的目標(biāo)
模塊最終的目標(biāo)是在指定位置渲染dom結(jié)構(gòu).
所以不管是什么類(lèi)型的應(yīng)用,本質(zhì)上就是渲染dom結(jié)構(gòu),只是可能加載方式的,加載的前提,運(yùn)行的時(shí)機(jī)不同罷了.這些都可以通過(guò)插件去解決.
3.2 模塊的本質(zhì)
模塊的本質(zhì)是一個(gè)被封裝統(tǒng)一方法的前端資源集.
這里可以抽象一個(gè)模塊為Entry和App
Entry
Entry 接受注冊(cè)的全局監(jiān)聽(tīng)事件去判斷對(duì)于自身模塊加載和卸載.且接受其他模塊發(fā)送的信息.Entry就是App的管理者
class Entry{
constructor(jsUrl,name,plugins){
}
checkShouldLoad(event){
}
checkShouldUnload(event){
}
onEntryNotice(entry,notice){
}
}
App
app 就是模塊的主要內(nèi)容,滿(mǎn)足以下接口設(shè)定即可
class App{
render=()=>{
ReactDOM.render(
React.createElement('div', null, 'Hello Micro Front For React'),
document.getElementById('root')
);
},
destory=()=>{
ReactDOM.unmountComponentAtNode(document.getElementById("root"))
}
}
分離Moudle為Entry和App最主要的原因就是減少網(wǎng)頁(yè)第一次被加載時(shí),減少加載資源的體積大小.
2.3.1 加載方式
requirejs
2.3.2 加載時(shí)機(jī)
可以由模塊統(tǒng)一確定.無(wú)論是點(diǎn)擊菜單,路由切換.
3. 關(guān)于插件
3.1 插件的作用
為模塊的加載做環(huán)境準(zhǔn)備
監(jiān)控模塊的運(yùn)行情況
優(yōu)化模塊加載,如增加loading,避免重復(fù)加載基礎(chǔ)環(huán)境
...
3.2 ReactPlugin實(shí)現(xiàn)demo
import BasePlugin from "./../Core/Plugin"
import loader from "./../Utils/loader"
class ReactPlugin extends BasePlugin{
lifeMethod(moudle,lifeName){
switch(lifeName){
case "beforeload":
return new Promise((resolve,reject)=>{
/** 避免基礎(chǔ)環(huán)境多次的加載 */
if(window.React && window.ReactDOM){
resolve()
return
}
loader.load("./react16.development.js",()=>{
resolve()
})
})
}
}
}
export default ReactPlugin
4.整體DEMO
http://microfront.dishenghk.cn
import BaseEntry from "./Core/Entry.js";
import {start} from "./Core/app"
class ReactEntry extends BaseEntry{
//合適加載
checkShouldLoad(event){
console.log(event)
return event.target && event.target.dataset && event.target.dataset.show==="react"
}
//合適卸載
checkShouldUnload(event){
return event.type==='hashchange'
}
//其他模塊發(fā)送的信息,當(dāng)其他模塊加載時(shí)的動(dòng)作
onEntryNotice(entry,notice){
//當(dāng)其他的模塊被加載時(shí)候我們destroy即可
console.log(entry,notice,this.module)
if(notice && notice.type ==='loaded'){
this.module && this.module.destory();
}
}
}
class VueEntry extends BaseEntry{
onEntryNotice(entry,notice){
if(notice && notice.type ==='loaded'){
this.module && this.module.destory();
}
}
}
//默認(rèn)加載React和Vue環(huán)境的加載
const microService=start();
microService.registerEntry(new ReactEntry("./test.js","test",["ReactPlugin"]))
microService.registerEntry(new VueEntry("./vuetest.js","vuetest",["VuePlugin"]))
//注冊(cè)全局加載時(shí)機(jī)
microService.registerWindowLoadChance("onclick","test")
microService.registerWindowLoadChance("onhashchange","test")
window.microService=microService
//也可以直接加載模塊
window.loadVueTest=function loadVueTest(){
microService.loadMoudle("vuetest");
}