mizu.js

mizu.js

Lightweight HTML templating library for any-side rendering.

mizu.js is a TypeScript/JavaScript library designed to provide a simple and efficient way to create dynamic web pages without the need for a complex framework nor additional setup.

Ideal for fast prototyping, generating static HTML pages, and developers seeking to streamline their workflow without the need of setting up a fully-fledged environment.

  • No build steps
  • No configuration needed
  • Any-side rendering
  • Great for static site generation
  • Highly customizable

🌊 Fun Fact: This very page is generated using mizu.js!

Why choose mizu.js?

Any-side rendering
Compatible with most JavaScript runtimes and browsers. Templates can be rendered server-side, client-side, or using a mixture of both.
Straightforward
Simply include its <script> or import statement to get started. No configuration or build steps are required.
Intuitive
Rely on HTML attributes and vanilla JavaScript expressions, no need to learn a new syntax or language.
Customizable
Make your own build using our custom builder to select specific features and reduce the final build size according to your project's needs, and create your own directives easily through developer-friendly APIs.
Open-core
Released under the AGPLv3 License and freely available at github.com/lowlighter/mizu.
Commercial use at a company of more than 100 employees OR more than $12,000 in annual revenue requires a minimum of $1 monthly sponsorship.

Usage

mizu.js is currently in active early development.

If you encounter any issues, please report them at github.com/lowlighter/mizu/issues/new.

  • Compatibility is planned with NodeJS, Bun and browsers, but mizu.js is currently only tested with Deno.
  • Reactivity support is planned but not yet implemented.
  • Some directives combinations (mainly conditional ones with other ones) may not be properly handled yet.
  • Some features may be missing or incomplete.
  • API is not stabilized yet and may change at any moment.

To utilize mizu.js, just include the following line in your HTML document:

<script type="module" href="https://mizu.sh/mizu.js">

Or to use it as a JavaScript module:

import * as Mizu from "@lowlighter/mizu"

Assets are hosted on Vercel but mizu.js is also available on npm, jsr, and CDN services that distributes npm packages such as JSdelivr.

All published versions are available in the /v/ directory. By default, the main branch is served.

À la carte

This feature is not available yet.

Preset builds

mizu.js provides preset builds for added convenience. If you need a specific set of features, you can also use the custom builder.

Create a custom build

This feature is not available yet.

Directives

General

*mizu
Priority0 — META

Enable mizu rendering for the element and its children.

<script src="https://mizu.sh/mizu.js"></script>
<main *mizu="">
  <!--...-->
</main>
Can be set multiple times in same document, but must be defined only once per subtree to avoid unexpected behavior.
If you are using mizu API programmatically, you can chose whether to require this directive or not using the implicit option when calling rendering functions. By default, it is explicit in Client-Side API and implicit in Server-Side API.
For optimization purposes, this directive does not support any attribute tag or modifiers.

Contextual

*set="context"
Priority11 — CONTEXT

Set a context for an element and its children.

<div *set="{ foo: 'bar' }">
  <!--<span *text="foo"></span>-->
</div>

*ref="name"
Priority82 — REFERENCE

Create a reference to the element for later use.

<div *ref="foo" data-text="bar">
  <!--<p *text="$refs.foo.dataset.text"></p>-->
</div>
Redefining a reference will overwrite its previous value for the rest of the subtree without affecting the parent subtree.

Variables

$refsRecord<PropertyKey, HTMLElement> A record of all previously referenced elements in current subtree.

Modifiers

.raw[boolean=true] Whether to skip attribute value evalutation or not.

Conditional

*if="expression"
Defaultfalse Priority23 — TOGGLE

Conditionally render an element.

<div *if="true">
  <!--...-->
</div>

*else="expression"
Defaulttrue Priority23 — TOGGLE

Conditionally render an element after another *if or *else directive.

<div *if="false"></div>
<div *else="false"></div>
<div *else=""><!--...--></div>
Must be defined on an element immediately preceded by another element with either a *if or *else directive.

*show="expression"
Defaultfalse Priority71 — DISPLAY

Conditionally display an element.

<div *show="true">
  <!--...-->
</div>
The CSS display property is set to none !important when hidden.
Unlike *if and *else directives, element is not removed from the DOM when hidden.

Iterative

*for="expression"
Priority21 — EXPAND

Render an element for each iteration.

<!--<ul>-->
  <li *for="let item of items"></li>
