Angular Universal 的演进历史
想象這樣一個場景:您已經在您的 Web 項目上工作了幾個月,這很可能是一個 Web 應用程序,更具體地說,是一個“單頁應用程序”。 但是現在是時候將您的應用程序交付并發布給數百萬用戶和……搜索引擎了。 為了使您的應用程序成功,它必須被搜索引擎索引,即需要添加 SEO 支持!
我們可以把 Angular Universal 理解成:Universal is Angular for the Headless Web.
您不再需要瀏覽器容器(也稱為 WebView)來運行 Angular。 由于它與 DOM 無關,因此 Angular 可以在任何有 JavaScript 運行時的地方運行,比如 Node.js.
此圖說明了 Universal 在瀏覽器之外運行典型 Angular Web 應用程序的能力。 顯然我們需要一個 JavaScript 運行時,這就是我們默認支持 Node.js(由 V8 引擎提供支持)的原因。 當然,現在也涌現出了越來越多的其他服務器端技術,如 PHP、Java、Python、Go……
有了 Angular Universal 之后,您的應用程序可以在瀏覽器之外解釋——讓我們以服務器為例——請求您的 SPA 的客戶端將收到所請求路由/URL 的靜態完全呈現頁面。 此頁面包含所有相關資源,即圖像、樣式表、字體……甚至是通過 Angular 服務傳入的數據。
Universal 能夠重新連接一些默認的 Angular provider 實現,以便它們可以在目標平臺上工作。 當客戶端收到渲染的頁面時,它也會收到原始的 Angular 應用程序—— Angular Universal 使得應用程序在瀏覽器里看起來幾乎是瞬間就完成了加載。 加載后,Angular 客戶端應用會處理剩下的事情。
事實上,Universal 與 Preboot.js 庫捆綁在一起,其唯一作用是確保兩個狀態同步。Preboot.js 在幕后所做的只是簡單而智能地記錄 Angular 引導程序之前發生的事件; 并在 Angular 完成加載后對這些事件進行重播。
由于 Angular 的渲染抽象,Universal 成為可能。 事實上,當您編寫應用程序代碼時,該邏輯會被 Angular 的編譯器解析為 AST——我們在這里真正簡化了事情。 然后 AST 被 Angular 的渲染層使用,它使用一個不依賴于 DOM 的抽象渲染器。 Angular 允許您使用不同的渲染器。 默認情況下,Angular 附帶 DOMRenderer,因此您的應用程序可以在瀏覽器中呈現,這可能是 95% 的用例。
這就是 Universal 的用武之地。 Universal 帶有一堆預渲染器,適用于所有主流技術和構建工具。
Dependency Injection and Providers
Angular 的另一個亮點是它的 DI 系統。 事實上,Angular 是唯一實現這種設計模式的前端框架,它允許輕松完成如此多的偉大任務(比如控制反轉)。 多虧了 DI,您可以例如在運行時交換兩個不同的實現,這在測試中被大量使用。
在 Universal,我們利用這個 DI 系統為您提供許多特定于目標平臺的服務。 對于 Node,我們提供了一個自定義的 ServerModule,它實現了 Node 的服務器特定 API,例如請求,而不是瀏覽器的 XHR。 Universal 還附帶了一個特定于 Node 的自定義渲染器,當然,我們為您提供了一堆預渲染器——我們稱之為——例如用于您的 Node 后端技術的 Express 渲染器或 Webpack 渲染器。 對于其他非 JavaScript 技術,例如 .NetCore 或 Java,您也應該期待其他預渲染器。
好消息是 Universal Application 與經典的 Angular 應用程序沒有什么不同。 應用程序邏輯實際上保持不變。
只要有可能,在直接接觸 DOM 之前請三思。 每次要與瀏覽器的 DOM 交互時,請確保使用 Angular Renderer 或渲染抽象。
下圖是 Angular Universal Application Structure.
browser.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './index'; @NgModule({bootstrap: [ AppComponent ],declarations: [ AppComponent ],imports: [BrowserModule.withServerTransition({appId: 'some-app-id'}),...] }) export class AppBrowserModule {}請注意,您需要使用 withServerTransition() 方法初始化 BrowserModule。 這將確保基于瀏覽器的應用程序將從服務器呈現的應用程序過渡。
server.module.ts
該模塊專用于您的服務器環境。 ServerModule 提供了一組來自 @angular/platform-server 包的 provider.
import { NgModule } from '@angular/core'; import { ServerModule } from '@angular/platform-server'; import { AppComponent, AppBrowserModule } from './browser.module'; @NgModule({bootstrap: [ AppComponent ],declarations: [ AppComponent ],imports: [ServerModule,AppBrowserModule,...] }) export class AppServerModule {}在 AppServerModule 中,您應該同時導入 ServerModule 和 AppBrowserModule,以便它們共享相同的 appId,即 AppBrowserModule 使用的 transition ID。
client.ts
該文件負責在客戶端引導您的應用程序。 這里沒有什么新東西,只是通常的引導過程(在 AOT 模式下):
import { platformBrowser } from '@angular/platform-browser'; import { AppModuleNgFactory } from './ngfactory/src/app.ngfactory'; import { enableProdMode } from '@angular/core'; enableProdMode(); platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);server.ts
此文件確實特定于您的服務器/后端環境。 在這里,我們的目標是 Node.js,更準確地說是 Express 框架來處理所有客戶端請求和渲染過程。 為此,我們正在使用和注冊代表 Express 的 Angular Universal 渲染引擎的 ngExpressEngine(見下一段):
import { platformServer, renderModuleFactory } from '@angular/platform-server'; import { AppServerModuleNgFactory } from './ngfactory/src/app.server.ngfactory'; import { enableProdMode } from '@angular/core'; import { AppServerModule } from './server.module'; import * as express from 'express'; import {ngExpressEngine} from './express-engine';enableProdMode();const app = express();app.engine('html', ngExpressEngine({baseUrl: 'http://localhost:4200',bootstrap: [AppServerModuleNgFactory] }));app.set('view engine', 'html'); app.set('views', 'src')app.get('/', (req, res) => {res.render('index', {req}); });app.listen(8200,() => {console.log('listening...') });給 express 開發一個簡單的渲染器:
const fs = require('fs'); const path = require('path'); import {renderModuleFactory} from '@angular/platform-server';export function ngExpressEngine(setupOptions){return function(filePath, options, callback){renderModuleFactory(setupOptions.bootstrap[0], {document: fs.readFileSync(filePath).toString(),url: options.req.url}).then(string => {callback(null, string);});} }這里唯一重要的部分是 renderModuleFactory 方法。 該方法所做的基本上是將 Angular 應用程序引導到從文檔解析的虛擬 DOM 樹中,并將結果 DOM 狀態序列化為字符串,然后將其傳遞給 Express 引擎 API。
您當然可以向此渲染器添加一些緩存機制,以避免在每次請求時從磁盤讀取。 這是一個簡單的例子:
由于您可以完全控制服務器呈現的內容,因此您可以輕松添加任何您想要的 SEO 支持。 我們可以想象使用@angular/platform-browser 提供的 Meta 和 Title:
import { Component } from '@angular/core'; import { Meta, Title } from "@angular/platform-browser"; @Component({selector: 'home-view',template: `<h3>Home View</h3>` }) export class HomeView {constructor(seo: Meta, title: Title) {title.setTitle('Current Title Page');seo.addTags([{name: 'author', content: 'Wassim Chegham'},{name: 'keywords', content: 'angular,universal,iot,omega2+'},{name: 'description', content: 'Angular Universal running on Omega2+'}]);} }最后的效果如下:
更多Jerry的原創文章,盡在:“汪子熙”:
總結
以上是生活随笔為你收集整理的Angular Universal 的演进历史的全部內容,希望文章能夠幫你解決所遇到的問題。