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

prerender-macro

v0.1.1

Published

<p align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://github.com/aralroca/prerender-macro/assets/13313058/6dd31f2c-5cf4-445c-89d4-9a4e0acd4cdc" height="128"> <img src="https://github.com/aralroca/prerend

Downloads

34

Readme

npm version npm PRs Welcome

At a glance

prerender-macro plugin allows Partial Prerendering (PPR) to make hybrid pages between dynamic and static components, avoiding the rendering in runtime of the static ones, this rendering is done in build-time thanks to Bun's macros.

import StaticComponent from "@/static-component" with { type: "prerender" };
import DynamicComponent from "@/dynamic-component";

// ...
return (
  <>
    <StaticComponent foo="bar" />
    <DynamicComponent />
  </>
);

In this way:

  • The bundle is smaller because instead of carrying all the JS it only carries the prerendered HTML.
  • The runtime speed of rendering is faster, it only has to render the dynamic components.

How does it work?

This plugin transforms the previous code into this code:

import { prerender } from "prerender-macro/prerender" with { "type": "macro" };
import DynamicComponent from "@/dynamic-component";

// ...
return (
  <>
    {prerender({
      componentPath: "@/static-component",
      componentModuleName: "default",
      componentProps: { foo: "bar" },
    })}
    <DynamicComponent />
  </>
);

And pass it back through the Bun transpiler to run the macro. Bun macro together with the prerender helper takes care of converting the component to html string in build-time. This way it will only be necessary in runtime to make the rendering of those dynamic.

[!IMPORTANT]

Macros can accept component properties, but only in limited cases. The value must be statically known. For more info take a look at the Bun Macros Arguments documentation.

Quick start

Install

bun install prerender-macro

Use it in Bun.build

To use it you have to set the prerenderConfigPath (mandatory), which is the path where you have the configuration export, if it is in the same file you can use import.meta.url.

import prerenderMacroPlugin from "prerender-macro";

// The configuration should be adapted to the framework that you are using:
export const prerenderConfig = {
  render: (Component, props) => /* mandatory */,
  postRender: () => /* optional */
};

Bun.build({
  plugins: [prerenderMacroPlugin({ prerenderConfigPath: import.meta.url })],
  entrypoints,
  outdir,
  root,
});

Configuration

The prerender-macro plugin needs this mandatory configuration to work:

| Parameter | Description | Mandatory | | --------------------- | --------------------------------------------------------------- | --------- | | prerenderConfigPath | String path of the file with the prerenderConfig named export | true |

The configuration can be in another file, but it must have the named export prerenderConfig.

It is necessary to do it this way because this configuration will be executed when doing the prerender inside a Bun macro, and at this point we cannot pass it from the plugin because it would need to be serialized, so it is better that you directly access it.

The prerenderConfig named export needs this mandatory configuration to work:

| Parameter | Description | Mandatory | Can be async | | ------------ | ------------------------------------------------------------------------------------------------------------------- | --------- | ------------ | | render | Function to render the component on your framework (AOT) | true | yes | | postRender | Function to make a post rendering in runtime (JIT) | false | no |

[!NOTE]

It is not necessary to indicate the jsx-runtime, it will work with the one you have and it can connect with any JSX framework.

Configuration examples in different frameworks

| Framework | Render ahead of time | Inject ahead of time | Preserves the HTML structure | Demo | | --------------------------------------------------------------------------- | -------------------- | -------------------- | ---------------------------- | ---------------------------- | | Brisa | ✅ | ✅ | ✅ | 🔗 | | React | ✅ | ❌ | ❌ | 🔗 | | Preact | ✅ | ✅ | ❌ | 🔗 | | Kitajs/html | ✅ | ✅ | ✅ | 🔗 |

[!TIP]

👉 Add your framework

Brisa (experimental)

Configuration example:

import prerenderMacroPlugin, { type PrerenderConfig } from "prerender-macro";
import { dangerHTML } from "brisa";
import { renderToString } from "brisa/server";