<!--</ul>-->
Evaluated expression can either be:
  • Any syntax supported inside for, for...in and for...of loops.
  • Any iterable object that implements Symbol.iterator.
    • Iterated key will be exposed as $key variable.
    • Iterated value will be exposed as $value variable.
  • A finite number.
    • Directive will be applied the specified number of times.

Variables

$idstring Evaluated value of the *id directive if it exists, or the identifier auto-generated by the directive. $iterationsnumber Total number of iterations. $inumber Current iteration index (0 indexed). $Inumber Current iteration index (1 indexed, same as $i + 1). $firstnumber Whether this is the first iteration (same as $i === 0). $lastnumber Whether this is the last iteration (same as $i === ($iterations - 1)).

*id="expression"
Priority0 — META

Hint for *for directive to differentiate generated elements.

<!--<ol>-->
  <li *for="const {id} of items" *id="id"></li>
<!--</ol>-->
Must be defined on an element with a *for directive.
Identifier must be unique within the loop, any duplicate is replaced by the last occurrence found.

*empty
Priority23 — TOGGLE

Conditionally render an element after a *for directive.

<article *for="const article of articles"></article>
<p *empty.not="" *text="`${$generated} results`"></p>
<p *empty=""><!-- No results.--></p>
Must be defined on an element immediately preceded by another element with either a *for or *empty directive (elements generated by *for directive does not apply to this restriction).

Variables

$generatednumber Number of elements actually generated by the matching *for directive (this value may differ from the actual number of iterations processed, like if conditional directives were used).

Modifiers

.not[boolean] Invert conditional logic so that the element is rendered when there was at least one iteration.

Content

*text="content"
Default'' Priority41 — CONTENT

Set element's textContent.

<p *text="'...'">
  <!--...-->
</p>
HTML content is escaped.

*code="content"
Defaultthis.textContent Priority41 — CONTENT

Set element's content after performing syntax highlighting.

<code *code[ts]="'...'">
  <!--<span class="hljs-*">...</span>-->
</code>
Using this will dynamically import highlight.js.
Unsupported languages defaults to plaintext

Modifiers

[string] Any supported language identifier or alias. .trim[boolean=true] Trim whitespaces and shared indentation.

*markdown="content"
Default'' Priority41 — CONTENT

Set element's content after performing Markdown rendering.

<div *markdown="'*...*'">
  <!--<em>...</em>-->
</div>
Using this value will dynamically import jsr.io/@libs/markdown.
Missing implementation.

*toc="selector"
Default'main' Priority41 — CONTENT

Create a table of contents from <h1>...<h6> found in specified selector.

<nav *toc="'main'">
  <!--<ul>...</ul>-->
</nav>
Headings elements in selected target must satisfy the following conditions:
  • Have an id attribute.
  • Have an immediate <a> child with an anchor link (which should points towards its parent id).
  • Must be in descending order level, and any level must not be skipped.
When a heading element is found, next level headings will be searched in HTMLElement.parentElement. If the parent element is a <hgroup>, the search will be performed its grand-parent element instead.

Modifiers

[string] Specify which heading levels should be processed by this directive.
  • The syntax for heading levels constraints is defined as follows:
    • Expression is case-insensitive.
    • A single heading level can be specified (e.g. *toc[h2]).
      • A plus sign + may be added to also process heading elements with a higher value (e.g. *toc[h2+]).
    • A range can be defined using a minus sign - between two headings levels (e.g. *toc[h2-h4]).

*clean
Priority49 — CONTENT_CLEANING

Clean up element subtree from specified content.

<div *clean="">
  <!--...-->
</div>

Modifiers

.comments[boolean=true] Clean up all Comment nodes from subtree. .spaces[boolean] Clean up all spaces (except non-breaking spaces &nbsp;) from subtree. .directives[boolean] Clean up all directives from subtree after processing the subtree entirely.

Custom elements

*custom-element="tagname"
Priority81 — CUSTOM_ELEMENT

Register a new custom element.

<template *custom-element="my-element">
  <ul><slot name="items"></slot></ul>
</template>
Must be defined on a <template> element.
Custom elements registered this way do not use Shadow DOMs but rather have their content rendered directly.
Specified tagname must satisfy the definition of a valid name.
Valid custom element names may be specified as is.

Variables

$slotsRecord<PropertyKey, HTMLSlotElement> A record of specified #slot elements by <slot> name (unamed slot is accessible using $slots[""]). $attrsRecord<PropertyKey, string> A record of specified attributes on the custom element.

#slot
Priority0 — META

Specify target <slot> in an element defined by a *custom-element directive.

<my-element>
  <li #items=""><!--...---></li>
