Skip to main content
The Deno 2 Release Candidate is here
Learn more

van-jsx

GitHub npm bundlejs

A small 1kb JSX libs for building SSR/UI with vanilla and hooks.

  • Control JSX with vanilla-js and hooks.
  • Fast SSR without rehydration or re-render.
  • TypeScript support out of the box.
  • No virtual-dom.

Install

Npm

npm i van-jsx

Deno

import {...} from "https://deno.land/x/van_jsx/mod.ts";

Usage

/* @jsx h */
/* @jsxFrag h.Fragment */

import { h, render, use } from "van-jsx";

const Counter = () => {
  const state = { count: 0 };
  const [btn, Button] = use.button();
  const [count, Count] = use.span();

  use.mount(() => {
    // ready to use vanilla.
    btn.onclick = () => {
      count.innerText = (state.count += 1).toString();
    };
  });

  return (
    <Button>
      Click Me <Count>{state.count}</Count>
    </Button>
  );
};

render(<Counter />, document.getElementById("root"));

// No problem
// render(
//   <>
//     <Counter />
//     <Counter />
//     <Counter />
//   </>,
//   document.getElementById("root"),
// );

SSR

/* @jsx h */
/* @jsxFrag h.Fragment */

import { h, initSSR, rewind } from "van-jsx";
import App from "./app.tsx";

// example using express
app.get("/", (req, res) => {
  initSSR();
  const html = (
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <title>My App</title>
      </head>
      <body>
        <App />
        <script src="/client.js"></script>
      </body>
    </html>,
  );
  res.send(html);
});

// on the client interactive
rewind(<App />);

Use

use.[HTMLElement]

Hook HTMLElement to markup.

use.element

Hook HTMLElement / FunctionComponent to markup.

use.mount

Access Dom after render.

Options Hook

Options.elem

Attach where element is created.

options.elem = (data) => {
  console.log(data);
};

Options.fc

Attach where FunctionComponent is created.

options.fc = (data) => {
  console.log(data);
};

Example Todo App

const Item: FC<{ name: string }> = (props) => {
  const [item, TodoItem] = use.li();
  const [remove, Remove] = use.button();

  use.mount(() => {
    remove.onclick = () => item.remove();
  });

  return (
    <TodoItem>
      <span>{props.name}</span>
      <Remove>remove</Remove>
    </TodoItem>
  )
}
const Todo: FC<{ data: string[] }> = (props) => {
  // inital state from server
  const state = { todos: props.data };

  const [form, Form] = use.form();
  const [input, TodoInput] = use.input();
  const [list, TodoList] = use.div();

  use.mount(() => {
    form.onsubmit = (e) => {
      e.preventDefault();
      list.append(<Item name={input.value}/>);
      input.value = "";
      input.focus();
    };
  });

  return (
    <>
      <h1>Welcome Todo</h1>
      <Form>
        <TodoInput placeholder="text..." />
        <button type="submit">Submit</button>
      </Form>
      <TodoList>{state.todos.map((name) => <Item name={name}/>)}</TodoList>
    </>
  );
};

License

MIT