From a064acc2b9c653a632adb425543920e420866cdf Mon Sep 17 00:00:00 2001 From: Aral Roca Date: Thu, 21 Mar 2024 18:31:41 +0100 Subject: [PATCH] feat: add macro --- package/index.ts | 79 ++++++++++++++++++++++++++++++++++--- tests/brisa/plugin.test.tsx | 61 ++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 tests/brisa/plugin.test.tsx diff --git a/package/index.ts b/package/index.ts index d7f9058..e584c26 100644 --- a/package/index.ts +++ b/package/index.ts @@ -1,4 +1,5 @@ import type { BunPlugin } from "bun"; +import ts from "typescript"; type PrerenderPluginParams = { renderToStringPath: string; @@ -7,7 +8,6 @@ type PrerenderPluginParams = { injectStringIntoJSXModuleName: string; }; -const filter = new RegExp(`*.(ts|js)x$`); const defaultConfig: PrerenderPluginParams = { renderToStringPath: "brisa/server", renderToStringModuleName: "renderToString", @@ -24,10 +24,17 @@ export default function prerenderPlugin({ return { name: "prerender-plugin", setup(build) { - build.onLoad({ filter }, async ({ path, loader }) => ({ - contents: prerenderPluginTransformation(await Bun.file(path).text()), - loader, - })); + build.onLoad( + { filter: /.*/, namespace: "prerender" }, + async ({ path, loader }) => { + return { + contents: prerenderPluginTransformation( + await Bun.file(path).text(), + ), + loader, + }; + }, + ); }, } satisfies BunPlugin; } @@ -43,5 +50,65 @@ export default function prerenderPlugin({ * */ export function prerenderPluginTransformation(code: string) { - return code; + const result = ts.transpileModule(code, { + compilerOptions: { + module: ts.ModuleKind.ESNext, + target: ts.ScriptTarget.ESNext, + jsx: ts.JsxEmit.React, + }, + }); + + const ast = ts.createSourceFile( + "file.tsx", + result.outputText, + ts.ScriptTarget.ESNext, + true, + ts.ScriptKind.TSX, + ); + const imports = ast.statements.filter(ts.isImportDeclaration); + const importsWithPrerender = imports.filter( + (node) => + node.attributes?.elements?.some( + (element: any) => + element.name.escapedText === "type" && + element.value.text === "prerender", + ), + ); + + if (!importsWithPrerender.length) return code; + + const importPrerenderMacro = ts.factory.createImportDeclaration( + undefined, + ts.factory.createImportClause( + false, + undefined, + ts.factory.createNamedImports([ + ts.factory.createImportSpecifier( + false, + ts.factory.createIdentifier("prerender"), + ts.factory.createIdentifier("__prerender__macro"), + ), + ]), + ), + ts.factory.createStringLiteral("prerender-macro/prerender"), + ts.factory.createImportAttributes( + ts.factory.createNodeArray([ + ts.factory.createImportAttribute( + ts.factory.createStringLiteral("type"), + ts.factory.createStringLiteral("macro"), + ), + ]), + ), + ); + + return ts + .createPrinter() + .printNode( + ts.EmitHint.Unspecified, + ts.factory.updateSourceFile(ast, [ + importPrerenderMacro, + ...ast.statements, + ]), + ast, + ); } diff --git a/tests/brisa/plugin.test.tsx b/tests/brisa/plugin.test.tsx new file mode 100644 index 0000000..7be324b --- /dev/null +++ b/tests/brisa/plugin.test.tsx @@ -0,0 +1,61 @@ +import { describe, it, expect } from "bun:test"; +import { prerenderPluginTransformation } from "prerender-macro"; + +export const toInline = (s: string) => s.replace(/\s*\n\s*/g, ""); +export const normalizeQuotes = (s: string) => toInline(s).replaceAll("'", '"'); + +describe("Brisa", () => { + describe("plugin", () => { + it('should not transform if there is not an import attribute with type "prerender"', () => { + const input = ` + import StaticComponent from "@/components/static"; + import DynamicComponent from "@/components/dynamic"; + + export default function Test() { + return ( +
+ + +
+ ); + } + `; + const output = normalizeQuotes(prerenderPluginTransformation(input)); + const expected = normalizeQuotes(input); + + expect(output).toBe(expected); + }); + it("should transform a static component without props", () => { + const input = ` + import StaticComponent from "@/components/static" with { type: "prerender" }; + import DynamicComponent from "@/components/dynamic"; + + export default function Test() { + return ( +
+ + +
+ ); + } + `; + const output = normalizeQuotes(prerenderPluginTransformation(input)); + const expected = normalizeQuotes(` + import { prerender as __prerender_macro } from "prerender-macro/prerender" with { "type": "macro" }; + import StaticComponent from "@/components/static" with { type: "prerender" }; + import DynamicComponent from "@/components/dynamic"; + + export default function Test() { + return ( +
+ {__prerender_macro({ componentPath: "@/components/static", componentModuleName: "default", componentProps: {} })} + +
+ ); + } + `); + + expect(output).toBe(expected); + }); + }); +});