</my-element>
Elements without a slot handle are appended to the unnamed (default) slot.
Elements targetting a same slot are all appended to the target in the order they appear.

*is="tagname"
Priority22 — MORPHING

Set an element tagname.

<div *is="'section'">
  <!--...-->
</div>
As references may change, any equality test with elements using this directive might not work as expected.

Events

@event="listener"
Defaultnull Multiple Priority61 — INTERACTIVITY

Listen for a dispatched Event.

<button @click="this.value = 'Clicked!'">
  <!--Not clicked yet.-->
</button>
Multiple listeners can be attached in a single directive using the empty shorthand @="object" (e.g. @="{ foo() {}, bar() {} }").
  • Modifiers are applied to all specified listeners in the directive (e.g. @.prevent="{}").
  • Tags may be specified to use this syntax multiple times which can be useful to attach listeners with different modifiers (e.g. @[1]="{}" @[2].prevent="{}").
  • As HTML attributes are case-insensitive, it is currently the only way to listen for events with uppercase letters or illegal attribute characters (e.g. @="{ FooBar() {}, Foobar() {} }").
To listen for events with dots . in their names, surround them by brackets { } (e.g. @{my.event}).

Variables

$eventEvent (in listener only) The dispatched Event.

Modifiers

[string] Optional tag that can be used to attach multiple listeners to the same event (e.g. @click[1], @click[2], etc.). .prevent[boolean] Call event.preventDefault() when triggered. .stop[boolean] Call event.stopPropagation() when triggered. .once[boolean] Register listener with { once: true }. .passive[boolean] Register listener with { passive: true }. .capture[boolean] Register listener with { capture: true }. .self[boolean] Listener is triggered only if event.target is the element itself. .attach["element" | "window" | "document"] Change where the listener is attached to (using window or document can be useful to create global listeners). .throttle[duration250ms] Prevent listener from being called more than once during the specified time frame. .debounce[duration250ms] Prevent listener from executing until the specified time frame has passed without any activity. .keys[string] Specify which keys must be pressed for the listener to trigger when receiving a KeyboardEvent.
  • The syntax for keys constraints is defined as follows:
    • Expression is case-insensitive.
    • A combination can be defined using a plus sign + between each key (e.g. @keypress.keys[ctrl+space]).
    • Multiple key combinations can be specified by separating them with a comma , (e.g. @keypress.keys[ctrl+space,shift+space]).
  • The following keys and aliases are supported:
    • alt for "Alt".
    • ctrl for "Control".
    • shift for "Shift".
    • meta for "Meta".
    • space for " ".
    • key for any key except "Alt", "Control", "Shift" and "Meta".
    • Any value possibly returned by event.key.

Binding

:attribute="value"
Multiple Priority51 — ATTRIBUTES

Bind an element's attribute value.

<a :href="url">
  <!--...-->
</a>
:class and :style have specific handling described below.
Multiple attributes can be bound in a single directive using the empty shorthand :="object" (e.g. :="{ foo: 'bar', bar: true }").
Any boolean attribute defined by the HTML spec is handled accordingly (removed from the element when falsy).
Bound attributes with null or undefined values are removed from the element.

:class="value"
Multiple Priority51 — ATTRIBUTES

Bind an element's class attribute.

<p :class="{ foo: true, bar: false }">
  <!--...-->
</p>
Evaluated expression can be either be:
  • A string of space-separated class names (e.g. "foo bar").
  • A Record<PropertyKey, boolean> of class names mapped to their current state (e.g. { foo: true, bar: false }).
  • An Array of any of the listed supported types (e.g. [ "foo", { bar: false }, [] ]).
Initial class attribute value is preserved.
Class names with at least one truthy state are processed as active.

:style="value"
Multiple Priority51 — ATTRIBUTES

Bind an element's style attribute.

<p :style="{ color: 'salmon' }">
  <!--...-->
</p>
Evaluated expression can be either be:
  • A string supported by HTMLElement.style.cssText (e.g. "color: blue;").
  • A Record<PropertyKey, unknown> of CSS properties mapped to their current value (e.g. { backgroundColor: "red", "border-color": "green", width: 1 }).
    • camelCase may be used inplace of kebab-case to avoid escaping CSS properties names.
    • Values of type number are implicitely converted to px unit whenever applicable.
  • An Array of any of the listed supported types (e.g. [ "color: blue", { backgroundColor: "red" }, [] ]).
Initial style attribute value is preserved.
CSS properties values are processed in the order they are defined, regardless of whether they were marked as !important.

Modeling

::attribute="value"
Multiple Priority51 — ATTRIBUTES

