自定义 Exports/Hooks

pageContext.exports 包含以下的 export 值:

  • 当前页面的 .page.js/.page.server.js/.page.client.js 文件
  • _default.page.* 文件

例如:

// /pages/countries.page.js
// Environment: Node.js & Browser

export const title = "Earth Countries";
// /pages/countries.page.server.js
// Environment: Node.js

export const dataUrl = "https://restcountries.com/v3.1/all";
// /renderer/_default.page.server.js
// Environment: Node.js

export { onBeforeRender };

import fetch from "node-fetch";

async function onBeforeRender(pageContext) {
  const response = await fetch(pageContext.exports.dataUrl);
  // ...
}

function render(pageContext) {
  const titleTag = `<title>${pageContext.exports.title}</title>`;
  // ...
}
// /renderer/_default.page.client.js
// Environment: Browser

export const clientRouting = true;
export { render };

function render(pageContext) {
  if (!pageContext.isHydration) {
    // 页面导航时更新网站标题
    document.title = pageContext.exports.title;
  }
  // ...
}

注意 pageContext.exports.title 在 Node.js 和浏览器中都可以使用:因为 export { title } 定义在 .page.js 文件中,该文件在 Node.js 和浏览器中都会加载

pageContext.exports.dataUrl 在浏览器中不可用: .page.server.js 文件仅在 Node.js 中加载

我们称这项技术为 Custom Exports / Custom Hooks

Example: Page Layout

一种常见的用法是使用自定义 export pageContext.exports.Layout 来定义页面的 layout 布局

// /pages/product.page.js
// Environment: Browser & Node.js

export { Layout } from "../layouts/Responsive";
export function Page() {
  /* ... */
}
// /pages/admin.page.js
// Environment: Browser & Node.js

export { Layout } from "../layouts/Dashboard";
export function Page() {
  /* ... */
}
// /renderer/_default.page.server.js
// Environment: Node.js

import { renderToHtml } from "some-ui-framework";
import { escapeInject, dangerouslySkipEscape } from "vite-plugin-ssr";

export function render(pageContext) {
  const { Page, Layout } = pageContext.exports;
  const pageHtml = renderToHtml(
    <Layout>
      <Page />
    </Layout>
  );
  return escapeInject`<!DOCTYPE html>
    <html>
      <body>
        <div id="root">${dangerouslySkipEscape(pageHtml)}</div>
      </body>
    </html>`;
}
// /renderer/_default.page.client.js
// Environment: Browser

import { renderDom } from "some-ui-framework";

export function render(pageContext) {
  // `.page.js` 文件在浏览器中加载:`Layout` 和 `Page` 也可以在这里使用
  const { Page, Layout } = pageContext.exports;
  renderDom(
    <Layout>
      <Page />
    </Layout>,
    document.getElementById("root")
  );
}

注意我们使用 pageContext.exports.Page 而不是 pageContext.Page:因为 pageContext.Page 实际上只是 pageContext.exports.Page ?? pageContext.exports.default 的别名

Example: 数据查询

另一个常见的例子是使用自定义 export pageContext.exports.query 来定义页面的数据查询

// /pages/product.page.js
export const query = { modelName: "Product", select: ["name", "price"] };
// /pages/user.page.js
export const query = { modelName: "User", select: ["firstName", "lastName"] };
// /renderer/_default.page.server.js
// Environment: Node.js

export { onBeforeRender };
export { render };
export const passToClient = ["pageProps"];

import { renderToHtml } from "some-ui-framework";
import { escapeInject, dangerouslySkipEscape } from "vite-plugin-ssr";
import { runQuery } from "some-sql-engine";

async function onBeforeRender(pageContext) {
  const { query } = pageContext.exports;
  const data = await runQuery(query);
  const pageProps = { data };
  return { pageContext: { pageProps } };
}

function render(pageContext) {
  const { Page, pageProps } = pageContext;
  const pageHtml = renderToHtml(<Page {...pageProps} />);
  return escapeInject`<!DOCTYPE html>
    <html>
      <body>
        <div id="root">${dangerouslySkipEscape(pageHtml)}</div>
      </body>
    </html>`;
}
// /renderer/_default.page.client.js
// Environment: Browser

export { render };

import { renderDom } from "some-ui-framework";

function render(pageContext) {
  const { Page, pageProps } = pageContext;
  renderDom(<Page {...pageProps} />, document.getElementById("root"));
}

Example: Meta 数据

我们还可以使用自定义 export pageContext.export.meta 来定义页面的 meta 数据

// /pages/product.page.js
// Environment: Node.js
export const title = "Product";
export const getDescription = (pageProps) =>
  `Product: ${pageProps.productName}`;
// /pages/user.page.js
// Environment: Node.js
export const title = "User";
export const getDescription = (pageProps) =>
  `User: ${pageProps.firstName} ${pageProps.lastName}`;
// /renderer/_default.page.server.js
// Environment: Node.js

export { render };

import { renderToHtml } from "some-ui-framework";
import { escapeInject, dangerouslySkipEscape } from "vite-plugin-ssr";

function render(pageContext) {
  const { Page, pageProps } = pageContext;
  const { title, getDescription } = pageContext.exports;
  const description = getDescription(pageProps);
  const pageHtml = renderToHtml(<Page {...pageProps} />);
  return escapeInject`<!DOCTYPE html>
    <html>
      <head>
        <title>${title}</title>
        <meta name="description" content="${description}" />
      </head>
      <body>
        <div id="root">${dangerouslySkipEscape(pageHtml)}</div>
      </body>
    </html>`;
}