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.
Use the funcstache library to begin rendering Mustache files into HTML documents. It provides:
The library is constructed to maximize asynchronous processing of a Mustache template and streaming the result to a client as quickly as possible.
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) → WritableStream
When using a StacheTransformStream you will need to provide a Mustache template in 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 provided by `readable` (a 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!"
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 provided by `readable` (a 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);
}
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.