*mizu
Phase1 — ELIGIBILITY
1 — ELIGIBILITY
Enable mizu.js rendering for the element and its children.
<main *mizu> <!--...--> </main>
#!/usr/bin/env -S deno serve --allow-read --allow-env // Server-Side Rendering (SSR) with Mizu import Mizu from "@mizu/render/server" export default { async fetch() { const headers = new Headers({ "Content-Type": "text/html; charset=utf-8" }) const body = await Mizu.render(`<div *text="foo"></div>`, { context: { foo: "🌊 Yaa, mizu!" } }) return new Response(body, { headers }) }, }
#!/usr/bin/env -S deno run --allow-read --allow-env --allow-net --allow-write=/tmp/output // Static Site Generation (SSG) with Mizu import Mizu from "@mizu/render/server" await Mizu.generate([ // Copy content from strings [`<div *text="foo"></div>`, "index.html", { render: { context: { foo: "🌊 Yaa, mizu!" } } }], // Copy content from callback return [() => JSON.stringify(Date.now()), "timestamp.json"], // Copy content from local files ["**/*", "static", { directory: "/fake/path" }], // Copy content from URL [new URL("https://matcha.mizu.sh/matcha.css"), "styles.css"], ], { clean: true, output: "/tmp/output" })
1 2 3 4 5 6 7 8 9 10 11
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Server-Side Rendering (SSR) with Mizu import Mizu from "@mizu/render/server" import { createServer } from "node:http" createServer(async (_, response) => { response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }) response.end(await Mizu.render(`<div *text="foo"></div>`, { context: { foo: "🌊 Yaa, mizu!" } })) }).listen(8000, "0.0.0.0", () => console.log("Server is listening"))
// Static Site Generation (SSG) with Mizu import Mizu from "@mizu/render/server" await Mizu.generate([ // Copy content from strings [`<div *text="foo"></div>`, "index.html", { render: { context: { foo: "🌊 Yaa, mizu!" } } }], // Copy content from callback return [() => JSON.stringify(Date.now()), "timestamp.json"], // Copy content from local files ["**/*", "static", { directory: "/fake/path" }], // Copy content from URL [new URL("https://matcha.mizu.sh/matcha.css"), "styles.css"], ], { clean: true, output: "/tmp/output" })
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8 9 10 11 12 13
// Server-Side Rendering (SSR) with Mizu import Mizu from "@mizu/render/server" Bun.serve({ port: 8000, async fetch() { const headers = new Headers({ "Content-Type": "text/html; charset=utf-8" }) const body = await Mizu.render(`<div *text="foo"></div>`, { context: { foo: "🌊 Yaa, mizu!" } }) return new Response(body, { headers }) }, }) console.log("Server is listening")
// Static Site Generation (SSG) with Mizu import Mizu from "@mizu/render/server" await Mizu.generate([ // Copy content from strings [`<div *text="foo"></div>`, "index.html", { render: { context: { foo: "🌊 Yaa, mizu!" } } }], // Copy content from callback return [() => JSON.stringify(Date.now()), "timestamp.json"], // Copy content from local files ["**/*", "static", { directory: "/fake/path" }], // Copy content from URL [new URL("https://matcha.mizu.sh/matcha.css"), "styles.css"], ], { clean: true, output: "/tmp/output" })
1 2 3 4 5 6 7 8 9 10 11 12 13
1 2 3 4 5 6 7 8 9 10 11 12 13
$
deno add jsr:@mizu/render
npx jsr add @mizu/render
bunx jsr add @mizu/render
<!DOCTYPE html> <html> <head> <title>IIFE</title> <meta charset="UTF-8" /> </head> <body> <main *mizu *set="{ foo: '🌊 Yaa, mizu!' }"> <div *mustache>{{ foo }}</div> </main> <!-- Use the IIFE version to automatically start mizu --> <script src="https://mizu.sh/client.js" defer></script> </body> </html>
<!DOCTYPE html> <html> <head> <title>ESM</title> <meta charset="UTF-8" /> </head> <body> <main *mizu> <div *mustache>{{ foo }}</div> </main> <!-- Use the ESM version to manually control mizu --> <script type="module"> import Mizu from "https://mizu.sh/client.mjs" await Mizu.render(document.body, { context: { foo: "🌊 Yaa, mizu!" } }) </script> </body> </html>
Switch between browsers and runtimes using the icons above.
Change the current example by clicking on the tabs in the mockups.
Try out our online interactive playground and start experimenting with mizu.js now!
Simply include the library and start building amazing things instantly with vanilla JavaScript expressions and HTML.
Compatible across a wide range of JavaScript and TypeScript runtimes, including all major browsers.
Render your content wherever you need it and however you want it with user-friendly APIs.
Cherry-pick features and craft your own setup easily with developer-friendly APIs and our custom builder.
Build, share and reuse custom elements and directives to supercharge your development.
Want to effortlessly theme your page? Check out matcha.css!Licensed under the MIT License and source code fully available on github.com.
If you enjoy using mizu.js, consider supporting its development.Yes, but hear us out!
Remember when building a web page was as simple as writing some HTML, adding a bit of JavaScript, and styling with CSS? mizu.js brings back that simplicity, offering a modern yet flexible approach to web development.
By adhering closely to web standards and embracing the simplicity of plain HTML and JavaScript, mizu.js offers an almost non-existent learning curve. This makes it an excellent choice for both beginners and seasoned developers.
Whether you're serving content from your favorite runtime, generating static websites, or creating dynamic pages in the browser, mizu.js adapts to your needs. — All of it without the hassle of bundlers, transpilers, or countless dependencies.
We recognize the power and utility of comprehensive frameworks like React, Vue.js, and Angular. These tools excel at building complex applications and boast large communities and ecosystems. However, they can be overwhelming for smaller projects, often requiring a build step and a steep learning curve with their specific syntax and concepts.
Lightweight alternatives such as Alpine.js and htmx are also available. While they perform well in the browser, they were often not designed for server-side use, limiting their applicability across different scenarios.
mizu.js draws inspiration from all these frameworks but is designed from the ground up to be:
If all of this sounds appealing to you, then mizu.js might be the right choice for your next project.
It depends.
On the server-side, mizu.js employs a Virtual DOM to simulate the browser environment. The current implementation leverages JSDOM, but you can use any other Virtual DOM library that adheres to web standards. Non-compliant implementations may lead to unexpected behaviors.
On the client-side, mizu.js interacts directly with the actual DOM. It tracks processed elements and their states using weak references to prevent memory leaks and enhance performance.
We want to ensure mizu.js delivers a reliable and consistent experience across all supported environments, which is why each feature is meticulously documented to detail its behavior and functionality.
Our codebase is thoroughly tested and covered to guarantee that each feature performs as expected. Integration tests are also performed on various platforms and environment to ensure a consistent experience as advertised.
If you encounter any issues or undocumented behavior, please open a new issue so we can address it promptly.
æ°´
is the Japanese kanji for water
.
Like water, mizu.js is fluid and adaptable, seamlessly fitting into various use cases and execution environments.
Like water, mizu.js is simple and fundamental, staying close to vanilla JavaScript and HTML with a minimal learning curve.
Like water, mizu.js is customizable, it can be mixed with other libraries and additional features to suit your needs.
Need to add some flavor to your page? Add some matcha.css to your mizu.js project!*mizu
1 — ELIGIBILITY
Enable mizu.js rendering for the element and its children.
<main *mizu> <!--...--> </main>
*set="context"
11 — CONTEXT
Set context values for an element and its children.
<div *set="{ foo: 'bar' }"> <!--<span *text="foo"></span>--> </div>
*ref="name"
82 — REFERENCE
Create a reference to an element for later use.
<div *ref="foo" data-text="bar"> <!--<p *text="$refs.foo.dataset.text"></p>--> </div>
*if="expression"
23 — TOGGLE
Conditionally render an element.
<div *if="true"> <!--...--> </div>
*else="expression"
true
Phase23 — TOGGLE
Conditionally render an element placed after another *if
or *else
directive.
<div *if="false"></div> <div *else="false"></div> <div *else><!--...--></div>
*show="expression"
true
Phase71 — DISPLAY
Conditionally display an element.
<div *show="true"> <!--...--> </div>
*for="expression"
21 — EXPAND
Render an element for each iteration performed.
<!--<ul>--> <li *for="let item of items"></li> <!--</ul>-->
*id="expression"
0 — META
Hint for *for
directive to differentiate generated elements.
<!--<ol>--> <li *for="const {id} of items" *id="id"></li> <!--</ol>-->
*empty
23 — 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>
*text="content"
this.innerHTML
Phase41 — CONTENT
Set element's textContent
.
<p *text="'...'"> <!--...--> </p>
*html="content"
41 — CONTENT
Set element's innerHTML
.
<template *html="'<p>...</p>'"> <!--<p>...</p>--> </template>
*mustache
42 — CONTENT_INTERPOLATION
Enable content interpolation within mustaches
({{
and }}
) from Text
child nodes.
<p *mustache> <!--{{ ... }}--> </p>
*code="content"
this.textContent
Phase41 — CONTENT
Set element's innerHTML
after performing syntax highlighting.
<code *code[ts]="'...'"> <!--<span class="hljs-*">...</span>--> </code>
*markdown="content"
this.textContent
Phase41 — CONTENT
Set element's innerHTML
after performing markdown rendering.
<div *markdown="'*...*'"> <!--<em>...</em>--> </div>
*toc="selector"
'main'
Phase41 — CONTENT
Create a table of contents from <h1>...<h6>
elements found in selected target.
<nav *toc="'main'"> <!--<ul>...</ul>--> </nav>
*clean
49 — CONTENT_CLEANING
Clean up the element and its children from specified content.
<div *clean> <!--...--> </div>
*custom-element="tagname"
81 — CUSTOM_ELEMENT
Register a new custom element.
<template *custom-element="my-element"> <ul><slot name="items"></slot></ul> </template>
#
0 — META
Specify target <slot>
in an element defined by a *custom-element
directive.
<my-element> <li #items><!--...---></li> </my-element>
*is="tagname"
22 — MORPHING
Set an element tagname.
<div *is="'section'"> <!--...--> </div>
@="listener"
null
Multiple Phase61 — INTERACTIVITY
Listen for a dispatched Event
.
<button @click="this.value = 'Clicked!'"> <!--Not clicked yet.--> </button>
:="value"
$<attribute>
Multiple Phase51 — ATTRIBUTE
Bind an element's attribute
value.
<a :href="url"> <!--...--> </a>
:class="value"
$<attribute>
Multiple Phase51 — ATTRIBUTE
Bind an element's class
attribute.
<p :class="{ foo: true, bar: false }"> <!--...--> </p>
:style="value"
$<attribute>
Multiple Phase51 — ATTRIBUTE
Bind an element's style
attribute.
<p :style="{ color: 'salmon' }"> <!--...--> </p>
::="model"
value
Phase52 — ATTRIBUTE_MODEL_VALUE
Bind an <input>
, <select>
or <textarea>
element's value
attribute in a bi-directional manner.
<select ::value="foo"> <!--<option>...</option>--> </select>
%http="url"
33 — HTTP_REQUEST
Perform a fetch()
call that can be handled by %response
directives.
<div %http="https://example.com"> <!--...--> </div>
%header[]="value"
31 — HTTP_HEADER
Set HTTP headers for a %http
directive.
<div %header[x-foo]="'bar'"> <!--...--> </div>
%body="content"
32 — HTTP_BODY
Set HTTP body for a %http
directive.
<div %body.json="{foo:'bar'}"> <!--...--> </div>
%response="expression"
null
Multiple Phase34 — HTTP_CONTENT
Reacts to a %http
directive's Response
.
<div %http="'https://example.com'" %response.html> <!--...--> </div>
%@="listener"
null
Multiple Phase35 — HTTP_INTERACTIVITY
Listen for a dispatched Event
and re-evaluates %http
directive before reacting to its Response
.
<button %http="https://example.com" %@click.html> <!--...--> </button>
*once
99 — POSTPROCESSING
Render an element once and skip subsequent updates.
<div *once> <!--...--> </div>
*refresh="interval"
99 — POSTPROCESSING
Reprocess an element at a specified interval (in seconds).
<div *refresh="1.5"> <!--<time *text="new Date()"></time>--> </div>
*eval="expression"
89 — CUSTOM_PROCESSING
Evaluate a JavaScript expression in the context of the element.
<div *eval="console.log('$data')"> <!--...--> </div>
*skip
2 — PREPROCESSING
Prevent an element from being processed.
<div *skip> <!--<p *text="foo"></p>--> </div>
These directives are for development and testing purposes only. They help developers validate features and renderings before production.
Do not use in production environments.
~test="expression"
10 — TESTING
Special directive for testing purposes.
<samp ~test[testing].text="'...'"> <!--...--> </samp>
New features that are still in the design phase are published in this package to gather community feedback. These features may undergo significant changes during development and might not be included in the stable release.
Use at your own risk.
*noop
10 — TESTING
This directive does nothing.
<div *noop></div>
Set up mizu.js in your browser environment using one of two methods:
On the client-side...
.js
)This setup automatically starts rendering the page once the script is loaded. It's the simplest way to get started but limited to the default configuration.
<script src="https://mizu.sh/client.js" defer></script>
.mjs
)This setup requires you to import and start mizu.js manually, allowing customization of the rendering process, such as setting the initial context and loading additional directives.
<script type="module">
import Mizu from "https://mizu.sh/client.mjs"
await Mizu.render(document.body, { context: { foo: "🌊 Yaa, mizu!" } })
</script>
Looking to effortlessly theme your new web page? Check out matcha.css!
<link rel="stylesheet" href="https://matcha.mizu.sh/matcha.css">
To set up mizu.js in a server environment, install it locally. mizu.js packages are hosted on .
On the server side...
Deno supports the jsr:
specifier natively, allowing you to import mizu.js directly.
import Mizu from "jsr:@mizu/render/server" await Mizu.render(`<div *text="foo"></div>`, { context: { foo: "🌊 Yaa, mizu!" } })
Alternatively, add it to your project using the Deno CLI.
deno add jsr:@mizu/render
Add mizu.js to your project using the JSR npm compatibility layer.
# NodeJS npx jsr add @mizu/render
# Bun bunx jsr add @mizu/render
Once installed, use it in your project.
import Mizu from "@mizu/render/server" await Mizu.render(`<div *text="foo"></div>`, { context: { foo: "🌊 Yaa, mizu!" } })
A HTML attribute recognized by mizu.js which instructs how it should process the element.
The syntax is as follows:
Directives names often begin with special characters to prevent conflicts with standard HTML attributes and to clearly indicate their specific purpose:
*
for generic directives.#
for directives targeting <slot>
elements. @
for directives related to Event
handling. :
for binding HTML attributes. ::
for bi-directional binding.%
for HTTP directives. %@
for combined HTTP and Event directives.~
for testing directives. Typically, this serves as the directive's argument. At most one tag can be specified per directive.
Modifiers adjust the behavior of the directive. You can specify multiple modifiers on the same directive.
The value of the modifier. The modifier may have an explicit default value, which is used if no value is specified.
The value is validated and cast to the expected type, defaulting if invalid. Modifiers may accept one of the following types:
boolean
, unset defaults to true
. string
, unset defaults to ""
. number
, unset defaults to 0
. duration
, unset defaults to 0ms
(supported units: ms
, s
, m
). Set through the HTML attribute value, it is evaluated asynchronously within the context of the current element during processing.
A directive may have a Default expression, used when no specific expression is provided.
Unless stated otherwise, any JavaScript expression is permitted.
All expressions are evaluated within a specific context, which includes all defined variables and functions. You can define these through the user API or directly via contextual directives.
Certain directives may also provide special variables based on the current processing state, prefixed with a dollar sign ($
).
$
), it is recommended to avoid this practice to prevent confusion with special variables. In case of a conflict, the special variables will shadow user-defined ones until being shadowed themselves.__mizu_internal
is reserved and cannot be used in expressions.mizu.js leverages @libs/reactive to monitor context changes and trigger re-renders as needed.
During element processing, all get
operations are cached with the element as a reference. Whenever a set
operation is performed, mizu.js identifies which elements depend on the changed value and re-processes them.
Rendering is the process of evaluating and applying mizu.js directives. This occurs recursively on a subtree and modifies the current DOM.
Each directive is tied to a specific phase, dictating the order of processing. The sequence in which directives appear on an element is irrelevant unless they share the same phase.
Directives within the same phase cannot coexist on the same element unless explicitly allowed to be specified Multiple times.
00
META
01
ELIGIBILITY
02
PREPROCESSING
10
TESTING
11
CONTEXT
21
EXPAND
22
MORPHING
23
TOGGLE
31
HTTP_HEADER
32
HTTP_BODY
33
HTTP_REQUEST
34
HTTP_CONTENT
35
HTTP_INTERACTIVITY
41
CONTENT
42
CONTENT_INTERPOLATION
49
CONTENT_CLEANING
51
ATTRIBUTE
52
ATTRIBUTE_MODEL_VALUE
59
ATTRIBUTE_CLEANING
61
INTERACTIVITY
71
DISPLAY
81
CUSTOM_ELEMENT
82
REFERENCE
89
CUSTOM_PROCESSING
99
POSTPROCESSING
This section is targeted at users who want to use mizu.js rendering and directives.
If you wish to develop custom directives, please refer to the Developer API section instead.
Full API documentation is available at jsr.io/@mizu/render/client.
The following directives are enabled by default in the client-side API. You can customize the enabled directives by creating a custom instance rather than using the default one.
*mizu
*set="context"
*ref="name"
*if="expression"
*else="expression"
*show="expression"
*for="expression"
*empty
*text="content"
*html="content"
*mustache
*code="content"
*custom-element="tagname"
#
@="listener"
:="value"
:class="value"
:style="value"
::="model"
%http="url"
%header[]="value"
%body="content"
%response="expression"
%@="listener"
*once
*refresh="interval"
*eval="expression"
*skip
Client.constructor(options?: ClientOptions)
Client
constructor.
Client.defaults: Required<ClientOptions>
Client.default: Client
Default Client
instance.
Client.context: Record<PropertyKey, any>
Rendering context.
All properties assigned to this object are available during rendering.
Changes to this object are reactive and will trigger a re-render of related elements. This is achieved using Context
, which leverages Proxy
handlers.
You cannot reassign this property directly to ensure reactivity is maintained. To achieve a similar effect, use
Object.assign()
.
Client.render(element: T, options?: ClientRenderOptions) => Promise<T>
Start rendering all subtrees marked with the *mizu
attribute.
const mizu = new Client({ context: { foo: "bar" } }) await mizu.render()
Client.flush() => Promise<void>
Flush the reactive render queue of Renderer
.
Full API documentation is available at jsr.io/@mizu/render/server.
The following directives are enabled by default in the server-side API. You can customize the enabled directives by creating a custom instance rather than using the default one.
*mizu
*set="context"
*ref="name"
*if="expression"
*else="expression"
*show="expression"
*for="expression"
*empty
*text="content"
*html="content"
*mustache
*code="content"
*markdown="content"
*toc="selector"
*clean
*custom-element="tagname"
#
*is="tagname"
:="value"
:class="value"
:style="value"
%http="url"
%header[]="value"
%body="content"
%response="expression"
*once
*eval="expression"
*skip
Server.constructor(options?: ServerOptions)
Server
constructor.
Server.defaults: Required<ServerOptions>
Server.default: Server
Default Server
instance.
Server.context: Record<PropertyKey, any>
Default rendering context.
All properties assigned to this object are accessible during rendering.
Server.render(content: string | Arg<Renderer["render"]>, options?: ServerRenderOptions & Pick<ServerOptions, "warn">) => Promise<string>
Parse an HTML string and render all subtrees.
The *mizu
attribute is only required if implicit
is set to false
.
const mizu = new Server({ context: { foo: "bar" } }) await mizu.render(`<html><body><a ~test.text="foo"></a></body></html>`)
Server.generate(sources: Array<StringSource | GlobSource | CallbackSource | URLSource>, options?: ServerGenerateOptions) => Promise<void>
Generate static files from various sources.
Options:
output
: Specify the path to the output directory.clean
: Empty the output
directory before generating files.Supported sources:
StringSource
: Generate content from raw strings.GlobSource
: Generate content from local files matching the provided glob patterns.CallbackSource
: Generate content from callback returns.URLSource
: Generate content from fetched URLs.Each source can be templated using mizu rendering by passing a render
option.
const mizu = new Server({ directives: ["@mizu/test"], generate: { output: "/fake/output" } }) await mizu.generate( [ // Copy content from strings [ "<p>foo</p>", "string.html" ], [ "<p ~test.text='foo'></p>", "string_render.html", { render: { context: { foo: "bar" } } } ], // Copy content from local files [ "**\/*", "public", { directory: "/fake/static" } ], [ "*.html", "public", { directory: "/fake/partials", render: { context: { foo: "bar "} } } ], // Copy content from callback return [ () => JSON.stringify({ foo: "bar" }), "callback.json" ], [ () => `<p ~test.text="'foo'"></p>`, "callback.html", { render: { context: { foo: "bar" } } } ], // Copy content from URL [ new URL(`data:text/html,<p>foobar</p>`), "url.html" ], [ new URL(`data:text/html,<p ~test.text="foo"></p>`), "url_render.html", { render: { context: { foo: "bar" } } } ], ], // No-op: do not actually write files and directories { fs: { readdir: () => Promise.resolve([] as string[]), mkdir: () => null as any, write: () => null as any } }, )
This section is targeted at developers who want to create custom mizu.js rendering and directives.
If you wish to render templates, please refer to the User API section instead.
Full API documentation is available at jsr.io/@mizu/internal/engine.
This section covers the Directive
interface, essential for creating custom directives. Each built-in directive is also an instance of this interface. It follows the steps outlined in the Rendering concept.
When manipulating the DOM, use the Renderer
methods to ensure compatibility between virtual and real DOMs. These methods also provide helpful tools to simplify the creation of custom directives.
Full API documentation is available at jsr.io/@mizu/internal.
This package includes several APIs used by mizu.js, such as the Renderer
, virtual DOM implementations, the testing framework, and more. These symbols are documented in the source code and are intended for development environments.
mizu.js currently follows ZeroVer versioning. This means breaking changes may occur in minor versions, even for previously defined features.
It will eventually stabilize and follow SemVer versioning after more feedback and testing.
Some aspects of mizu.js are not fully defined yet. While these are mentioned in the documentation, they are gathered here for easy reference.
Relying on undefined behavior in your applications is strongly discouraged, as these behaviors may change in future versions without being considered breaking changes. Additionally, any unusual behavior encountered is not considered a bug until properly specified.
If you wish to participate in the design and definition of these features, check out the issues tagged with spec
.
<template>
elements, but future versions may introduce specific behavior for these elements.<template>
elements, but future versions may introduce specific behavior for these elements.<template>
elements, but future versions may introduce specific behavior for these elements.<template>
elements, but future versions may introduce specific behavior for these elements.{{
and }}
) and triple mustaches ({{{
and }}}
), but future versions may introduce specific behavior for these..nullish
, .boolean
, .number
, and .string
modifiers are currently implemented as boolean modifiers, but future versions may change this behavior to offer more parsing features.