` node for each injection, which may affect the structure of your rendered output. Unlike [Brisa](#brisa-experimental), where this issue is avoided, the extra `
` nodes can lead to unexpected layout changes or styling issues.
+### Kitajs/html
+
+Configuration example:
+
+```tsx
+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.
diff --git a/bun.lockb b/bun.lockb
index 3eee38f..8bac8e4 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/examples/kitajs-html/README.md b/examples/kitajs-html/README.md
new file mode 100644
index 0000000..9793c76
--- /dev/null
+++ b/examples/kitajs-html/README.md
@@ -0,0 +1,17 @@
+# `prerender-macro` Kitajs/html Example
+
+This is an example with kitajs-html SSR without hotreloading and with a build process.
+
+To test it:
+
+- Clone the repo: `git clone git@github.com:aralroca/prerender-macro.git`
+- Install dependencies: `cd prerender-macro && bun install`
+- Run demo: `bun run demo:kitajs-html`
+- Open http://localhost:1234 to see the result
+- Look at `examples/kitajs-html/dist/index.js` to verify how the static parts have been converted to HTML in string.
+
+The static component is translated to html in string in build-time:
+
+```tsx
+"Static Component \uD83E\uDD76 Random number = 0.41381527597071954";
+```
diff --git a/examples/kitajs-html/package.json b/examples/kitajs-html/package.json
new file mode 100644
index 0000000..ca9ddb8
--- /dev/null
+++ b/examples/kitajs-html/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "kitajs-example",
+ "private": true,
+ "module": "build.tsx",
+ "type": "module",
+ "scripts": {
+ "start": "bun run src/build.ts && bun run dist/index.js"
+ },
+ "devDependencies": {
+ "@types/bun": "latest"
+ },
+ "peerDependencies": {
+ "typescript": "5.4.3"
+ },
+ "dependencies": {
+ "prerender-macro": "workspace:*"
+ }
+}
diff --git a/examples/kitajs-html/src/build.ts b/examples/kitajs-html/src/build.ts
new file mode 100644
index 0000000..636131a
--- /dev/null
+++ b/examples/kitajs-html/src/build.ts
@@ -0,0 +1,16 @@
+import { join } from "node:path";
+import prerenderMacro from "prerender-macro";
+
+const { success, logs } = await Bun.build({
+ entrypoints: [join(import.meta.dir, "index.tsx")],
+ outdir: join(import.meta.dir, "..", "dist"),
+ target: "bun",
+ plugins: [
+ prerenderMacro({
+ prerenderConfigPath: join(import.meta.dir, "prerender.tsx"),
+ }),
+ ],
+});
+
+if (success) console.log("Build complete ✅");
+else console.error("Build failed ❌", logs);
diff --git a/examples/kitajs-html/src/components/dynamic-component.tsx b/examples/kitajs-html/src/components/dynamic-component.tsx
new file mode 100644
index 0000000..e892a15
--- /dev/null
+++ b/examples/kitajs-html/src/components/dynamic-component.tsx
@@ -0,0 +1,7 @@
+export default function DynamicComponent({ name }: { name: string }) {
+ return (
+
+ {name} Component 🔥 Random number = {Math.random()}
+
+ );
+}
diff --git a/examples/kitajs-html/src/components/static-component.tsx b/examples/kitajs-html/src/components/static-component.tsx
new file mode 100644
index 0000000..a4db70e
--- /dev/null
+++ b/examples/kitajs-html/src/components/static-component.tsx
@@ -0,0 +1,7 @@
+export default function StaticComponent({ name }: { name: string }) {
+ return (
+
+ {name} Component 🥶 Random number = {Math.random()}
+
+ );
+}
diff --git a/examples/kitajs-html/src/index.tsx b/examples/kitajs-html/src/index.tsx
new file mode 100644
index 0000000..642e685
--- /dev/null
+++ b/examples/kitajs-html/src/index.tsx
@@ -0,0 +1,27 @@
+import DynamicComponent from "./components/dynamic-component";
+import StaticComponent from "./components/static-component" with { type: "prerender" };
+
+Bun.serve({
+ port: 1234,
+ fetch: async (request: Request) => {
+ const page = await (
+
+
+
Prerender Macro | Brisa example
+
+
+
+
+
+
Refresh
+
+
+ );
+
+ return new Response(page, {
+ headers: new Headers({ "Content-Type": "text/html" }),
+ });
+ },
+});
+
+console.log("Server running at http://localhost:1234");
diff --git a/examples/kitajs-html/src/prerender.tsx b/examples/kitajs-html/src/prerender.tsx
new file mode 100644
index 0000000..d4938b6
--- /dev/null
+++ b/examples/kitajs-html/src/prerender.tsx
@@ -0,0 +1,10 @@
+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,
+});
diff --git a/examples/kitajs-html/tsconfig.json b/examples/kitajs-html/tsconfig.json
new file mode 100644
index 0000000..925a0bf
--- /dev/null
+++ b/examples/kitajs-html/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "module": "esnext",
+ "target": "esnext",
+ "moduleResolution": "bundler",
+ "moduleDetection": "force",
+ "jsx": "react-jsx",
+ "jsxImportSource": "@kitajs/html",
+ "plugins": [{ "name": "@kitajs/ts-html-plugin" }]
+ },
+ "include": ["src"]
+}
diff --git a/package.json b/package.json
index f9c3417..e242dc1 100644
--- a/package.json
+++ b/package.json
@@ -6,23 +6,27 @@
"devDependencies": {
"react": "18.2.0",
"react-dom": "18.2.0",
- "@types/bun": "1.0.10",
- "@types/react": "18.2.69",
+ "@types/bun": "1.0.11",
+ "@types/react": "18.2.72",
"@types/react-dom": "18.2.22",
- "brisa": "0.0.37",
- "preact": "10.20.1"
+ "brisa": "0.0.38",
+ "preact": "10.20.1",
+ "@kitajs/html": "4.0.0-next.3",
+ "@kitajs/ts-html-plugin": "4.0.0-next.3"
},
"dependencies": {
"typescript": "5.4.3"
},
"scripts": {
- "test": "bun run test:brisa && bun run test:react && bun run test:preact",
+ "test": "bun run test:brisa && bun run test:react && bun run test:preact && bun run test:kitajs-html",
"test:brisa": "cd tests/brisa && bun test && cd ../..",
"test:react": "cd tests/react && bun test && cd ../..",
"test:preact": "cd tests/preact && bun test && cd ../..",
+ "test:kitajs-html": "cd tests/kitajs-html && bun test && cd ../..",
"demo:react": "cd examples/react && bun start",
"demo:brisa": "cd examples/brisa && bun start",
- "demo:preact": "cd examples/preact && bun start"
+ "demo:preact": "cd examples/preact && bun start",
+ "demo:kitajs-html": "cd examples/kitajs-html && bun start"
},
"workspaces": [
"package",
diff --git a/tests/kitajs-html/components.tsx b/tests/kitajs-html/components.tsx
new file mode 100644
index 0000000..c14f224
--- /dev/null
+++ b/tests/kitajs-html/components.tsx
@@ -0,0 +1,18 @@
+export default function Foo({
+ name = "foo",
+ nested = {},
+}: {
+ name: string;
+ nested: { foo?: string };
+}) {
+ return (
+
+ Foo, {name}
+ {nested.foo}!
+
+ );
+}
+
+export function Bar({ name = "bar" }: { name: string }) {
+ return
Bar, {name}!
;
+}
diff --git a/tests/kitajs-html/config.tsx b/tests/kitajs-html/config.tsx
new file mode 100644
index 0000000..d4938b6
--- /dev/null
+++ b/tests/kitajs-html/config.tsx
@@ -0,0 +1,10 @@
+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,
+});
diff --git a/tests/kitajs-html/package.json b/tests/kitajs-html/package.json
new file mode 100644
index 0000000..7df73b6
--- /dev/null
+++ b/tests/kitajs-html/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "test-kitajs-html",
+ "private": true,
+ "scripts": {
+ "test": "bun test"
+ },
+ "dependencies": {
+ "prerender-macro": "workspace:*"
+ }
+}
diff --git a/tests/kitajs-html/plugin.test.tsx b/tests/kitajs-html/plugin.test.tsx
new file mode 100644
index 0000000..414ec9b
--- /dev/null
+++ b/tests/kitajs-html/plugin.test.tsx
@@ -0,0 +1,189 @@
+import { join } from "node:path";
+import { describe, it, expect } from "bun:test";
+import { transpile, type TranspilerOptions } from "prerender-macro";
+
+const format = (s: string) => s.replace(/\s*\n\s*/g, "").replaceAll("'", '"');
+const configPath = join(import.meta.dir, "config.tsx");
+const currentFile = import.meta.url.replace("file://", "");
+const bunTranspiler = new Bun.Transpiler({ loader: "tsx" });
+
+function transpileAndRunMacros(config: TranspilerOptions) {
+ // Bun transpiler is needed here to run the macros
+ return format(bunTranspiler.transformSync(transpile(config)));
+}
+
+describe("Kitajs/html", () => {
+ describe("plugin", () => {
+ it('should not transform if there is not an import attribute with type "prerender"', () => {
+ const code = `
+ import Foo from "./components";
+ import { Bar } from "./components";
+
+ export default function Test() {
+ return (
+
+
+
+
+ );
+ }
+ `;
+ const output = transpileAndRunMacros({
+ code,
+ path: currentFile,
+ pluginConfig: { prerenderConfigPath: configPath },
+ });
+ const expected = format(bunTranspiler.transformSync(code));
+
+ expect(output).toBe(expected);
+ });
+ it("should transform a static component", () => {
+ const code = `
+ import Foo from "./components" with { type: "prerender" };
+ import { Bar } from "./components";
+
+ export default function Test() {
+ return (
+
+
+
+
+ );
+ }
+ `;
+ const output = transpileAndRunMacros({
+ code,
+ path: currentFile,
+ pluginConfig: { prerenderConfigPath: configPath },
+ });
+ const expected = format(`
+ import Foo from "./components";
+ import {Bar} from "./components";
+
+ export default function Test() {
+ return jsxDEV("div", {
+ children: ["
Foo, foo!
",
+ jsxDEV(Bar, {}, undefined, false, undefined, this)
+ ]}, undefined, true, undefined, this);
+ }
+ `);
+
+ expect(output).toBe(expected);
+ });
+
+ it("should transform a static component from named export", () => {
+ const code = `
+ import { Bar } from "./components" with { type: "prerender" };
+ import Foo from "./components";
+
+ export default function Test() {
+ return (
+
+
+
+
+ );
+ }
+ `;
+ const output = transpileAndRunMacros({
+ code,
+ path: currentFile,
+ pluginConfig: { prerenderConfigPath: configPath },
+ });
+ const expected = format(`
+ import {Bar} from "./components";
+ import Foo from "./components";
+
+ export default function Test() {
+ return jsxDEV("div", {
+ children: [jsxDEV(Foo, {}, undefined, false, undefined, this),
+ "
Bar, bar!
"
+ ]}, undefined, true, undefined, this)
+ ;}
+ `);
+
+ expect(output).toBe(expected);
+ });
+
+ it("should transform a static component from named export and a fragment", () => {
+ const code = `
+ import { Bar } from "./components" with { type: "prerender" };
+ import Foo from "./components";
+
+ export default function Test() {
+ return (
+ <>
+
+
+ >
+ );
+ }
+ `;
+ const output = transpileAndRunMacros({
+ code,
+ path: currentFile,
+ pluginConfig: { prerenderConfigPath: configPath },
+ });
+ const expected = format(`
+ import {Bar} from "./components";
+ import Foo from "./components";
+
+ export default function Test() {
+ return jsxDEV(Fragment, {children: [jsxDEV(Foo, {}, undefined, false, undefined, this),
+ "
Bar, bar!
"
+ ]}, undefined, true, undefined, this);
+ }
+ `);
+
+ expect(output).toBe(expected);
+ });
+
+ it("should transform a static component when is not inside JSX", () => {
+ const code = `
+ import { Bar } from "./components" with { type: "prerender" };
+
+ export default function Test() {
+ return
;
+ }
+ `;
+ const output = transpileAndRunMacros({
+ code,
+ path: currentFile,
+ pluginConfig: { prerenderConfigPath: configPath },
+ });
+ const expected = format(`
+ import {Bar} from "./components";
+
+ export default function Test() {
+ return "
Bar, bar!
";
+ }
+ `);
+
+ expect(output).toBe(expected);
+ });
+
+ it("should transform a static component with props", () => {
+ const code = `
+ import Foo from "./components" with { type: "prerender" };
+
+ export default function Test() {
+ return
;
+ }
+ `;
+ const output = transpileAndRunMacros({
+ code,
+ path: currentFile,
+ pluginConfig: { prerenderConfigPath: configPath },
+ });
+ const expected = format(`
+ import Foo from "./components";
+
+ export default function Test() {
+ return "
Foo, Kitajs/html works!
";
+ }
+ `);
+
+ expect(output).toBe(expected);
+ });
+ });
+});
diff --git a/tests/kitajs-html/prerender.test.tsx b/tests/kitajs-html/prerender.test.tsx
new file mode 100644
index 0000000..bc806ff
--- /dev/null
+++ b/tests/kitajs-html/prerender.test.tsx
@@ -0,0 +1,28 @@
+import { join } from "node:path";
+import { describe, expect, it } from "bun:test";
+import { prerender } from "prerender-macro/prerender";
+
+describe("Kitajs/html", () => {
+ describe("prerender", () => {
+ it("should work with default module", async () => {
+ const result = await prerender({
+ componentPath: join(import.meta.dir, "components.tsx"),
+ componentModuleName: "default",
+ componentProps: { name: "Kitajs/html" },
+ prerenderConfigPath: join(import.meta.dir, "config.tsx"),
+ });
+
+ expect(result).toBe("
Foo, Kitajs/html!
");
+ });
+ it("should work with named module", async () => {
+ const result = await prerender({
+ componentPath: join(import.meta.dir, "components.tsx"),
+ componentModuleName: "Bar",
+ componentProps: { name: "Kitajs/html" },
+ prerenderConfigPath: join(import.meta.dir, "config.tsx"),
+ });
+
+ expect(result).toBe("
Bar, Kitajs/html!
");
+ });
+ });
+});
diff --git a/tests/kitajs-html/tsconfig.json b/tests/kitajs-html/tsconfig.json
new file mode 100644
index 0000000..b713caf
--- /dev/null
+++ b/tests/kitajs-html/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "module": "esnext",
+ "target": "esnext",
+ "moduleResolution": "bundler",
+ "moduleDetection": "force",
+ "jsx": "react-jsx",
+ "jsxImportSource": "@kitajs/html",
+ "plugins": [{ "name": "@kitajs/ts-html-plugin" }]
+ }
+}