Bind an element's attribute value in a bi-directional manner.

<select ::value="foo">
  <!--<option>...</option>-->
</select>
Missing implementation.

HTTP

%http="url"
Priority33 — HTTP_REQUEST

Perform a fetch() call that can be handled by %response directives.

<div %http="https://example.com">
  <!--...-->
</div>
Valid URLs may be specified as is.

Modifiers

.follow[boolean=true] Control whether fetch() should follow redirections or not. .history[boolean] Whether to push state into browser history. Note that specified URL must be the same origin as the current URL. .method[string] Set HTTP method (value is always uppercased). Multiple usage of this modifier can result in unexpected behavior. .get[boolean] Alias for .method[get]. .head[boolean] Alias for .method[head] .post[boolean] Alias for .method[post] .put[boolean] Alias for .method[put] .patch[boolean] Alias for .method[patch] .delete[boolean] Alias for .method[delete]

%@event="url"
Defaultnull Multiple Priority35 — HTTP_INTERACTIVITY

Listen for a dispatched Event and perform a fetch() call that can be handled by %response directives.

<button %@click="https://example.com">
  <!--...-->
</button>

%body="content"
Priority32 — HTTP_BODY

Set HTTP body for a %http directive.

<div %body.json="{foo:'bar'}">
  <!--...-->
</div>

Variables

$headersHeaders A Headers object that contains all registered headers from %header directives.

Modifiers

.type["text" | "form" | "json" | "xml"] Format body with specified type. Multiple usage of this modifier can result in unexpected behavior. .header[boolean=true] Automatically set Content-Type header when using a .type modifier.
  • text: set Content-Type: text/plain.
  • form: set Content-Type: application/json.
  • json: set Content-Type: application/x-www-form-urlencoded.
  • xml: set Content-Type: application/xml.
.text[boolean] Alias for .type[text]. .form[boolean] Alias for .type[form]. .json[boolean] Alias for .type[json]. .xml[boolean] Alias for .type[xml].

%response="expression"
Defaultnull Multiple Priority34 — HTTP_CONTENT

Reacts to a %http directive's response.

<div %http="'https://example.com'" %response.html="">
  <!--...-->
</div>

Variables

$responseResponse A Response object that contains the fetched data. $contentunknown A variable that contains the consumed response body (typing depends on which modifier is used).

Modifiers

[string] Specify which status code trigger this directive.
  • The syntax for status code constraints is defined as follows:
    • A range can be defined using a minus sign - between two numbers (e.g. %response[200-299]).
    • Multiple ranges and status can be specified by separating them with a comma , (e.g. %response[200,201-204]).
  • The following aliases (case-insensitive) are supported:
    • 2XX for 200-299.
    • 3XX for 300-399.
    • 4XX for 400-499.
    • 5XX for 500-599.
.consume["void" | "text" | "html" | "json" | "xml"] Consume response.body. Multiple usage of this modifier can result in unexpected behavior. .void[boolean] Alias for .consume[void]. .text[boolean] Alias for .consume[text]. .html[boolean] Alias for .consume[html]. .json[boolean] Alias for .consume[json]. .xml[boolean] Alias for .consume[xml].

Processing

*once
Priority99 — POSTPROCESSING

Render element once and skip subsequent updates.

<div *once="">
  <!--...-->
</div>

*refresh="interval"
Priority99 — POSTPROCESSING

Force element to be processed again at a specified interval (in milliseconds).

<div *refresh="5 * 1000">
  <!--<time *text="new Date()"></time>-->
</div>
Context is recreated starting from the element itself and the initial root context, meaning that anything in-between is not available. This directive should only be used on elements that can be rendered independently to prevent unexpected behavior.
When used, rendering occur regardless of any detected changes. This is useful to update content that cannot be directly observed, but it is advised to use this sparingly to avoid performance issues.
Set the interval to null to clear the refresh.

*eval="expression"
Defaultnull Priority89 — CUSTOM_PROCESSING

Evaluate JavaScript expression in the context of the element.

<div *eval="console.log('foo')">
  <!--...-->
</div>
Usage of this directive is discouraged and it is recommended to use alternative directives instead whenever possible for security reasons and improved maintainability. This is only provided to help covering edge cases.
Directive is executed after the element and all of its children have been completely processed, but before post-processing directives.

*skip
Priority1 — PREPROCESSING

Prevent element from being processed.

<div *skip="">
  <!--<p *text="foo"></p>-->
</div>

Concepts

This section has not been documented yet.

API

This section has not been documented yet.