欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入淺析Angular SSR

 更新時(shí)間:2022年11月06日 11:13:05   作者:世開Coding  
Angular Universal 主要關(guān)注將 Angular App 如何進(jìn)行服務(wù)端渲染和生成靜態(tài) HTML,對于用戶交互復(fù)雜的 SPA 并不推薦使用 SSR,本文是在 Angular 14 環(huán)境中完成,有些內(nèi)容對于新的 Angular 版本可能并不適用,感興趣的朋友一起通過本文學(xué)習(xí)

你知道 Angular Universal 嗎?可以幫助網(wǎng)站提供更好的 SEO 支持哦!

一般來說,普通的 Angular 應(yīng)用是在 瀏覽器 中運(yùn)行,在 DOM 中對頁面進(jìn)行渲染,并與用戶進(jìn)行交互。而 Angular Universal 是在 服務(wù)端 進(jìn)行渲染(Server-Side Rendering,SSR),生成靜態(tài)的應(yīng)用程序網(wǎng)頁,然后在客戶端展示,好處是可以更快地進(jìn)行渲染,在提供完整的交互之前就可以為用戶提供內(nèi)容展示。

本文是在 Angular 14 環(huán)境中完成,有些內(nèi)容對于新的 Angular 版本可能并不適用,請參考 Angular 官方文檔。

使用 SSR 的好處

對 SEO 更加友好

雖然現(xiàn)在包括 Google 在內(nèi)的某些搜索引擎和社交媒體聲稱已經(jīng)能支持對由 JavaScript(JS)驅(qū)動的 SPA(Single-Page Application)應(yīng)用進(jìn)行爬取,但是結(jié)果似乎差強(qiáng)人意。靜態(tài) HTML 網(wǎng)站的 SEO 表現(xiàn)還是要好于動態(tài)網(wǎng)站,這也是 Angular 官網(wǎng)所持有的觀點(diǎn)(Angular 可是 Google 的!)。

Universal 可以生成無 JS 的靜態(tài)版本的應(yīng)用程序,對搜索、外鏈、導(dǎo)航的支持更好。

提高移動端的性能

某些移動端設(shè)備可能不支持 JS 或者對 JS 的支持非常有限,導(dǎo)致網(wǎng)站的訪問體驗(yàn)非常差。這種情況下,我們需要提供無 JS 版本的應(yīng)用,以便為用戶提供更好的體驗(yàn)。

更快地展示首頁

對于用戶的使用體驗(yàn)來說,首頁展示速度的快慢至關(guān)重要。根據(jù) eBay 的數(shù)據(jù),搜索結(jié)果的展示速度每提高 100 毫秒,“添加至購物車”的使用率就提高 0.5%。

使用了 Universal 之后,應(yīng)用程序的首頁會以完整的形態(tài)展示給用戶,這是純的 HTML 網(wǎng)頁,即使不支持 JS,也可以展示。此時(shí),網(wǎng)頁雖然不能處理瀏覽器的事件,但是支持通過 routerLink 進(jìn)行跳轉(zhuǎn)。

這么做的好處是,我們可以先用靜態(tài)網(wǎng)頁抓住用戶的注意力,在用戶瀏覽網(wǎng)頁的時(shí)候,同時(shí)加載整個(gè) Angular 應(yīng)用。這給了用戶一個(gè)非常好的極速加載的體驗(yàn)。

為項(xiàng)目增加 SSR

Angular CLI 可以幫助我們非常便捷的將一個(gè)普通的 Angular 項(xiàng)目轉(zhuǎn)變?yōu)橐粋€(gè)帶有 SSR 的項(xiàng)目。創(chuàng)建服務(wù)端應(yīng)用只需要一個(gè)命令:

ng add @nguniversal/express-engine

建議在運(yùn)行該命令之前先提交所有的改動。

這個(gè)命令會對項(xiàng)目做如下修改:

  • 添加服務(wù)端文件:

    • main.server.ts - 服務(wù)端主程序文件
    • app/app.server.module.ts - 服務(wù)端應(yīng)用程序主模塊
    • tsconfig.server.json - TypeScript 服務(wù)端配置文件
    • server.ts - Express web server 的運(yùn)行文件
  • 修改的文件:

    • package.json - 添加 SSR 所需要的依賴和運(yùn)行腳本
    • angular.json - 添加開發(fā)、構(gòu)建 SSR 應(yīng)用所需要的配置

替換瀏覽器 API

由于 Universal 應(yīng)用不是在瀏覽器中執(zhí)行,因此一些瀏覽器的 API 或功能將不可用。例如,服務(wù)端應(yīng)用是無法使用瀏覽器中的全局對象 window、document,navigatorlocation。

