Skip to content
This repository was archived by the owner on Jul 6, 2025. It is now read-only.

Commit 0ded64e

Browse files
committed
Support export methods in api route
1 parent 6209a67 commit 0ded64e

File tree

5 files changed

+47
-56
lines changed

5 files changed

+47
-56
lines changed

framework/react/router.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ export const Router: FC<RouterProps> = ({ ssrContext, suspense, createPortal })
6767
if (deployId) {
6868
url += `?v=${deployId}`;
6969
}
70-
const { default: defaultExport, data: withData } = await import(url);
70+
const { default: defaultExport, data, GET } = await import(url);
71+
const withData = Boolean(data ?? GET);
7172
routeModules[filename] = { defaultExport, withData };
7273
return { defaultExport, withData };
7374
};

server/build.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -93,24 +93,16 @@ export async function build(serverEntry?: string) {
9393
`;
9494
}).join("\n"),
9595
...routeFiles.map(([filename, exportNames], idx) => {
96-
const hasDefaultExport = exportNames.includes("default");
97-
const hasDataExport = exportNames.includes("data");
98-
if (!hasDefaultExport && !hasDataExport) {
96+
if (exportNames.length === 0) {
9997
return [];
10098
}
10199
const url = `http://localhost:${modulesProxyPort}${filename.slice(1)}`;
102100
return [
103-
`import { ${
104-
[
105-
hasDefaultExport && `default as $${idx}`,
106-
hasDataExport && `data as $$${idx}`,
107-
].filter(Boolean).join(", ")
108-
} } from ${JSON.stringify(url)};`,
101+
`import { ${exportNames.map((name) => `${name} as ${"$".repeat(idx + 1)}${idx}`).join(", ")} } from ${
102+
JSON.stringify(url)
103+
};`,
109104
`revive(${JSON.stringify(filename)}, { ${
110-
[
111-
hasDefaultExport && `default: $${idx}`,
112-
hasDataExport && `data: $$${idx}`,
113-
].filter(Boolean).join(", ")
105+
exportNames.map((name) => `${name}: ${"$".repeat(idx + 1)}${idx}`).join(", ")
114106
} });`,
115107
];
116108
}),

server/generate.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,31 @@ import type { Route } from "../framework/core/route.ts";
33

