@barndev/htjs
TypeScript icon, indicating that this package has built-in type declarations

1.0.6 • Public • Published

@barndev/htjs

npm bundle size npm bundle size minzip npm version

Lightweight, no build, plain JS alternative to JSX

<script type="module">
    import { h, render } from 'https://esm.sh/preact';
    import { useState } from 'https://esm.sh/preact/hooks';
    import { $, _, bind } from 'https://esm.sh/@barndev/htjs';

    bind(h);

    const Counter = $(({ initialCount = 0 }) => {
        const [count, setCount] = useState(initialCount);
        const increment = () => setCount((currentCount) => currentCount + 1);
        const decrement = () => setCount((currentCount) => currentCount - 1);

        return _.div(
            _.p(`The count is ${count}`),
            _.button({ onClick: increment })('Increment'),
            _.button({ onClick: decrement })('Decrement')
        );
    });

    render(Counter({ initialCount: 3 })(), document.body);
</script>

JSX is great, but there are certain scenarios where it feels like more pain than it's worth. For example, many applications aim to be zero-build. This can come in many forms, including vanilla-JS-only applications, applications that opt for JSDoc over Typescript, or applications that only use reactivity for small pieces of web pages in individual <script> tags. Zero-build apps cannot use JSX. An additional reason to opt for a JSX-alternative is found in applications that are not bootstrapped with JSX support out of the box. For example, a backend application that just wants to serve some HTML with client side reactivity. Instead of creating an entire build pipeline for 4 JS components and one HTML file, or re-learning how to setup babel for JSX, Typscript, and my-one-project-specific-case every time you need to use it, it may be time to ditch JSX or reach for a JSX alternative. @barndev/htjs provides an intuative API for describing UI with natural indentation.

For an alternative JSX alternative, try htm.

Installation

@barndev/htjs is available on npm and esm.sh CDN.

Install via npm

npm i @barndev/htjs

Import from esm.sh

import { _, $, bind } from 'https://esm.sh/@barndev/htjs';

Usage

Binding

@barndev/htjs provides an API for describing the UI - the actual creation of UI elements is handled by an external createElement function that you provide. React's createElement and Preact's h functions work out of the box, but createElement can be any function that takes in type, props and ...children as args and returns an element object. Before any HTJS elements can be used, the bind function must be called to set createElement:

import { h } from 'preact';
import { bind } from '@barndev/htjs';

bind(h);

Creating Elements

HTJS elements and components are expressed with factory functions that have 2 overloads:

with props

_.div({ className: 'p-4' })(
    _.h1({ className: 'text-black' })('Title'),
    _.a({ href: 'https://github.com/benarmstrongg/htjs' })('Link'),
    _.input({ type: 'text', onChange: handleChange })()
);

without props

_.div(_.h1('Title'), _.p('Description'));

If props are provided, _.<elem> returns a factory-factory, or a function that returns a function that returns a createElement. If props are omitted, a normal createElement factory function is returned with props set to null.

Creating Components

The $ function creates a component given a function. The usage is the same as the _.<elem> function, using the with-or-without-props approach.

const Card = $({ children } =>
    div({ className: 'p-2' })(children)
);
const Button = $((props) => {
    return button({
        className: 'btn btn-primary'
        onClick: props.onClick
    })(props.text);
});

Card(Button({ text: 'Click Me', onClick: alert('Clicked!') })());

The $ method is required to opt in to the with-or-without-props API, and only objects created with this function will be treated as components. While regular JS functions can be used to group elements, it's important to note that these are not components. The element returned by createElement will be that of the root element in the return statement, not that of a component. This means things like React/Preact hooks will not work inside these functions.

// ok
const Form = ({ initialState, onChange }) => _.form(
    _.input({
        value: initialState.name,
        placeholder: 'Name',
        onChange
    })(),
    _.input({
        value: initialState.address,
        placeholder: 'Address',
        onChange
    })(),
);

// if it needs state, it must be wrapped in $
// not ok
const Form = () => {
    const [state, setState] = useState({});
    // ...
}
// ok
const Form = $(() = => {
    const [state, setState] = useState({});
    // ...
});

/elems

The alternate @barndev/htjs/elems entrypoint exports elements as individual functions such as h1, select, and dialog. This adds over 5 KB to the bundle size. This entrypoint also reexports the $ function and exports its own bind function that must be used instead of the root bind.

import { h } from 'https://esm.sh/preact';
// bind() from /elems must be used
import { bind, $, select, options } from 'https://esm.sh/@barndev/htjs/elems';

bind(h);

const MyDropdown = $(({ handleChange }) =>
    select({ onChange: handleChange })(option('None'), option('Some'));
);

Integrations

As previously mentioned, @barndev/htjs is built to work with React and Preact by default Initial render of virtual DOM nodes is slightly different between these libraries:

Preact

import { h, render } from 'preact';
import { bind } from '@barndev/htjs';
import App from './App';

bind(h);
render(App(), document.body);

React

import React from 'react';
import ReactDOM from 'react-dom';
import { $, bind } from '@barndev/htjs';
import App from './App';

bind(React.createElement);
ReactDOM.createRoot(document.getElementById('root')).render(
    $(React.StrictMode)(App())
);

Additionally, any createElement function that adheres to the following signature can be passed to bind and used:

function createElement(type, props, ...children) {
    // ...
    return element;
}

Readme

Keywords

Package Sidebar

Install

npm i @barndev/htjs

Weekly Downloads

1

Version

1.0.6

License

MIT

Unpacked Size

278 kB

Total Files

11

Last publish

Collaborators

  • barndev