export const prerenderConfig = {
  render: async (Component, props) =>
    dangerHTML(await renderToString(<Component {...props} />)),
} satisfies PrerenderConfig;

export const plugin = prerenderMacroPlugin({
  prerenderConfigPath: import.meta.url,
});

[!NOTE]

Brisa elements can be seamlessly coerced with Bun's AST and everything can be done AOT without having to use a postRender.

[!NOTE]

Brisa does not add extra nodes in the HTML, so it is a prerender of the real component, without modifying its structure.

[!WARNING]

Brisa is an experimental framework that we are building.

Brisa is not yet public but it will be in the next months. If you want to be updated, subscribe to my blog newsletter.

React

For React components, since React does not have a built-in function for injecting HTML strings directly into JSX, you need to use dangerouslySetInnerHTML. This allows you to bypass React's default behavior and inject raw HTML into the DOM.

Configuration example:

import prerenderMacroPlugin, { type PrerenderConfig } from "prerender-macro";
import { renderToString } from "react-dom/server";

export const prerenderConfig = {
  render: async (Component, props) => {
    return renderToString(<Component {...props} />);
  },
  postRender: (htmlString) => (
    <div dangerouslySetInnerHTML={{ __html: htmlString }} />
  ),
} satisfies PrerenderConfig;

export const plugin = prerenderMacroPlugin({
  prerenderConfigPath: import.meta.url,
});

[!IMPORTANT]

React elements have the $$typeof symbol and therefore cannot coerce to Bun's AST. This is why it is necessary to do the postRender in JIT.

[!CAUTION]

Additional <div> Nodes: Using dangerouslySetInnerHTML to inject HTML strings into JSX components results in the creation of an additional <div> node for each injection, which may affect the structure of your rendered output. Unlike Brisa, where this issue is avoided, the extra <div> nodes can lead to unexpected layout changes or styling issues.

Preact

For Preact components, since Preact does not have a built-in function for injecting HTML strings directly into JSX, you need to use dangerouslySetInnerHTML. This allows you to bypass Preact's default behavior and inject raw HTML into the DOM.

Configuration example:

import prerenderMacroPlugin, { type PrerenderConfig } from "prerender-macro";
import { render } from "preact-render-to-string";

export const prerenderConfig = {
  render: async (Component, props) => {
    return (
      <div
        dangerouslySetInnerHTML={{ __html: render(<Component {...props} />) }}
      />
    );
  },
} satisfies PrerenderConfig;

export const plugin = prerenderMacroPlugin({
  prerenderConfigPath: import.meta.url,
});

[!NOTE]

Preact elements can be seamlessly coerced with Bun's AST and everything can be done AOT without having to use a postRender.

[!CAUTION]

Additional <div> Nodes: Using dangerouslySetInnerHTML attribute to inject HTML strings into JSX components results in the creation of an additional <div> node for each injection, which may affect the structure of your rendered output. Unlike Brisa, where this issue is avoided, the extra <div> nodes can lead to unexpected layout changes or styling issues.

Kitajs/html

Configuration example:

import { createElement } from "@kitajs/html";
import prerenderMacroPlugin, { type PrerenderConfig } from "prerender-macro";

export const prerenderConfig = {
  render: createElement,
} satisfies PrerenderConfig;

export const plugin = prerenderMacroPlugin({
  prerenderConfigPath: import.meta.url,
});

[!NOTE]

Kitajs/html elements can be seamlessly coerced with Bun's AST and everything can be done AOT without having to use a postRender.

[!NOTE]

Kitajs/html does not add extra nodes in the HTML, so it is a prerender of the real component, without modifying its structure.

Add your framework example

This project is open-source and totally open for you to contribute by adding the JSX framework you use, I'm sure it can help a lot of people.

To add your framework you have to:

  • Fork & clone
  • Create a folder inside tests with your framework that is a copy of some other framework. The same for examples.
  • Make the changes and adapt the example and tests to your framework
  • Update the package.json scripts to add your framework
  • Update the README.md adding the documentation of your framework.
  • Open a PR with the changes.

Contributing

See Contributing Guide and please follow our Code of Conduct.

License

MIT