npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nerve-templates

v0.0.9

Published

writing HTML templates as Javascript data structures

Readme

Nerve

Write HTML templates as Javascript data structures. Use CSS selectors to describe DOM elements in a hierarchical style like JSON.

WIP Warning: Nerve is a work in progress. Use at your own risk.

Give me

npm install nerve-templates

The nerve object is a simple object containing methods designed to be used together. It is designed to be mixed into another component or view library, but its API can also be used independently.

A browserified core.bundle.js file is supplied that can be loaded client-side.

The Nutshell

Take something like this, where the keys of an object literal are CSS selectors, and the values may be objects, arrays, or strings for interpolation or otherwise:

var template = { 'div.some-class-1': {
		'ul.my-list[data-attr="custom"]': [
			{
				li: 'Favorite Show: ~~foo~~'
			},
			{
				li: 'Other Favorite Show: ~~bar~~'
			}
		]
	}
}

Turn it into an HTML template we could hand to something like Underscore, assuming that double tildes mark variable interpolation:

var string = nerve.stringify.normalized( nerve.normalize( template ) );

// string outputs: '<div class="some-class-1"><ul class="my-list" data-attr="custom"><li>Favorite Show: ~~foo~~</li><li>Other Favorite Show: ~~bar~~</li></div>'

// using underscore's _.template
var render = _.template( string );
var html = render({
	foo: 'Bob\'s Burgers',
	bar: 'Archer'
});

// our stringified html:
<div class="some-class-1">
    <ul class="my-list" data-attr="custom">
        <li>Favorite Show: Bob's Burgers</li>
        <li>Other Favorite SHow: Archer</li>
    </ul>
</div>

Why?

Writing templates as Javascript lets us embed them in Components or Views without string concatenation or importing them as HTML files.

HTML syntax (XML) isn't fun to write by hand (/opinion).

CSS selectors represent DOM elements well in a terse syntax that is expressive and well understood by many developers and designers. JSON easily models the nested hierarchy of HTML/XML.

Expressing templates as Javascript opens up other possibilities:

  • APIs for templates. Mutate the template without touching the DOM.
  • Server side rendering.
  • No transpiling.
  • Swap in parsing modules for things that aren't HTML.

For science, and fun.

The Pain

{
	div#someId: 'fubar'
}

// Syntax Error!

We can get around the characters in CSS selectors that we can't write in the keys of an object literal by wrapping them in quotes, explicitly stating that the key is a string.

{
	'div#someId': 'fubar'
}

// <div id="someId">fubar</div>

We can't use the same key twice in an object literal. In an object like this, only the last key gets saved:

c = {
	li: 'foo',
	li: 'wat',
	li: 'bar'
}

// c.li outputs 'bar', the last key overrules any duplicates

We can use arrays to get around this if we need multiple elements with the same selector.

c = [
	{ li: 'foo' },
	{ li: 'bar' }
]

// <li>foo</li><li>bar</li>

#Nested Structures A test structure as complex as this:

testStructure = [{
	'#a.foo[data-val=1][data-val="a"]': {
		'#b.bar[data-val=2]': {
			'#c.baz.banksy[data-val=3]': 'hello World!'
		},

		'#d.bar[data-val=4]': {
			'#e.baz.banksy[data-val=5]': [{
					'div': 'blah'
				}, {
					'div': 'blah'
				}, {
					'div': 'blah'
				}, {
					'span': 'this is just a test template'
				}, {
					'div#amazing.my-other-class': 'a series of nested elements…'
				}, {
					'nav': {
						'span': 'this is a nested child'
					}
				}, {
					'div': 'blah'
				}
			]
		}
	}
}]

Will yield:

<div id="a" class="foo" data-val="1" data-val="a">
    <div id="b" class="bar" data-val="2">
        <div id="c" class="baz banksy" data-val="3">hello World!</div>
    </div>
    <div id="d" class="bar" data-val="4">
        <div id="e" class="baz banksy" data-val="5">
            <div>blah</div>
            <div>blah</div>
            <div>blah</div><span>this is just a test template</span>
            <div id="amazing" class="my-other-class">a series of nested elements…</div>
            <nav><span>this is a nested child</span></nav>
            <div>blah</div>
        </div>
    </div>
</div>

Inline Scripting

Templates need logic. What if we use functions to embed logic in our templates?

template = {
	'div.foo': function() {
		if (this.data) {
			return {
				span: this.data
			}
		} else {
			return {
				span: 'not sure'
			}
		}
	}
}

nerve.stringify.normalized( nerve.normalize(template) ); 

Outputs:

<div class="foo"><span>not sure</span></div>

The methods in the nerve object are designed to be mixed into a larger component/view library. Let's assume such a library. Give a Component instance where we want to render some state object:

var myTest = new Component({
	data: true,

	template: {
		'div#test': function() {
			if (this.data) {
				return {
					span: this.data
				}
			} else {
				return {
					span: 'nevermore'
				}
			}
		}
	}
});

myTest.stringify.normalized( myTest.normalize( myTest.template ) );

Should output as a string:

<div class="test">true</div>

Functions run during a template's normalization, but bind "this" to a parent instance. This allows the template to grab properties from the instance, or other variables in scope, by reference.

It also offers a workaround for dealing with context in nested objects.

var myTest = new Component({
	data: 'my nested datum',

	template: {
		div: {
			div: {
				div: function() {
					return this.data
				}
			}
		}
	}
});

myTest.stringify.normalized( myTest.normalize( myTest.template ) );

// <div><div><div>my nested datum</div></div></div>

The idea is give the developer the full power of Javascript within their templates. Logic gets run when a template is normalized. Since we stringify after that, we don't need underscore-style microtemplates or helper methods.

Interpolating Data in Keys

Let's still assume our psuedo-Component library. What if we want to render a custom attribute with some dynamic data property?

Assuming that we use double brackets for marking references for interpolation:

var myTest = new Component({
	data: {
		id: 'my-custom-id'
	},

	template: {
		'div#<<this.data.id>>': 'should have a unique id'
	}
});

myTest.stringify.normalized( myTest.normalize( myTest.template ) );

Should output as a string:

<div id="my-custom-id">should have a unique id</div>

This is particularly useful for rendering image "src" attributes and other potentially dynamic properties.

API

Core

nerve.normalize( templateStruct )

Parse a nested structure and return a more accessible version of itself that we can manipulate or stringify.

var templateStruct = {
	'div#hashTag.wow.much-css[data-has="aValue"]': 'something neat'
}

// outputs:
[
	{
		attrs: [
			{ attrKey: 'data-has' },
			{ attrVal: 'aValue' }
		],
		classes: [
			'wow', 'much-css'
		],
		id: 'hashTag',
		inner: 'something neat',
		tagName: 'div',
		type: 'html'
	}
]

Nested structures are recursively placed in the inner property, which is either an array or a string. The custom 'type' property is used for easier type checking. We can also use it to check for custom types like components.

nerve.stringify

Container for stringification methods.

nerve.stringify.normalized( normalizedStruct )

Takes a normalized structure (see above) and outputs the html equivalent. It relies on the rest of the stringification module, and is the main entrance point for stringifying a nested object.

nerve.parse, nerve.parse.css

Containers for parsing CSS selectors and other template schemes.

nerve.interpolate

Used to render in-context variable references in keys when demarked with double brackets (eg. <<this.data.foo>>). Custom syntaxes may be supported in the future.

Todos

Testability. Expose edge cases. Keep functions isolated and modularized. Parsing configuration / alternate syntaxes.

License

MIT