@funcstache/stache-stream
v0.3.3
Published
A [Mustache](https://mustache.github.io/) template renderer for JavaScript focused on returning results as quickly as possible.
Maintainers
Readme
Streaming Mustache Template Rendering
A Mustache template renderer for JavaScript focused on returning results as quickly as possible.
This library renders a Mustache template as it's read, rather than loading the entire template into memory before processing. The values of tags in the Mustache template are rendered as soon as possible and written to the output stream.
stache-stream is intended to be used as part of a server-side rendering platform that generates HTML, whose goal is to provide a superior Web user experience on all kinds of devices - especially devices with lower memory and CPU capabilities.
[!TIP] There are two other packages with higher level abstractions for streaming mustache rendering.
- The "funcstache" package is a library that renders a complete mustache template from a collection of components in a file system. See the funcstache docs.
- Koa middleware for serving rendered HTML. See the koa-middleware docs.
Overview
The library is constructed to maximize asynchronous processing of a template and streaming a result as quickly as possible.
- A ReadableStream created from a Mustache template, for example, a string or a file.
- Context that prefers the use of async functions (lambdas) to provide the data rendered into the template.
- A WritableStream that will receive the rendered template output.
This functionality is wrapped in a StacheTransformStream that implements a NodeJS TransformStream.
The components that comprise a StacheTransformStream instance process a Mustache Template in this
order:
ReadableStream -> (Parse → Tokenize → Queue → Template) → WritableStreamUse
When using a StacheTransformStream you will need to provide a Mustache template as the source
ReadableStream and options that include the ContextProvider to be used to render data into the
template.
The following example includes a template with two variables that are replaced. The output illustrates how chunks are written to the output as they become available.
// Mustache template in the ReadableStream:
// "Dear {{title}} {{name}},\n\nYou may already be a winner!"
const stacheTransformStream = new StacheTransformStream({
contextProvider: {
context: { name: "Alice Brown" },
getContextValue: async (tag) => (tag.key === "title" ? "Doctor" : ""),
},
});
for await (const chunk of readable.pipeThrough(stacheTransformStream)) {
await writable.getWriter().write(chunk);
}
// Writable stream output chunks:
// "Dear "
// "Doctor"
// " "
// "Alice Brown"
// ",\n\nYou may already be a winner!"Asynchronously render a partial's replacement
A more complex example renders a partial tag by reading the partial's replacement - another
mustache template - from a file. The replacement template has a variable for the current date
which named newsItemDate which is replaced with a value from newsItem's parent's context.
// Mustache template in the ReadableStream:
// "<article>Latest news: {{ >newsItem }}</article>"
// `newsItem` template:
// "<p>As of {{newsItemDate}} - in the news...</p>"
const stacheTransformStream = new StacheTransformStream({
contextProvider: {
context: {
newsItemDate: new Date().toString(),
newsItem: async (tag: Tag): Promise<ReadableStream> => {
const fileName = "latest-news-item.mustache";
const file = fs.createReadStream(fileName, { encoding: "utf-8" });
return ReadableStream.from(file);
},
},
},
});
for await (const chunk of readable.pipeThrough(stacheTransformStream)) {
await writable.getWriter().write(chunk);
}Improve web page performance
The previous example is trivial and a little silly, but being able to render a template containing HTML and write it to an output stream as soon as possible can have significant performance improvements for display in a web browser. Consider the following pseudo-code for a Mustache template that outputs a complete HTML file.
<!-- HTML with embedded Mustache tags. -->
<html>
<head>
<!-- load CSS files and other assets -->
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<!--
The content for the page at this route is generated dynamically, maybe it's a table with data
that varies depending on the search params...
-->
<section>{{>aboveTheFoldContent}}</section>
<section>{{>content}}</section>
</body>
</html>When rendered using stache-stream all the content up to the {{>content}} partial tag is sent to
the browser immediately. Browsers will parse streamed response data as it arrives and where possible
will begin to render the web page. In the case of a head tag with CSS and other assets the browser
will begin to download those assets in the background as soon as they are parsed. This is a main
goal of stache-stream to improve the experience in a browser by allowing web pages to load and
render more quickly.
We can also break the page content into separate blocks - for example, content that appears "above the fold" will be transformed on the server and sent down as soon as that is complete, without waiting for the rest of the content to be rendered. THis can be especially helpful when display data is fetched from storage.
