摘要:面對如何在現有的低版本的框架服務上,執行新版本的前端服務問題,華為雲前端推出了一種融合方案,該方案能讓獨立的Angular專案整體執行在低版本的框架服務上,通過各種適配手段,讓Angular專案也能獲取到外層框架服務的資源。
華為雲前端服務前期採用AngularJs作為框架技術棧,技術較為老舊,效能較差,在華為雲快速發展的今天,顯然不能滿足要求。因此我們必須要升級前端技術棧,使用Angular2+來承載我們的前端服務。GeminiDB作為新服務,也是資料庫乃至華為雲未來的重點服務,作為前端部分,必須在技術上使用最前沿的框架,以最大地提高使用者體驗。
但是技術棧的升級不是一蹴而就的,尤其是在華為雲,所有的雲服務必須在框架服務的底座上執行,而框架服務承載了所有的雲服務,如果要進行技術棧升級,必然是一個緩慢的過程。GeminiDB作為華為雲服務裡的一員,也不可能脫離框架服務而存在。因此存在一個問題,就是如何在現有的低版本的框架服務上,執行新版本的前端服務。
為了解決以上問題,華為雲前端推出了一種融合方案,該方案能讓獨立的Angular專案整體執行在低版本的框架服務上,通過各種適配手段,讓Angular專案也能獲取到外層框架服務的資源。
底層專案
底層專案使用webpack打包,打包後通過在index.html裡引入businessAll.js檔案,以該檔案為入口啟動整個框架服務。
<script type="text/javascript" src="businessAll.js"></script>
在底層框架服務啟動後,再渲染出具體雲服務內容。
<div class="service-content-view" ui-view ng-animate="{enter:'fade-enter'}"></div>
Angular專案
Angular專案支援獨立執行,有單獨的index.html,也有單獨的main.ts入口。但是如果希望Angular專案執行在底層框架服務上,就必須把Angular專案看作是一個獨立的模組,把專案整體引入到底層專案中。因此,我們可以預先把Angular專案編譯好,放到底層專案的一個目錄下。在執行底層專案時,在index.html裡將Angular專案引進來,獨立執行。
<link rel="stylesheet" type="text/css" href="{底層專案中Angular專案的路徑}/styles.css" /> <script type="text/javascript" src="{底層專案中Angular專案的路徑}/runtime.js"></script> <script type="text/javascript" src="{底層專案中Angular專案的路徑}/polyfills.js"></script> <script type="text/javascript" src="{底層專案中Angular專案的路徑}/main.js"></script>
專案融合
底層專案和Angular專案均能獨立,但是要讓兩者融合起來,會遇到以下幾個問題:
1.底層專案中如何渲染出Angular專案。
2.Angular專案依賴底層專案的資源,如何保證Angular專案在底層專案執行起來後再執行。
3.如何解決底層專案和Angular專案的路由衝突問題。
渲染Angular專案
底層專案分為兩部分,一部分是底層框架服務,另一部分是具體雲服務。現在我們要做的是把老的雲服務專案替換成新的Angular專案,因此我們可以直接在渲染老的雲服務的地方替換成新的Angular專案的渲染容器。
<div class="service-content-view" ui-view ng-animate="{enter:'fade-enter'}"></div> <app-root></app-root>
底層框架服務對頁面渲染上做了一些體驗上的優化,因此必須保留原模板中的ui-view,使底層專案正常執行起來,實際上老的雲服務專案的渲染內容已經轉發到新的Angular專案上面。
Angular專案對底層專案的依賴
底層框架服務給雲服務提供了很多公共變數與服務,這些變數和服務是各個雲服務必須要使用的,否則雲服務將不能正常運作。
啟動順序問題
對於Angular專案來說,要使用底層框架服務提供的內容,首先要求Angular專案在底層專案執行起來之後再執行。這裡採用Augular中的APP_INITIALIZER令牌來解決這個問題。APP_INITIALIZER是一個函式,在程式初始化的時候被呼叫。這裡在根模組的providers中以factory的形式來配置。
import { BrowserModule } from "@angular/platform-browser"; import { NgModule } from "@angular/core"; import { AppInitService } from './services/app-init.service'; import { AppComponent } from "./app.component"; @NgModule({ declarations: [AppComponent], imports: [BrowserModule], providers: [ AppInitService, { provide: APP_INITIALIZER, useFactory: initializeApp, deps: [AppInitService], multi: true } ], bootstrap: [AppComponent] }) export class AppModule {} export function initializeApp(appInitService: AppInitService) { return (): Promise<any> => { return appInitService.Init(); }; }
在appInitService裡,先獲取到底層框架的資源,再進行Angular專案的初始化。
import { Injectable } from '@angular/core'; @Injectable() export class AppInitService { constructor() {} Init() { return new Promise<void>((resolve, reject) => { // 獲取到底層框架服務的資源 resolve(); }); } }
資源依賴問題
底層專案使用的是AngularJs,Angular專案獲取底層框架服務提供的資源不能通過Angular的方式引入,因此需要藉助AngularJS的注入器獲取在底層框架中註冊的服務元件:
static get(inject: string): any { return (window as any).angular.element('html').injector().get(inject);} 如,要獲取 $rootScope: rootScope = (window as any).angular.element('html').injector().get(‘$rootScope’);
路由衝突問題
Angular專案本身有自己的路由,但是Angular專案是執行在底層框架之上的,Angular專案的路由將會被底層框架所攔截。因此,我們也需要在底層框架的專案中配置相同的路由,以免Angular專案中的有效路由被底層框架識導向為404。
Angular專案路由:
{ path: '', redirectTo: 'ng2app1', pathMatch: 'full' }, { path: 'ng2app1', loadChildren: './ng2app1/ng2app1.module#Ng2app1Module', }, { path: 'ng2app2', loadChildren: './ng2app2/ng2app2.module#Ng2app2Module', } 底層框架路由: var configArr = [ { name: 'ng2app1', url: '/ng2app1' }, { name: 'ng2app2', url: '/ng2app2' } ];
另外,由於底層專案使用的是hash路由,Angular專案中也要做相應的配置,預設是使用的是PathLocationStrategy,需要切換到hash模式。
import { LocationStrategy, HashLocationStrategy } from '@angular/common'; ... providers: [ { provide: LocationStrategy, useClass: HashLocationStrategy } ]
總結
以上方案是在底層框架升級週期長的前提下的一個臨時方案,實際上還是存在著不少的問題。比如底層框架對於老的雲服務容器是有統一管理的,老的雲服務容器會針對不同的場景能夠自適應,而融合方案中的Angular專案則不能;每次啟動整個專案時,必須要預先編譯好裡面的Angular專案,再去啟動外層的底層框架,開發效率比較低。因此,後續GeminiDB服務應該在底層框架升級後,儘快適應到新的底層框架體系中,提高服務的可用性和穩定性。