想象这样一个场景:你已经在你的web项目上工作了几个月,它很可能是一个web应用程序,更具体地说,是一个“单页应用程序”。但现在是向数百万用户和……搜索引擎交付和发布您的应用程序的时候了。为了使您的应用程序成功,它必须被搜索引擎索引,即需要添加SEO支持!我们可以将AngularUniversal理解为:UniversalisAngularfortheHeadlessWeb。您不再需要浏览器容器(也称为WebView)来运行Angular。因为它与DOM无关,所以Angular可以在任何有JavaScript运行时的地方运行,例如Node.js。此图说明了Universal在浏览器之外运行典型的AngularWeb应用程序的能力。显然我们需要一个JavaScript运行时,这就是我们默认支持Node.js(由V8引擎提供支持)的原因。当然,还有越来越多的其他服务器端技术涌现,例如PHP、Java、Python、Go……有了AngularUniversal,您的应用程序就可以在浏览器之外进行解释——让我们以服务器端为例——客户端请求您的SPA将收到针对请求的路由/URL的静态完全呈现页面。该页面包含所有相关资源,即图像、样式表、字体……甚至是通过Angular服务传入的数据。Universal有能力重新连接一些默认的Angular提供者实现,以便它们在目标平台上工作。当客户端收到呈现的页面时,它也会收到原始的Angular应用程序——AngularUniversal使应用程序看起来几乎是瞬间加载到浏览器中。加载后,Angular客户端应用程序会处理剩下的事情。事实上,Universal与Preboot.js库捆绑在一起,其唯一作用是保持两种状态同步。Preboot.js在幕后所做的只是简单而巧妙地记录Angular引导之前发生的事件;并在Angular完成加载后重播这些事件。由于Angular的渲染抽象,通用成为可能。事实上,当您编写应用程序代码时,该逻辑会被Angular的编译器解析为AST——我们在这里真正简化了事情。Angular的渲染层随后使用AST,它使用不依赖于DOM的抽象渲染器。Angular允许你使用不同的渲染器。Angular默认带有DOMRenderer,因此您的应用程序可以在浏览器中呈现,这可能是95%的用例。这就是Universal的用武之地。Universal为所有主要技术和构建工具提供了一堆预渲染器。依赖注入和提供者Angular的另一个亮点是它的DI系统。事实上,Angular是唯一实现这种设计模式的前端框架,它可以轻松完成许多伟大的任务(例如控制反转)。感谢DI你可以,例如在运行时交换两个不同的实现,这在测试中被大量使用。在Universal,我们利用此DI系统为您提供许多针对特定目标的服务。对于Node,我们提供了一个自定义的ServerModule,它实现了Node的特定于服务器的API,例如请求,而不是浏览器的XHR。Universal还附带了一个特定于Node的自定义渲染器,当然我们为您提供了一堆预渲染器——我们称之为预渲染器——例如用于Node后端技术设备的Express渲染器或Webpack渲染器。对于其他非JavaScript技术,例如.NetCore或Java,您还应该期待其他预渲染器。好消息是通用应用程序与经典的Angular应用程序没有什么不同。应用程序逻辑几乎保持不变。只要有可能,在直接接触DOM之前请三思。每次你想与浏览器的DOM交互时,请确保使用Angular渲染器或渲染抽象。下图是AngularUniversalApplicationStructure.browser.module.tsimport{NgModule}from'@angular/core';import{BrowserModule}from'@angular/platform-b??rowser';import{AppComponent}from'./index';@NgModule({bootstrap:[AppComponent],declarations:[AppComponent],imports:[BrowserModule.withServerTransition({appId:'some-app-id'}),...]})exportclassAppBrowserModule{}注意你BrowserModule需要使用withServerTransition()方法进行初始化。这将确保基于浏览器的应用程序将从服务器呈现的应用程序过渡。server.module.ts此模块特定于您的服务器环境。ServerModule提供了一组来自@angular/platform-serverpackage.import{NgModule}from'@angular/core';import{ServerModule}from'@angular/platform-server';import{AppComponent,AppBrowserModule}from'...以便它们共享相同的appId,即AppBrowserModule使用的转换ID。client.ts该文件负责在客户端引导您的应用程序。这里没有什么新东西,只是通常的引导过程(在AOT模式下):import{platformBrowser}from'@angular/platform-b??rowser';import{AppModuleNgFactory}from'./ngfactory/src/app.ngfactory';import{enableProdMode}来自'@angular/core';enableProdMode();platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);server.ts该文件确实特定于您的服务器/后端环境。这里我们的目标是Node.js,更准确地说是处理所有客户端请求和渲染过程的Express框架。为此,我们使用并注册了代表Express的Angular通用渲染引擎的ngExpressEngine(见下一段):从'./ngfactory/src/app.server.ngfactory'导入{AppServerModuleNgFactory};从'@angular/core'导入{enableProdMode};从'./server.module'导入{AppServerModule};导入*作为expressfrom'express';从'./express-engine'导入{ngExpressEngine};enableProdMode();constapp=express();app.engine('html',ngExpressEngine({baseUrl:'http://localhost:4200',引导程序:[AppServerModuleNgFactory]}));app。set('视图引擎','html');应用程序。设置('视图','src')应用程序。get('/',(req,res)=>{res.render('index',{req});});app.listen(8200,()=>{console.log('监听...')});为express开发一个简单的渲染器:constfs=require('fs');constpath=require('path');从'@angular/platform-server'导入{renderModuleFactory};导出函数ngExpressEngine(setupOptions){returnfunction(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。您当然可以向此渲染器添加一些缓存机制,以避免在每次请求时都从磁盘读取。这是一个简单的例子:constfs=require('fs');constpath=require('路径');从'@angular/platform-server'导入{renderModuleFactory};常量缓存=新地图();exportfunctionngExpressEngine(setupOptions){returnfunction(filePath,options,callback){if(!cache.has(filePath)){constcontent=fs.readFileSync(filePath).toString();cache.set(文件路径,内容);}renderModuleFactory(setupOptions.bootstrap[0],{document:cache.get(filePath),url:options.req.url}).then(string=>{callback(null,string);});}}感谢您您可以完全控制服务器呈现的内容,因此您可以轻松添加所需的任何SEO支持。我们可以想象使用@angular/platform-b??rowser提供的Meta和Title:import{Component}from'@angular/core';从“@angular/platform-b??rowser”导入{元,标题};@Component({selector:'home-view',template:`
