Home

Awesome

<em>µ</em>content

Build Status Coverage Status

sunflowers

<sup>Social Media Photo by Bonnie Kittle on Unsplash</sup>

📣 Community Announcement

Please ask questions in the dedicated forum to help the community around this project grow ♥


A <em>micro</em> SSR oriented HTML/SVG content generator, but if you are looking for a <em>micro</em> FE content generator, check µhtml out.

const {render, html} = require('ucontent');
const fs = require('fs');

const stream = fs.createWriteStream('test.html');
stream.once('open', () => {
  render(
    stream,
    html`<h1>It's ${new Date}!</h1>`
  ).end();
});

V2 Breaking Change

The recently introduced data helper could conflict with some node such as <object>, hence it has been replaced by the .dataset utility. Since element.dataset = object is an invalid operation, the sugar to simplify data- attributes is now never ambiguous and future-proof: <element .dataset=${...} /> it is.

This is aligned with µhtml and lighterhtml recent changes too.

API

Both html and svg supports µhtml utilities but exclusively for feature parity <sup><sub>(html.for(...) and html.node are simply aliases for the html function)</sub></sup>.

Except for html and svg tags, all other tags can be used as regular functions, as long as the passed value is a string, or a specialized instance.

This allow content to be retrieved a part and then be used as is within these tags.

import {readFileSync} from 'fs';
const code = js(readFileSync('./code.js'));
const style = css(readFileSync('./style.css'));
const partial = raw(readFileSync('./partial.html'));

const head = title => html`
  <head>
    <title>${title}</title>
    <style>${style}</style>
    <script>${code}</script>
  </head>
`;

const body = () => html`<body>${partial}</body>`;

const page = title => html`
  <!doctype html>
  <html>
    ${head(title)}
    ${body()}
  </html>
`;

All pre-generated content can be passed along, automatically avoiding minification of the same content per each request.

// will be re-used and minified only once
const jsContent = js`/* same JS code to serve */`;
const cssContent = css`/* same CSS content to serve */`;

require('http')
  .createServer((request, response) => {
    response.writeHead(200, {'content-type': 'text/html;charset=utf-8'});
    render(response, html`
      <!doctype html>
      <html>
        <head>
          <title>µcontent</title>
          <style>${cssContent}</style>
          <script>${jsContent}</script>
        </head>
      </html>
    `.min()).end();
  })
  .listen(8080);

If one of the HTML interpolations is null or undefined, an empty string will be placed instead.

Note: When writing to stream objects using the render() API make sure to call end on it

Production: HTML + SVG Implicit Minification

While both utilities expose a .min() helper, repeated minification of big chunks of layout can be quite expensive.

As the template literal is the key to map updates, which happen before .min() gets invoked, it is necessary to tell upfront if such template should be minified or not, so that reusing the same template later on, would result into a pre-minified set of chunks.

In order to do so, html and svg expose a minified boolean property, which is false by default, but it can be switched to true in production.

import {render, html, svg} from 'ucontent';

// enable pre minified chunks
const {PRODUCTION} = process.env;
html.minified = !!PRODUCTION;
svg.minified = !!PRODUCTION;

const page = () => html`
  <!doctype html>
  <html>
    <h1>
      This will always be minified
    </h1>
    <p>
      ${Date.now()} + ${Math.random()}
    </p>
  </html>
`;
// note, no .min() necessary

render(response, page()).end();

In this way, local tests would have a clean layout, while production code will always be minified, where each template literal will be minified once, instead of each time .min() is invoked.

Attributes Logic

Benchmark

Directly from pelo project but without listeners, as these are mostly useless for SSR.

Rendering a simple view 10,000 times:

node test/pelo.js
tagtime (ms)
ucontent117.668ms
pelo129.332ms

How To Live Test

Create a test.js file in any folder you like, then npm i ucontent in that very same folder.

Write the following in the test.js file and save it:

const {render, html} = require('ucontent');

require('http').createServer((req, res) => {
  res.writeHead(200, {'content-type': 'text/html;charset=utf-8'});
  render(res, html`
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>ucontent</title>
    </head>
    <body>${html`
      <h1>Hello There</h1>
      <p>
        Thank you for trying µcontent at ${new Date()}
      </p>
    `}</body>
    </html>
  `)
  .end();
}).listen(8080);

You can now node test.js and reach localhost:8080, to see the page layout generated.

If you'd like to test the minified version of that output, invoke .min() after the closing </html> template tag:

  render(res, html`
    <!DOCTYPE html>
    <html lang="en">
      ...
    </html>
  `.min()
  ).end();

You can also use html.minified = true on top, and see similar results.

API Summary Example

import {render, css, js, html, raw} from 'ucontent';

// turn on implicit html minification (production)
html.minified = true;

// optionally
// svg.minified = true;

render(content => response.end(content), html`
  <!doctype html>
  <html lang=${user.lang}>
    <head>
      <!-- dynamic interpolations -->
      ${meta.map(({name, content}) =>
                    html`<meta name=${name} content=${content}>`)}
      <!-- explicit CSS minification -->
      <style>
      ${css`
        body {
          font-family: sans-serif;
        }
      `}
      </style>
      <!-- explicit JS minification -->
      <script>
      ${js`
        function passedThrough(event) {
          console.log(event);
        }
      `}
      </script>
    </head>
    <!-- discarded callback events -->
    <body onclick=${() => ignored()}>
      <div
        class=${classes.join(' ')}
        always=${'escaped'}
        .contentEditable=${false}
        .dataset=${{name: userName, id: userId}}
        aria=${{role: 'button', labelledby: 'id'}}
        onmouseover=${'passedThrough.call(this,event)'}
      >
        Hello ${userName}!
        ${raw`<some> valid, or even ${'broken'}, </content>`}
      </div>
    </body>
  </html>
`);