Angular 提供了兩個(gè)可注入對象,用于在服務(wù)端替換對等的對象:LocationDOCUMENT。

例如,在瀏覽器中,我們通過 window.location.href 獲取當(dāng)前瀏覽器的地址,而改成 SSR 之后,代碼如下:

import { Location } from '@angular/common';

export class AbmNavbarComponent implements OnInit{
  // ctor 中注入 Location
  constructor(private _location:Location){
    //...
  }

  ngOnInit() {
    // 打印當(dāng)前地址
    console.log(this._location.path(true));
  }
}

同樣,對于在瀏覽器使用 document.getElementById() 獲取 DOM 元素,在改成 SSR 之后,代碼如下:

import { DOCUMENT } from '@angular/common';

export class AbmFoxComponent implements OnInit{
  // ctor 中注入 DOCUMENT
  constructor(@Inject(DOCUMENT) private _document: Document) { }

  ngOnInit() {
    // 獲取 id 為 fox-container 的 DOM
    const container = this._document.getElementById('fox-container');
  }
}

使用 URL 絕對地址

在 Angular SSR 應(yīng)用中,HTTP 請求的 URL 地址必須為 絕對地址(即,以 http/https 開頭的地址,不能是相對地址,如 /api/heros)。Angular 官方推薦將請求的 URL 全路徑設(shè)置到 renderModule()renderModuleFactory()options 參數(shù)中。但是在 v14 自動生成的代碼中,并沒有顯式調(diào)用這兩個(gè)方法的代碼。而通過讀 Http 請求的攔截,也可以達(dá)到同樣的效果。

下面我們先準(zhǔn)備一個(gè)攔截器,假設(shè)文件位于項(xiàng)目的 shared/universal-relative.interceptor.ts 路徑:

import { HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Inject, Injectable, Optional } from '@angular/core';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { Request } from 'express';

// 忽略大小寫檢查
const startsWithAny = (arr: string[] = []) => (value = '') => {
    return arr.some(test => value.toLowerCase().startsWith(test.toLowerCase()));
};

// http, https, 相對協(xié)議地址
const isAbsoluteURL = startsWithAny(['http', '//']);

@Injectable()
export class UniversalRelativeInterceptor implements HttpInterceptor {
    constructor(@Optional() @Inject(REQUEST) protected request: Request) { }

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        // 不是絕對地址的 URL
        if (!isAbsoluteURL(req.url)) {
            let protocolHost: string;
            if (this.request) {
                // 如果注入的 REQUEST 不為空,則從注入的 SSR REQUEST 中獲取協(xié)議和地址
                protocolHost = `${this.request.protocol}://${this.request.get(
                    'host'
                )}`;
            } else {
                // 如果注入的 REQUEST 為空,比如在進(jìn)行 prerender build:
                // 這里需要添加自定義的地址前綴,比如我們的請求都是從 abmcode.com 來。
                protocolHost = 'https://www.abmcode.com';
            }
            const pathSeparator = !req.url.startsWith('/') ? '/' : '';
            const url = protocolHost + pathSeparator + req.url;
            const serverRequest = req.clone({ url });
            return next.handle(serverRequest);

        } else {
            return next.handle(req);
        }
    }
}
import { HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Inject, Injectable, Optional } from '@angular/core';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { Request } from 'express';

// 忽略大小寫檢查
const startsWithAny = (arr: string[] = []) => (value = '') => {
    return arr.some(test => value.toLowerCase().startsWith(test.toLowerCase()));
};

// http, https, 相對協(xié)議地址
const isAbsoluteURL = startsWithAny(['http', '//']);

@Injectable()
export class UniversalRelativeInterceptor implements HttpInterceptor {
    constructor(@Optional() @Inject(REQUEST) protected request: Request) { }

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        // 不是絕對地址的 URL
        if (!isAbsoluteURL(req.url)) {
            let protocolHost: string;
            if (this.request) {
                // 如果注入的 REQUEST 不為空,則從注入的 SSR REQUEST 中獲取協(xié)議和地址
                protocolHost = `${this.request.protocol}://${this.request.get(
                    'host'
                )}`;
            } else {
                // 如果注入的 REQUEST 為空,比如在進(jìn)行 prerender build:
                // 這里需要添加自定義的地址前綴,比如我們的請求都是從 abmcode.com 來。
                protocolHost = 'https://www.abmcode.com';
            }
            const pathSeparator = !req.url.startsWith('/') ? '/' : '';
            const url = protocolHost + pathSeparator + req.url;
            const serverRequest = req.clone({ url });
            return next.handle(serverRequest);

        } else {
            return next.handle(req);
        }
    }
}

