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 🙏

© 2024 – Pkg Stats / Ryan Hefner

template-extensions

v0.7.3

Published

An umbrella of low-level JavaScript API's to process templates and HTML

Downloads

12

Readme

Template Extensions size npm version

  • Friendly HTML-based template syntax w/ efficient updates via DOM parts
  • Custom syntax with template processors
  • Clear HTML / JS separation
  • Progressive enhancement (SSR/SSG)
  • Based on web component spec proposals

Examples

Install

  • npm: npm i template-extensions
  • cdn: https://cdn.jsdelivr.net/npm/template-extensions

Basics

<template id="tpl">
  <div class="foo {{y}}">{{x}} world</div>
</template>

When passing the above template to the TemplateInstance constructor 2 DOM parts are created that represent {{x}} and {{y}} in the HTML. The second constructor argument is the state object (or params) that will fill in the values of the DOM parts.

import { TemplateInstance } from 'template-extensions';

const tplInst = new TemplateInstance(tpl, { x: 'Hello', y: 'bar'} /*, processor*/);
document.append(tplInst);

A TemplateInstance instance is a subclass of DocumentFragment so it can be appended directly to the DOM. In addition it gets a new update(params) method that can be called to efficiently update the DOM parts.

An optional third argument is de template processor. This hook allows you to handle each expression and DOM part in a specialized way. It allows you to write your own template language syntax, think Vue. Everything between the curly braces can be parsed and evaluated as you see fit.

Usage

Simple render

<template id="info">
  <section>
    <h1>{{name}}</h1>
    Email: <a href="mailto:{{email}}">{{email}}</a>
  </section>
</template>
<script type="module">
  import { TemplateInstance } from 'template-extensions';

  const params = { name: 'Ryosuke Niwa', email: '[email protected]' };
  const content = new TemplateInstance(info, params);
  document.body.append(content);
  // later on
  content.update({ name: 'Ryosuke Niwa', email: '[email protected]' });
</script>

Simple hydrate (Codesandbox)

Note that assigning a template instance to an existing element is only concerned about the structure and content leading up to the expressions. In the example below it's fine to skip the static text content of the paragraph in the template.

<div id="root">
  <section>
    <h1>Ryosuke Niwa</h1>
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
      tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
      veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
      commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
      velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
      cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
      est laborum.
    </p>
    Email: <a href="mailto:[email protected]">[email protected]</a>
  </section>
</div>
<template id="info">
  <section>
    <h1>{{name}}</h1>
    <p></p>
    Email: <a href="mailto:{{email}}">{{email}}</a>
  </section>
</template>
<script type="module">
  import { AssignedTemplateInstance } from 'template-extensions';

  const params = { name: 'Ryosuke Niwa', email: '[email protected]' };
  const content = new AssignedTemplateInstance(root, info, params);
  // later on
  content.update({ name: 'Ryosuke Niwa', email: '[email protected]' });
</script>

Template Processor

Default processor

The default processor looks a bit like the function below. Each time templateInstance.update(params) is called this function runs and iterates through each DOM part and evaluates what needs to happen.

function processCallback(instance, parts, params) {
  if (!params) return;
  for (const [expression, part] of parts) {
    if (expression in params) {
      const value = params[expression];
      if (
        typeof value === 'boolean' &&
        part instanceof AttrPart &&
        typeof part.element[part.attributeName] === 'boolean'
      ) {
        part.booleanValue = value;
      } else if (typeof value === 'function' && part instanceof AttrPart) {
        part.value = undefined;
        part.element[part.attributeName] = value;
      } else {
        part.value = value;
      }
    }
  }
}

The default processor handles property identity (i.e. part.value = params[expression]), boolean attribute or function.

const el = document.createElement('template')
el.innerHTML = `<div x={{x}} hidden={{hidden}} onclick={{onclick}}></div>`

const tpl = new TemplateInstance(el, { x: 'Hello', hidden: false, onclick: () => {} })
el.getAttribute('x') // 'Hello'
el.hasAttribute('hidden') // false
el.onclick // function

InnerTemplatePart

The InnerTemplatePart enables you to write conditionals and loops in your templates. The inner templates are expected to have a directive and expression attribute.

if

<template>
  <div>
    <template directive="if" expression="isActive">{{x}}</template>
  </div>
</template>

foreach

<template>
  <div>
    <template directive="foreach" expression="items">
      <li class={{class}} data-value={{value}}>{{label}}</li>
    </template>
  </div>
</template>
export const processor = {
  processCallback(instance, parts, params) {
    if (!params) return;
    for (const [expression, part] of parts) {
      if (part instanceof InnerTemplatePart) {
        switch (part.directive) {
          case 'foreach': {
            part.replace((params[expression] ?? []).map(item => {
              return new TemplateInstance(part.template, item, processor);
            }));
            break;
          }
        }
        continue;
      }
      // handle rest of expressions...
      if (expression in params) {
    }
  }
};

renderToString(html, params, processor=defaultProcessor)

Renders HTML with expressions and inner templates to a string. No JSDOM required.
It's possible to use the same template processor for client and server.

import { renderToString } from 'template-extensions/src/extras/ssr.js';

console.log(renderToString(`<div class="my-{{x}}-state {{y}}">{{z}}</div>`, {
  x: 'foo',
  y: 'bar',
  z: 'baz',
}))
// <div class="my-foo-state bar">baz</div>

Credit

Big thanks to everyone who contributed to the Template Instantiation and Dom Parts proposals.

The Template Instantiation and DOM parts code is based on the great work of Keith Cirkel and Dmitry Iv..

  • https://github.com/github/template-parts
  • https://github.com/github/jtml
  • https://github.com/dy/template-parts

Interfaces

export class AssignedTemplateInstance {
  constructor(
    container: HTMLElement | ShadowRoot,
    template: HTMLTemplateElement,
    params: unknown,
    processor?: TemplateTypeInit
  );
  update(params: unknown): void;
}

export class TemplateInstance extends DocumentFragment {
  constructor(
    template: HTMLTemplateElement,
    params: unknown,
    processor?: TemplateTypeInit
  );
  update(params: unknown): void;
}

type Expression = string;

type TemplateProcessCallback = (
  instance: TemplateInstance,
  parts: Iterable<[Expression, Part]>,
  params: unknown
) => void;

export type TemplateTypeInit = {
  processCallback: TemplateProcessCallback;
  createCallback?: TemplateProcessCallback;
};

export interface Part {
  value: string | null;
  toString(): string;
}

export class AttrPart implements Part {
  constructor(element: Element, attributeName: string, namespaceURI?: string);
  get element(): Element;
  get attributeName(): string;
  get attributeNamespace(): string | null;
  get value(): string | null;
  set value(value: string | null);
  get booleanValue(): boolean;
  set booleanValue(value: boolean);
  get list(): AttrPartList;
}

export class AttrPartList {
  get length(): number;
  item(index: number): AttrPart;
  append(...items): void;
  toString(): string;
}

export class ChildNodePart implements Part {
  constructor(parentNode: Element, nodes: Node[]);
  get parentNode(): Element;
  get value(): string;
  set value(string: string);
  get previousSibling(): ChildNode | null;
  get nextSibling(): ChildNode | null;
  replace(...nodes: Array<string | ChildNode>): void;
}

export class InnerTemplatePart extends ChildNodePart {
  constructor(parentNode: Element, template: HTMLTemplateElement);
  get directive(): string | null;
  get expression(): Expression | null;
}