Streaming Templates in node and the browser

I’m currently building a simple web app and I needed a simple templating engine that can stream dynamically generated responses to the network. It’s hard to build sites that are fast. One known pattern for building sites that render quickly is to ensure that the browser gets the HTML as quickly as possible. Yet, many of the tools (middleware and templating engines) that developers use to build sites wait for the response from the server application to be completely created before they are put on to the wire and send to the client.


This content originally appeared on Modern Web Development with Chrome and was authored by Paul Kinlan

<p>I'm currently building a simple web app and I needed a simple templating engine that can stream dynamically generated responses to the network. </p> <p>It's hard to build sites that are fast. One known pattern for building sites that render quickly is to ensure that the browser gets the HTML as quickly as possible. Yet, many of the tools (middleware and templating engines) that developers use to build sites wait for the response from the server application to be completely created before they are put on to the wire and send to the client.</p> <p>Streaming template engines are important because they don't wait until the entire response is ready before sending the data on, but they are also able to wait for gathered data (say from a database request) before proceeding with the rest of the response.</p> <figure><video src="https://paul.kinlan.me/videos/2020-06-12-streaming-templates-in-node-and-the-browser-0.mp4" alt="Streaming templating in action" controls></video></figure> <p>It feels like there are scant few streaming templating engines in Node. There are fewer that work in Node, in the Browser and in a service worker.</p> <p>I came across the awesome <a href="https://www.npmjs.com/package/flora-tmpl">flora-tmpl</a> project. It's a streaming templating engine that uses template literals <code>html`Hello ${world}`;</code> to make it easy to author streaming templates, and if you just need them for Node JS, then you should totally use it.</p> <p>The project I am building needs to work in Node, the Browser and a Service Worker (I am running my app in both Node and the SW) and whilst it would have been possible to browserify the library, it makes it a lot larger than I need it to be. I know enough about <code>ReadableStreams</code> in the browser to damage, so I wanted to see if I could port <code>flora-tmpl</code> to use the WhatWG streams API...</p> <p><a href="https://www.npmjs.com/package/whatwg-flora-tmpl">whatwg-flora-tmpl</a> (I might have to change the name, it's not affiliated with the whatwg or flora really) is a small library that does pretty much everything <code>flora</code> does, but using the WhatWG Streams API in the browser (and a polyfill in Node).</p> <p>The template literal function takes your, well, template and returns a <code>ReadableStream</code>, the template function will then push string literals on the stream, and when it needs to evaluate a variable, it will do that and then enqueue that on to the stream too. This means, for example that you can generate responses in a service worker fetch event like: <code>new Response(tmpl`Hello ${world}`)</code>;</p> <p>It's not just basic values that can be evaluated (such as strings, numbers and arrays), it can also evaluate <code>ReadableStreams</code>, which means you stream templates in your template.</p> <p>The demo for the video at the start of this article is below (you wouldn't do this in real life, it's just to show the templating engine can also embed readable streams which means it can embed the templating engine and stream that response too)</p> <div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-JavaScript" data-lang="JavaScript"><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">tmpl</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">'../lib/index.js'</span>; <span style="color:#66d9ef">import</span> <span style="color:#a6e22e">streams</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">"web-streams-polyfill"</span>; <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">read</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">stream</span>) => { <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">reader</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">stream</span>.<span style="color:#a6e22e">getReader</span>(); <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">decoder</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">TextDecoder</span>(); <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">reader</span>.<span style="color:#a6e22e">read</span>().<span style="color:#a6e22e">then</span>(<span style="color:#66d9ef">function</span> <span style="color:#a6e22e">process</span>(<span style="color:#a6e22e">result</span>) { <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">done</span>) { <span style="color:#66d9ef">return</span>; } <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">decoder</span>.<span style="color:#a6e22e">decode</span>(<span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">value</span>)); <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">reader</span>.<span style="color:#a6e22e">read</span>().<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">process</span>); }); }; <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">encoder</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">TextEncoder</span>(); <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">title</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">'Awesome'</span>; <span style="color:#a6e22e">tmpl</span><span style="color:#e6db74">`<html> </span><span style="color:#e6db74"> <head> </span><span style="color:#e6db74"> <title></span><span style="color:#e6db74">${</span><span style="color:#a6e22e">title</span><span style="color:#e6db74">}</span><span style="color:#e6db74"></title> </span><span style="color:#e6db74"> </head> </span><span style="color:#e6db74"><body> </span><span style="color:#e6db74"></span><span style="color:#e6db74">${</span><span style="color:#66d9ef">new</span> <span style="color:#a6e22e">streams</span>.<span style="color:#a6e22e">ReadableStream</span>({ <span style="color:#a6e22e">start</span>(<span style="color:#a6e22e">controller</span>) { <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">counter</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">interval</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">setInterval</span>(<span style="color:#a6e22e">async</span> () => { <span style="color:#a6e22e">controller</span>.<span style="color:#a6e22e">enqueue</span>(<span style="color:#a6e22e">encoder</span>.<span style="color:#a6e22e">encode</span>(<span style="color:#e6db74">`</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">counter</span><span style="color:#f92672">++</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>)); <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">counter</span> <span style="color:#f92672">>=</span> <span style="color:#ae81ff">10</span>) { <span style="color:#a6e22e">controller</span>.<span style="color:#a6e22e">close</span>(); <span style="color:#a6e22e">clearInterval</span>(<span style="color:#a6e22e">interval</span>); <span style="color:#e6db74">}</span><span style="color:#e6db74"> </span><span style="color:#e6db74"> }, 1000); </span><span style="color:#e6db74"> } </span><span style="color:#e6db74">})} </span><span style="color:#e6db74"></body>`</span>.<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">read</span>); </code></pre></div><p>A better (albeit a lot more complex) example is the one I am using in my project.</p> <div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-JavaScript" data-lang="JavaScript"><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">head</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">data</span>, <span style="color:#a6e22e">bodyTemplate</span>) => <span style="color:#a6e22e">template</span><span style="color:#e6db74">`<!DOCTYPE html> </span><span style="color:#e6db74"><html> </span><span style="color:#e6db74"> <head> </span><span style="color:#e6db74"> <title>Baby Logger</title> </span><span style="color:#e6db74"> <script src="https://paul.kinlan.me/client.js" type="module" defer></script> </span><span style="color:#e6db74"> <link rel="stylesheet" href="https://paul.kinlan.me/styles/main.css"> </span><span style="color:#e6db74"> <link rel="manifest" href="https://paul.kinlan.me/manifest.json"> </span><span style="color:#e6db74"> <meta name="viewport" content="width=device-width"> </span><span style="color:#e6db74"> </head> </span><span style="color:#e6db74"> <body> </span><span style="color:#e6db74"> </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">bodyTemplate</span><span style="color:#e6db74">}</span><span style="color:#e6db74"> </span><span style="color:#e6db74"> </body> </span><span style="color:#e6db74"></html>`</span>; <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">body</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">data</span>, <span style="color:#a6e22e">items</span>) => <span style="color:#a6e22e">template</span><span style="color:#e6db74">`<header> </span><span style="color:#e6db74"> <h1>Baby Log</h1> </span><span style="color:#e6db74"> <div><a href="https://paul.kinlan.me/">All</a>, <a href="https://paul.kinlan.me/feeds">Feeds</a>, <a href="https://paul.kinlan.me/sleeps">Sleeps</a>, <a href="https://paul.kinlan.me/poops">Poops</a>, <a href="https://paul.kinlan.me/wees">Wees</a></div> </span><span style="color:#e6db74"> </header> </span><span style="color:#e6db74"> <main> </span><span style="color:#e6db74"> <header> </span><span style="color:#e6db74"> <h2></span><span style="color:#e6db74">${</span><span style="color:#a6e22e">data</span>.<span style="color:#a6e22e">header</span><span style="color:#e6db74">}</span><span style="color:#e6db74"></h2> </span><span style="color:#e6db74"> </header> </span><span style="color:#e6db74"> <section> </span><span style="color:#e6db74"> </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">items</span><span style="color:#e6db74">}</span><span style="color:#e6db74"> </span><span style="color:#e6db74"> </section> </span><span style="color:#e6db74"> </main> </span><span style="color:#e6db74"> <footer> </span><span style="color:#e6db74"> <span>Add</span><a href="https://paul.kinlan.me/feeds/new" title="Add a feed">?</a><a href="https://paul.kinlan.me/sleeps/new" title="Add a Sleep">?</a><a href="https://paul.kinlan.me/poops/new" title="Add a Poop">?</a><a href="https://paul.kinlan.me/wees/new" title="Add a Wee">⛲️</a> </span><span style="color:#e6db74"> </footer>`</span>; <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">aggregate</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">data</span>) => { <span style="color:#75715e">// Do a lot of work in IndexedDB and other data munging </span><span style="color:#75715e"></span>} <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Response</span>(<span style="color:#a6e22e">template</span><span style="color:#e6db74">`</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">head</span>(<span style="color:#a6e22e">data</span>, <span style="color:#a6e22e">body</span>(<span style="color:#a6e22e">data</span>, <span style="color:#a6e22e">template</span><span style="color:#e6db74">`</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">aggregate</span>(<span style="color:#a6e22e">data</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>) )<span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>); </code></pre></div><p>We have many templates, <code>head</code> generates the skeleton of the HTML, <code>body</code> is the content of the page based on the output of <code>aggregate</code>. The latter of those functions does a lot of asynchronous work which takes some time, however the response can start rendering pretty much straight away because the <code>head</code> and <code>body</code> functions mostly output text.</p> <p>I think it's pretty neat, and hopefully I can write up a lot more about the architecture of the app I am building that uses this across the Server, Client and Service Worker.</p> <p>Finally, a big shout out to <a href="https://twitter.com/matthewcp">Matthew Phillips</a> who wrote the awesome original version of <a href="https://github.com/matthewp/flora">flora-tmpl</a>.</p>


This content originally appeared on Modern Web Development with Chrome and was authored by Paul Kinlan


Print Share Comment Cite Upload Translate Updates
APA

Paul Kinlan | Sciencx (2020-06-12T23:32:44+00:00) Streaming Templates in node and the browser. Retrieved from https://www.scien.cx/2020/06/12/streaming-templates-in-node-and-the-browser/

MLA
" » Streaming Templates in node and the browser." Paul Kinlan | Sciencx - Friday June 12, 2020, https://www.scien.cx/2020/06/12/streaming-templates-in-node-and-the-browser/
HARVARD
Paul Kinlan | Sciencx Friday June 12, 2020 » Streaming Templates in node and the browser., viewed ,<https://www.scien.cx/2020/06/12/streaming-templates-in-node-and-the-browser/>
VANCOUVER
Paul Kinlan | Sciencx - » Streaming Templates in node and the browser. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2020/06/12/streaming-templates-in-node-and-the-browser/
CHICAGO
" » Streaming Templates in node and the browser." Paul Kinlan | Sciencx - Accessed . https://www.scien.cx/2020/06/12/streaming-templates-in-node-and-the-browser/
IEEE
" » Streaming Templates in node and the browser." Paul Kinlan | Sciencx [Online]. Available: https://www.scien.cx/2020/06/12/streaming-templates-in-node-and-the-browser/. [Accessed: ]
rf:citation
» Streaming Templates in node and the browser | Paul Kinlan | Sciencx | https://www.scien.cx/2020/06/12/streaming-templates-in-node-and-the-browser/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.