van-jsx

GitHub npm bundlejs

A small ~1kb JSX libs for building Vanilla App.

  • Control JSX with vanilla-js.
  • SSR Ready.
  • TypeScript support out of the box.
  • No virtual-dom.
  • Router with SSR support.
  • Helmet with SSR support.

Starter

Client App

npx degit herudi/van-jsx-starter my-app

cd my-app

// install deps
npm install

// run dev
npm run dev

// build
npm run build

SSR App

Coming Soon...

Install

Npm

npm i van-jsx

Deno

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

Usage

/* @jsx h */

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

const Counter = () => {
  const state = { count: 0 };
  const Host = createHost();

  Host.controller = ({ btn, count }) => {
    btn.onclick = () => {
      count.innerText = (state.count += 1).toString();
    };
  };

  return (
    <Host>
      <button ref="btn">Click Me</button>
      <h1 ref="count">{state.count}</h1>
    </Host>
  );
};

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

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

SSR

/* @jsx h */

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

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

// on the client interactive
hydrate(<App />, document.getElementById("root"));

Router

import { createRouter, Link } from "van-jsx/router";

const App = () => {
  const Route = createRouter();
  return (
    <>
      <nav>
        <Link href="/">Home</Link>
        <Link href="/about">About</Link>
      </nav>
      <Route path="/" component={() => <Home />} />
      <Route path="/about" component={() => <About />} />
    </>
  );
};

Helmet

import { Helmet } from "van-jsx/helmet";

const App = () => {
  return (
    <Host>
      <Helmet>
        <title>Hello App</title>
        <script>{`console.log("hello from head")`}</script>
      </Helmet>
      <Helmet footer>
        <script>{`console.log("hello from body")`}</script>
      </Helmet>
      <div>
        <h1>Hello App</h1>
      </div>
    </Host>
  );
};

Helmet SSR

import { Helmet } from "van-jsx/helmet";

// example using express
app.get("/", (req, res) => {
  const { head, attr, body, footer } = Helmet.rewind(<App />);
  const html = (
    <html lang="en" {...attr.html.toJSON()}>
      <head>
        <meta charset="utf-8" />
        {head}
      </head>
      <body {...attr.body.toJSON()}>
        <div id="root">
          {body}
        </div>
        <script src="/client.js"></script>
        {footer}
      </body>
    </html>
  );
  res.send(html);
});

Host

Just Fragment to control jsx with vanilla-js.

const MyComp = () => {
  const Host = createHost();

  Host.controller = ({ my_text }) => {
    // my_text is a ref="my_text"

    my_text.innerText = "Bar";
  };

  return (
    <Host>
      <h1 ref="my_text">Foo</h1>
    </Host>
  );
};

// Dom updated.
// <h1>Bar</h1>

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

Lazy

const Home = lazy(() => import("./home.tsx"));

Lazy SSR

const Home = await lazySSR(() => import("./home.tsx"));

Example Todo App

const Item: FC<{ name: string }> = (props) => {
  // create Host as li
  const Host = createHost("li");

  Host.controller = ({ item, remove }) => {
    remove.onclick = () => item.remove();
  };

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

  const Host = createHost();

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

  return (
    <Host>
      <h1>Welcome Todo</h1>
      <form ref="form">
        <input ref="input" placeholder="text..." />
        <button type="submit">Submit</button>
      </form>
      <div ref="list">{state.todos.map((name) => <Item name={name} />)}</div>
    </Host>
  );
};

License

MIT