Angular6 服務端渲染

若是不想查看本文,直接尋找問題的解決方案,請搜索'坑'javascript

原理

服務端渲染即在服務端渲染產生頁面以後直接返回到客戶端查看css

第一次請求網頁地址的時候,返回已經在服務端渲染好的靜態html文件,上面沒有點擊事件,鍵盤事件,和交互js,這段頁面用一個ID標註,而後開始在客戶端渲染頁面,渲染好以後,根據ID替換在服務端渲染的頁面,填補了main.js(有可能較大)的下載時間+頁面渲染事件的空窗期,使頁面在slow3G的狀況下依然流暢html

優點

  1. 幫助網絡爬蟲(SEO)
  2. 提高在手機和低功耗設備上的性能
  3. 迅速顯示出第一個頁面

開發流程

安裝依賴

$ npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader webpack-cli
複製代碼

在app.module.ts中添加

@NgModule({
  bootstrap: [AppComponent],
  imports: [
	// 加上下面這句,appId就是上面提到用於替換的惟一標識
    BrowserModule.withServerTransition({appId: 'my-app'}),
    ...
  ],

})
export class AppModule {}
複製代碼

同目錄下建立app.server.module.ts

import {NgModule} from '@angular/core';
import {ServerModule} from '@angular/platform-server';
import {ModuleMapLoaderModule} from '@nguniversal/module-map-ngfactory-loader';

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

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    ModuleMapLoaderModule // 很是重要,用來支持惰性加載的
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {}
複製代碼

src下建立main.js

export { AppServerModule } from './app/app.server.module';
複製代碼

複製ts.app.json爲ts.server.json並修改

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "baseUrl": "./",
    // 重要
    "module": "commonjs",
    "types": []
  },
  "exclude": [
    "test.ts",
    "**/*.spec.ts"
  ],
  // 指向上面創建的AppServerModule
  "angularCompilerOptions": {
    "entryModule": "app/app.server.module#AppServerModule"
  }
複製代碼

在angular.json中修改配置,打包server

"architect": {
  "build": { ... }
  "server": {
    "builder": "@angular-devkit/build-angular:server",
    "options": {
      "outputPath": "dist/my-project-server",
      "main": "src/main.server.ts",
      "tsConfig": "src/tsconfig.server.json"
    }
  }
}
複製代碼

此時 ng run projectName:server應該能夠獲得下面結果java

$ ng run my-project:server

Date: 2017-07-24T22:42:09.739Z
Hash: 9cac7d8e9434007fd8da
Time: 4933ms
chunk {0} main.js (main) 9.49 kB [entry] [rendered]
chunk {1} styles.css (styles) 0 bytes [entry] [rendered]
複製代碼

注意!坑1:在服務器渲染的時候路徑和編譯的時候不一樣,若是在這部報錯找不到'src/app/.....'的時候,是你使用了src/的絕對路徑,須要所有改成../../的相對位置node

設置服務器環境

在根目錄下,新建server.ts,並往裏面寫入webpack

// 這些必須在最前面引入
import 'zone.js/dist/zone-node';
import 'reflect-metadata';

import { renderModuleFactory } from '@angular/platform-server';
import { enableProdMode } from '@angular/core';
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

import * as express from 'express';
import { join } from 'path';
import { readFileSync } from 'fs';

//坑3:報錯document not defined,經過引入domino來解決
const domino = require('domino');
const fs = require('fs');
const path = require('path');
const template = fs.readFileSync('./dist/browser/index.html').toString();
const win = domino.createWindow(template);
const files = fs.readdirSync(`${process.cwd()}/dist/server`);
global['navigator'] = win.navigator;
global['window'] = win;
Object.defineProperty(win.document.body.style, 'transform', {
  value: () => {
    return {
      enumerable: true,
      configurable: true
    };
  },
});
global['document'] = win.document;
global['CSS'] = null;

enableProdMode();

const app = express();

const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');

// 這裏要根據咱們本身的目錄來,指向的是瀏覽器端編譯的index.html
const template = readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();

const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./server/main');


app.engine('html', (_, options, callback) => {
  renderModuleFactory(AppServerModuleNgFactory, {
    document: template,
    url: options.req.url,
	// 依賴注入,這裏是咱們實現懶加載的一點
    extraProviders: [
      provideModuleMap(LAZY_MODULE_MAP)
    ]
  }).then(html => {
    callback(null, html);
  });
});

app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));

// 靜態文件
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));

// angular路由
app.get('*', (req, res) => {
  res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
});

// api的話寫在中間,能夠做爲一個mock服務器

// 啓動
app.listen(PORT, () => {
  console.log(`Node server listening on http://localhost:${PORT}`);
});
複製代碼

打包並在服務器上使用

設置 webpack 配置,以處理 Node Express 的 server.ts 文件,並啓動應用服務器。web

在應用的根目錄下,建立一個 Webpack 配置文件 webpack.server.config.js,它會把 server.ts 及其依賴編譯到 dist/server.js 中。redis

const path = require('path');
const webpack = require('webpack');

// 坑2:用webpack引入後臺的nodemodule的時候注意某些server端專用的npm包是要加上commonjs前綴的
var fs = require('fs');
var nodeModules = {};
fs.readdirSync('node_modules')
  .filter(function(x) {
    return ['.bin'].indexOf(x) === -1;
  })
  .forEach(function(mod) {
    if (mod=='redis'||mod=='express'){
      nodeModules[mod] = 'commonjs ' + mod;
    }
  });

module.exports = {
  entry: {  server: './server.ts' },
  resolve: { extensions: ['.js', '.ts'] },
  target: 'node',
  // this makes sure we include node_modules and other 3rd party libraries
  externals: nodeModules,
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js'
  },
  module: {
    rules: [
      { test: /\.ts$/, loader: 'ts-loader' }
    ]
  },
  plugins: [
    new webpack.ContextReplacementPlugin(
      /(.+)?angular(\\|\/)core(.+)?/,
      path.join(__dirname, 'src'), 
      {} 
    ),
    new webpack.ContextReplacementPlugin(
      /(.+)?express(\\|\/)(.+)?/,
      path.join(__dirname, 'src'),
      {}
    )
  ]
}
複製代碼

如今咱們使用 node dist/server.js應該是能夠啓動服務的,進入localhost:4000就能夠訪問到工程express

腳本

"scripts": {
  "build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
  "serve:ssr": "node dist/server.js",
  "build:client-and-server-bundles": "ng build --prod && ng run my-project:server:production",
  "webpack:server": "webpack --config webpack.server.config.js --progress --colors",
  ...
}
複製代碼

執行npm run build:ssr 以後執行npm run serve:srr便可npm

坑3:報錯NotYetImplemented 這個實際上是由於你引用了Cookie或者什麼之類在server上訪問不到的模塊,這些模塊須要你本身在工程裏面進行排查和debug,目前沒有更好的解決方法

坑4:報錯_angular_common_http__WEBPACK_IMPORTED_MODULE_5__.ɵHttpInterceptingHandler is not a constructor 這個是angular/core版本的問題,須要在package.json中升級angular/core就能夠解決,參考:stackoverflow.com/questions/5…

相關文章
相關標籤/搜索