mono-jsx
v0.0.0-alpha.12
Published
`<html>` as a `Response`.
Downloads
392
Readme
mono-jsx
mono-jsx is a JSX runtime that renders <html>
element to a Response
object in JavaScript runtimes like Node.js, Deno, Bun, Cloudflare Workers, etc.
- No build step needed
- Lightweight(7KB gzipped), zero dependencies
- Minimal state runtime
- Streaming rendering
- Universal, works in Node.js, Deno, Bun, Cloudflare Workers, etc.
export default {
fetch: (req) => (
<html>
<h1>Hello World!</h1>
</html>
),
};
Installation
mono-jsx supports all modern JavaScript runtimes including Node.js, Deno, Bun, Cloudflare Workers, etc.
You can install it via npm i
, deno add
, or bun add
.
# Node.js, Cloudflare Workers, or other node-compatible runtimes
npm i mono-jsx
# Deno
deno add npm:mono-jsx
# Bun
bun add mono-jsx
Setup JSX runtime
To use mono-jsx as the JSX runtime, add the following configuration to your tsconfig.json
(deno.json
for Deno):
{
"compilerOptions": {
"allowJs": true, // required for `.jsx` extension in Node.js
"jsx": "react-jsx",
"jsxImportSource": "mono-jsx"
}
}
You can also run npx mono-jsx setup
to add the configuration automatically.
npx mono-jsx setup
Alternatively, you can use pragma directive in your JSX file.
/** @jsxImportSource mono-jsx */
Usage
To create a html response in server-side, you just need to return a <html>
element in the fetch
method.
// app.jsx
export default {
fetch: (req) => (
<html>
<h1>Hello World!</h1>
</html>
),
};
For Deno/Bun users, you can run the app.jsx
directly.
deno serve app.jsx
bun run app.jsx
Node.js does not support JSX module and declarative fetch server, we recommend using mono-jsx with hono.
import { serve } from "@hono/node-server";
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => (
<html>
<h1>Hello World!</h1>
</html>
));
serve(app);
and you will need tsx to start the app.
npx tsx app.jsx
If you are building a web app with Cloudflare Workers, you can use the wrangler dev
command to start the app in local development.
npx wrangler dev app.jsx
Using JSX
mono-jsx uses JSX to describe the user interface, similar to React but with some differences.
Using Standard HTML Property Names
mono-jsx uses standard HTML property names instead of React's overthinked property names.
className
->class
htmlFor
->for
onChange
->onInput
Composition class
mono-jsx allows you to compose the class
property using an array of strings, objects, or expressions.
<div class={["container box", isActive && "active", { hover: isHover }]} />;
Using Pseudo Classes and Media Queries in the style
Property
mono-jsx allows you to use pseudo classes, pseudo elements, media queries, and css nesting in the style
property.
<a
style={{
color: "black",
"::after": { content: "↩️" },
":hover": { textDecoration: "underline" },
"@media (prefers-color-scheme: dark)": { color: "white" },
"& .icon": { width: "1em", height: "1em", marginRight: "0.5em" },
}}
>
<img class="icon" src="link.png" />
Link
</a>;
<slot>
Element
mono-jsx uses <slot>
element to render the slotted content(Equivalent to React's children
proptery). Plus, you also can add the name
attribute to define a named slot.
function Container() {
return (
<div class="container">
<slot /> {/* <h1>Hello world!</h1> */}
<slot name="desc" /> {/* <p>This is a description.</p> */}
</div>
);
}
function App() {
return (
<Container>
<p slot="desc">This is a description.</p>
<h1>Hello world!</h1>
</Container>
);
}
html
Tag Function
mono-jsx doesn't support the dangerouslySetInnerHTML
property, instead, it provides a html
tag function to render raw HTML in JSX.
function App() {
return <div>{html`<h1>Hello world!</h1>`}</div>;
}
The html
tag function is a global function injected by mono-jsx, you can use it in any JSX expression without importing it.
You also can use the css
and js
, that are just aliases of the html
tag function, to render CSS and JavaScript code.
function App() {
return (
<head>
<style>{css`h1 { font-size: 3rem; }`}</style>
<script>{js`console.log("Hello world!")`}</script>
</head>
);
}
[!WARNING] the
html
tag function is unsafe that can cause XSS vulnerabilities.
Event Handlers
mono-jsx allows you to write event handlers directly in the JSX code, like React.
function Button() {
return <button onClick={(evt) => alert("BOOM!")}>Click Me</button>;
}
Note, the event handler would never be called in server-side. Instead it will be serialized to a string and sent to the client-side. This means you should NOT use any server-side variables or functions in the event handler.
function Button() {
let message = "BOOM!";
return (
<button
onClick={(evt) => {
Deno.exit(0); // ❌ Deno is unavailable in the browser
alert(message); // ❌ message is a server-side variable
document.title = "BOOM!"; // ✅ document is a browser API
$state.count++; // ✅ $state is the mono-jsx specific usage
}}
>
Click Me
</button>
);
}
Plus, mono-jsx supports the mount
event that will be triggered when the element is mounted in the client-side.
function App() {
return (
<div onMount={(evt) => console.log(evt.target, "Mounted!")}>
<h1>Hello World!</h1>
</div>
);
}
Using State
mono-jsx provides a minimal state runtime that allows you to update view based on state changes in client-side.
function App() {
// Initialize the state 'count' with value `0`
$state.count = 0;
return (
<div>
{/* use the state */}
<span>{$state.count}</span>
{/* computed state */}
<span>doubled: {$computed(() => 2 * $state.count)}</span>
{/* update the state in event handlers */}
<button onClick={() => $state.count--}>-</button>
<button onClick={() => $state.count++}>+</button>
</div>
);
}
To support type checking in TypeScript, declare the State
interface in the global scope:
declare global {
interface State {
count: number;
}
}
[!NOTE] The
$state
and$computed
are global variables injected by mono-jsx.
Using Hooks
mono-jsx provides some hooks to allow you to access rendering context in function components.
$request
Hook
The $request
hook allows you to access the current request object which is set in the root <html>
element.
async function App() {
const request = $request();
return (
<p>
{request.method} {request.url}
</p>
);
}
export default {
fetch: (req) => (
<html request={req}>
<h1>Hello World!</h1>
<App />
</html>
),
};
$store
Hook
The $store
hook allows you to access the global store object which is set in the root <html>
element.
function App() {
const { count } = $store();
return <p>{ count }</p>;
}
export default {
fetch: (req) => (
<html store={{ count: 0 }}>
<h1>Hello World!</h1>
<App />
</html>
),
};
Using Hooks in Async Function Components
If you are using hooks in an async function component, you need to call these hooks before any await
statement.
async function AsyncApp() {
const request = $request();
const data = await fetchData(new URL(request.url).searchParams.get("id"));
const request2 = $request(); // ❌ request2 is undefined
return (
<p>
{data.title}
</p>
);
}
## Built-in Elements
mono-jsx provides some built-in elements to help you build your app.
### `<toggle>` element
`<toggle>` element allows you to toggle the visibility of the slotted content.
```jsx
function App() {
$state.show = false
return (
<div>
<toggle value={$state.show}>
<h1>Hello World!</h1>
</toggle>
<button onClick={() => $state.show = !$state.show}>{$computed(() => $state.show ? "Hide" : "Show")}</button>
</div>
);
}
<switch>
element
<switch>
element allows you to switch the slotted content based on the value
property. You need to define the slot
attribute in the slotted content to match the value
, otherwise, the default slots will be rendered.
function App() {
return (
<div>
<switch value={$state.lang}>
<h1 slot="en">Hello, world!</h1>
<h1 slot="zh">你好,世界!</h1>
<h1>✋🌎❗️</h1>
</switch>
<button onClick={() => $state.lang = "en"}>English</button>
<button onClick={() => $state.lang = "zh"}>中文</button>
</div>
);
}
<cache>
element
Work in progress...
Streaming Rendering
mono-jsx renders your <html>
as a readable stream, that allows async function components are rendered asynchrously. You can set a placeholder
attribute to show a loading state while the async component is loading.
async function Sleep(ms) {
await new Promise((resolve) => setTimeout(resolve, ms));
return <solt />;
}
export default {
fetch: (req) => (
<html>
<h1>Hello World!</h1>
<Sleep ms={1000} placeholder={<p>Sleeping...</p>}>
<p>After 1 second</p>
</Sleep>
</html>
),
};
You can also set rendering
attribute to control the rendering strategy of the async component. Currently, only eager
is supported that renders the async component immediately.
async function Sleep(ms) {
await new Promise((resolve) => setTimeout(resolve, ms));
return <solt />;
}
export default {
fetch: (req) => (
<html>
<h1>Hello World!</h1>
<Sleep ms={1000} rendering="eager">
<p>After 1 second</p>
</Sleep>
</html>
),
};