{"id":15011405,"url":"https://github.com/aralroca/prerender-macro","last_synced_at":"2025-04-12T03:42:11.047Z","repository":{"id":229442870,"uuid":"773707500","full_name":"aralroca/prerender-macro","owner":"aralroca","description":"Bun plugin to prerender JSX components using a kind of macro","archived":false,"fork":false,"pushed_at":"2024-03-27T17:46:34.000Z","size":126,"stargazers_count":93,"open_issues_count":3,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-25T23:22:17.232Z","etag":null,"topics":["brisa","bunjs","jsx","macros","partial-prerendering","plugin","preact","prerender","react"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aralroca.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-18T08:57:18.000Z","updated_at":"2025-03-16T19:34:42.000Z","dependencies_parsed_at":"2024-11-07T10:01:42.593Z","dependency_job_id":"584cff9e-32de-41ba-b09e-682def01d41c","html_url":"https://github.com/aralroca/prerender-macro","commit_stats":{"total_commits":65,"total_committers":1,"mean_commits":65.0,"dds":0.0,"last_synced_commit":"853f58c7b5a174a4bcd2ecd05f6c7d074ccc856f"},"previous_names":["aralroca/prerender-macro"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aralroca%2Fprerender-macro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aralroca%2Fprerender-macro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aralroca%2Fprerender-macro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aralroca%2Fprerender-macro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aralroca","download_url":"https://codeload.github.com/aralroca/prerender-macro/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248514221,"owners_count":21116899,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["brisa","bunjs","jsx","macros","partial-prerendering","plugin","preact","prerender","react"],"created_at":"2024-09-24T19:41:03.092Z","updated_at":"2025-04-12T03:42:11.029Z","avatar_url":"https://github.com/aralroca.png","language":"TypeScript","readme":"\u003cp align=\"center\"\u003e\n    \u003cpicture\u003e\n      \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://github.com/aralroca/prerender-macro/assets/13313058/6dd31f2c-5cf4-445c-89d4-9a4e0acd4cdc\" height=\"128\"\u003e\n      \u003cimg src=\"https://github.com/aralroca/prerender-macro/assets/13313058/24a41ba3-081e-4e7f-b6b4-fdc4d62c29ec\" height=\"128\"\u003e\n    \u003c/picture\u003e\n    \u003ch1 align=\"center\"\u003ePrerender Macro\u003c/h1\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003eBun plugin to \u003cb\u003eprerender\u003c/b\u003e JSX components using a kind of \u003cb\u003emacro\u003c/b\u003e.\u003c/p\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![npm version](https://badge.fury.io/js/prerender-macro.svg)](https://badge.fury.io/js/prerender-macro)\n![npm](https://img.shields.io/npm/dw/prerender-macro)\n[![PRs Welcome][badge-prwelcome]][prwelcome]\n\u003ca href=\"https://github.com/aralroca/prerender-macro/actions?query=workflow%3ATest\" alt=\"Tests status\"\u003e\n\u003cimg src=\"https://github.com/aralroca/prerender-macro/workflows/Test/badge.svg\" /\u003e\u003c/a\u003e\n\u003ca href=\"https://twitter.com/intent/follow?screen_name=aralroca\"\u003e\n\u003cimg src=\"https://img.shields.io/twitter/follow/aralroca?style=social\u0026logo=x\"\n            alt=\"follow on Twitter\"\u003e\u003c/a\u003e\n\n\u003c/div\u003e\n\n[badge-prwelcome]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square\n[prwelcome]: http://makeapullrequest.com\n\n\u003cp align=\"center\"\u003eWork in every JSX Framework.\u003c/p\u003e\n\n- [At a glance](#at-a-glance)\n  - [How does it work?](#how-does-it-work)\n- [Quick Start](#quick-start)\n  - [Install](#install)\n  - [Use it in `Bun.build`](#use-it-in-bunbuild)\n- [Configuration](#configuration)\n- [Configuration examples in different frameworks](#configuration-examples-in-different-frameworks)\n  - [Brisa](#brisa-experimental)\n  - [React](#react)\n  - [Preact](#preact)\n  - [Kitajs/html](#kitajshtml)\n  - [Add your framework example](#add-your-framework-example)\n- [Contributing](#contributing)\n- [License](#license)\n\n## At a glance\n\n`prerender-macro` plugin allows [Partial Prerendering](https://aralroca.com/blog/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.\n\n```tsx\nimport StaticComponent from \"@/static-component\" with { type: \"prerender\" };\nimport DynamicComponent from \"@/dynamic-component\";\n\n// ...\nreturn (\n  \u003c\u003e\n    \u003cStaticComponent foo=\"bar\" /\u003e\n    \u003cDynamicComponent /\u003e\n  \u003c/\u003e\n);\n```\n\nIn this way:\n\n- The bundle is smaller because instead of carrying all the JS it only carries the prerendered HTML.\n- The runtime speed of rendering is faster, it only has to render the dynamic components.\n\n\u003cfigure align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/aralroca/prerender-macro/assets/13313058/8ab3cf1d-c395-494e-88aa-69ca207d7bdc\" alt=\"React example\" class=\"center\" /\u003e\n\u003c/figure\u003e\n\n### How does it work?\n\nThis plugin transforms the previous code into this code:\n\n```tsx\nimport { prerender } from \"prerender-macro/prerender\" with { \"type\": \"macro\" };\nimport DynamicComponent from \"@/dynamic-component\";\n\n// ...\nreturn (\n  \u003c\u003e\n    {prerender({\n      componentPath: \"@/static-component\",\n      componentModuleName: \"default\",\n      componentProps: { foo: \"bar\" },\n    })}\n    \u003cDynamicComponent /\u003e\n  \u003c/\u003e\n);\n```\n\nAnd pass it back through the [Bun transpiler](https://bun.sh/docs/api/transpiler) to run the macro. [Bun macro](https://bun.sh/docs/bundler/macros#:~:text=Bun%20Macros%20are%20import%20statements,number%20of%20browsers%20and%20runtimes) 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.\n\n\u003e [!IMPORTANT]\n\u003e\n\u003e 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](https://bun.sh/docs/bundler/macros#arguments) documentation.\n\n## Quick start\n\n### Install\n\n```sh\nbun install prerender-macro\n```\n\n### Use it in `Bun.build`\n\nTo 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`.\n\n```tsx\nimport prerenderMacroPlugin from \"prerender-macro\";\n\n// The configuration should be adapted to the framework that you are using:\nexport const prerenderConfig = {\n  render: (Component, props) =\u003e /* mandatory */,\n  postRender: () =\u003e /* optional */\n};\n\nBun.build({\n  plugins: [prerenderMacroPlugin({ prerenderConfigPath: import.meta.url })],\n  entrypoints,\n  outdir,\n  root,\n});\n```\n\n## Configuration\n\nThe `prerender-macro` plugin needs this mandatory configuration to work:\n\n| Parameter             | Description                                                     | Mandatory |\n| --------------------- | --------------------------------------------------------------- | --------- |\n| `prerenderConfigPath` | String path of the file with the `prerenderConfig` named export | `true`    |\n\nThe configuration can be in another file, but it must have the named export `prerenderConfig`.\n\nIt 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.\n\nThe `prerenderConfig` named export needs this mandatory configuration to work:\n\n| Parameter    | Description                                                                                                         | Mandatory | Can be async |\n| ------------ | ------------------------------------------------------------------------------------------------------------------- | --------- | ------------ |\n| `render`     | Function to render the component on your framework ([AOT](https://en.wikipedia.org/wiki/Ahead-of-time_compilation)) | `true`    | `yes`        |\n| `postRender` | Function to make a post rendering in runtime ([JIT](https://en.wikipedia.org/wiki/Just-in-time_compilation))        | `false`   | `no`         |\n\n\u003e [!NOTE]\n\u003e\n\u003e 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**.\n\n## Configuration examples in different frameworks\n\n| Framework                                                                   | Render ahead of time | Inject ahead of time | Preserves the HTML structure | Demo                         |\n| --------------------------------------------------------------------------- | -------------------- | -------------------- | ---------------------------- | ---------------------------- |\n| \u003cdiv style=\"font-size: 16px;\"\u003e\u003ca href=\"#brisa-experimental\"\u003eBrisa\u003c/a\u003e\u003c/div\u003e | ✅                   | ✅                   | ✅                           | [🔗](/examples/brisa/)       |\n| \u003cdiv style=\"font-size: 16px;\"\u003e\u003ca href=\"#react\"\u003eReact\u003c/a\u003e\u003c/div\u003e              | ✅                   | ❌                   | ❌                           | [🔗](/examples/react/)       |\n| \u003cdiv style=\"font-size: 16px;\"\u003e\u003ca href=\"#preact\"\u003ePreact\u003c/a\u003e\u003c/div\u003e            | ✅                   | ✅                   | ❌                           | [🔗](/examples/preact/)      |\n| \u003cdiv style=\"font-size: 16px;\"\u003e\u003ca href=\"#kitajshtml\"\u003eKitajs/html\u003c/a\u003e\u003c/div\u003e   | ✅                   | ✅                   | ✅                           | [🔗](/examples/kitajs-html/) |\n\n\u003e [!TIP]\n\u003e\n\u003e 👉 [Add your framework](#add-your-framework-example)\n\n### Brisa _(experimental)_\n\nConfiguration example:\n\n```tsx\nimport prerenderMacroPlugin, { type PrerenderConfig } from \"prerender-macro\";\nimport { dangerHTML } from \"brisa\";\nimport { renderToString } from \"brisa/server\";\n\nexport const prerenderConfig = {\n  render: async (Component, props) =\u003e\n    dangerHTML(await renderToString(\u003cComponent {...props} /\u003e)),\n} satisfies PrerenderConfig;\n\nexport const plugin = prerenderMacroPlugin({\n  prerenderConfigPath: import.meta.url,\n});\n```\n\n\u003e [!NOTE]\n\u003e\n\u003e Brisa elements can be seamlessly coerced with Bun's AST and everything can be done AOT without having to use a `postRender`.\n\n\u003e [!NOTE]\n\u003e\n\u003e Brisa does not add extra nodes in the HTML, so it is a prerender of the real component, without modifying its structure.\n\n\u003e [!WARNING]\n\u003e\n\u003e Brisa is an _experimental_ framework that we are building.\n\nBrisa is not yet public but it will be in the next months. If you want to be updated, subscribe to my [blog newsletter](https://aralroca.com/blog).\n\n### React\n\nFor 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.\n\nConfiguration example:\n\n```tsx\nimport prerenderMacroPlugin, { type PrerenderConfig } from \"prerender-macro\";\nimport { renderToString } from \"react-dom/server\";\n\nexport const prerenderConfig = {\n  render: async (Component, props) =\u003e {\n    return renderToString(\u003cComponent {...props} /\u003e);\n  },\n  postRender: (htmlString) =\u003e (\n    \u003cdiv dangerouslySetInnerHTML={{ __html: htmlString }} /\u003e\n  ),\n} satisfies PrerenderConfig;\n\nexport const plugin = prerenderMacroPlugin({\n  prerenderConfigPath: import.meta.url,\n});\n```\n\n\u003e [!IMPORTANT]\n\u003e\n\u003e 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](https://en.wikipedia.org/wiki/Just-in-time_compilation).\n\n\u003e [!CAUTION]\n\u003e\n\u003e **Additional `\u003cdiv\u003e` Nodes**: Using `dangerouslySetInnerHTML` to inject HTML strings into JSX components results in the creation of an additional `\u003cdiv\u003e` node for each injection, which may affect the structure of your rendered output. Unlike [Brisa](#brisa-experimental), where this issue is avoided, the extra `\u003cdiv\u003e` nodes can lead to unexpected layout changes or styling issues.\n\n### Preact\n\nFor 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.\n\nConfiguration example:\n\n```tsx\nimport prerenderMacroPlugin, { type PrerenderConfig } from \"prerender-macro\";\nimport { render } from \"preact-render-to-string\";\n\nexport const prerenderConfig = {\n  render: async (Component, props) =\u003e {\n    return (\n      \u003cdiv\n        dangerouslySetInnerHTML={{ __html: render(\u003cComponent {...props} /\u003e) }}\n      /\u003e\n    );\n  },\n} satisfies PrerenderConfig;\n\nexport const plugin = prerenderMacroPlugin({\n  prerenderConfigPath: import.meta.url,\n});\n```\n\n\u003e [!NOTE]\n\u003e\n\u003e Preact elements can be seamlessly coerced with Bun's AST and everything can be done AOT without having to use a `postRender`.\n\n\u003e [!CAUTION]\n\u003e\n\u003e **Additional `\u003cdiv\u003e` Nodes**: Using `dangerouslySetInnerHTML` attribute to inject HTML strings into JSX components results in the creation of an additional `\u003cdiv\u003e` node for each injection, which may affect the structure of your rendered output. Unlike [Brisa](#brisa-experimental), where this issue is avoided, the extra `\u003cdiv\u003e` nodes can lead to unexpected layout changes or styling issues.\n\n### Kitajs/html\n\nConfiguration example:\n\n```tsx\nimport { createElement } from \"@kitajs/html\";\nimport prerenderMacroPlugin, { type PrerenderConfig } from \"prerender-macro\";\n\nexport const prerenderConfig = {\n  render: createElement,\n} satisfies PrerenderConfig;\n\nexport const plugin = prerenderMacroPlugin({\n  prerenderConfigPath: import.meta.url,\n});\n```\n\n\u003e [!NOTE]\n\u003e\n\u003e Kitajs/html elements can be seamlessly coerced with Bun's AST and everything can be done AOT without having to use a `postRender`.\n\n\u003e [!NOTE]\n\u003e\n\u003e Kitajs/html does not add extra nodes in the HTML, so it is a prerender of the real component, without modifying its structure.\n\n### Add your framework example\n\nThis 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.\n\nTo add your framework you have to:\n\n- Fork \u0026 clone\n- Create a folder inside [`tests`](/tests/) with your framework that is a copy of some other framework. The same for [`examples`](/examples/).\n- Make the changes and adapt the example and tests to your framework\n- Update the package.json scripts to add your framework\n- Update the [`README.md`](/README.md) adding the documentation of your framework.\n- Open a PR with the changes.\n\n## Contributing\n\nSee [Contributing Guide](CONTRIBUTING.md) and please follow our [Code of Conduct](CODE_OF_CONDUCT.md).\n\n## License\n\n[MIT](LICENSE)\n","funding_links":[],"categories":["Extensions"],"sub_categories":["Utilities"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faralroca%2Fprerender-macro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faralroca%2Fprerender-macro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faralroca%2Fprerender-macro/lists"}