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

easy-jsx-html-engine

v0.1.0

Published

Dead simple HTML engine using JSX syntax.

Downloads

6

Readme

Easy JSX HTML Engine

Dead simple HTML engine using JSX syntax. Inspired by @kitajs/html with safety by default and WebAPI streams.

Quick Start

Open your terminal and run the following command:

npm install easy-jsx-html-engine

Add the following options to your tsconfig.json file:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "easy-jsx-html-engine",
  }
}

Then, you can use the engine like this:

const html = (
  <h1>Hello, World!</h1>
).toHTML();

You can create a custom component like this:

const MyComponent = ({ name }: { name: string }) => (
  <h1>Hello, {name}!</h1>
);

const html = (
  <MyComponent name="World" />
).toHTML();

It also works with async functions:

async function MyAsyncComponent({ name }: { name: string }) {
  return (
    <h1>Hello, {name}!</h1>
  );
}

const html = (
  await <div><MyAsyncComponent name="World" /></div>
).toHTML();

ErrorBoundary is supported:

async function BadComponent() {
  throw new Error('Bad component');
}

const html = (
  <ErrorBoundary catch={<h1>Something went wrong</h1>}>
    <BadComponent />
  </ErrorBoundary>
).toHTML();

Even Suspense is supported:

import { renderToStream } from "easy-jsx-html-engine/stream-webapi";

async function MyAsyncComponent({ name }: { name: string }) {
  return (
    <h1>Hello, {name}!</h1>
  );
}

const stream: ReadableStream = renderToStream(
  (rid) => (
    <Suspense rid={rid} fallback={<h1>Loading...</h1>} catch={<h1>Something went wrong</h1>}>
      <MyAsyncComponent />
    </Suspense>
  ),
);

Safety By Default

The engine is designed to be safe by default to prevent unwanted html injections.

This relies on a simple interface with a toHTML method that returns a string. This is what the engine uses to determine whether to escape the content or not.

However, it sometimes may be necessary to inject unescaped content. In this case, you can use the dangerouslyPreventEscaping function:

const html = (
  <div>
    {dangerouslyPreventEscaping('<h1>Hello, World!</h1>')}
  </div>
).toHTML();

Async Components

You may use async functions to create components and even insert promises as a child, however this causes all parent elements to become async unless a Suspense component is used.

The engine will wait for all child promises to resolve with Promise.all before rendering the parent element.

const html = (
  await (
    <h1>Hello, {Promise.resolve("World"}!</h1>
  )
).toHTML();

Error Boundary

You may use the ErrorBoundary component to catch errors and display a fallback component.

It works great with async components and promises:

const html = (
  await (
    <ErrorBoundary catch={(err) => <div>Something went wrong: {err.message}</div>}>
      <h1>Hello, {Promise.reject(new Error("no"))}!</h1>
    </ErrorBoundary>
  )
).toHTML();

But needs a little extra for sync errors:

const html = (
  await (
    <ErrorBoundary catch={(err) => <div>Something went wrong: {err.message}</div>}>
      {() => {
        throw new Error("no");
      }}
    </ErrorBoundary>
  )
).toHTML();

Suspense and Streams

Suspense is an extension of the ErrorBoundary component that allows you to display a fallback component while waiting for async components to resolve.

This works by rendering a placeholder component in place of the actual content and then replacing it with the resolved content once it is ready.

Note that this is only effective when rendering to a stream and requires an implementation specific to your runtime environment.

There is currently only one implementation for environments with WebAPI streams (such as service workers, Bun, Deno, and Cloudflare Workers):

import { renderToStream } from "easy-jsx-html-engine/stream-webapi";

const server = Bun.serve({
  port: 3000,
  async fetch(request) {
    return new Response(
      await renderToStream(
        (rid) => (
          <Suspense rid={rid} fallback={<h1>Loading...</h1>} catch={<h1>Something went wrong</h1>}>
            <MyAsyncComponent />
          </Suspense>
        ),
      )
    );
  },
});

renderToStream returns a string | Promise<string> | ReadableStream<Uint8Array> depending on the component tree. If the tree contains any Suspense elements, it will return a ReadableStream. Otherwise, it will return a string or Promise<string>. While it is possible to use renderToStream without awaiting the result, it is recommended to ensure that no errors occur if a promise is returned unexpectedly.

The rid or request ID is used to identify the stream and is passed to the Suspense component. This is necessary to ensure that the correct stream is resumed when the async component resolves. It is only valid for the duration of the request and should not be stored for any longer than that.

Contexts

Just like @kitajs/html, there is no support for contexts for the same reasons. In short, the purpose of contexts is to avoid prop drilling, but there is no way to keep track of the context in an async environment without prop drilling, thus there is ultimately no benefit.

Dependencies

This library has only 2 dependencies: clsx for class name building and html-escaper for escaping html content, both of which are very tiny and excellent libraries with no dependencies of their own.

Credits

This library was heavily inspired by @kitajs/html as mentioned above, but is completely rewritten from scratch in Typescript with a focus to overcome some of the usability issues and limitations of the original library. Mainly, requiring the "safe" attribute to escape content and the NodeJS requirement. Additionally, it is distributed as an ES module and the stream implementation is designed to be pluggable for different runtime environments.