Files
meteor/packages/server-render/client-sink.js
Ben Newman fb73388ce3 Make server-render API more flexible and isomorph-ish.
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.
2017-06-29 15:08:32 -04:00

44 lines
1.2 KiB
JavaScript

const doc = document;
const head = doc.getElementsByTagName("head")[0];
const body = doc.body;
export class ClientSink {
appendToHead(nodeOrHtml) {
appendContent(head, nodeOrHtml);
}
appendToBody(nodeOrHtml) {
appendContent(body, nodeOrHtml);
}
appendToElementById(id, nodeOrHtml) {
appendContent(doc.getElementById(id), nodeOrHtml);
}
renderIntoElementById(id, nodeOrHtml) {
const element = doc.getElementById(id);
while (element.lastChild) {
element.removeChild(element.lastChild);
}
appendContent(element, nodeOrHtml);
}
}
function appendContent(destination, nodeOrHtml) {
if (typeof nodeOrHtml === "string") {
// Make a shallow clone of the destination node to ensure the new
// children can legally be appended to it.
const container = destination.cloneNode(false);
// Parse the HTML into the container, allowing for multiple children.
container.innerHTML = nodeOrHtml;
// Transplant the children to the destination.
while (container.firstChild) {
destination.appendChild(container.firstChild);
}
} else if (Array.isArray(nodeOrHtml)) {
nodeOrHtml.forEach(elem => appendContent(destination, elem));
} else {
destination.appendChild(nodeOrHtml);
}
}