Resolve page errors gracefully
This commit is contained in:
parent
c6166f5fd2
commit
12de7ff891
@ -38,6 +38,8 @@ export default function watcher() {
|
|||||||
|
|
||||||
console.log(`Page ${action}: ${filename}. Rebuilding ...`);
|
console.log(`Page ${action}: ${filename}. Rebuilding ...`);
|
||||||
|
|
||||||
|
global.ROUTER.reload();
|
||||||
|
|
||||||
await global.BUNDLER_CTX?.dispose();
|
await global.BUNDLER_CTX?.dispose();
|
||||||
global.BUNDLER_CTX = undefined;
|
global.BUNDLER_CTX = undefined;
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import grabDirNames from "../../../utils/grab-dir-names";
|
import grabDirNames from "../../../utils/grab-dir-names";
|
||||||
import grabRouteParams from "../../../utils/grab-route-params";
|
import grabRouteParams from "../../../utils/grab-route-params";
|
||||||
import grabRouter from "../../../utils/grab-router";
|
|
||||||
import type {
|
import type {
|
||||||
BundlerCTXMap,
|
|
||||||
BunextPageModule,
|
BunextPageModule,
|
||||||
|
BunextPageModuleServerReturn,
|
||||||
|
BunxRouteParams,
|
||||||
GrabPageComponentRes,
|
GrabPageComponentRes,
|
||||||
} from "../../../types";
|
} from "../../../types";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import AppNames from "../../../utils/grab-app-names";
|
import AppNames from "../../../utils/grab-app-names";
|
||||||
import { existsSync } from "fs";
|
import { existsSync } from "fs";
|
||||||
|
import grabPageErrorComponent from "./grab-page-error-component";
|
||||||
|
|
||||||
class NotFoundError extends Error {}
|
class NotFoundError extends Error {}
|
||||||
|
|
||||||
@ -23,20 +24,22 @@ export default async function grabPageComponent({
|
|||||||
file_path: passed_file_path,
|
file_path: passed_file_path,
|
||||||
}: Params): Promise<GrabPageComponentRes> {
|
}: Params): Promise<GrabPageComponentRes> {
|
||||||
const url = req?.url ? new URL(req.url) : undefined;
|
const url = req?.url ? new URL(req.url) : undefined;
|
||||||
const router = grabRouter();
|
const router = global.ROUTER;
|
||||||
|
|
||||||
const {
|
const { PAGES_DIR } = grabDirNames();
|
||||||
BUNX_ROOT_500_PRESET_COMPONENT,
|
|
||||||
BUNX_ROOT_404_PRESET_COMPONENT,
|
|
||||||
PAGES_DIR,
|
|
||||||
} = grabDirNames();
|
|
||||||
|
|
||||||
const routeParams = req ? await grabRouteParams({ req }) : undefined;
|
let routeParams: BunxRouteParams | undefined = undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const match = url ? router.match(url.pathname) : undefined;
|
routeParams = req ? await grabRouteParams({ req }) : undefined;
|
||||||
|
|
||||||
console.log("match", match);
|
let url_path = url ? url.pathname : undefined;
|
||||||
|
|
||||||
|
if (url_path && url?.search) {
|
||||||
|
url_path += url.search;
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = url_path ? router.match(url_path) : undefined;
|
||||||
|
|
||||||
if (!match?.filePath && url?.pathname) {
|
if (!match?.filePath && url?.pathname) {
|
||||||
throw new NotFoundError(`Page ${url.pathname} not found`);
|
throw new NotFoundError(`Page ${url.pathname} not found`);
|
||||||
@ -91,14 +94,22 @@ export default async function grabPageComponent({
|
|||||||
|
|
||||||
const module: BunextPageModule = await import(`${file_path}?t=${now}`);
|
const module: BunextPageModule = await import(`${file_path}?t=${now}`);
|
||||||
|
|
||||||
const serverRes = await (async () => {
|
const serverRes: BunextPageModuleServerReturn = await (async () => {
|
||||||
try {
|
try {
|
||||||
if (routeParams) {
|
if (routeParams) {
|
||||||
return await module["server"]?.(routeParams);
|
const serverData = await module["server"]?.(routeParams);
|
||||||
|
return {
|
||||||
|
...serverData,
|
||||||
|
query: match?.query,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return {};
|
return {
|
||||||
|
query: match?.query,
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {};
|
return {
|
||||||
|
query: match?.query,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@ -132,55 +143,10 @@ export default async function grabPageComponent({
|
|||||||
meta,
|
meta,
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const is404 = error instanceof NotFoundError;
|
return await grabPageErrorComponent({
|
||||||
const errorRoute = is404 ? "/404" : "/500";
|
error,
|
||||||
const presetComponent = is404
|
routeParams,
|
||||||
? BUNX_ROOT_404_PRESET_COMPONENT
|
is404: error instanceof NotFoundError,
|
||||||
: BUNX_ROOT_500_PRESET_COMPONENT;
|
});
|
||||||
|
|
||||||
try {
|
|
||||||
const match = router.match(errorRoute);
|
|
||||||
const filePath = match?.filePath || presetComponent;
|
|
||||||
|
|
||||||
const bundledMap = match?.filePath
|
|
||||||
? (global.BUNDLER_CTX_MAP?.find(
|
|
||||||
(m) => m.local_path === match.filePath,
|
|
||||||
) ?? ({} as BundlerCTXMap))
|
|
||||||
: ({} as BundlerCTXMap);
|
|
||||||
|
|
||||||
const module: BunextPageModule = await import(filePath);
|
|
||||||
const Component = module.default as FC<any>;
|
|
||||||
const component = <Component />;
|
|
||||||
|
|
||||||
return {
|
|
||||||
component,
|
|
||||||
routeParams,
|
|
||||||
module,
|
|
||||||
bundledMap,
|
|
||||||
};
|
|
||||||
} catch {
|
|
||||||
const DefaultNotFound: FC = () => (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: "100vw",
|
|
||||||
height: "100vh",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{is404 ? "404 Not Found" : "500 Internal Server Error"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
component: <DefaultNotFound />,
|
|
||||||
routeParams,
|
|
||||||
module: { default: DefaultNotFound },
|
|
||||||
bundledMap: {} as BundlerCTXMap,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
75
src/functions/server/web-pages/grab-page-error-component.tsx
Normal file
75
src/functions/server/web-pages/grab-page-error-component.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import type { FC } from "react";
|
||||||
|
import grabDirNames from "../../../utils/grab-dir-names";
|
||||||
|
import type {
|
||||||
|
BundlerCTXMap,
|
||||||
|
BunextPageModule,
|
||||||
|
BunxRouteParams,
|
||||||
|
GrabPageComponentRes,
|
||||||
|
} from "../../../types";
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
error?: any;
|
||||||
|
routeParams?: BunxRouteParams;
|
||||||
|
is404?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function grabPageErrorComponent({
|
||||||
|
error,
|
||||||
|
routeParams,
|
||||||
|
is404,
|
||||||
|
}: Params): Promise<GrabPageComponentRes> {
|
||||||
|
const router = global.ROUTER;
|
||||||
|
|
||||||
|
const { BUNX_ROOT_500_PRESET_COMPONENT, BUNX_ROOT_404_PRESET_COMPONENT } =
|
||||||
|
grabDirNames();
|
||||||
|
|
||||||
|
const errorRoute = is404 ? "/404" : "/500";
|
||||||
|
const presetComponent = is404
|
||||||
|
? BUNX_ROOT_404_PRESET_COMPONENT
|
||||||
|
: BUNX_ROOT_500_PRESET_COMPONENT;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const match = router.match(errorRoute);
|
||||||
|
const filePath = match?.filePath || presetComponent;
|
||||||
|
|
||||||
|
const bundledMap = match?.filePath
|
||||||
|
? (global.BUNDLER_CTX_MAP?.find(
|
||||||
|
(m) => m.local_path === match.filePath,
|
||||||
|
) ?? ({} as BundlerCTXMap))
|
||||||
|
: ({} as BundlerCTXMap);
|
||||||
|
|
||||||
|
const module: BunextPageModule = await import(filePath);
|
||||||
|
const Component = module.default as FC<any>;
|
||||||
|
const component = <Component>{<span>{error.message}</span>}</Component>;
|
||||||
|
|
||||||
|
return {
|
||||||
|
component,
|
||||||
|
routeParams,
|
||||||
|
module,
|
||||||
|
bundledMap,
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
const DefaultNotFound: FC = () => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "100vw",
|
||||||
|
height: "100vh",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1>{is404 ? "404 Not Found" : "500 Internal Server Error"}</h1>
|
||||||
|
<span>{error.message}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
component: <DefaultNotFound />,
|
||||||
|
routeParams,
|
||||||
|
module: { default: DefaultNotFound },
|
||||||
|
bundledMap: {} as BundlerCTXMap,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,55 +1,59 @@
|
|||||||
|
import type { GrabPageComponentRes } from "../../../types";
|
||||||
import isDevelopment from "../../../utils/is-development";
|
import isDevelopment from "../../../utils/is-development";
|
||||||
import genWebHTML from "./generate-web-html";
|
import genWebHTML from "./generate-web-html";
|
||||||
import grabPageComponent from "./grab-page-component";
|
import grabPageComponent from "./grab-page-component";
|
||||||
|
import grabPageErrorComponent from "./grab-page-error-component";
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
req: Request;
|
req: Request;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function ({ req }: Params): Promise<Response> {
|
export default async function handleWebPages({
|
||||||
|
req,
|
||||||
|
}: Params): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const {
|
const componentRes = await grabPageComponent({ req });
|
||||||
component,
|
return await generateRes(componentRes);
|
||||||
bundledMap,
|
|
||||||
module,
|
|
||||||
serverRes,
|
|
||||||
meta,
|
|
||||||
head,
|
|
||||||
routeParams,
|
|
||||||
} = await grabPageComponent({ req });
|
|
||||||
|
|
||||||
const html = await genWebHTML({
|
|
||||||
component,
|
|
||||||
pageProps: serverRes,
|
|
||||||
bundledMap,
|
|
||||||
module,
|
|
||||||
meta,
|
|
||||||
head,
|
|
||||||
});
|
|
||||||
|
|
||||||
const res_opts: ResponseInit = {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/html",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isDevelopment()) {
|
|
||||||
res_opts.headers = {
|
|
||||||
...res_opts.headers,
|
|
||||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
||||||
Pragma: "no-cache",
|
|
||||||
Expires: "0",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = new Response(html, res_opts);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log(`Handle web pages Error =>`, error.message);
|
const componentRes = await grabPageErrorComponent({ error });
|
||||||
|
return await generateRes(componentRes);
|
||||||
return new Response(error.message || `Page Not Found`, {
|
|
||||||
status: 404,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function generateRes({
|
||||||
|
component,
|
||||||
|
module,
|
||||||
|
bundledMap,
|
||||||
|
head,
|
||||||
|
meta,
|
||||||
|
routeParams,
|
||||||
|
serverRes,
|
||||||
|
}: GrabPageComponentRes) {
|
||||||
|
const html = await genWebHTML({
|
||||||
|
component,
|
||||||
|
pageProps: serverRes,
|
||||||
|
bundledMap,
|
||||||
|
module,
|
||||||
|
meta,
|
||||||
|
head,
|
||||||
|
});
|
||||||
|
|
||||||
|
const res_opts: ResponseInit = {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "text/html",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isDevelopment()) {
|
||||||
|
res_opts.headers = {
|
||||||
|
...res_opts.headers,
|
||||||
|
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||||
|
Pragma: "no-cache",
|
||||||
|
Expires: "0",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = new Response(html, res_opts);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
export default function DefaultNotFoundPage() {
|
import type { PropsWithChildren } from "react";
|
||||||
|
|
||||||
|
export default function DefaultNotFoundPage({ children }: PropsWithChildren) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -8,9 +10,12 @@ export default function DefaultNotFoundPage() {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "20px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>404 Not Found</span>
|
<h1>404 Not Found</h1>
|
||||||
|
<span>{children}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
export default function DefaultServerErrorPage() {
|
import type { PropsWithChildren } from "react";
|
||||||
|
|
||||||
|
export default function DefaultServerErrorPage({
|
||||||
|
children,
|
||||||
|
}: PropsWithChildren) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -8,9 +12,12 @@ export default function DefaultServerErrorPage() {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "20px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>500 Internal Server Error</span>
|
<h1>500 Internal Server Error</h1>
|
||||||
|
<span>{children}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -176,8 +176,10 @@ export type BunextPageServerFn<
|
|||||||
|
|
||||||
export type BunextPageModuleServerReturn<
|
export type BunextPageModuleServerReturn<
|
||||||
T extends { [k: string]: any } = { [k: string]: any },
|
T extends { [k: string]: any } = { [k: string]: any },
|
||||||
|
Q extends { [k: string]: any } = { [k: string]: any },
|
||||||
> = {
|
> = {
|
||||||
props?: T;
|
props?: T;
|
||||||
|
query?: Q;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BunextPageModuleMetadata = {
|
export type BunextPageModuleMetadata = {
|
||||||
|
|||||||
@ -1,14 +1,7 @@
|
|||||||
import grabDirNames from "./grab-dir-names";
|
|
||||||
|
|
||||||
export default function grabRouter() {
|
export default function grabRouter() {
|
||||||
const { PAGES_DIR } = grabDirNames();
|
// if (process.env.NODE_ENV !== "production") {
|
||||||
|
// global.ROUTER.reload();
|
||||||
|
// }
|
||||||
|
|
||||||
if (process.env.NODE_ENV == "production") {
|
return global.ROUTER;
|
||||||
return global.ROUTER;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Bun.FileSystemRouter({
|
|
||||||
style: "nextjs",
|
|
||||||
dir: PAGES_DIR,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user