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

knap

v0.2.2

Published

A flexible template engine for creating Markdown.

Readme

Knap

Knap is a flexible template engine for creating Markdown. It is shared by Obsidian tools, including Web Clipper and Importer.

Knap provides tokenization, parsing, logic, rendering, structured errors, and filters. Applications supply variables and runtime integrations. Knap uses an AST interpreter. It does not use eval or execute arbitrary JavaScript.

The Obsidian Web Clipper documentation includes examples of Knap's shared logic, filters, and variable syntax.

Install

pnpm add knap

Use

import {
	createEngine,
	standardFilters,
	type TemplateVariables,
} from 'knap';

const engine = createEngine({ filters: standardFilters });

const variables: TemplateVariables = {
	title: '  An imported note  ',
	tags: ['reference', 'reading'],
};

const result = await engine.render(
	`# {{ title | trim }}

{% if tags %}Tags: {{ tags | join:", " }}{% endif %}`,
	{ variables },
);

if (result.errors.length === 0) {
	console.log(result.output);
}

Every error contains a stable code, message, line, and column. Use renderOrThrow() when exceptions fit the calling application better:

const output = await engine.renderOrThrow('{{ title | upper }}', { variables });

Filters that deliberately preserve their input after invalid runtime data can report non-fatal diagnostics in result.warnings. Each warning includes a stable code, message, filter, line, and column. Warnings do not make renderOrThrow() throw. Identical warnings from repeated evaluation of the same filter expression are deduplicated within each render.

Syntax

The syntax is inspired by Twig and Liquid.

{{ title }}
{{ title | upper }}
{{ published | date:"YYYY-MM-DD" }}
{{ First name | trim }}

{% if author %}
By {{ author.name }}
{% elseif site %}
From {{ site }}
{% else %}
Unknown source
{% endif %}

{% for item in links %}
- {{ item.title }}: {{ item.url }}
{% endfor %}

{% set heading = title | upper %}

The language supports chained filters, if/elseif/else, for, set, nested properties, array access, comparisons, boolean operators, nullish fallbacks, and whitespace control.

Application variables

Values absent from the variables object can be resolved asynchronously. Knap does not know what a browser tab, vault, selector, schema, or model is.

const result = await engine.render('{{ remoteValue | upper }}', {
	variables: {},
	context: { documentId: 'example' },
	resolveVariable: async (name, { context }) => {
		if (name === 'remoteValue') {
			return loadValue(context.documentId);
		}
		return undefined;
	},
});

Local variables take precedence over the resolver.

Filters

Filters are registered explicitly when an engine is created. The standard registry is available as standardFilters; DOM-dependent filters are available separately from knap/html.

Parameters follow a filter name after a colon, and filters can be chained with |:

{{ title | trim | upper }}
{{ published | date:"YYYY-MM-DD" }}

Standard filters

| Filter | Purpose | | --- | --- | | blockquote | Format text as a Markdown block quote. | | calc | Apply a basic arithmetic operation to a number. | | callout | Format content as an Obsidian callout. | | camel, kebab, pascal, snake | Convert text to the named casing style. | | capitalize, lower, title, upper | Change text capitalization. | | date | Parse and format a date. | | date_modify | Add or subtract a date unit. | | decode_uri | Decode percent-encoded URI text. | | duration | Format ISO 8601 durations or a number of seconds. | | first, last, nth, slice | Select values or ranges from arrays and text. | | footnote | Format values as Markdown footnotes. | | fragment_link | Add text-fragment links using a source URL parameter. | | image | Format a URL as a Markdown image. | | join, split | Join arrays or split strings. | | length | Return the length of a value. | | link, wikilink | Format Markdown links or Obsidian wikilinks. | | list | Format array-like data as a list. | | map | Map fields from structured array data. | | merge | Merge structured values. | | number_format, round | Format or round numeric values. | | object | Select or reshape structured object data. | | remove_attr, strip_attr | Remove selected HTML attributes or all except selected attributes. | | remove_tags, strip_tags | Remove selected HTML tags or all except selected tags. | | replace | Apply one or more text or regular-expression replacements. | | replace_tags | Replace selected HTML tag names. | | reverse, unique | Reverse or deduplicate array-like data. | | safe_name | Sanitize text for use as a file name. | | strip_md, stripmd | Remove Markdown formatting. | | table | Format structured data as a Markdown table. | | template | Apply a small value-substitution template to structured data. | | trim | Remove surrounding whitespace. | | uncamel | Convert camel-cased text into words. | | unescape | Unescape encoded text. | | yaml | Format a value as a YAML-safe scalar. |

