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

@chilts/weave

v0.4.0

Published

A Pug-inspired templating engine that compiles to TypeScript.

Readme

Weave

A Pug-inspired templating engine that compiles to TypeScript. Write indentation-based templates, get typed functions that return HTML.

views/*.weave  →  weave compiler  →  lib/views/*.ts  →  tsc

Why Weave?

Pug is great. Weave takes the same indentation-based syntax and makes it TypeScript-native — your templates become typed functions, checked by tsc like any other code.

  • Type-safe templates — template parameters are real TypeScript types
  • Type-safe mixins — positional args with TS type annotations
  • Tailwind-friendly — class names with colons (.md:flex, .hover:bg-blue-500) just work
  • Single-pass escaping — values are escaped once at render time, never pre-escaped
  • No runtime template engine — compiled output is plain TypeScript

Example

A type definition:

export interface SimpleProps {
    title: string
    content: string
}

A .weave template:

import type { SimpleProps } from '../types/views.js'

template simple({ title, content }: SimpleProps)
  doctype html
  html(lang="en")
    head
      title= title
    body
      h1= title
      p= content

Compiles to a .ts file:

import { h, doctype } from "@chilts/weave"
import type { SimpleProps } from '../types/views.js'

export function simple({ title, content }: SimpleProps) {
    return doctype()
        + h("html", { lang: "en" },
            h("head", {},
                h("title", {}, title)
            ),
            h("body", {},
                h("h1", {}, title),
                h("p", {}, content)
            )
        )
}

Now tsc catches type errors, your IDE gives you autocomplete, and calling simple({ title: "Hello", content: "World" }) returns HTML.

Usage

Templates are just functions that return strings, so they work with any framework:

import express from 'express'
import { simple } from './lib/views/simple.js'

const app = express()

app.get('/', (req, res) => {
    res.send(simple({ title: "Hello", content: "World" }))
})

app.listen(3000)

Syntax

Weave's syntax will be familiar if you've used Pug.

Elements, classes, IDs, and attributes

h1 Hello World
p.intro This is a paragraph with a class
.container
  span#main.text-lg.md:text-xl Content here
a(href="/about" class="nav-link") About

Conditionals

if role === "admin"
  p.badge You are an administrator
else if role === "editor"
  p.badge You are an editor
else
  p Welcome, visitor

Iteration

for ( const post of posts )
  h2= post.title
  p= post.content

Mixins

Mixins use positional args. An implicit attrs parameter captures any extra attributes passed at the call site.

Definition:

mixin button(label: string, variant: "primary" | "secondary" = "primary")
  button.btn(class=`btn-${variant}` ...attrs)= label

Usage:

+button("Save", "primary")(class="mr-2")
+button("Cancel", "secondary")
+button("Delete", "secondary")(class="ml-4" data-confirm="true")

Layouts with extends and blocks

A layout defines named blocks as content slots:

template layout({ title }: LayoutProps)
  doctype html
  html(lang="en")
    head
      title= title
    body
      main
        block content
      footer
        p © 2024

A page extends the layout, filling in the blocks. Data is passed explicitly — nothing is implicitly inherited:

template home({ title, welcomeMessage }: HomeProps)
  extends layout({ title })
    block content
      h1= welcomeMessage
      p Welcome to our site.

Blocks can have default content in the layout. If a page doesn't override a block, the default renders.

Includes

Include another .weave file to use its mixins:

include ./components/button

template actionsPage({ title }: ActionsPageProps)
  doctype html
  html(lang="en")
    body
      +button("Save", "primary")

Text content

Use | (pipe) to add literal text as a child of an element:

p
  | This is plain text inside a paragraph.

A bare | on its own line (with no text after it) emits a single space. This is useful for adding whitespace between sibling elements:

p Hello
  |
  strong= email
  | .

This renders as: Hello <strong>[email protected]</strong>. — with a space between "Hello" and the email address.

Inline elements

Weave does not support inline element syntax (e.g. Pug's #[strong bold] tag interpolation). To mix elements within text, place each element on its own indented line and use | for spacing and text fragments.

Raw blocks

Use ! after a tag to write literal content that won't be escaped or parsed as Weave syntax. This is useful for inline CSS, JavaScript, or any other content that should be passed through as-is:

style!
  body { font-family: sans-serif; max-width: 640px; margin: 0 auto; }
  h1 { border-bottom: 2px solid #eee; }
script!
  console.log("hello")

The indented content is compiled to a raw() call, so the output is not HTML-escaped. Works with classes and IDs too: div.content!.

For inline raw expressions, use raw() directly: div.prose= raw(html).

Special characters

Weave escapes all text content by default. HTML entities like &laquo; will be escaped and rendered literally in the browser rather than interpreted. Use the actual Unicode character instead:

a(href="/back") « Back

This keeps the escaping model simple and predictable — all text is escaped, no exceptions.

Imports

Standard TypeScript imports pass through to the compiled output:

import type { BlogPost } from '../types/models.js'

Development

npm run build       # compile TypeScript to dist/
npm test            # run tests (vitest)
npm run lint        # type-check without emitting (tsc --noEmit)

Status

Weave is in early design. The examples/ directory contains paired .weave and .ts files that define the target syntax and expected compiler output. The compiler is under active development.

Editor Support

  • Emacseditors/emacs/ — major mode with syntax highlighting, indentation, and comment support

License

ISC