Skip to content

Commit

Permalink
feat: simplify and fix preact
Browse files Browse the repository at this point in the history
  • Loading branch information
aralroca committed Mar 24, 2024
1 parent 580b906 commit b555b37
Show file tree
Hide file tree
Showing 17 changed files with 286 additions and 230 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ Configuration example:
```tsx
import prerenderMacroPlugin, { type PrerenderConfig } from "prerender-macro";
import { render } from "preact-render-to-string";
import { h } from "preact";

export const prerenderConfig = {
render: async (Component, props) => {
Expand Down
16 changes: 16 additions & 0 deletions examples/preact/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# `prerender-macro` Preact Example

This is an example with React SSR without hotreloading and with a build process.

To test it:

- `bun install`
- `bun run start`
- Open http://localhost:1234 to see the result
- Look at `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
"<div>Static Component \uD83E\uDD76 Random number = 0.41381527597071954</div>";
```
19 changes: 19 additions & 0 deletions examples/preact/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "preact-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:*",
"preact-render-to-string": "6.4.1"
}
}
16 changes: 16 additions & 0 deletions examples/preact/src/build.ts
Original file line number Diff line number Diff line change
@@ -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);
7 changes: 7 additions & 0 deletions examples/preact/src/components/dynamic-component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function DynamicComponent({ name }: { name: string }) {
return (
<div>
{name} Component 🔥 Random number = {Math.random()}
</div>
);
}
7 changes: 7 additions & 0 deletions examples/preact/src/components/static-component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function StaticComponent({ name }: { name: string }) {
return (
<div>
{name} Component 🥶 Random number = {Math.random()}
</div>
);
}
27 changes: 27 additions & 0 deletions examples/preact/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import DynamicComponent from "./components/dynamic-component";
import StaticComponent from "./components/static-component" with { type: "prerender" };
import { render } from "preact-render-to-string";

Bun.serve({
port: 1234,
fetch: async () => {
return new Response(
render(
<html>
<head>
<title>Prerender Macro | Preact example</title>
<meta charSet="utf-8" />
</head>
<body>
<StaticComponent name="Static" />
<DynamicComponent name="Dynamic" />
<a href="/">Refresh</a>
</body>
</html>,
),
{ headers: new Headers({ "Content-Type": "text/html" }) },
);
},
});

console.log("Server running at http://localhost:1234");
12 changes: 12 additions & 0 deletions examples/preact/src/prerender.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { render } from "preact-render-to-string";
import type { PrerenderConfig } from "prerender-macro";

export const prerenderConfig = {
render: async (Component, props) => {
return (
<div
dangerouslySetInnerHTML={{ __html: render(<Component {...props} />) }}
/>
);
},
} satisfies PrerenderConfig;
22 changes: 22 additions & 0 deletions examples/preact/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"jsxImportSource": "preact",
"jsx": "react-jsx",
"baseUrl": ".",
"lib": ["dom", "dom.iterable", "esnext"],
"module": "esnext",
"target": "esnext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"skipLibCheck": true,
"paths": {
"react": ["node_modules/preact/compat"],
"react-dom": ["./node_modules/preact/compat/"]
},
"typeRoots": ["src/types", "./node_modules/@types"]
},
"resolve": {
"extensions": [".js", ".jsx", ".ts", ".tsx", ".json", ".svg", ".css"]
},
"include": ["src"]
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "prerender-macro",
"version": "0.0.6",
"version": "0.0.7",
"module": "package/index.ts",
"type": "module",
"devDependencies": {
Expand All @@ -21,7 +21,8 @@
"test:react": "cd tests/react && bun test && cd ../..",
"test:preact": "cd tests/preact && bun test && cd ../..",
"demo:react": "cd examples/react && bun start",
"demo:brisa": "cd examples/brisa && bun start"
"demo:brisa": "cd examples/brisa && bun start",
"demo:preact": "cd examples/preact && bun start"
},
"workspaces": [
"package",
Expand Down
48 changes: 10 additions & 38 deletions package/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import type { BunPlugin, TranspilerOptions } from "bun";
import type { BunPlugin } from "bun";
import { dirname } from "node:path";
import ts from "typescript";

let transpiler = new Bun.Transpiler({ loader: "tsx" });
const JSX_RUNTIME = ["jsx-runtime", "jsx-dev-runtime"];

export type PluginConfig = {
prerenderConfigPath: string;
tsconfig?: TranspilerOptions["tsconfig"];
};

export type PrerenderConfig = {
Expand All @@ -18,6 +14,13 @@ export type PrerenderConfig = {
postRender?: (htmlString: string) => JSX.Element;
};

export type TranspilerOptions = {
code: string;
path: string;
pluginConfig: PluginConfig;
prerenderConfig?: PrerenderConfig;
};

export default function plugin(pluginConfig: PluginConfig) {
if (!pluginConfig?.prerenderConfigPath) {
throw new Error("prerender-macro: prerenderConfigPath config is required");
Expand Down Expand Up @@ -53,12 +56,7 @@ export function transpile({
path,
pluginConfig,
prerenderConfig,
}: {
code: string;
path: string;
pluginConfig: PluginConfig;
prerenderConfig?: PrerenderConfig;
}) {
}: TranspilerOptions) {
const sourceFile = createSourceFile(code);
const importsWithPrerender = getImportsWithPrerender(sourceFile, path);

Expand All @@ -73,11 +71,9 @@ export function transpile({
prerenderConfig,
) as ts.SourceFile;

const modifiedCode = ts
return ts
.createPrinter()
.printNode(ts.EmitHint.Unspecified, modifiedAst, sourceFile);

return runMacros(modifiedCode);
}

function createSourceFile(code: string) {
Expand Down Expand Up @@ -301,30 +297,6 @@ function replaceJSXToMacroCall(
);
}

function runMacros(code: string) {
// This is totally necessary to execute the Bun macros
let codeAfterMacros = transpiler.transformSync(code);

// WORKAROUND: Find the JSX runtime to add the import
// Issue: https://github.com/oven-sh/bun/issues/7499
if (!globalThis.jsxRuntime) {
for (let key of globalThis.Loader.registry.keys()) {
if (JSX_RUNTIME.some((k) => key.includes(k))) {
globalThis.jsxRuntime = key;
break;
}
}
}

// WORKAROUND: Add the JSX runtime import
// Issue: https://github.com/oven-sh/bun/issues/7499
if (globalThis.jsxRuntime) {
codeAfterMacros = `import {jsx, jsxDEV, jsxs, Fragment} from "${globalThis.jsxRuntime}";\n${codeAfterMacros}`;
}

return codeAfterMacros;
}

declare global {
var jsxRuntime: string | undefined;
}
101 changes: 43 additions & 58 deletions tests/brisa/plugin.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { join } from "node:path";
import { describe, it, expect } from "bun:test";
import { transpile } from "prerender-macro";
import { transpile, type TranspilerOptions } from "prerender-macro";

const toInline = (s: string) => s.replace(/\s*\n\s*/g, "");
const normalizeQuotes = (s: string) => toInline(s).replaceAll("'", '"');
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" });

const jsxRuntimePath = import.meta.resolveSync("brisa/jsx-dev-runtime");
const importJSXRuntime = `import {jsx, jsxDEV, jsxs, Fragment} from "${jsxRuntimePath}";`;
function transpileAndRunMacros(config: TranspilerOptions) {
// Bun transpiler is needed here to run the macros
return format(bunTranspiler.transformSync(transpile(config)));
}

describe("Brisa", () => {
describe("plugin", () => {
Expand All @@ -26,14 +28,12 @@ describe("Brisa", () => {
);
}
`;
const output = normalizeQuotes(
transpile({
code,
path: currentFile,
pluginConfig: { prerenderConfigPath: configPath },
}),
);
const expected = normalizeQuotes(code);
const output = transpileAndRunMacros({
code,
path: currentFile,
pluginConfig: { prerenderConfigPath: configPath },
});
const expected = format(bunTranspiler.transformSync(code));