Filter metadata, including parameter validation and examples, is exported as standardFilterMetadata. Invalid filter names and invalid parameters are reported by engine.validate() and engine.render().

When a filter cannot use runtime input but preserves that input for compatibility, engine.render() reports a non-fatal structured warning. This includes values such as an unparseable date or an invalid regular expression.

HTML preset

Import htmlFilters from knap/html to enable:

| Filter | Purpose | | --- | --- | | html_to_json | Convert HTML elements into structured JSON values. | | remove_html | Remove selected HTML elements and their contents. |

These filters are opt-in because they require browser-compatible DOM globals such as DOMParser; they do not ship in the root runtime graph:

import { createEngine, standardFilters } from 'knap';
import { htmlFilters } from 'knap/html';

const engine = createEngine({
	filters: {
		...standardFilters,
		...htmlFilters,
	},
});

Custom filters

The engine's filter registry is used for both validation and rendering. Filter parameters use Knap's colon-delimited parameter syntax.

import {
	createEngine,
	standardFilters,
	type TemplateFilter,
} from 'knap';

const markdown: TemplateFilter = (html, param, context) => {
	const baseUrl = param?.replace(/^(['"])(.*)\1$/s, '$2');
	return convertToMarkdown(html, baseUrl, context);
};

markdown.metadata = {
	example: 'markdown:"https://example.com"',
	validateParams: param => ({
		valid: Boolean(param),
		error: 'requires a base URL',
	}),
};

const engine = createEngine({
	filters: {
		...standardFilters,
		markdown,
	},
});

Custom filters may be asynchronous. A filter can also report a non-fatal diagnostic while returning a fallback value:

const lookup: TemplateFilter = async (value, _param, context) => {
	const result = await findValue(value);
	if (result === undefined) {
		context?.reportWarning?.({ message: `Could not find ${value}` });
		return value;
	}
	return result;
};

The filter parameter is passed in its serialized Knap form so filters that accept multiple parameters can preserve delimiters and quoting. A custom filter that expects one scalar parameter can normalize surrounding quotes as above.

Host data needed by a custom filter belongs in the generic engine context:

type HostContext = { sourceUrl: string };

const sourceLink: TemplateFilter<HostContext> = (value, _param, filterContext) => {
	return `[${value}](${filterContext?.context?.sourceUrl})`;
};

const engine = createEngine<HostContext>({
	filters: { ...standardFilters, source_link: sourceLink },
});

await engine.render('{{ title | source_link }}', {
	variables: { title: 'Knap' },
	context: { sourceUrl: 'https://example.com' },
});

API

  • createEngine({ filters }) creates an immutable engine-scoped registry.
  • engine.render(template, input, options?) returns output, structured errors, and non-fatal warnings.
  • engine.renderOrThrow(template, input, options?) returns output or throws TemplateRenderError.
  • engine.parse(template) returns the AST and parser diagnostics.
  • engine.validate(templateOrAst) validates syntax and the configured filters.
  • tokenize(template), parse(template), validateVariables(ast), and validateFilters(ast, metadata) support editor tooling.
  • standardFilters contains environment-neutral filters.
  • standardFilterMetadata describes the standard registry for standalone validation.
  • applyFiltersWithRegistry(value, filterString, registry, context) applies a filter chain when a host needs filter syntax outside a full render.
  • htmlFilters is available from knap/html.

Development

pnpm install
pnpm check

Knap is available under the MIT License.