刷新頁面?路由拆分?No,動態加載組件。
本文分爲如下四部分:javascript
對於前端微服化來講,有這麼一些方案:css
所以,當咱們考慮前端微服務化的時候,咱們但願:html
在過去的幾星期裏,我花費了大量的時間在學習 Single-SPA 的代碼。可是,我發現它在開發和部署上真的太麻煩了,徹底達不到獨立部署地標準。按 Single-SPA 的設計,我須要在入口文件中聲名個人應用,而後才能去構建:前端
declareChildApplication('inferno', () => import('src/inferno/inferno.app.js'), pathPrefix('/inferno'));
同時,在個人應用裏,我還須要去指定個人生命週期。這就意味着,當我開發了一個新的應用時,必須更新兩份代碼:主工程和應用。這時咱們還很可能在同一個源碼裏工做。 java
當出現多個團隊的時候,在同一份源碼裏工做,顯然變得至關的不可靠——好比說,對方團隊使用的是 Tab,而咱們使用的是 2 個空格,隔壁的老王用的是 4 個空格。git
一個單體的前端應用最大的問題是,構建出來的 js、css 文件至關的巨大。而微前端則意味着,這個文件被獨立地拆分紅多個文件,它們即可以獨立去部署應用。github
等等,咱們是否真的須要技術無關?若是咱們不須要技術無關的話,微前端問題就很容易解決了。json
事實上,對於大部分的公司和團隊來講,技術無關只是一個無關痛癢的話術。當一家公司的幾個創始人使用了 Java,那麼極有可能在將來的選型上繼續使用 Java。除非,一些額外的服務來使用 Python 來實現人工智能。所以,在大部分的狀況下,仍然是技術棧惟一。bootstrap
對於前端項目來講,更是如此:一個部門裏基本上只會選用一個框架。前端框架
因而,咱們選擇了 Angular。
使用路由跳轉來進行前端微服務化,是一種很簡單、高效的切分方式。然而,路由跳轉地過程當中,會有一個白屏的過程。在這個過程當中,跳轉前的應用和將要跳轉的應用,都失去了對頁面的控制權。若是這個應用出了問題,那麼用戶就會一臉懵逼。
理想的狀況下,它應該能夠被控制。
互聯網本質是去中心化的嗎?不,DNS 決定了它不是。TAB,決定了它不是。
微服務從本質上來講,它應該是去中心化的。可是,它又不能是徹底的去中心化。對於一個微服務來講,它須要一個服務註冊中心:
服務提供方要註冊通告服務地址,服務的調用方要能發現目標服務。
對於一個前端應用來講,這個東西就是路由。
從頁面上來講,只有咱們在網頁上添加一個菜單連接,用戶才能知道某個頁面是可使用的。
而從代碼上來講,那就是咱們須要有一個地方來管理咱們的應用:**發現存在哪些應用,哪一個應用使用哪一個路由。
管理好咱們的路由,實際上就是管理好咱們的應用。
在設計一個微前端框架的時候,爲每一個項目取一個名字的問題糾結了我好久——怎麼去規範化這個東西。直到,我再一次想到了康威定律:
系統設計(產品結構等同組織形式,每一個設計系統的組織,其產生的設計等同於組織之間的溝通結構。
換句人話說,就是同一個組織下,不可能有兩個項目的名稱是同樣的。
因此,這個問題很簡單就解決了。
Single-SPA 設計了一個基本的生命週期(雖然它沒有統一管理),它包含了五種狀態:
因而,我在設計上基本上沿用了這個生命週期。顯然,諸如 load 之類對於個人設計是多餘的。
從某種意義上來講,整個每系統是圍繞着應用配置進行的。若是應用的配置能自動化,那麼整個系統就自動化。
當咱們只開發一個新的組件,那麼咱們只須要更新咱們的組件,並更新配置便可。而這個配置自己也應該是能自動生成的。
基於以上的前提,系統的工做流程以下所示:
總體的工程流程以下所示:
故而,其對應的架構以下圖所示:
咱們作的部署策略以下:咱們的應用使用的配置文件叫 apps.json
,由主工程去獲取這個配置。每次部署的時候,咱們只須要將 apps.json
指向最新的配置文件便可。配置的文件類以下所示:
一個應用的配置以下所示:
{ "name": "help", "selector": "help-root", "baseScriptUrl": "/assets/help", "styles": [ "styles.bundle.css" ], "prefix": "help", "scripts": [ "inline.bundle.js", "polyfills.bundle.js", "main.bundle.js" ] }
這裏的 selector
對應於應用所須要的 DOM 節點,prefix 則是用於 URL 路由上。這些都是自動從 index.html
文件和 package.json
中獲取生成的。
因爲如今的應用變成了兩部分:主工程和應用部分。就會出現一個問題:只有一個工程能捕獲路由變化。當由主工程去改變應用的二級路由時,就沒法有效地傳達到子應用。在這時,只能經過事件的方式去通知子應用,子應用也須要監測是不是當前應用的路由。
if (event.detail.app.name === appName) { let urlPrefix = 'app' if (urlPrefix) { urlPrefix = `/${window.mooa.option.urlPrefix}/` } router.navigate([event.detail.url.replace(urlPrefix + appName, '')]) }
類似的,當咱們須要從應用 A 跳轉到應用 B 時,咱們也須要這樣的一個機制:
window.addEventListener('mooa.routing.navigate', function(event: CustomEvent) { const opts = event.detail if (opts) { navigateAppByName(opts) } })
剩下的諸如 Loading 動畫也是相似的。
So,咱們就有了前端微服務框架 Mooa。它基於 single-spa && single-spa-angular-cli,而且符合以上的設計思想。
GayHub 地址:https://github.com/phodal/mooa
對於主工程而言,只須要如下的幾行代碼就能夠完成上面的功能:
http.get<any[]>('/assets/apps.json') .subscribe(data => { data.map((config) => { that.mooa.registerApplication(config.name, config, mooaRouter.matchRoute(config.prefix)); }); this.mooa.start(); }); this.router.events.subscribe((event: any) => { if (event instanceof NavigationEnd) { that.mooa.reRouter(event); } });
並添加一個對應的子應用路由:
{ path: 'app/:appName/:route', component: HomeComponent }
則如上所述的四個步驟。
對於子工程而言,則只須要一個對應的 Hook 操做:
mooaPlatform.mount('help').then((opts) => { platformBrowserDynamic().bootstrapModule(AppModule).then((module) => { opts['attachUnmount'](module); }); });
並設置好對應的 base_href:
providers: [ {provide: APP_BASE_HREF, useValue: mooaPlatform.appBase()}, ]
嗯,就是這麼簡單。DEMO 視頻以下:
Demo 地址見:http://mooa.phodal.com/
GitHub 示例:https://github.com/phodal/mooa