乐趣区

关于angular:Angular-UniversalAngular-统一平台简介

Angular Universal

本文介绍 Angular Universal(对立平台),一项在服务端运行 Angular 利用的技术,即服务器端渲染。

如下图 package.json 里定义的依赖 @nguniversal/express-engine 所示:

规范的 Angular 利用会运行在浏览器中,它会在 DOM 中渲染页面,以响应用户的操作。而 Angular Universal 会在服务端运行,生成一些动态的利用页面,稍后再通过客户端进行启动。这意味着该利用的渲染通常会更快,让用户能够在利用变得齐全可交互之前,先查看利用的布局。

服务器端渲染返回的 HTML 源代码,尽管没有加载对应的 .js 文件,无奈响应用户输出,然而能够给用户一个直观残缺的页面布局。

Node.js Express Web 服务器则会依据客户端的申请,利用 Universal 编译 HTML 页面。

要创立服务端利用模块 app.server.module.ts,请运行以下 CLI 命令:

ng add @nguniversal/express-engine

该命令会主动生成如下绿色高亮所示的文件:

要应用 Universal 在本地零碎中渲染你的利用,请应用如下命令:

npm run dev:ssr

这个 serve-ssr 定义在 Angular.json 里:

而 server target 定义如下:

通过 routerLinks 导航时能失常工作,因为它们应用的是原生的链接标签(\<a>).

不反对除了点击 routerLink 以外的任何用户事件。你必须期待残缺的客户端利用启动并运行,或者应用 preboot 之类的库来缓冲这些事件,这样你就能够在客户端脚本加载结束后重放这些事件。

Angular Universal 能够为你生成利用的动态版本,它易搜寻、可链接,浏览时也不用借助 JavaScript。它也让站点能够被预览,因为每个 URL 返回的都是一个齐全渲染好的页面。

应用 Angular Universal,你能够为利用生成“着陆页”,它们看起来就和残缺的利用一样。这些着陆页是纯 HTML,并且即便 JavaScript 被禁用了也能显示。这些页面不会解决浏览器事件,不过它们能够用 routerLink 在这个网站中导航。

在实践中,你可能要应用一个着陆页的动态版本来放弃用户的注意力。同时,你也会在幕后加载残缺的 Angular 利用。用户会感觉着陆页简直是立刻呈现的,而当残缺的利用加载完之后,又能够取得残缺的交互体验。

Universal Web 服务器应用 Universal 模板引擎渲染出的动态 HTML 来响应对利用页面的申请。服务器接管并响应来自客户端(通常是浏览器)的 HTTP 申请,并回复动态文件,如脚本、CSS 和图片。它能够间接响应数据申请,也能够作为独立数据服务器的代理进行响应。

任何一种 Web 服务器技术都能够作为 Universal 利用的服务器,只有它能调用 Universal 的 renderModule() 函数。这里所探讨的这些准则和决策点也实用于任何 Web 服务器技术。

Universal 利用应用 platform-server 包(而不是 platform-browser),它提供了 DOM 的服务端实现、XMLHttpRequest 以及其它不依赖浏览器的底层个性。

服务器(这个例子中应用的是 Node.js Express 服务器)会把客户端对利用页面的申请传给 NgUniversal 的 ngExpressEngine。在外部实现上,它会调用 Universal 的 renderModule() 函数,它还提供了缓存等有用的工具函数。

对于具体的调试步骤,参考我这些文章:

  • SAP Spartacus 服务器端渲染单步调试步骤之一:应用程序筹备工作
  • SAP Spartacus 服务器端渲染单步调试步骤之二:在服务器端执行应用程序 Angular 代码

应用浏览器 API

因为 Universal 利用并没有运行在浏览器中,因而该服务器上可能会短少浏览器的某些 API 和其它能力。

比方,服务端利用不能引用浏览器独有的全局对象,比方 window、document、navigator 或 location。

Angular 提供了一些这些对象的可注入的形象层,比方 Location 或 DOCUMENT,它能够作为你所调用的 API 的等效替身。如果 Angular 没有提供它,你也能够写一个本人的形象层,当在浏览器中运行时,就把它委托给浏览器 API,当它在服务器中运行时,就提供一个符合要求的代用实现(也叫垫片 – shimming)。

同样,因为没有鼠标或键盘事件,因而 Universal 利用也不能依赖于用户点击某个按钮来显示每个组件。Universal 利用必须仅仅依据客户端过去的申请决定要渲染的内容。把该利用做成可路由的,就是一种好计划。

Universal 模板引擎

server.ts 的外围逻辑如下代码所示:

const server = express();
  const distFolder = join(process.cwd(), 'dist/mystore/browser');
  const indexHtml = existsSync(join(distFolder, 'index.original.html'))
    ? 'index.original.html'
    : 'index';

  server.set('trust proxy', 'loopback');

  server.engine(
    'html',
    ngExpressEngine({bootstrap: AppServerModule,})
  );

其中 ngExpressEngine 的起源:

const ngExpressEngine = NgExpressEngineDecorator.get(engine, { timeout: 90000, concurrency: 1, 
  forcedSsrTimeout:90000,
  maxRenderTime:100000,
  cache: true, cacheSize: 10,
  renderingStrategyResolver: (req) => RenderingStrategy.DEFAULT});

ngExpressEngine() 是对 Universal 的 renderModule() 函数的封装。它会把客户端申请转换成服务端渲染的 HTML 页面。它承受一个具备下列属性的对象,类型为 NgSetupOptions:

bootstrap:在服务器上渲染时用于疏导应用程序的根 NgModule 或 NgModule 工厂。对于 SAP Commerce Cloud 利用,它是 AppServerModule。它是 Universal 服务端渲染器和 Angular 利用之间的桥梁。

ngExpressEngine() 函数返回了一个会解析成渲染好的页面的承诺(Promise)。接下来你的引擎要决定拿这个页面做点什么。在这个引擎的 Promise 回调函数中,把渲染好的页面返回给了 Web 服务器,而后服务器通过 HTTP 响应把它转发给了客户端。

过滤申请的 URL

Web 服务器必须把对利用页面的申请和其它类型的申请辨别开。

这可不像拦挡对根门路 / 的申请那么简略。浏览器能够申请利用中的任何一个路由地址,比方 /dashboard、/heroes 或 /detail:12。事实上,如果利用只会通过服务器渲染,那么利用中点击的任何一个链接都会发到服务器,就像导航时的地址会发到路由器一样。

侥幸的是,利用的路由具备一些独特特色:它们的 URL 个别不带文件扩展名。(数据申请也可能短少扩展名,然而它们很容易辨认进去,因为它们总是以 /api 结尾,所有的动态资源的申请都会带有一个扩展名,比方 main.js 或 /node_modules/zone.js/dist/zone.js)。

因为应用了路由,所以咱们能够轻松的辨认出这三种类型的申请,并别离解决它们。

  • 数据申请:申请的 URL 用 /api 结尾
  • 利用导航:申请的 URL 不带扩展名
  • 动态资源:所有其它申请。

Node.js Express 服务器是一系列中间件形成的管道,它会挨个对 URL 申请进行过滤和解决。你能够调用 app.get() 来配置 Express 服务器的管道,就像上面这个数据申请一样:

// TODO: implement data requests securely
server.get('/api/**', (req, res) => {res.status(404).send('data requests are not yet supported');
});

上述代码的含意是,以后 SSR 服务器,不反对解决数据申请。

下列代码会过滤出不带扩展名的 URL,并把它们当做导航申请进行解决。

// All regular routes use the Universal engine
server.get('*', (req, res) => {res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl}] });
});

平安的提供动态文件

独自的 server.use() 会解决所有其它 URL,比方对 JavaScript、图片和样式表等动态资源的申请。
要保障客户端只能下载那些容许他们拜访的文件,你应该把所有面向客户端的资源文件都放在 /dist 目录下,并且只容许客户端申请来自 /dist 目录下的文件。

const distFolder = join(process.cwd(), 'dist/mystore/browser');
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {maxAge: '1y'}));
退出移动版