breezewind
v1.2.0
Published
Simple and extensible JSON based templating engine
Downloads
27
Maintainers
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 oneval
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)
returnss
ifpredicate
evaluates astrue
.or(...parts: unknown[])
returnstrue
in case any of the parts evaluates astrue
.and(...parts: unknown[])
returnstrue
in case all of the parts evaluate astrue
.get(<context>, <selector>, <defaultValue>)
tries to get withselector
fromcontext
. If this process fails,defaultValue
is returned instead.concat(...<string>)
concatenates given strings into a single string.trim(s: string, c: string)
trims characterc
from both ends of strings
.stringify(input: unknown)
appliesJSON.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 aclass
shortcut (maps given input to aclass
attribute) andclassList
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
deno task build:breezewind-for-npm <VERSION>
whereVERSION
is0.1.0
for examplecd breezewind/npm
npm publish
. You may need to pass--otp
here as well (preferred for security)
License
MIT.