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

mono-jsx

v0.0.0-alpha.12

Published

`<html>` as a `Response`.

Downloads

392

Readme

mono-jsx

<html> as a Response

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>
  ),
};