mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Render callbacks can now inject HTML content into multiple different
elements, and may also append content to the <head> or <body> elements, on
both the client and the server.
This new API was inspired by trying to use the styled-components npm
package on the server, which involves not only rendering and injecting
static HTML somewhere in the <body>, but also appending the resulting
<style> tag(s) into the <head>:
import { onPageLoad } from "meteor/server-render";
import { renderToString } from "react-dom/server";
import { ServerStyleSheet } from "styled-components";
onPageLoad(sink => {
const sheet = new ServerStyleSheet();
const html = renderToString(sheet.collectStyles(
<App location={sink.request.url} />
));
sink.renderIntoElementById("app", html);
sink.appendToHead(sheet.getStyleTags());
});
Note that the server-render package now exports an onPageLoad function,
rather than the old renderIntoElementById function. The functionality of
renderIntoElementById is now exposed by the {Client,Server}Sink API.
I say the client-side version of this API is 'isomorphish' to the
server-side version, because ClientSink methods can accept DOM nodes in
addition to raw HTML strings, whereas DOM nodes don't really make sense on
the server.
105 lines
3.0 KiB
JavaScript
105 lines
3.0 KiB
JavaScript
import { Tinytest } from "meteor/tinytest";
|
|
import { WebAppInternals } from "meteor/webapp";
|
|
import { onPageLoad } from "meteor/server-render";
|
|
import { parse } from "parse5";
|
|
|
|
const skeleton = `
|
|
<h1>Look, Ma... static HTML!</h1>
|
|
<div id="container-2"></div>
|
|
<p>
|
|
<div id="container-1">
|
|
</div>
|
|
</p>`;
|
|
|
|
Tinytest.add('server-render - boilerplate', function (test) {
|
|
// This test is not a very good demonstration of the server-render
|
|
// abstraction. In normal usage, you would call renderIntoElementById
|
|
// and not think about the rest of this stuff. The extra complexity owes
|
|
// to the trickiness of testing this package without using a real
|
|
// browser to parse the resulting HTTP response.
|
|
|
|
const realCallback =
|
|
// Use the underlying abstraction to set the static HTML skeleton.
|
|
WebAppInternals.registerBoilerplateDataCallback(
|
|
"meteor/server-render",
|
|
(request, data, arch) => {
|
|
if (request.isServerRenderTest) {
|
|
test.equal(arch, "web.browser");
|
|
test.equal(request.url, "/server-render/test");
|
|
data.body = skeleton;
|
|
}
|
|
return realCallback.call(this, request, data, arch);
|
|
}
|
|
);
|
|
|
|
const callback1 = onPageLoad(sink => {
|
|
sink.renderIntoElementById("container-1", "<oyez/>");
|
|
});
|
|
|
|
// This callback is async, and that's fine because
|
|
// WebAppInternals.getBoilerplate is able to yield. Internally the
|
|
// webapp package uses a function called getBoilerplateAsync, so the
|
|
// Fiber power-tools need not be involved in typical requests.
|
|
const callback2 = onPageLoad(async sink => {
|
|
sink.renderIntoElementById(
|
|
"container-2",
|
|
(await "oy") + (await "ez")
|
|
);
|
|
});
|
|
|
|
try {
|
|
const boilerplate = WebAppInternals.getBoilerplate({
|
|
isServerRenderTest: true,
|
|
browser: { name: "fake" },
|
|
url: "/server-render/test"
|
|
}, "web.browser");
|
|
|
|
const ids = [];
|
|
const seen = new Set;
|
|
|
|
function walk(node) {
|
|
if (node && ! seen.has(node)) {
|
|
seen.add(node);
|
|
|
|
if (node.nodeName === "div" && node.attrs) {
|
|
node.attrs.some(attr => {
|
|
if (attr.name === "id") {
|
|
const id = attr.value;
|
|
|
|
if (id === "container-1") {
|
|
test.equal(node.childNodes[0].nodeName, "oyez");
|
|
ids.push(id);
|
|
} else if (id === "container-2") {
|
|
const child = node.childNodes[0];
|
|
test.equal(child.nodeName, "#text");
|
|
test.equal(child.value.trim(), "oyez");
|
|
ids.push(id);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (node.childNodes) {
|
|
node.childNodes.forEach(walk)
|
|
}
|
|
}
|
|
}
|
|
|
|
walk(parse(boilerplate));
|
|
|
|
test.equal(ids, ["container-2", "container-1"]);
|
|
|
|
} finally {
|
|
// Cleanup to minimize interference with other tests:
|
|
WebAppInternals.registerBoilerplateDataCallback(
|
|
"meteor/server-render",
|
|
realCallback
|
|
);
|
|
|
|
onPageLoad.remove(callback1);
|
|
onPageLoad.remove(callback2);
|
|
}
|
|
});
|