Skip to content

c-cpt/lit-ntml

 
 

Repository files navigation

lit-ntml

Inspired by lit-html but for Node.js.


Version Node version MIT License

Downloads Total downloads Packagephobia Bundlephobia

Build Status CircleCI Dependency Status codecov Coverage Status

codebeat badge Codacy Badge Code of Conduct

Lightweight and modern templating for SSR in Node.js, inspired by lit-html.

This module also gets featured in web-padawan/awesome-lit-html. Make sure to check the repo out for awesome things inspired by lit-html. 👍💯

Table of contents

Features

  • await all tasks including Functions, Promises, and whatnot.
  • minify: true to minify rendered HTML string.
  • parse: 'html'|'fragment'|true|false to parse content as HTML fragment string (default) or HTML string.
  • pretty: { ocd: boolean } to prettify content with ocd set to true (default) or false.
  • Compatible for ES Modules (import ntml from 'ntml') and CommonJS (const { ntml } = require('ntml');).
  • Parses PromiseList or List by default, without explicit joining. See demo.
  • Uses htmlMinifier to minify HTML string.
  • Uses parse5 to parse HTML string by default.
  • Uses pretty to prettify HTML string by default.
  • Support HTML syntax highlighting + autocompletion with vscode-lit-html in JavaScript's template string.
  • Support native ES Module via .mjs

Pre-requisite

How to use

Install

# Install via NPM
$ npm install lit-ntml

Enable syntax highlighting when writing HTML with template literal

Visual Studio Code

  1. Install vscode-lit-html extension.
  2. If the extension does not provide that syntax highlighting and autocompletion, try writing your templates in .jsx file (or .tsx file if you're TypeScript user) . That should work.

Code examples

ES Modules or TypeScript

Await all tasks (Promises, Functions, strings, etc)
/** Import project dependencies */
import ntml from 'lit-ntml';

/** Setting up */
const html = ntml();
const header = text => () => new Promise(yay => setTimeout(() => yay(`<div class="header">${text}</div>`), 3e3));
const content = text => async () => `<div class="content">${text}</div>`;
const someLoremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';

const rendered = await html`
  <html lang="en">
    <body>
      <style>
        body {
          padding: 0;
          margin: 0;
          font-size: 16px;
          font-family: 'sans-serif';
          box-sizing: border-box;
        }

        .header {
          background: #0070fb;
          color: #fff;
        }

        .content {
          background: #f5f5f5;
          color: #000;
        }
      </style>

      <main>
        <div>Hello, world! ${header('Hello, world!')} ${content('lorem ipsum')}</div>
        <div>${someLoremIpsum}</div>
      </main>
    </body>
  </html>
`;

console.log('#', rendered); /** <html lang="en>...</html> */
Minify rendered HTML string
/** Import project dependencies */
import ntml from 'lit-ntml';

/** Setting up */
const html = ntml({
  minify: true,
  // options: { minify: {...} }, // Optional htmlMinifier.Options
});

const minifyAfterRendered = await html`
  <html lang="en">
    <body>
      <style>
        body {
          padding: 0;
          margin: 0;
          font-size: 16px;
          font-family: 'sans-serif';
          box-sizing: border-box;
        }

        .header {
          background: #0070fb;
          color: #fff;
        }

        .content {
          background: #f5f5f5;
          color: #000;
        }
      </style>

      <main>
        <div>Hello, world!</div>
        <div>This content will be minified!</div>
      </main>
    </body>
  </html>
`;

console.log('#', minifyAfterRendered); /** <html lang="en"><body><style>...</style><main>...</main></body></html> */
Parse PromiseList or List
/** Import project dependencies */
import assert from 'assert';
import ntml from 'lit-ntml';

/** Setting up */
const html = ntml();
const nameList = [
  'John Doe',
  'Michael CEO',
  'Cash Black',
  'Vict Fisherman',
];
const expected = `<h1>Hello, World!</h1>
<ul>
  <li>John Doe</li>
  <li>Michael CEO</li>
  <li>Cash Black</li>
  <li>Vict Fisherman</li>
</ul>`;

const listRendered = await html`<h1>Hello, World!</h1>
<ul>${
  nameList.map(n => html`<li>${n}</li>`)
}</ul>`;
const asyncListRendered = await html`<h1>Hello, World!</h1>
<ul>${
  nameList.map(async n => html`<li>${n}</li>`)
}</ul>`;

assert.strictEqual(listRendered, expected); // OK
assert.strictEqual(asyncListRendered, expected); // OK

Node.js

const { ntml } = require('ntml');

(async () => {
  const html = ntml();

  const rendered = await html`<div>haha</div>`;

  console.log('#', rendered);
  /**
   * <div>haha</div>
   */
})();

API Reference

DEFAULT_MINIFY_OPTIONS

{
  collapseBooleanAttributes: true,
  collapseWhitespace: true,
  minifyCSS: true,
  minifyJS: true,
  processConditionalComments: true,
  quoteCharacter: '"',
  removeComments: true,
  removeOptionalTags: true,
  removeRedundantAttributes: true,
  removeScriptTypeAttributes: true,
  removeStyleLinkTypeAttributes: true,
  sortAttributes: true,
  sortClassName: true,
  trimCustomFragments: true,
}

NtmlOpts

  • minify <?boolean> Optional minification flag. If true, minify rendered HTML string. Defaults to false.
  • options <?Object> Optional settings.

ntml([options])

  • options <?NtmlOpts> Optional configuration for the templating.
  • returns: <Promise<string>> Promise which resolves with rendered HTML string.

Caveat

Writing CSS styles outside of HTMLStyleElement can lead to unexpected parsing behavior, such as:

CSS styles outside of <style>

import ntml from 'lit-ntml';

const html = ntml();
const style = () => html`
  body {}

  div {}
`;

const main = () => html`
  <style>${style()}</style>
`;

/**
 * <!DOCTYPE>
 * <html> 
 *   <head>
 *     <style>
 *       <!DOCTYPE html>
 *       <html>
 *         <head>
 *           <style>
 *             body {}
 *
 *             div {}
 *           </style>
 *         </head>
 *       </html>
 *     </style>
 *   </head>
 * </html>
 * 
 */

It's clearly that the style tag element has been wrapped inside another html tag element. This is an unexpected behavior. However, it kind of makes sense as from the above scenario each of the new content is rendered separately with lit-ntml and the lit-ntml has no knowledge about what will be rendered next and before. To avoid such behavior, do one of the following:

  1. Wrap with any valid HTML element tag

    const style = () => html`
    <style>
      body {}
    
      main {}
    </style>`;
  2. Make sure options[parse] is set to false or fragment

    const { ntml } = require('lit-ntml');
    const html = ntml({
      options: {
        parse: 'fragment', // or parse: false,
      },
    });
    const style = () => html`
    body {}
    main {}
    `;
    const main = () => html`<style>${style}</style>`;

License

MIT License © Rong Sen Ng

About

Inspired by lit-html but for Node.js

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 100.0%