The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

如何在 Angular CLI 建立的專案加入 Angular Universal 伺服器渲染功能

自 Angular 4.0 開始 Angular Universal 就已經正式併入 Angular 核心功能,本篇文章將示範如何將一個由 Angular CLI 建立的專案,加入 Angular Universal 伺服器渲染能力,只要透過 Node.js 知名的 Express 網站框架,即可快速實現 Angular 的伺服器渲染能力 ( SSR ) ( Server-side Rendering )。

1. 建立全新 Angular 4 專案

ng new demo1

2. 安裝 ts-node 套件 ( --save-dev )

由於我們的 Express 程式碼會以 TypeScript 撰寫,因此你必須額外安裝 ts-node 套件才能讓 Express + TypeScript 正確執行。

npm install --save-dev ts-node

3. 安裝 @angular/platform-server 套件

注意:自 npm 5.0 之後,npm install 不需要特別加上 --save 參數,只要專案下有 package.json 檔案,在 npm install 之後預設就會自動儲存套件到 package.json 定義檔中。

npm install @angular/platform-server

4. 更新 AppModule 模組 ( src/app/app.module.ts )

修正預設 AppModule 中 BrowserModule 的匯入方式

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'demo1' })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

5. 建立一個全新的 AppServerModule 模組 ( src/app/app.server.module.ts )

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    ServerModule,
    AppModule
  ],
  bootstrap: [AppComponent]
})
export class AppServerModule { }

6. 加入 ExpressJS / Node.js 主程式 ( src/server.ts )

由於 renderModuleFactory 函式需要透過 AOT (Ahead of Time) 編譯過的檔案,其傳入的第一個參數 AppServerModuleNgFactory 必須要先執行 ng server --prodngc 命令才能產生相關檔案,在這些檔案出現之前,你可能會在 Visual Studio Code 看見找不到參考檔案的錯誤訊息。

import 'reflect-metadata';
import 'zone.js/dist/zone-node';
import { platformServer, renderModuleFactory } from '@angular/platform-server'
import { enableProdMode } from '@angular/core'
import { AppServerModuleNgFactory } from '../dist/ngfactory/src/app/app.server.module.ngfactory'
import * as express from 'express';
import { readFileSync } from 'fs';
import { join } from 'path';

const PORT = 4000;

enableProdMode();

const app = express();

let template = readFileSync(join(__dirname, '..', 'dist', 'index.html')).toString();

app.engine('html', (_, options, callback) => {
  const opts = { document: template, url: options.req.url };

  renderModuleFactory(AppServerModuleNgFactory, opts)
    .then(html => callback(null, html));
});

app.set('view engine', 'html');
app.set('views', 'src')

app.get('*.*', express.static(join(__dirname, '..', 'dist')));

app.get('*', (req, res) => {
  res.render('index', { req });
});

app.listen(PORT, () => {
  console.log(`listening on http://localhost:${PORT}!`);
});

7. 更新 src/tsconfig.app.json 定義檔

我們要跳過 Express 程式 ( src/server.ts ),因為這段 Code 是 Node.js 的程式,會影響 Angular 本身的 TypeScript 編譯,因此需要排除在外。

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "module": "es2015",
    "baseUrl": "",
    "types": []
  },
  "exclude": [
    "server.ts",
    "test.ts",
    "**/*.spec.ts"
  ]
}

8. 更新 tsconfig.json 定義檔

請務必加入 "angularCompilerOptions" 設定到 tsconfig.json 定義檔中。

{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "baseUrl": "src",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2016",
      "dom"
    ]
  },
  "angularCompilerOptions": {
    "genDir": "./dist/ngfactory",
    "entryModule": "./src/app/app.module#AppModule"
  }
}

9. 更新 package.json

加入 "start" 與 "prestart" 到 "scripts" 區段中,因為在正式執行 src/server.ts 之前,必須先執行 ng build --prod 與 ngc 命令。

注意:專案內的 ngc 命令 (AOT 編譯工具) 位於 node_modules/.bin 目錄下。

  "scripts": {
    "ng": "ng",
    "prestart": "ng build --prod && ngc",
    "start": "ts-node src/server.ts",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },

10. 執行 npm start 即可透過 ExpressJS 執行網站,預設網址為 http://localhost:4000/

image

如果查看 HTML 原始碼的話,就會發現 Angular Universal 的伺服器渲染機制已經成功啟動! ^__^

image

 

本篇文章已經將變更紀錄上傳至 GitHub,想看 Angular CLI 建立之後到底做出了哪些變化,可以參考 angular-universal-demo1 專案的版本變更紀錄

 

相關連結

留言評論