Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
@itrocks/template
Advanced tools
The W3C-valid, browser-previewable, concise, and fast HTML template engine that enables delimiter-less translations
The W3C-valid, browser-previewable, concise, and fast HTML template engine that enables delimiter-less translations.
npm i @itrocks/template
console.log(
new Template({
users: [
{ age: 10, name: 'kid' },
{ age: 20, name: 'old-timer' }
]
})
.parseBuffer(`
<ul>
<!--users-->
<li>{name} is {age} years old</li>
<!--end-->
</ul>
`)
)
Result:
<ul>
<li>kid is 10 years old</li>
<li>old-timer is 20 years old</li>
</ul>
You can also parse a template file:
console.log(await new Template(data).parseFile('template.html'))
The template.html
template file will validate W3C and display well in your browser:
<!DOCTYPE HTML>
<html lang="en">
<head><title>Title</title></head>
<body>
<!--BEGIN-->
<ul>
<!--users-->
<li>{name} is {age} years old</li>
<!--end-->
</ul>
<!--END-->
</body>
</html>
Since the engine supports asynchronous operations (e.g., reading files, calling async functions, resolving async data), parsing returns a Promise that you should handle with await.
Templates are designed to pass W3C validation, even when handling complex dynamic data. This ensures your final HTML is as close as possible to valid, standard-compliant markup.
You can write templates that remain fully readable and functional when opened directly in a browser. This allows you to validate your layout and scripts before injecting dynamic data. The dynamic portions are minimally intrusive, ensuring a clean HTML structure that can be tested as-is.
The template syntax is designed to be as concise and unobtrusive as possible. You can often use template delimiters in standard HTML without needing to escape them. For example:
The engine can detect literal text content in the HTML without requiring special markers. This makes it straightforward to integrate translation systems (via external plugins). You can write your pages in your primary language and apply translations automatically later without losing readability.
Simple Expressions: Use braces {expression}
.
If a brace is followed by a space or an unrecognized special character, it is not treated as a delimiter. This allows you to use braces in JavaScript code blocks inside your HTML without escaping.
Example:
<span>{user.name}</span>
Blocks (Conditions, Loops, Descent): Use HTML comment blocks <!--expression-->
and <!--end-->
.
If the opening comment <!--
is followed by a space or an unrecognized character, it won't be treated as a delimiter.
This allows you to maintain normal comments in your HTML as long as they are spaced, e.g., <!-- comment -->
.
Example:
<ul>
<!--users-->
<li>{name}</li>
<!--end-->
</ul>
Use braces {}
as normal, except in attributes
that reference files or URLs where W3C validation forbids braces.
In these cases, use parentheses ()
.
Affected attributes: action, formation, href, and src.
Example:
<a href="app://(link)">Follow the white rabbit</a>
<span data-property="{property.name}">{property.value}</span>
Given data:
{ link: '/exit/the/matrix', property: { name: 'voyager', value: 'Neo' } }
Results in:
<a href="/exit/the/matrix">Follow the white rabbit</a>
<span data-property="voyager">Neo</span>
app:// Prefix for Href-like Attributes:
Using app://
before a dynamic link can prevent IDE-integrated validators from complaining about missing files.
app://
will be stripped out at runtime.
Inside <script>
tags, {expression}
and <!--expression-->
are never interpreted.
If you need dynamic data in your script, store it in the DOM or in an attribute.
Example:
<script> console.log('{name}') </script>
With data { name: 'Nick' }
, the script still logs {name}
, unchanged:
<script> console.log('{name}') </script>
Another example:
<script data-name='{name}'> console.log(document.currentScript.dataset.name) </script>
Results with console output Nick
:
<script data-name='Nick'> console.log(document.currentScript.dataset.name) </script>
Note: {expression}
will still be interpreted in attributes that might contain JavaScript code
if not properly spaced.
Wrap a property in {}
to display its value:
console.log(
new Template({ var: 15 }).parseBuffer('<span>{var}</span>')
)
Result:
<span>15</span>
If a context property is a function, it will be called and its return value displayed:
console.log(
new Template({ var: () => 15 }).parseBuffer('<span>{var}</span>')
)
Result:
<span>15</span>
Use {.}
to reference the current data context:
console.log(new Template(15).parseBuffer('<span>{.}</span>'))
Result:
<span>15</span>
Prefix the expression with ?
to remove the attribute
if the value is falsy:
<span font-style="color:{?color};">{name}</span>
With data { color: 'red', name: 'Bad cop' }
:
<span font-style="color:red;">Bad cop</span>
With data { name: 'Good cop' }
(no color):
<span>Good cop</span>
End your block expression with ?
to render the block only
if the value is truthy:
<span><!--user?-->{user.name} is {user.age} years old<!--end--></span>
Result on empty data {}
:
<span></span>
Result on data { user: { age: 10, name: 'kid' } }
:
<span>kid is 10 years old</span>
You can use dot notation within {your.expression}
:
console.log(
new Template({ user: { age: 10, name: 'kid' } })
.parseBuffer('<span>{user.name} is {user.age} years old</span>')
)
Or use a block to avoid repeating:
console.log(
new Template({ user: { age: 10, name: 'kid' } })
.parseBuffer('<span><!--user-->{name} is {age} years old<!--end--></span>')
)
Both produce:
<span>kid is 10 years old</span>
Use *
to iterate over all values of an object:
console.log(
new Template({ object: { first: 'kid', next: 'old-timer' } })
.parseBuffer('<ul><!--object.*--><li>{.}<!--end--></ul>')
)
Result:
<ul><li>kid</li><li>old-timer</li></ul>
console.log(
new Template({ users: ['kid', 'old-timer'] })
.parseBuffer('<ul><!--users--><li>{.}</li><!--end--></ul>')
)
Result:
<ul><li>kid</li><li>old-timer</li></ul>
console.log(
new Template({
users: [
{ age: 10, name: 'kid' },
{ age: 20, name: 'old-timer' }
]
})
.parseBuffer(`
<ul>
<!--users-->
<li>{name} is {age} years old</li>
<!--end-->
</ul>
`)
)
Result:
<ul>
<li>kid is 10 years old</li>
<li>old-timer is 20 years old</li>
</ul>
Use -
to go back up one level in the data context:
console.log(
new Template({ name: 'Eddie', data: { age: 30, status: 'well' } })
.parseBuffer(`
<!--data-->
<ol>
<li>name: {-.name}</li>
<li>age: {age}</li>
<li>status: {status}</li>
</ol>
<!--end-->
`)
)
Result:
<ol><li>name: Eddie</li><li>age: 30</li><li>status: well</li></ol>
Values in quotes inside {}
are treated as literals:
<span>This is a {'user'}</span>
Result:
<span>This is a user</span>
If a descendant property or method isn't found on the current data object, the engine attempts to use the Str helper object, which provides string formatting functions. If no matching function is found, an error is thrown.
console.log(
new Template({ name: 'EDDIT' })
.parseBuffer('<span>{name.lcFirst}</span>')
)
Result:
<span>eDDIE</span>
Any expression starting with ./
or ../
is considered a template include:
<div>
{./another-template.html}
</div>
To keep templates W3C-compliant and browser-viewable, each HTML template may contain full HTML structure.
When including a template into another, you can specify what portion should be rendered using
<!--BEGIN-->
and <!--END-->
.
<!DOCTYPE html>
<html lang="en">
<head><title>Title of the template</title></head>
<body>
<!--BEGIN-->
<span>Content here</span>
<!--END-->
</body>
</html>
BEGIN
, END
, and end
are reserved keywords for delimiters and cannot be used as simple expressions or block names.
@itrocks/template can parse HTML content to identify literal text, making i18n integration easier. You can apply translations via a plugin without having to mark texts explicitly. Composite translations, where certain parts of a phrase are replaced by translated elements or non-literal data, are supported.
Example of a custom template class that applies translations:
import Template from '@itrocks/template'
const TRANSLATIONS: Record<string, string> = {
'What is my name': 'Quel est mon nom',
'My name is $1': 'Mon nom est $1',
'My $1 is $2': 'Mon $1 est $2',
'name': 'nom de famile'
}
class MyTemplate extends Template
{
doLiteral = true
applyLiterals(text: string, parts: string[] = [])
{
return TRANSLATIONS[text].replace(
/\$([0-9]+)/g,
(_, index) => TRANSLATIONS[parts[+index]]
)
}
}
Using this class:
console.log(
new MyTemplate({ name: 'Nick' })
.parseBuffer(`
<h2>What is my name</h2>
<p>My name is {name}</p>
`)
)
Results in:
<h2>Quel est mon nom</h2>
<p>Mon nom est Nick</p>
Inline HTML elements are considered part of the phrase, so their text is also translated:
console.log(
new MyTemplate({ name: 'Nick' })
.parseBuffer(`
<h2>What is my name</h2>
<p>My <span>name</span> is {name}</p>
`)
)
Results in:
<h2>Quel est mon nom</h2>
<p>Mon <span>nom de famille</span> est Nick</p>
This approach ensures translations occur without complex template markers, preserving the natural readability of the original HTML.
What are event hooks?
Event hooks are optional callback functions you can provide to the template engine to monitor and react to parsing events. They are useful for debugging, logging, or customizing behavior during template processing.
Available Hooks:
onAttribute(name: string, value: string): void
Called each time the parser reads an attribute.
Use this to log attribute parsing, enforce certain naming conventions, or filter attributes dynamically.
onTagOpen(name: string): void
Called as soon as a tag is detected (after the <
but before reading its attributes).
Useful for tracking which tags are encountered and in what order.
onTagOpened(name: string): void
Called once all attributes of a tag have been processed and the tag is considered "opened."
You can use this to finalize any logic that depends on knowing the full set of attributes.
onTagClose(name: string): void
Called just before closing a tag.
Helpful to validate the structure of your HTML or to capture any final metadata before the tag is completed.
What are variable parsers?
In addition to normal object property access and function calls, you can define custom parsers to handle variables based on specific prefixes. This gives you flexibility to extend the template engine to support custom syntaxes, transformations, or data lookups.
How to define a custom parser:
A parser is a tuple [prefix, callback]
, where:
prefix
is a single-character string that identifies when your parser should be invoked.callback(variable: string, data: any): any
is the function that transforms the variable name into a value.For example, you could define a parser that, when it sees a variable starting with @
,
fetches data from an external API or applies a custom transformation.
Usage Example:
const myParser: VariableParser = [
'@',
(variable, data) => {
const key = variable.substring(1) // remove '@'
return fetchFromCustomDataSource(key)
}
]
template.parsers.push(myParser)
When the template encounters something like {@userId}, it will call myParser to resolve it.
By default, the template engine attempts to interpret {...}
and <!--...-->
blocks as expressions or directives.
If you disable doExpression
, the engine will treat them as literal text and not attempt to parse them.
template.doExpression = false
if you only want to read the template as-is without any dynamic substitution.When doLiteral
is set to true
, the template engine enters a mode that carefully analyzes which parts of the template
are textual literals, making it possible to apply advanced transformations such as translation or other textual
operations without additional markers.
template.doLiteral = true
if you want the engine to identify literal strings and apply transformations like
internationalization easily.<link>
and <script>
Tags from Included TemplatesWhen you include another template inside your main template, you may have resources
(like <link>
or <script src="...">
tags) defined in the <head>
of the included template.
If these tags are placed outside of the <!--BEGIN-->
and <!--END-->
markers,
the engine will automatically propagate them to the parent template's <head>
section.
How it works:
<link>
, <script>
), if found outside of <!--BEGIN-->
/<!--END-->
,
are added to a buffer.<head>
is updated to include these resources,
ensuring that all required styles and scripts from included templates are present in the final output.This means you can maintain a single source of truth for your resources in each template and have them automatically merged into the final HTML, making includes more modular and easier to manage.
Beyond simply inserting the content of another template, you can:
containerFileName
parameter when calling parseFile
to define a "container" template that wraps the
included content.Example:
await new Template({ name: 'Nick' })
.parseFile('child-template.html', 'container-template.html')
If container-template.html
expects a content() function or a data placeholder to insert the child-template,
the engine will handle the injection automatically.
What is debugEvents()?
debugEvents()
is a utility method that sets default hooks (onAttribute, onTagOpen, onTagOpened, onTagClose)
to print out parsing details to the console. It's extremely useful for diagnosing issues during template parsing:
How to use:
const template = new Template(data)
template.debugEvents()
await template.parseBuffer(yourTemplateString)
As the template is parsed, the console will show events like:
attribute name = value
tag.open = div
tag.opened = div
tag.closed = div
This allows you to see exactly how the parser is interpreting your template, helping you quickly locate any unexpected parsing behavior.
Str
helpers and custom parsers),onElement
events,FAQs
The W3C-valid, browser-previewable, concise, and fast HTML template engine that enables delimiter-less translations
The npm package @itrocks/template receives a total of 266 weekly downloads. As such, @itrocks/template popularity was classified as not popular.
We found that @itrocks/template demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.