Skip to content

Commit

Permalink
feat(esbuild): generate report
Browse files Browse the repository at this point in the history
  • Loading branch information
magne4000 committed Jul 17, 2024
1 parent 97d7201 commit 873e0ca
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 5,776 deletions.
109 changes: 92 additions & 17 deletions packages/universal-middleware/src/build.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { join, parse, resolve } from "node:path";
import { join, parse, posix, relative, resolve } from "node:path";
import { createUnplugin } from "unplugin";

export interface Options {
servers?: (typeof defaultWrappers)[number][];
serversExportNames?: string;
entryExportNames?: string;
ignoreRecommendations?: boolean;
buildEnd?: (report: Report[]) => void | Promise<void>;
}

export interface Report {
in: string;
out: string;
type: "handler" | "middleware";
exports: string;
}

const defaultWrappers = ["hono", "express", "hattip"] as const;
Expand Down Expand Up @@ -144,6 +152,44 @@ function cleanPath(s: string) {
return s.replace(`?middleware`, "").replace(`?handler`, "");
}

function findDuplicateReports(reports: Report[]): Map<string, Report[]> {
const exportCounts: Record<string, number> = {};
const duplicates = new Map<string, Report[]>();

// Count occurrences of each 'exports' value
reports.forEach((report) => {
exportCounts[report.exports] = (exportCounts[report.exports] || 0) + 1;
});

// Collect reports that have duplicates
reports.forEach((report) => {
if (exportCounts[report.exports] > 1) {
if (!duplicates.has(report.exports)) {
duplicates.set(report.exports, []);
}
duplicates.get(report.exports)!.push(report);
}
});

return duplicates;
}

function formatDuplicatesForErrorMessage(duplicates: Map<string, Report[]>) {
let formattedMessage = "The following files have overlapping exports:\n";

duplicates.forEach((reports, exportValue) => {
formattedMessage += `exports: ${exportValue}\n`;
reports.forEach((report) => {
formattedMessage += ` in: ${report.in}, out: ${report.out}\n`;
});
});

formattedMessage +=
"Make sure you are using esbuild `entryPoints` object syntax or that `serversExportNames` option contains [dir].";

return formattedMessage;
}

const universalMiddleware = createUnplugin((options?: Options) => {
return {
name: namespace,
Expand Down Expand Up @@ -202,6 +248,7 @@ const universalMiddleware = createUnplugin((options?: Options) => {
if (!normalizedInput) return;

const outbase = builder.initialOptions.outbase ?? "";
const outdir = builder.initialOptions.outdir ?? "dist";

builder.initialOptions.entryPoints = normalizedInput;
appendVirtualInputs(
Expand Down Expand Up @@ -245,11 +292,11 @@ const universalMiddleware = createUnplugin((options?: Options) => {
};
});

builder.onEnd((result) => {
builder.onEnd(async (result) => {
const serversExportNames =
options?.serversExportNames ?? "./[name]-[type]-[server]";
options?.serversExportNames ?? "./[dir]/[name]-[type]-[server]";
const entryExportNames =
options?.entryExportNames ?? "./[name]-[type]";
options?.entryExportNames ?? "./[dir]/[name]-[type]";

const entries = Object.entries(normalizedInput);
const outputs = Object.entries(result.metafile!.outputs);
Expand All @@ -268,41 +315,69 @@ const universalMiddleware = createUnplugin((options?: Options) => {

return false;
})?.[0];
const parsed = parse(cleanV);
const parsed = parse(dest!);
return [
cleanV,
{
dest,
in: cleanV,
out: dest!,
id: k,
dir: parsed.dir,
dir: relative(outdir, parsed.dir),
name: parsed.name,
type: filterInput(v),
type: filterInput(v)! as "handler" | "middleware",
exports: "",
},
];
}),
);

Object.entries(mapping).forEach(([k, v]) => {
if (!k.startsWith(namespace)) {
(v as Record<string, unknown>).exports = entryExportNames
.replace("[name]", v.name)
.replace("[type]", v.type!);
(v as Record<string, unknown>).exports =
"./" +
posix.normalize(
entryExportNames
.replace("[dir]", v.dir)
.replace("[name]", v.name)
.replace("[type]", v.type),
);
}
});

Object.entries(mapping).forEach(([k, v]) => {
if (k.startsWith(namespace)) {
const [, , server, type, handler] = k.split(":");
(v as Record<string, unknown>).exports = serversExportNames
.replace("[name]", mapping[handler].name)
.replace("[type]", type)
.replace("[server]", server);
(v as Record<string, unknown>).exports =
"./" +
posix.normalize(
serversExportNames
.replace("[name]", mapping[handler].name)
.replace("[dir]", mapping[handler].dir)
.replace("[type]", type)
.replace("[server]", server),
);
}
});

// TODO: test with https://esbuild.github.io/api/#outbase
const reports = Object.values(mapping).reduce((acc, curr) => {
acc.push({
in: curr.in,
out: curr.out,
type: curr.type,
exports: curr.exports,
});

return acc;
}, [] as Report[]);

const duplicates = findDuplicateReports(reports);

if (duplicates.size > 0) {
const message = formatDuplicatesForErrorMessage(duplicates);
throw new Error(message);
}

// console.log(mapping);
await options?.buildEnd?.(reports);
});
},
},
Expand Down
33 changes: 26 additions & 7 deletions packages/universal-middleware/test/esbuild.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ describe("esbuild", () => {
result.outputFiles.filter((f) => !f.path.includes("dist/chunk-")),
).toHaveLength(expectNbOutput(1));

console.log(result.outputFiles);

expect(findOutput(result, entry)).toSatisfy((s: string) =>
s.startsWith("dist/handler"),
);
Expand Down Expand Up @@ -200,12 +198,11 @@ describe("esbuild", () => {
});

it("fails when bundle is not true", async () => {
const entry1 = "test/files/folder1/handler.ts";
const entry2 = "test/files/folder2/handler.ts";
await expect(
build({
entryPoints: [
"test/handler.ts?handler",
"test/middleware.ts?middleware",
],
entryPoints: [entry1 + "?handler", entry2 + "?handler"],
plugins: [unplugin.esbuild()],
outdir: "dist",
write: false,
Expand All @@ -214,7 +211,29 @@ describe("esbuild", () => {
format: "esm",
target: "es2022",
}),
).rejects.toThrow();
).rejects.toThrow("bundle");
});

it("fails when exports overlap", async () => {
const entry1 = "test/files/folder1/handler.ts";
const entry2 = "test/files/folder2/handler.ts";
await expect(
build({
entryPoints: [entry1 + "?handler", entry2 + "?handler"],
plugins: [
unplugin.esbuild({
serversExportNames: "[name]-[type]-[server]",
}),
],
outdir: "dist",
bundle: true,
write: false,
metafile: true,
platform: "neutral",
format: "esm",
target: "es2022",
}),
).rejects.toThrow("The following files have overlapping exports");
});
});

Expand Down
Loading

0 comments on commit 873e0ca

Please sign in to comment.