Skip to content

Commit 46935ac

Browse files
committed
fix: esm runtime evaluation
1 parent afbb5d1 commit 46935ac

File tree

4 files changed

+46
-49
lines changed

4 files changed

+46
-49
lines changed

src/core/generateOpenApiSpec.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,13 @@ describe("generateOpenApiSpec", () => {
7878
operationId: "getUsers",
7979
parameters: [
8080
{
81+
description: "List of the column names",
8182
in: "query",
8283
name: "select",
8384
required: true,
8485
schema: {
8586
default: [],
87+
description: "List of the column names",
8688
items: {
8789
enum: [
8890
"id",
@@ -243,11 +245,13 @@ describe("generateOpenApiSpec", () => {
243245
operationId: "getUsers",
244246
parameters: [
245247
{
248+
description: "List of the column names",
246249
in: "query",
247250
name: "select",
248251
required: true,
249252
schema: {
250253
default: [],
254+
description: "List of the column names",
251255
items: {
252256
enum: [
253257
"id",

src/core/next.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import fs from "node:fs/promises";
22
import path from "node:path";
3+
import { defineRoute } from "@omer-x/next-openapi-route-handler";
4+
import { z } from "zod";
35
import { directoryExists } from "./dir";
46
import injectSchemas from "./injectSchemas";
57
import { detectMiddlewareName } from "./middleware";
@@ -18,9 +20,12 @@ export async function findAppFolderPath() {
1820
return null;
1921
}
2022

21-
function safeEval(code: string, routePath: string) {
23+
async function safeEval(code: string, routePath: string) {
2224
try {
23-
return eval(code);
25+
if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
26+
return eval(code);
27+
}
28+
return await import(/* webpackIgnore: true */ `data:text/javascript,${encodeURIComponent(code)}`);
2429
} catch (error) {
2530
// eslint-disable-next-line no-console
2631
console.log(`An error occured while evaluating the route exports from "${routePath}"`);
@@ -31,10 +36,16 @@ function safeEval(code: string, routePath: string) {
3136
export async function getRouteExports(routePath: string, routeDefinerName: string, schemas: Record<string, unknown>) {
3237
const rawCode = await fs.readFile(routePath, "utf-8");
3338
const middlewareName = detectMiddlewareName(rawCode);
34-
const code = transpile(rawCode, routeDefinerName, middlewareName);
39+
const isCommonJS = typeof module !== "undefined" && typeof module.exports !== "undefined";
40+
const { transpileModule } = await import(/* webpackIgnore: true */ "typescript");
41+
const code = transpile(isCommonJS, rawCode, middlewareName, transpileModule);
3542
const fixedCode = Object.keys(schemas).reduce(injectSchemas, code);
43+
(global as Record<string, unknown>)[routeDefinerName] = defineRoute;
44+
(global as Record<string, unknown>).z = z;
3645
(global as Record<string, unknown>).schemas = schemas;
37-
const result = safeEval(fixedCode, routePath);
46+
const result = await safeEval(fixedCode, routePath);
3847
delete (global as Record<string, unknown>).schemas;
48+
delete (global as Record<string, unknown>)[routeDefinerName];
49+
delete (global as Record<string, unknown>).z;
3950
return result as Record<string, { apiData?: OperationObject } | undefined>;
4051
}

src/core/transpile.test.ts

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { transpile } from "./transpile";
33

44
describe("transpile", () => {
55
it("should inject export fixers", () => {
6-
const result = transpile("", "defineRoute", null);
6+
const result = transpile(true, "", null);
77
expect(result).toContain("exports.GET = void 0;");
88
expect(result).toContain("exports.POST = void 0;");
99
expect(result).toContain("exports.PUT = void 0;");
@@ -13,40 +13,8 @@ describe("transpile", () => {
1313
expect(result).toContain("module.exports = { GET: exports.GET, POST: exports.POST, PUT: exports.PUT, PATCH: exports.PATCH, DELETE: exports.DELETE };");
1414
});
1515

16-
it("should inject '@omer-x/next-openapi-route-handler' while it is necessary", async () => {
17-
const repoName = "omermecitoglu/example-user-service";
18-
const branchName = "main";
19-
const filePath = "src/app/users/route.ts";
20-
const response = await fetch(`https://raw.githubusercontent.com/${repoName}/refs/heads/${branchName}/${filePath}`);
21-
const example = await response.text();
22-
const result = transpile(example, "defineRoute", null);
23-
expect(result).toContain("require(\"@omer-x/next-openapi-route-handler\");");
24-
});
25-
26-
it("should not inject '@omer-x/next-openapi-route-handler' while it is not necessary", () => {
27-
const result = transpile("", "defineRoute", null);
28-
expect(result).not.toContain("require(\"@omer-x/next-openapi-route-handler\");");
29-
});
30-
31-
it("should inject 'zod' while it is necessary", () => {
32-
const rawCodeExample = `
33-
import z from "zod";
34-
35-
const schema = z.object({
36-
name: z.string().min(3).max(255).optional(),
37-
});
38-
`;
39-
const result = transpile(rawCodeExample, "defineRoute", null);
40-
expect(result).toContain("require(\"zod\");");
41-
});
42-
43-
it("should not inject 'zod' while it is not necessary", () => {
44-
const result = transpile("", "defineRoute", null);
45-
expect(result).not.toContain("require(\"zod\");");
46-
});
47-
4816
it("should inject a placeholder function for the middleware", () => {
49-
const result = transpile("", "defineRoute", "myAwesomeMiddleware");
50-
expect(result).toContain("var myAwesomeMiddleware = function (handler) { return handler; };");
17+
const result = transpile(true, "", "myAwesomeMiddleware");
18+
expect(result).toContain("const myAwesomeMiddleware = (handler) => handler;");
5119
});
5220
});

src/core/transpile.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,38 @@
1-
import { transpile as tsTranspile } from "typescript";
21
import removeImports from "~/utils/removeImports";
2+
import type ts from "typescript";
33

4-
function fixExports(code: string) {
4+
function fixExportsInCommonJS(code: string) {
55
const validMethods = ["GET", "POST", "PUT", "PATCH", "DELETE"];
6-
const exportFixer1 = validMethods.map(method => `exports.${method} = void 0;\n`);
7-
const exportFixer2 = `module.exports = { ${validMethods.map(m => `${m}: exports.${m}`).join(", ")} }`;
6+
const exportFixer1 = validMethods.map(method => `exports.${method} = void 0;\n`).join("\n");
7+
const exportFixer2 = `module.exports = { ${validMethods.map(m => `${m}: exports.${m}`).join(", ")} };`;
88
return `${exportFixer1}\n${code}\n${exportFixer2}`;
99
}
1010

1111
function injectMiddlewareFixer(middlewareName: string) {
1212
return `const ${middlewareName} = (handler) => handler;`;
1313
}
1414

15-
export function transpile(rawCode: string, routeDefinerName: string, middlewareName: string | null) {
16-
const code = fixExports(removeImports(rawCode));
15+
export function transpile(
16+
isCommonJS: boolean,
17+
rawCode: string,
18+
middlewareName: string | null,
19+
transpileModule: (input: string, transpileOptions: ts.TranspileOptions) => ts.TranspileOutput,
20+
) {
1721
const parts = [
18-
`import ${routeDefinerName} from '@omer-x/next-openapi-route-handler';`,
19-
"import z from 'zod';",
2022
middlewareName ? injectMiddlewareFixer(middlewareName) : "",
21-
code,
23+
removeImports(rawCode),
2224
];
23-
return tsTranspile(parts.join("\n"));
25+
const output = transpileModule(parts.join("\n"), {
26+
compilerOptions: {
27+
module: isCommonJS ? 3 : 99,
28+
target: 99,
29+
sourceMap: false,
30+
inlineSourceMap: false,
31+
inlineSources: false,
32+
},
33+
});
34+
if (isCommonJS) {
35+
return fixExportsInCommonJS(output.outputText);
36+
}
37+
return output.outputText;
2438
}

0 commit comments

Comments
 (0)