然后在 app.server.module.ts 文件中 provide 出來:

import { UniversalRelativeInterceptor } from './shared/universal-relative.interceptor';
// ... 其他 imports

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    // 如果你用了 @angular/flext-layout,這里也需要引入服務(wù)端模塊
    FlexLayoutServerModule, 
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: UniversalRelativeInterceptor,
      multi: true
    }
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule { }

這樣任何對于相對地址的請求都會自動轉(zhuǎn)換為絕對地址請求,在 SSR 的場景下不會再出問題。

Prerender 預(yù)渲染靜態(tài) HTML

經(jīng)過上面的步驟后,如果我們通過 npm run build:ssr 構(gòu)建項(xiàng)目,你會發(fā)現(xiàn)在 dist/<your project>/browser 下面只有 index.html 文件,打開文件查看,發(fā)現(xiàn)其中還有 <app-root></app-root> 這樣的元素,也就是說你的網(wǎng)頁內(nèi)容并沒有在 html 中生成。這是因?yàn)?Angular 使用了動態(tài)路由,比如 /product/:id 這種路由,而頁面的渲染結(jié)果要經(jīng)過 JS 的執(zhí)行才能知道,因此,Angular 使用了 Express 作為 Web 服務(wù)器,能在服務(wù)端運(yùn)行時(shí)根據(jù)用戶請求(爬蟲請求)使用模板引擎生成靜態(tài) HTML 界面。

prerendernpm run prerender)會在構(gòu)建時(shí)生成靜態(tài) HTML 文件。比如我們做企業(yè)官網(wǎng),只有幾個(gè)頁面,那么我們可以使用預(yù)渲染技術(shù)生成這幾個(gè)頁面的靜態(tài) HTML 文件,避免在運(yùn)行時(shí)動態(tài)生成,從而進(jìn)一步提升網(wǎng)頁的訪問速度和用戶體驗(yàn)。

預(yù)渲染路徑配置

需要進(jìn)行預(yù)渲染(預(yù)編譯 HTML)的網(wǎng)頁路徑,可以有幾種方式進(jìn)行提供:

1.通過命令行的附加參數(shù):

ng run <app-name>:prerender --routes /product/1 /product/2

2.如果路徑比較多,比如針對 product/:id 這種動態(tài)路徑,則可以使用一個(gè)路徑文件:

routes.txt

/products/1
/products/23
/products/145
/products/555

然后在命令行參數(shù)指定該文件:

ng run <app-name>:prerender --routes-file routes.txt

