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

@barndev/htjs

v1.0.6

Published

Lightweight, no build, plain JS alternative to JSX

Downloads

21

Readme

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