nebbia
v3.0.6
Published
JavaScript Template literals (Template strings) compiler
Readme
Nebbia
Nebbia is a JavaScript Template literals (Template strings) compiler. It makes templates more expressive.
To improve reliability and maintainability the codebase has been migrated to TypeScript.
How it works?
Template literals are enclosed by the back-tick ` character instead of double or single quotes. Template literals can contain placeholders. These are indicated by the dollar sign and curly braces `${expression}`. The expressions in the placeholders and the text between them get passed to a function. The default function just concatenates the parts into a single string.
Template literals are useful, but placeholders can only contain expressions. The following example demonstrates the problem of string interpolation.
`${1 + 1}` // '2'
`${}` // SyntaxError: Unexpected token }
`${if (true) {}}` // SyntaxError: Unexpected token ifNebbia was created to help solve this problem. The compiler extends the capabilities of Template literals (Template strings) with the capabilities of standard JavaScript statements and declarations.
List of supported statements:
NOTE The
breakandcontinuestatements are supported only inside iteration blocks:for,for...in,for...of,while, anddo...while.
Nebbia was created to make template strings more forgiving. An empty expression ${} will not throw an exception. It supports closures and multiple nesting of expressions.
The compiled pattern does not use regular expressions.
Getting Started
Installation
To use Nebbia in your project, run:
npm i nebbiaNebbia is a Node.js® module with bundled TypeScript declarations. It is built for Node.js >=20.19.0 and targets ECMAScript 2023.
API docs
Table of Contents
nebbia(template)
template<String> The template source to compile. By default,'__string__'is the name of the internal variable used to concatenate strings. You can change this marker by assigning a value tonebbia.Node.unity.- returns: <String> Represents the
compiledtemplate strings of a node and its descendants. Template literals are enclosed by the back-tick`(grave accent).
template.html
<!DOCTYPE html>
<html>
<head>
<title>Nebbia</title>
</head>
<body>
<h1>${h1}</h1>
${if (list instanceof Array) {
<header></header>
${for (let i of list) {
<div>${i}</div>
}}
}
if (footer) {
<footer></footer>
}}
</body>
</html>template.txt
${if (user === 'admin') {
📆 To-do list
for (let i of list) {
- ${i.date} ${i.note}
}
}
else {
Offer a cup of coffee!
}}A JavaScript template literal inside a statement is processed by the parser as is.
${if (arg === `;)`) {
<p>Maybe it's a smile</p>
}}This is useful when you need to escape arguments in a statement. Otherwise, the parser will read the statement like arg === ;.
Statements by category
Nebbia uses JavaScript-like statement syntax to compile template strings. Several instructions can be in the same expression. Spaces and tabs are not taken into account by the parser.
if
import nebbia from 'nebbia';
const template = '${if (arg === true) {<p>${arg}</p>}}';
const invoke = new Function('arg', 'return ' + nebbia(template));
invoke(true); // <p>true</p>if...else
import nebbia from 'nebbia';
const template = '${if (arg === true) {<p>${arg}</p>} else {<p>else</p>}}';
const invoke = new Function('arg', 'return ' + nebbia(template));
invoke(false); // <p>else</p>if...else if
import nebbia from 'nebbia';
const template = '${if (arg === 1) {<p>one</p>} else if (arg === 2) {<p>two</p>} else {<p>else</p>}}';
const invoke = new Function('arg', 'return ' + nebbia(template));
invoke(2); // <p>two</p>for
import nebbia from 'nebbia';
const template = '${for (let i = 0; i < count; i++) {<p>${i}</p>}}';
const invoke = new Function('count', 'return ' + nebbia(template));
invoke(2); // <p>0</p><p>1</p>for...in
import nebbia from 'nebbia';
const template = '${for (let i in obj) {<p>${i}</p>}}';
const invoke = new Function('obj', 'return ' + nebbia(template));
invoke({
fruit: 'apple',
cart: 1
}); // <p>fruit</p><p>cart</p>for...of
import nebbia from 'nebbia';
const template = '${for (let i of list) {<p>${i}</p>}}';
const invoke = new Function('list', 'return ' + nebbia(template));
invoke([ 0, 1 ]); // <p>0</p><p>1</p>while
import nebbia from 'nebbia';
const template = '${while (list.length > 0) {<p>${list.pop()}</p>}}';
const invoke = new Function('list', 'return ' + nebbia(template));
invoke([ 0, 1 ]); // <p>1</p><p>0</p>do...while
import nebbia from 'nebbia';
const template = '${do {<p>${arg}</p>} while (arg-- > 0)}';
const invoke = new Function('arg', 'return ' + nebbia(template));
invoke(1); // <p>1</p><p>0</p>break
import nebbia from 'nebbia';
const template = '${for (let i = 0; i < list.length; i++) {${if (list[i] === stop) {${break}}}<p>${list[i]}</p>}}';
const invoke = new Function('list', 'stop', 'return ' + nebbia(template));
invoke([ 1, 2, 3 ], 3); // <p>1</p><p>2</p>continue
import nebbia from 'nebbia';
const template = '${for (let i = 0; i < list.length; i++) {${if (list[i] === skip) {${continue}}}<p>${list[i]}</p>}}';
const invoke = new Function('list', 'skip', 'return ' + nebbia(template));
invoke([ 1, 2, 3 ], 2); // <p>1</p><p>3</p>class Node
Node is the base AST class used by the compiler tree. Concrete node classes inherit its tree fields and methods.
The following classes inherit from Node’s methods and properties: Expression, Statement and Text.
static: Node.unity
<String> Returns the string concatenation keyword. Public export: nebbia.Node.unity. Default: '__string__'.
constructors
The base Node class is abstract in TypeScript. Use concrete node classes: Expression, Statement, and Text. They initialize default node instance values inherited from Node.
node.append(child)
Adds the specified node argument as the last child to the current node.
node.build()
- returns: <String> Represents the
compiledtemplate strings of a node and its descendants. Template literals are enclosed by the back-tick`(grave accent).
node.children
- <Array> Contains all the children of this
node.
node.name
- <String|null> Contains the name of the
node. The structure of the name will differ with thenodetype. E.g. A Statement will contain the name of the corresponding statement, a Text node will have the#textstring. Default:null.
node.parent
- <Node|null> Returns a
nodethat is the parent of thisnode. If there is no suchnode, for example when this node is the top of the tree or does not participate in a tree, this property returnsnull.
node.type
<Number|null> Returns an unsigned short representing the type of the node. Default: null.
Possible values are:
| Name | Value | |---------------------------------|:-----:| | Expression | 0 | | Text | 1 | | Statement | 2 |
node.value
<String> Returns the value of the current node.
class Expression
Represents a group of nodes resulting from parsing an expression into Statement and Text nodes.
class Text
Represents the textual content.
class Statement
Contains the name of the statement. The condition is stored in the value of the node.
nebbia.parse(template)
template<String> The template source to parse. By default,'__string__'is the name of the internal variable used to concatenate strings. You can change this marker by assigning a value tonebbia.Node.unity.- returns: <Expression> Returns the root expression node. The returned AST gives programmatic access to the template string structure.
An example of parsing the template:
template.html
<div>
${if (typeof value === 'string') {
<p>${value}</p>
}}
</div>index.js
import fs from 'fs';
import nebbia from 'nebbia';
const content = fs.readFileSync('./template.html');
const ast = nebbia.parse(content);
const template = ast.build();const ast:
const template:
`<div>
${((__string__)=>{if(typeof value === 'string')__string__+=`
<p>${value}</p>
`;return __string__})(``)}
</div>
`Development
Planned:
- Measure benchmark.