44
/** generate the `routes.gen.ts` follow the routes config */
55
export async function generate(routes: Route[]) {
6-
const routeFiles = await Promise.all(routes.map(async ([_, { filename }]) => {
7-
const code = await Deno.readTextFile(filename);
8-
const exportNames = await parseExportNames(filename, code);
9-
return [filename, exportNames];
10-
}));
6+
const routeFiles: [fiilename: string, exportNames: string[]][] = await Promise.all(
7+
routes.map(async ([_, { filename }]) => {
8+
const code = await Deno.readTextFile(filename);
9+
const exportNames = await parseExportNames(filename, code);
10+
return [filename, exportNames];
11+
}),
12+
);
1113

1214
const imports: string[] = [];
1315
const revives: string[] = [];
1416

1517
routeFiles.forEach(([filename, exportNames], idx) => {
16-
const hasDefaultExport = exportNames.includes("default");
17-
const hasDataExport = exportNames.includes("data");
18-
if (!hasDefaultExport && !hasDataExport) {
18+
if (exportNames.length === 0) {
1919
return [];
2020
}
21-
imports.push(`import { ${
22-
[
23-
hasDefaultExport && `default as $${idx}`,
24-
hasDataExport && `data as $$${idx}`,
25-
].filter(Boolean).join(", ")
26-
} } from ${JSON.stringify(filename)};`);
27-
revives.push(`revive(${JSON.stringify(filename)}, { ${
28-
[
29-
hasDefaultExport && `default: $${idx}`,
30-
hasDataExport && `data: $$${idx}`,
31-
].filter(Boolean).join(", ")
32-
} });`);
21+
imports.push(
22+
`import { ${exportNames.map((name) => `${name} as ${"$".repeat(idx + 1)}${idx}`).join(", ")} } from ${
23+
JSON.stringify(filename)
24+
};`,
25+
);
26+
revives.push(
27+
`revive(${JSON.stringify(filename)}, { ${
28+
exportNames.map((name) => `${name}: ${"$".repeat(idx + 1)}${idx}`).join(", ")
29+
} });`,
30+
);
3331
});
3432

3533
if (imports.length) {

server/mod.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -271,18 +271,19 @@ export const serve = (options: ServerOptions = {}) => {
271271
const fromFetchApi = accept === "application/json" || !accept?.includes("html");
272272
if (ret) {
273273
try {
274+
const { method } = req;
274275
const mod = await importRouteModule(filename);
275-
const dataConfig: Record<string, unknown> = util.isPlainObject(mod.data) ? mod.data : {};
276-
if (req.method !== "GET" || mod.default === undefined || fromFetchApi) {
276+
const dataConfig = util.isPlainObject(mod.data) ? mod.data : mod;
277+
if (method !== "GET" || mod.default === undefined || fromFetchApi) {
277278
Object.assign(ctx.params, ret.pathname.groups);
278-
const anyFetcher = dataConfig.any;
279+
const anyFetcher = dataConfig.any ?? dataConfig.ANY;
279280
if (typeof anyFetcher === "function") {
280281
const res = await anyFetcher(req, ctx);
281282
if (res instanceof Response) {
282283
return res;
283284
}
284285
}
285-
const fetcher = dataConfig[req.method.toLowerCase()];
286+
const fetcher = dataConfig[method.toLowerCase()] ?? dataConfig[method];
286287
if (typeof fetcher === "function") {
287288
const res = await fetcher(req, ctx);
288289
if (res instanceof Response) {

server/renderer.ts

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -130,17 +130,16 @@ export default {
130130
}
131131
}
132132
}
133-
const ttls = routeModules.filter(({ dataCacheTtl }) =>
134-
typeof dataCacheTtl === "number" && !Number.isNaN(dataCacheTtl) && dataCacheTtl > 0
135-
).map(({ dataCacheTtl }) => Number(dataCacheTtl));
136-
if (ttls.length > 1) {
133+
if (
134+
routeModules.every(({ dataCacheTtl: ttl }) => typeof ttl === "number" && !Number.isNaN(ttl) && ttl > 0)
135+
) {
136+
const ttls = routeModules.map(({ dataCacheTtl }) => Number(dataCacheTtl));
137137
headers.append("Cache-Control", `${cc}, max-age=${Math.min(...ttls)}`);
138-
} else if (ttls.length == 1) {
139-
headers.append("Cache-Control", `${cc}, max-age=${ttls[0]}`);
140138
} else {
141139
headers.append("Cache-Control", `${cc}, max-age=0, must-revalidate`);
142140
}
143141
if (csp) {
142+
// todo: parse nonce
144143
headers.append("Content-Security-Policy", [csp].flat().join("; "));
145144
}
146145
ssrRes = {
@@ -354,7 +353,7 @@ async function initSSR(
354353
// import module and fetch data for each matched route
355354
const modules = await Promise.all(matches.map(async ([ret, { filename }]) => {
356355
const mod = await importRouteModule(filename);
357-
const dataConfig: Record<string, unknown> = util.isPlainObject(mod.data) ? mod.data : {};
356+
const dataConfig = util.isPlainObject(mod.data) ? mod.data : mod;
358357
const rmod: RouteModule = {
359358
url: new URL(ret.pathname.input + url.search, url.href),
360359
params: ret.pathname.groups,
@@ -366,19 +365,19 @@ async function initSSR(
366365
// assign route params to context
367366
Object.assign(ctx.params, ret.pathname.groups);
368367

369-
// check `any` fetch of data, throw if it returns a response object
370-
const anyFetcher = dataConfig.any;
371-
if (typeof anyFetcher === "function") {
372-
const res = await anyFetcher(req, ctx);
373-
if (res instanceof Response) {
374-
throw res;
375-
}
376-
}
377-
378-
// check `get` of data, if `suspense` is enabled then return a promise instead
379-
const fetcher = dataConfig.get;
368+
// check the `get` method of data, if `suspense` is enabled then return a promise instead
369+
const fetcher = dataConfig.get ?? dataConfig.GET;
380370
if (typeof fetcher === "function") {
381371
const fetchData = async () => {
372+
// check the `any` method of data, throw the response object if it returns one
373+
const anyFetcher = dataConfig.any ?? dataConfig.ANY;
374+
if (typeof anyFetcher === "function") {
375+
const res = await anyFetcher(req, ctx);
376+
if (res instanceof Response) {
377+
throw res;
378+
}
379+
}
380+
382381
let res: unknown;
383382
try {
384383
res = fetcher(req, ctx);

0 commit comments

Comments
 (0)