Appearance
服务器渲染
¥Server Rendering
此包通过提供一种机制将 HTML 片段注入应用初始 HTML 响应的 <head>
和/或 <body>
,实现了对 Meteor 应用中服务器端渲染的通用支持。
¥This package implements generic support for server-side rendering in Meteor apps, by providing a mechanism for injecting fragments of HTML into the <head>
and/or <body>
of the application's initial HTML response.
用法
¥Usage
此包导出一个名为 onPageLoad
的函数,该函数接受一个回调函数,该函数将在页面加载时(在客户端)或每当发生新请求时(在服务器上)调用。
¥This package exports a function named onPageLoad
which takes a callback function that will be called at page load (on the client) or whenever a new request happens (on the server).
回调接收一个 sink
对象,该对象是 ClientSink
或 ServerSink
的实例,具体取决于环境。两种类型的 sink
具有相同的方法,但服务器版本仅接受 HTML 字符串作为内容,而客户端版本也接受 DOM Node。
¥The callback receives a sink
object, which is an instance of either ClientSink
or ServerSink
depending on the environment. Both types of sink
have the same methods, though the server version accepts only HTML strings as content, whereas the client version also accepts DOM nodes.
{Client,Server}Sink
对象的当前接口如下:
¥The current interface of {Client,Server}Sink
objects is as follows:
js
class Sink {
// Appends content to the <head>.
appendToHead(content)
// Appends content to the <body>.
appendToBody(content)
// Appends content to the identified element.
appendToElementById(id, content)
// Replaces the content of the identified element.
renderIntoElementById(id, content)
// Redirects request to new location.
redirect(location, code)
// server only methods
// sets the status code of the response.
setStatusCode(code)
// sets a header of the response.
setHeader(key, value)
// gets request headers
getHeaders()
// gets request cookies
getCookies()
}
sink
对象还可能根据环境公开其他属性。例如,在服务器上,sink.request
提供对当前 request
对象的访问,sink.arch
标识待处理的 HTTP 响应的目标体系结构(例如 "web.browser")。
¥The sink
object may also expose additional properties depending on the environment. For example, on the server, sink.request
provides access to the current request
object, and sink.arch
identifies the target architecture of the pending HTTP response (e.g. "web.browser").
以下是服务器上 onPageLoad
使用的基本示例:
¥Here is a basic example of onPageLoad
usage on the server:
js
import from "react";
import { renderToString } from "react-dom/server";
import { onPageLoad } from "meteor/server-render";
import App from "/imports/Server.js";
onPageLoad(sink => {
sink.renderIntoElementById("app", renderToString(
<App location={sink.request.url} />
));
});
客户端上同样如此:
¥Likewise on the client:
js
import React from "react";
import ReactDOM from "react-dom";
import { onPageLoad } from "meteor/server-render";
onPageLoad(async (sink) => {
const App = (await import("/imports/Client.js")).default;
ReactDOM.hydrate(<App />, document.getElementById("app"));
});
请注意,如果 onPageLoad
回调函数需要执行任何异步工作,则允许它返回 Promise
,因此可以通过 async
函数实现(如上面的客户端情况)。
¥Note that the onPageLoad
callback function is allowed to return a Promise
if it needs to do any asynchronous work, and thus may be implemented by an async
function (as in the client case above).
还请注意,客户端示例最终不会调用 sink
对象的任何方法,因为 ReactDOM.hydrate
有自己的类似 API。事实上,如果你对客户端应如何进行渲染有自己的想法,你甚至不需要在客户端上使用 onPageLoad
API。
¥Note also that the client example does not end up calling any methods of the sink
object, because ReactDOM.hydrate
has its own similar API. In fact, you are not even required to use the onPageLoad
API on the client, if you have your own ideas about how the client should do its rendering.
以下是服务器上 onPageLoad
使用的一个更复杂示例,涉及 styled-components
npm 包:
¥Here is a more complicated example of onPageLoad
usage on the server, involving the styled-components
npm package:
js
import React from "react";
import { onPageLoad } from "meteor/server-render";
import { renderToString } from "react-dom/server";
import { ServerStyleSheet } from "styled-components";
import App from "/imports/Server";
onPageLoad((sink) => {
const sheet = new ServerStyleSheet();
const html = renderToString(
sheet.collectStyles(<App location={sink.request.url} />)
);
sink.renderIntoElementById("app", html);
sink.appendToHead(sheet.getStyleTags());
});
在此示例中,回调不仅将 <App />
元素渲染到具有 id="app"
的元素中,还将渲染期间生成的任何 <style>
标记附加到响应文档的 <head>
。
¥In this example, the callback not only renders the <App />
element into the element with id="app"
, but also appends any <style>
tag(s) generated during rendering to the <head>
of the response document.
虽然这些示例都涉及 React,但 onPageLoad
API 被设计为对任何类型的服务器端渲染都通用。
¥Although these examples have all involved React, the onPageLoad
API is designed to be generically useful for any kind of server-side rendering.
流式 HTML
¥Streaming HTML
React 16 引入了 renderToNodeStream
,它允许以块的形式读取渲染的 HTML。这减少了 TTFB(第一个字节的时间)。
¥React 16 introduced renderToNodeStream
, which enables the reading of rendered HTML in chunks. This reduces the TTFB (time to first byte).
以下是使用 styled-components 的 renderToNodeStream
示例。请注意使用 sheet.interleaveWithNodeStream
而不是 sink.appendToHead(sheet.getStyleTags());
:
¥Here is a renderToNodeStream
example using styled-components. Note the use of sheet.interleaveWithNodeStream
instead of sink.appendToHead(sheet.getStyleTags());
:
js
import React from "react";
import { onPageLoad } from "meteor/server-render";
import { renderToNodeStream } from "react-dom/server";
import { ServerStyleSheet } from "styled-components";
import App from "/imports/Server";
onPageLoad((sink) => {
const sheet = new ServerStyleSheet();
const appJSX = sheet.collectStyles(<App location={sink.request.url} />);
const htmlStream = sheet.interleaveWithNodeStream(renderToNodeStream(appJSX));
sink.renderIntoElementById("app", htmlStream);
});
从请求中获取数据
¥Getting data from the request
在某些情况下,你希望根据请求的 URL 自定义元标记或其他响应内容,例如,如果你正在加载应用中特定产品的页面,则可能希望包含 社交预览 的图片和描述。
¥In some cases you want to customize meta tags or something else in your response based in the requested URL, for example, if your are loading a page with a specific product in your app maybe you want to include an image and a description for social previews.
你可以使用 sink
对象从请求中提取信息。
¥You can extract information from the request using the sink
object.
js
import { onPageLoad } from "meteor/server-render";
const getBaseUrlFromHeaders = (headers) => {
const protocol = headers["x-forwarded-proto"];
const { host } = headers;
// we need to have '//' to findOneByHost work as expected
return `${protocol ? `${protocol}:` : ""}//${host}`;
};
const getContext = (sink) => {
// more details about this implementation here
// https://github.com/meteor/meteor/issues/9765
const { headers, url, browser } = sink.request;
// no useful data will be found for galaxybot requests
if (browser && browser.name === "galaxybot") {
return null;
}
// when we are running inside cordova we don't want to resolve meta tags
if (url && url.pathname && url.pathname.includes("cordova/")) {
return null;
}
const baseUrl = getBaseUrlFromHeaders(headers);
const fullUrl = `${baseUrl}${url.pathname || ""}`;
return { baseUrl, fullUrl };
};
onPageLoad((sink) => {
const { baseUrl, fullUrl } = getContext(sink);
// product URL contains /product on it
const urlParseArray = fullUrl.split("/");
const productPosition = urlParseArray.indexOf("product");
const productId =
productPosition !== -1 &&
urlParseArray[productPosition + 1].replace("?", "");
const product = productId && ProductsCollection.findOne(productId);
const productTitle = product && `Buy now ${product.name}, ${product.price}`;
if (productTitle) {
sink.appendToHead(`<title>${productTitle}</title>\n`);
sink.appendToHead(`<meta property="og:title" content="${productTitle}">\n`);
if (product.imageUrl) {
sink.appendToHead(
`<meta property="og:image" content="${product.imageUrl}">\n`
);
}
}
});