3.在項(xiàng)目的 angular.json 文件配置需要的路徑:

 "prerender": {
   "builder": "@nguniversal/builders:prerender",
   "options": {
     "routes": [ // 這里配置
       "/",
       "/main/home",
       "/main/service",
       "/main/team",
       "/main/contact"
     ]
   },

配置完成后,重新執(zhí)行預(yù)渲染命令(npm run prerender 或者使用命令行參數(shù)則按照上面<1><2>中的命令執(zhí)行),編譯完成后,再打開 dist/<your project>/browser 下的 index.html 會發(fā)現(xiàn)里面沒有 <app-root></app-root> 了,取而代之的是主頁的實(shí)際內(nèi)容。同時(shí)也生成了相應(yīng)的路徑目錄以及各個(gè)目錄下的 index.html 子頁面文件。

SEO 優(yōu)化

SEO 的關(guān)鍵在于對網(wǎng)頁 title,keywordsdescription 的收錄,因此對于我們想要讓搜索引擎收錄的網(wǎng)頁,可以修改代碼提供這些內(nèi)容。

在 Angular 14 中,如果路由界面通過 Routes 配置,可以將網(wǎng)頁的靜態(tài) title 直接寫在路由的配置中:

{ path: 'home', component: AbmHomeComponent, title: '<你想顯示在瀏覽器 tab 上的標(biāo)題>' },

另外,Angular 也提供了可注入的 TitleMeta 用于修改網(wǎng)頁的標(biāo)題和 meta 信息:

import { Meta, Title } from '@angular/platform-browser';

export class AbmHomeComponent implements OnInit {

  constructor(
    private _title: Title,
    private _meta: Meta,
  ) { }

  ngOnInit() {
    this._title.setTitle('<此頁的標(biāo)題>');
    this._meta.addTags([
      { name: 'keywords', content: '<此頁的 keywords,以英文逗號隔開>' },
      { name: 'description', content: '<此頁的描述>' }
    ]);
  }
}

總結(jié)

Angular 作為 SPA 企業(yè)級開發(fā)框架,在模塊化、團(tuán)隊(duì)合作開發(fā)方面有自己獨(dú)到的優(yōu)勢。在進(jìn)化到 v14 這個(gè)版本中提供了不依賴 NgModule 的獨(dú)立 Component 功能,進(jìn)一步簡化了模塊化的架構(gòu)。

Angular Universal 主要關(guān)注將 Angular App 如何進(jìn)行服務(wù)端渲染和生成靜態(tài) HTML,對于用戶交互復(fù)雜的 SPA 并不推薦使用 SSR。針對頁面數(shù)量較少、又有 SEO 需求的網(wǎng)站或系統(tǒng),則可以考慮使用 Universal 和 SSR 技術(shù)。

相關(guān)文章

  • AngularJS中run方法的巧妙運(yùn)用

    AngularJS中run方法的巧妙運(yùn)用

    前端技術(shù)的發(fā)展是如此之快,各種優(yōu)秀技術(shù)、優(yōu)秀框架的出現(xiàn)簡直讓人目不暇接,緊跟時(shí)代潮流,學(xué)習(xí)掌握新知識自然是不敢怠慢。下面這篇文章主要給大家介紹了AngularJS中run方法的巧妙運(yùn)用,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-01-01
  • 深入理解AngularJS中的ng-bind-html指令和$sce服務(wù)

    深入理解AngularJS中的ng-bind-html指令和$sce服務(wù)

    這篇文章給大家詳細(xì)介紹了AngularJS中的ng-bind-html指令和$sce服務(wù),對大家學(xué)習(xí)AngularJS具有一定參考借鑒價(jià)值,有需要都可以參考學(xué)習(xí)。
    2016-09-09
  • Angular中點(diǎn)擊li標(biāo)簽實(shí)現(xiàn)更改顏色的核心代碼

    Angular中點(diǎn)擊li標(biāo)簽實(shí)現(xiàn)更改顏色的核心代碼

    這篇文章主要介紹了Angular中點(diǎn)擊li標(biāo)簽實(shí)現(xiàn)更改顏色的核心代碼,需要的朋友可以參考下
    2017-12-12
  • 詳解基于Angular4+ server render(服務(wù)端渲染)開發(fā)教程

    詳解基于Angular4+ server render(服務(wù)端渲染)開發(fā)教程

    本篇文章主要介紹了詳解基于Angular4+ server render(服務(wù)端渲染)開發(fā)教程 ,具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-08-08
  • Angular.JS中的this指向詳解

    Angular.JS中的this指向詳解

    這篇文章主要給大家介紹了關(guān)于Angular.JS中this指向的相關(guān)資料,文中介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。
    2017-05-05
  • Angular+ionic實(shí)現(xiàn)折疊展開效果的示例代碼

    Angular+ionic實(shí)現(xiàn)折疊展開效果的示例代碼

    這篇文章主要介紹了Angular+ionic實(shí)現(xiàn)折疊展開效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Angular 2父子組件之間共享服務(wù)通信的實(shí)現(xiàn)

    Angular 2父子組件之間共享服務(wù)通信的實(shí)現(xiàn)

    這篇文章主要給大家介紹了關(guān)于Angular 2父子組件之間共享服務(wù)通信的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。
    2017-07-07
  • AngularJS 讓人愛不釋手的八種功能

    AngularJS 讓人愛不釋手的八種功能

    AngularJS 讓人愛不釋手的八種功能,想知道AngularJS哪八種功能讓人喜歡就快點(diǎn)看下本文吧
    2016-03-03
  • AngularJS實(shí)踐之使用ng-repeat中$index的注意點(diǎn)

    AngularJS實(shí)踐之使用ng-repeat中$index的注意點(diǎn)

    最近通過客戶的投訴主要到在ng-repeat中使用了$index引發(fā)的一個(gè)bug,下面一起來看看這個(gè)錯(cuò)誤是如何引發(fā)的, 以及如何避免這種bug產(chǎn)生,然后說說我們從中得到的經(jīng)驗(yàn)和教訓(xùn)。有需要的朋友們可以參考借鑒,下面來一起看看吧。
    2016-12-12
  • AngularJS的ng-repeat指令與scope繼承關(guān)系實(shí)例詳解

    AngularJS的ng-repeat指令與scope繼承關(guān)系實(shí)例詳解

    這篇文章主要介紹了AngularJS的ng-repeat指令與scope繼承關(guān)系,結(jié)合實(shí)例形式通過ng-repeat指令詳細(xì)分析了scope繼承關(guān)系,需要的朋友可以參考下
    2017-01-01

最新評論