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

breezewind

v1.2.0

Published

Simple and extensible JSON based templating engine

Downloads

27

Readme

Breezewind - Breezing fast templating

Breezewind is a templating engine (JSON to HTML/XML) designed with the following considerations in mind:

  • Performance - It's doing only bare minimum
  • Extensibility - There's a way to write your own syntax extensions on top of the core and it ships with a few
  • eval free - To allow execution in environments, such as CloudFlare Workers, it doesn't rely on eval or related techniques for logic

Installation

Breezewind is available through npm or deno.land. It's coupled to Gustwind releases at deno.land as I don't know yet how to publish both from a monorepo.

Usage

The following example illustrates how to use the API to render a link:

import breeze from "breezewind";

breeze({
  component: {
    type: "a",
    attributes: {
      href: "https://gustwind.js.org/",
      title: "Gustwind",
    },
    children: "Link to Gustwind",
  },
  components: {},
  context: {},
  extensions: [],
  globalUtilities: {},
});

Components

Components allow React-style composition. In other words, they let you extract functionality that's shared and reuse it. Consider the following example:

import breeze from "breezewind";

breeze({
  component: {
    type: "Link",
    props: {
      href: "https://gustwind.js.org/",
      title: "Gustwind",
      children: "Link to Gustwind",
    },
  },
  components: {
    Link: {
      type: "a",
      attributes: {
        href: { utility: "get", parameters: ["props", "href"]},
        title: { utility: "get", parameters: ["props", "title"]},
      },
      children: { utility: "get", parameters: ["props", "children"]},
    }
  },
  context: {},
  extensions: [],
  globalUtilities: {},
});

Note that you can apply utilities within props as well to dig data from components props or global context.

Utilities

To work around the limitation of not having access to eval based techniques, Gustwind relies on a simple programming model based on function invocations. In the example below, { utility: "get", parameters: ["props", "href"]} was an example of this.

On top of this, it's possible to apply functions within parameters recursively. For example, you could concatenate to a url like this:

const concatenatedAttribute = {
  utility: "concat",
  parameters: [
    {
      utility: "get",
      parameters: ["props", "data.slug"]
    },
    "/"
  ]
};

The following utilities are provided out of the box:

  • id(s: unknown) returns the given parameter.
  • equals(a: unknown, b: unknown) performs a strict (===) comparison and return a boolean based on the result.
  • pick(predicate: boolean, s: unknown) returns s if predicate evaluates as true.
  • or(...parts: unknown[]) returns true in case any of the parts evaluates as true.
  • and(...parts: unknown[]) returns true in case all of the parts evaluate as true.
  • get(<context>, <selector>, <defaultValue>) tries to get with selector from context. If this process fails, defaultValue is returned instead.
  • concat(...<string>) concatenates given strings into a single string.
  • trim(s: string, c: string) trims character c from both ends of string s.
  • stringify(input: unknown) applies JSON.stringify(input, null, 2). Useful for debugging.

To implement your own, follow this signature: (...args: unknown[]) => unknown | Promise<unknown>.

Note that Breezewind context is available through this.context at utilities should you need to access it. For example, the get default utility leverages to allow access to the context through the utility.

To pass custom utilities to Breezewind, do the following:

import breeze from "breezewind";

breeze({
  component: {
    type: "Link",
    // Since we want to apply a utility, we have to use bindToProps.
    // This way the system knows what you want to preserve as an object.
    bindToProps: {
      children: { utility: "hello", parameters: ["hello"] },
    },
    props: {
      href: "https://gustwind.js.org/",
      title: "Gustwind",
    },
  },
  components: { ... },
  context: {},
  extensions: [],
  globalUtilities: {
    hello: (input: string) => input + ' ' + 'world!',
  },
  // Component utilities are scoped to a component or its children
  componentUtilities: {
    Button: {
      demo: (input: string) => input + ' ' + 'demo!',
    }
  }
});

To detect when rendering has started and ended (useful for instrumentation), you can use _onRenderStart(context: Context) and _onRenderEnd(context: Context). Each triggers once during the rendering process.

Context

To allow injecting data from outside to templates, Breezewind implements a global context. Use it like this:

import breeze from "breezewind";

breeze({
  component: {
    type: "Link",
    props: {
      href: "https://gustwind.js.org/",
      title: "Gustwind",
      children: { utility: "hello", parameters: [{
        // Note the access here!
        utility: "get", parameters: ["context", "hello"]
      }] },
    },
  },
  components: { ...},
  context: {
    hello: "hello",
  },
  extensions: [],
  globalUtilities: {
    hello: (input: string) => input + ' ' + 'world!',
  },
});

You have access to the context anywhere and it's comparable to the concept in React and other templating engines.

Extensions

Currently four official extensions are supported:

  • classShortcut((input: string) => (output: string)) provides a class shortcut (maps given input to a class attribute) and classList shortcut for toggling classes based on truths. Note that unlike the other extensions, this one is a factory accepting a function transforming the given class (useful with Tailwind or Twind for example).
  • foreach gives access to iteration allowing mapping arrays to flat structures.
  • inject(e => ({ ...e, attributes: { ...c.attributes, "data-id": "demo" }})) lets you inject a property to each node within a JSON tree. This is useful for injecting test ids for example.
  • visibleIf makes it possible to remove nodes from the tree based on a statement.

To activate extensions, do the following:

import { tw } from "twind";
import breeze from "breezewind";
import extensions from "breezewind/extensions";

breeze({
  component: {
    visibleIf: { utility: "get", parameters: ["context", "url"] },
    type: "a",
    class: "underline",
    classList: {
      "font-bold": [
        "gustwind.js.org",
        { utility: "get", parameters: ["context", "url"] }
      ]
    },
    attributes: {
      href: "https://gustwind.js.org/",
      title: "Gustwind",
    },
    children: "Link to Gustwind",
  },
  components: { ... },
  context: { url: "gustwind.js.org" },
  extensions: [
    extensions.visibleIf,
    extensions.classShortcut(tw),
    extensions.foreach,
  ],
  globalUtilities: {},
});

Rendering a doctype

To render a doctype, set closingCharacter to "" to avoid the default behavior that generates full tags:

import breeze from "breezewind";

breeze({
  component: {
    type: "!DOCTYPE",
    attributes: {
      html: "",
    },
    closingCharacter: "",
  },
  ...
});

Typing

To access types, use the following kind of syntax:

import breeze, { type Component } from "breezewind";

...

Playground

Use the playground below to experiment with the syntax:

:BreezewindPlayground:

Publishing to npm

  1. deno task build:breezewind-for-npm <VERSION> where VERSION is 0.1.0 for example
  2. cd breezewind/npm
  3. npm publish. You may need to pass --otp here as well (preferred for security)

License

MIT.