设想这样一个场景:您曾经在您的 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。
您当然能够向此渲染器增加一些缓存机制,以防止在每次申请时从磁盘读取。这是一个简略的例子:
const fs = require('fs');
const path = require('path');
import {renderModuleFactory} from '@angular/platform-server';
const cache = new Map();
export function ngExpressEngine(setupOptions){return function(filePath, options, callback){if (!cache.has(filePath)){const content = fs.readFileSync(filePath).toString();
cache.set(filePath, content);
}
renderModuleFactory(setupOptions.bootstrap[0], {document: cache.get(filePath),
url: options.req.url
})
.then(string => {callback(null, string);
});
}
}
因为您能够齐全管制服务器出现的内容,因而您能够轻松增加任何您想要的 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 的原创文章,尽在:” 汪子熙 ”: