构建时渲染

构建时渲染(Build-time rendering,简称 BTR)在构建过程中将一个路由渲染为一个 HTML,并将在初始视图中显示的、关键的 CSS 和资源嵌入到页面中。Dojo 能预渲染路由使用的初始 HTML,并直接注入到页面中,这样会带来很多与服务器端渲染(SSR)相同的好处,如性能提升、搜索引擎优化且没有引入 SSR 的复杂性。

使用 BTR

首先确保 index.html 中包含一个拥有 id 属性的 DOM 节点。Dojo 的虚拟 DOM 会使用这个节点来比较和渲染应用程序的 HTML。BTR 需要此设置,这样它就能渲染在构建阶段生成的 HTML。这将会为路由创建一个响应非常快的初始渲染。

index.html

  1. <!DOCTYPE html>
  2. <html lang="en-us">
  3. <head>
  4. <title>sample-app</title>
  5. <meta name="theme-color" content="#222127" />
  6. <meta name="viewport" content="width=device-width, initial-scale=1" />
  7. </head>
  8. <body>
  9. <div id="app"></div>
  10. </body>
  11. </html>

然后将应用程序挂载到指定的 DOM 节点上:

main.ts

  1. const r = renderer(() => w(App, {}));
  2. const domNode = document.getElementById('app') as HTMLElement;
  3. r.mount({ registry, domNode });

然后更新项目的 .dojorc 配置文件,设置根 DOM 节点的 id 和在构建时要渲染的路由。

.dojorc

  1. {
  2. "build-app": {
  3. "build-time-render": {
  4. "root": "app",
  5. "paths": [
  6. "#home",
  7. {
  8. "path": "#comments/9999",
  9. "match": ["#comments/.*"]
  10. }
  11. ]
  12. }
  13. }
  14. }

此配置描述了两个路由。一个是 home 路由,一个是较复杂的 comments 路由。comments 是一个比较复杂的路由,需要传入参数。match 参数用于确保在构建时为此路由生成的 HTML 可以应用到与此正则表达式匹配的任何路由上。

BTR 在构建时为每个渲染路径(path)生成一份屏幕快照,存在 ./output/info/screenshots 文件夹中。

History 管理器

构建时渲染支持使用 @dojo/framework/routing/history/HashHistory@dojo/framework/routing/history/StateHistory history 管理器的应用程序。当使用 HashHistory 时,确保所有的路径都是以 # 字符开头。

build-time-render 功能标记

运行时渲染公开了一个 build-time-render 功能标记,可用于跳过在构建时不能执行的功能。这样在创建一个初始渲染时,就可以避免对外部系统调用 fetch,而是提供静态数据。

  1. if (!has('build-time-render')) {
  2. const response = await fetch(/* remote JSON */);
  3. return response.json();
  4. } else {
  5. return Promise.resolve({
  6. /* predefined Object */
  7. });
  8. }

Dojo Blocks

Dojo 提供了一个 block 系统,在构建阶段的渲染过程中会执行 Node.js 代码。执行的结果会被写入到缓存中,然后在浏览器运行阶段会以相同的方式、透明的使用这些缓存。这就为使用一些浏览器中无法实现或者性能不佳的操作开辟了新的机会。

例如,Dojo 的 block 模块可以读取一组 markdown 文件,将其转换为 VNode,并使它们可以在应用程序中渲染,所有这些都可在构建时执行。然后 Dojo block 模块的构建结果会缓存在应用程序的包中,以便在运行时在浏览器中使用。

Dojo block 模块的用法与在 Dojo 部件中使用其它 meta 的用法类似。因此无需大量的配置或其他编写模式。

例如,block 模块读取一个文本文件,然后将内容返回给应用程序。

src/blocks/read-file.ts

  1. import * as fs from 'fs';
  2. import { resolve } from 'path';
  3. export default (path: string) => {
  4. path = resolve(__dirname, path);
  5. return fs.readFileSync(path, 'utf8');
  6. };

src/widgets/MyBlockWidget.tsx

  1. import Block from '@dojo/framework/core/meta/Block';
  2. import WidgetBase from '@dojo/framework/core/WidgetBase';
  3. import { v } from '@dojo/framework/core/vdom';
  4. import readFile from '../blocks/read-file';
  5. export default class MyBlockWidget extends WidgetBase {
  6. protected render() {
  7. const message = this.meta(Block).run(readFile)('../content/hello-dojo-blocks.txt');
  8. return v('div', [message]);
  9. }
  10. }

这个部件会在构建阶段运行 src/blocks/read-file.ts 模块来读取指定文件的内容。然后将内容作为文本节点,用作部件 VDOM 输出的子节点。