expect(output).toBe(expected);
});
Expand All @@ -51,15 +51,12 @@ describe("Brisa", () => {
);
}
`;
const output = normalizeQuotes(
transpile({
code,
path: currentFile,
pluginConfig: { prerenderConfigPath: configPath },
}),
);
const expected = normalizeQuotes(`
${importJSXRuntime}
const output = transpileAndRunMacros({
code,
path: currentFile,
pluginConfig: { prerenderConfigPath: configPath },
});
const expected = format(`
import Foo from "./components";
import {Bar} from "./components";
Expand Down Expand Up @@ -88,15 +85,12 @@ describe("Brisa", () => {
);
}
`;
const output = normalizeQuotes(
transpile({
code,
path: currentFile,
pluginConfig: { prerenderConfigPath: configPath },
}),
);
const expected = normalizeQuotes(`
${importJSXRuntime}
const output = transpileAndRunMacros({
code,
path: currentFile,
pluginConfig: { prerenderConfigPath: configPath },
});
const expected = format(`
import {Bar} from "./components";
import Foo from "./components";
Expand Down Expand Up @@ -125,15 +119,12 @@ describe("Brisa", () => {
);
}
`;
const output = normalizeQuotes(
transpile({
code,
path: currentFile,
pluginConfig: { prerenderConfigPath: configPath },
}),
);
const expected = normalizeQuotes(`
${importJSXRuntime}
const output = transpileAndRunMacros({
code,
path: currentFile,
pluginConfig: { prerenderConfigPath: configPath },
});
const expected = format(`
import {Bar} from "./components";
import Foo from "./components";
Expand All @@ -156,15 +147,12 @@ describe("Brisa", () => {
return <Bar />;
}
`;
const output = normalizeQuotes(
transpile({
code,
path: currentFile,
pluginConfig: { prerenderConfigPath: configPath },
}),
);
const expected = normalizeQuotes(`
${importJSXRuntime}
const output = transpileAndRunMacros({
code,
path: currentFile,
pluginConfig: { prerenderConfigPath: configPath },
});
const expected = format(`
import {Bar} from "./components";
export default function Test() {
Expand All @@ -183,15 +171,12 @@ describe("Brisa", () => {
return <Foo name="Brisa" nested={{ foo: ' works' }} />;
}
`;
const output = normalizeQuotes(
transpile({
code,
path: currentFile,
pluginConfig: { prerenderConfigPath: configPath },
}),
);
const expected = normalizeQuotes(`
${importJSXRuntime}
const output = transpileAndRunMacros({
code,
path: currentFile,
pluginConfig: { prerenderConfigPath: configPath },
});
const expected = format(`
import Foo from "./components";
export default function Test() {
Expand Down
Loading

0 comments on commit b555b37

Please sign in to comment.