Angular Universal 和 Express 的慢速 TTFB

Slow TTFB with Angular Universal and Express

我将此作为自答问题发布,因为我在调试此问题时没有在网络上找到任何有用的信息。

出于某种原因,我的 Angular 7 应用程序的 SSR 版本始终具有大约 5 秒的 TTFB。我正在使用 Angular Universal 的标准设置,使用 Express 进行一些修改,如下所示:

// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';

import { renderModuleFactory } from '@angular/platform-server';
import { enableProdMode } from '@angular/core';
import * as express from 'express';
import { join } from 'path';
import { readFileSync } from 'fs';
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express server
const compression = require('compression');
const app = express();
app.use(compression());

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

const domino = require('domino');
const template = readFileSync(join(DIST_FOLDER , 'browser', 'index.html')).toString();
const win = domino.createWindow(template);
global['window'] = win;
global['Node'] = win.Node;
global['navigator'] = win.navigator;
global['Event'] = win.Event;
global['KeyboardEvent'] = win.Event;
global['MouseEvent'] = win.Event;
global['Event']['prototype'] = win.Event.prototype;
global['document'] = win.document;

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');

app.engine('html', (_, options, callback) => {
  renderModuleFactory(AppServerModuleNgFactory, {
    // Our index.html
    document: template,
    url: options.req.url,
    // DI so that we can get lazy-loading to work differently (since we need it to just instantly render it)
    extraProviders: [
      provideModuleMap(LAZY_MODULE_MAP)
    ]
  }).then(html => {
    callback(null, html);
  });
});

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

// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));

// All regular routes use the Universal engine
app.get('*', (req, res) => {
  res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
});

// Start up the Node server
app.listen(PORT, () => {
  console.log(`Node server listening on http://localhost:${PORT}`);
});

我试过在服务器上缓存页面并删除非标准 Domino 内容以使其与教程一样,但没有任何改变 TTFB。这让我相信应用程序代码中的某些东西可能会减慢它的速度。有什么想法吗?

仔细想想,我记得我在 pre-loading 模块上添加了 5 秒的延迟,这样从应用程序跳出的用户就不必下载所有延迟加载的包.代码是这样的:

import { PreloadingStrategy } from "@angular/router";
import { Route }              from "@angular/router";
import { RouterModule }       from "@angular/router";
import { Routes }             from "@angular/router";
import { NgModule }           from "@angular/core";
import { Observable, timer }  from "rxjs";
import { of }                 from 'rxjs/internal/observable/of';
import { flatMap }            from "rxjs/internal/operators";

export class PreloadPublicModules implements PreloadingStrategy {
  preload(route : Route, load : Function) : Observable<any> {
    const loadRoute = (delay) => delay ? timer(5000).pipe(flatMap(_ => load())) : load();
    return route.data && route.data.preload ? loadRoute(5000) : of(null);
  }
}

const routes : Routes = [
  { path: '',          loadChildren: "app/public/home/home.module#HomeModule",                  data: { preload:  true } },
  { path: 'about',     loadChildren: "app/public/about/about.module#AboutModule",               data: { preload:  true } },
  { path: 'contact',   loadChildren: "app/public/contact/contact.module#ContactModule",         data: { preload:  true } },
  { path: 'dashboard', loadChildren: "app/internal/dashboard/dashboard.module#DashboardModule", data: { preload: false } },
];

@NgModule({
  imports   : [ RouterModule.forRoot(routes, { preloadingStrategy: PreloadPublicModules }) ],
  exports   : [ RouterModule ],
  providers : [ PreloadPublicModules ]
})
export class AppRoutingModule { }

移除延迟解决了 SSR 部分的问题;它不再有 5s TTFB。 PreloadPublicModules 函数现在是:

export class PreloadPublicModules implements PreloadingStrategy {
  preload(route : Route, load : Function) : Observable<any> {
    return route.data && route.data.preload ? load() : of(null);
  }
}