Inspired by lit-html but for Node.js.
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. 👍💯
-
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 withocd
set totrue
(default) orfalse
. - Compatible for ES Modules (
import ntml from 'ntml'
) and CommonJS (const { ntml } = require('ntml');
). - Parses
PromiseList
orList
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
# Install via NPM
$ npm install lit-ntml
- Install vscode-lit-html extension.
- 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.
/** 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> */
/** 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> */
/** 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
const { ntml } = require('ntml');
(async () => {
const html = ntml();
const rendered = await html`<div>haha</div>`;
console.log('#', rendered);
/**
* <div>haha</div>
*/
})();
{
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,
}
minify
<?boolean> Optional minification flag. If true, minify rendered HTML string. Defaults tofalse
.options
<?Object> Optional settings.minify
: <?Object> Optional htmlMinifer flags. Defaults to default_minify_options.parse
: <?string|?boolean> Optional parser flag. Defaults tofragment
. Available options:html
ortrue
Parse content as HTML string.fragment
orfalse
Parse content as HTML fragment string.
pretty
<?Object> Optional pretty flag. Defaults to{ ocd: true }
.
options
<?NtmlOpts> Optional configuration for the templating.- returns: <Promise<string>> Promise which resolves with rendered HTML string.
Writing CSS styles outside of HTMLStyleElement can lead to unexpected parsing behavior, such as:
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:
-
Wrap with any valid HTML element tag
const style = () => html` <style> body {} main {} </style>`;
-
Make sure
options[parse]
is set tofalse
orfragment
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>`;
MIT License © Rong Sen Ng