Bugfix and major refactor. Update server-side page rendering logic.
This commit is contained in:
parent
c2c63a1a99
commit
ef53ab5f2a
@ -6,6 +6,7 @@ export default async function () {
|
||||
const dirNames = grabDirNames();
|
||||
|
||||
execSync(`rm -rf ${dirNames.BUNEXT_CACHE_DIR}`);
|
||||
execSync(`rm -rf ${dirNames.BUNX_CWD_MODULE_CACHE_DIR}`);
|
||||
|
||||
const keys = Object.keys(dirNames) as (keyof ReturnType<
|
||||
typeof grabDirNames
|
||||
|
||||
63
src/functions/server/web-pages/grab-file-path-module.tsx
Normal file
63
src/functions/server/web-pages/grab-file-path-module.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import isDevelopment from "../../../utils/is-development";
|
||||
import * as esbuild from "esbuild";
|
||||
import postcss from "postcss";
|
||||
import tailwindcss from "@tailwindcss/postcss";
|
||||
import { readFile } from "fs/promises";
|
||||
import grabDirNames from "../../../utils/grab-dir-names";
|
||||
import path from "path";
|
||||
|
||||
type Params = {
|
||||
file_path: string;
|
||||
};
|
||||
|
||||
const tailwindPlugin: esbuild.Plugin = {
|
||||
name: "tailwindcss",
|
||||
setup(build) {
|
||||
build.onLoad({ filter: /\.css$/ }, async (args) => {
|
||||
const source = await readFile(args.path, "utf-8");
|
||||
const result = await postcss([tailwindcss()]).process(source, {
|
||||
from: args.path,
|
||||
});
|
||||
|
||||
return {
|
||||
contents: result.css,
|
||||
loader: "css",
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default async function grabFilePathModule<T extends any = any>({
|
||||
file_path,
|
||||
}: Params): Promise<T> {
|
||||
const dev = isDevelopment();
|
||||
const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
|
||||
const target_cache_file_path = path.join(
|
||||
BUNX_CWD_MODULE_CACHE_DIR,
|
||||
`${path.basename(file_path)}.js`,
|
||||
);
|
||||
|
||||
await esbuild.build({
|
||||
entryPoints: [file_path],
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
target: "es2020",
|
||||
platform: "node",
|
||||
external: ["react", "react-dom"],
|
||||
minify: true,
|
||||
define: {
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
dev ? "development" : "production",
|
||||
),
|
||||
},
|
||||
metafile: true,
|
||||
plugins: [tailwindPlugin],
|
||||
jsx: "automatic",
|
||||
outfile: target_cache_file_path,
|
||||
});
|
||||
|
||||
Loader.registry.delete(target_cache_file_path);
|
||||
const module = await import(`${target_cache_file_path}?t=${Date.now()}`);
|
||||
|
||||
return module as T;
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
import type { GrabPageReactBundledComponentRes } from "../../../types";
|
||||
import EJSON from "../../../utils/ejson";
|
||||
import grabTsxStringModule from "./grab-tsx-string-module";
|
||||
|
||||
type Params = {
|
||||
file_path: string;
|
||||
root_file?: string;
|
||||
server_res?: any;
|
||||
};
|
||||
|
||||
export default async function grabPageBundledReactComponent({
|
||||
file_path,
|
||||
root_file,
|
||||
server_res,
|
||||
}: Params): Promise<GrabPageReactBundledComponentRes | undefined> {
|
||||
try {
|
||||
let tsx = ``;
|
||||
|
||||
const server_res_json = EJSON.stringify(server_res || {})?.replace(
|
||||
/"/g,
|
||||
'\\"',
|
||||
);
|
||||
|
||||
if (root_file) {
|
||||
tsx += `import Root from "${root_file}"\n`;
|
||||
}
|
||||
|
||||
tsx += `import Page from "${file_path}"\n`;
|
||||
tsx += `export default function Main() {\n\n`;
|
||||
tsx += `const props = JSON.parse("${server_res_json}")\n\n`;
|
||||
tsx += ` return (\n`;
|
||||
if (root_file) {
|
||||
tsx += ` <Root {...props}><Page {...props} /></Root>\n`;
|
||||
} else {
|
||||
tsx += ` <Page {...props} />\n`;
|
||||
}
|
||||
tsx += ` )\n`;
|
||||
tsx += `}\n`;
|
||||
|
||||
const mod = await grabTsxStringModule({ tsx, file_path });
|
||||
const Main = mod.default;
|
||||
const component = <Main />;
|
||||
|
||||
return {
|
||||
component,
|
||||
server_res,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@ import path from "path";
|
||||
import AppNames from "../../../utils/grab-app-names";
|
||||
import { existsSync } from "fs";
|
||||
import grabPageErrorComponent from "./grab-page-error-component";
|
||||
import grabPageBundledReactComponent from "./grab-page-bundled-react-component";
|
||||
|
||||
class NotFoundError extends Error {}
|
||||
|
||||
@ -63,8 +64,6 @@ export default async function grabPageComponent({
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
|
||||
// const pageName = grabPageName({ path: file_path });
|
||||
|
||||
const root_pages_component_ts_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.ts`;
|
||||
const root_pages_component_tsx_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.tsx`;
|
||||
const root_pages_component_js_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.js`;
|
||||
@ -82,17 +81,7 @@ export default async function grabPageComponent({
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
const root_module = root_file
|
||||
? await import(`${root_file}?t=${now}`)
|
||||
: undefined;
|
||||
|
||||
const RootComponent = root_module?.default as FC<any> | undefined;
|
||||
|
||||
// const component_file_path = root_module
|
||||
// ? `${file_path}`
|
||||
// : `${file_path}?t=${global.LAST_BUILD_TIME ?? 0}`;
|
||||
|
||||
const module: BunextPageModule = await import(`${file_path}?t=${now}`);
|
||||
const module: BunextPageModule = await import(file_path);
|
||||
|
||||
const serverRes: BunextPageModuleServerReturn = await (async () => {
|
||||
try {
|
||||
@ -124,16 +113,18 @@ export default async function grabPageComponent({
|
||||
: undefined
|
||||
: undefined;
|
||||
|
||||
const Component = module.default as FC<any>;
|
||||
const Head = module.Head as FC<any>;
|
||||
|
||||
const component = RootComponent ? (
|
||||
<RootComponent {...serverRes}>
|
||||
<Component {...serverRes} />
|
||||
</RootComponent>
|
||||
) : (
|
||||
<Component {...serverRes} />
|
||||
);
|
||||
const { component } =
|
||||
(await grabPageBundledReactComponent({
|
||||
file_path,
|
||||
root_file,
|
||||
server_res: serverRes,
|
||||
})) || {};
|
||||
|
||||
if (!component) {
|
||||
throw new Error(`Couldn't grab page component`);
|
||||
}
|
||||
|
||||
return {
|
||||
component,
|
||||
@ -152,3 +143,34 @@ export default async function grabPageComponent({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// let root_module: any;
|
||||
|
||||
// if (root_file) {
|
||||
// if (isDevelopment()) {
|
||||
// root_module = await grabFilePathModule({
|
||||
// file_path: root_file,
|
||||
// });
|
||||
// } else {
|
||||
// root_module = root_file ? await import(root_file) : undefined;
|
||||
// }
|
||||
// }
|
||||
|
||||
// const RootComponent = root_module?.default as FC<any> | undefined;
|
||||
|
||||
// let module: BunextPageModule;
|
||||
|
||||
// if (isDevelopment()) {
|
||||
// module = await grabFilePathModule({ file_path });
|
||||
// } else {
|
||||
// module = await import(file_path);
|
||||
// }
|
||||
|
||||
// const Component = main_module.default as FC<any>;
|
||||
// const component = RootComponent ? (
|
||||
// <RootComponent {...serverRes}>
|
||||
// <Component {...serverRes} />
|
||||
// </RootComponent>
|
||||
// ) : (
|
||||
// <Component {...serverRes} />
|
||||
// );
|
||||
|
||||
76
src/functions/server/web-pages/grab-tsx-string-module.tsx
Normal file
76
src/functions/server/web-pages/grab-tsx-string-module.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import isDevelopment from "../../../utils/is-development";
|
||||
import * as esbuild from "esbuild";
|
||||
import postcss from "postcss";
|
||||
import tailwindcss from "@tailwindcss/postcss";
|
||||
import { readFile } from "fs/promises";
|
||||
import grabDirNames from "../../../utils/grab-dir-names";
|
||||
import path from "path";
|
||||
import { execSync } from "child_process";
|
||||
|
||||
type Params = {
|
||||
tsx: string;
|
||||
file_path: string;
|
||||
};
|
||||
|
||||
const tailwindPlugin: esbuild.Plugin = {
|
||||
name: "tailwindcss",
|
||||
setup(build) {
|
||||
build.onLoad({ filter: /\.css$/ }, async (args) => {
|
||||
const source = await readFile(args.path, "utf-8");
|
||||
const result = await postcss([tailwindcss()]).process(source, {
|
||||
from: args.path,
|
||||
});
|
||||
|
||||
return {
|
||||
contents: result.css,
|
||||
loader: "css",
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default async function grabTsxStringModule<T extends any = any>({
|
||||
tsx,
|
||||
file_path,
|
||||
}: Params): Promise<T> {
|
||||
const dev = isDevelopment();
|
||||
const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
|
||||
|
||||
const trimmed_file_path = file_path
|
||||
.replace(/.*\/src\/pages\//, "")
|
||||
.replace(/\.tsx$/, "");
|
||||
|
||||
const out_file_path = path.join(
|
||||
BUNX_CWD_MODULE_CACHE_DIR,
|
||||
`${trimmed_file_path}.js`,
|
||||
);
|
||||
|
||||
await esbuild.build({
|
||||
stdin: {
|
||||
contents: tsx,
|
||||
resolveDir: process.cwd(),
|
||||
loader: "tsx",
|
||||
},
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
target: "es2020",
|
||||
platform: "node",
|
||||
external: ["react", "react-dom"],
|
||||
minify: true,
|
||||
define: {
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
dev ? "development" : "production",
|
||||
),
|
||||
},
|
||||
metafile: true,
|
||||
plugins: [tailwindPlugin],
|
||||
jsx: "automatic",
|
||||
write: true,
|
||||
outfile: out_file_path,
|
||||
});
|
||||
|
||||
Loader.registry.delete(out_file_path);
|
||||
const module = await import(`${out_file_path}?t=${Date.now()}`);
|
||||
|
||||
return module as T;
|
||||
}
|
||||
@ -17,7 +17,17 @@ export default function DefaultServerErrorPage({
|
||||
}}
|
||||
>
|
||||
<h1>500 Internal Server Error</h1>
|
||||
<span>{children}</span>
|
||||
<div
|
||||
style={{
|
||||
maxWidth: "800px",
|
||||
overflowWrap: "break-word",
|
||||
wordBreak: "break-all",
|
||||
maxHeight: "80vh",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -243,6 +243,11 @@ export type GrabPageComponentRes = {
|
||||
head?: FC<BunextPageHeadFCProps>;
|
||||
};
|
||||
|
||||
export type GrabPageReactBundledComponentRes = {
|
||||
component: JSX.Element;
|
||||
server_res?: BunextPageModuleServerReturn;
|
||||
};
|
||||
|
||||
export type PageFiles = {
|
||||
local_path: string;
|
||||
url_path: string;
|
||||
|
||||
@ -16,6 +16,10 @@ export default function grabDirNames() {
|
||||
const CONFIG_FILE = path.join(ROOT_DIR, "bunext.config.ts");
|
||||
|
||||
const BUNX_CWD_DIR = path.resolve(ROOT_DIR, ".bunext");
|
||||
const BUNX_CWD_MODULE_CACHE_DIR = path.resolve(
|
||||
BUNX_CWD_DIR,
|
||||
"module-cache",
|
||||
);
|
||||
const BUNX_TMP_DIR = path.resolve(BUNX_CWD_DIR, ".tmp");
|
||||
const BUNX_HYDRATION_SRC_DIR = path.resolve(
|
||||
BUNX_CWD_DIR,
|
||||
@ -57,5 +61,6 @@ export default function grabDirNames() {
|
||||
BUNX_ROOT_404_FILE_NAME,
|
||||
HYDRATION_DST_DIR_MAP_JSON_FILE,
|
||||
BUNEXT_CACHE_DIR,
|
||||
BUNX_CWD_MODULE_CACHE_DIR,
|
||||
};
|
||||
}
|
||||
|
||||
68
src/utils/register-dev-plugin.ts
Normal file
68
src/utils/register-dev-plugin.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { resolve, dirname, extname } from "path";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
const SOURCE_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js"];
|
||||
|
||||
function getLoader(filePath: string) {
|
||||
const ext = extname(filePath).slice(1) as any;
|
||||
return SOURCE_EXTENSIONS.map((e) => e.slice(1)).includes(ext) ? ext : "js";
|
||||
}
|
||||
|
||||
function tryResolveSync(absPath: string): string | null {
|
||||
if (existsSync(absPath)) return absPath;
|
||||
for (const ext of SOURCE_EXTENSIONS) {
|
||||
const p = absPath + ext;
|
||||
if (existsSync(p)) return p;
|
||||
}
|
||||
for (const ext of SOURCE_EXTENSIONS) {
|
||||
const p = resolve(absPath, "index" + ext);
|
||||
if (existsSync(p)) return p;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function registerDevPlugin() {
|
||||
Bun.plugin({
|
||||
name: "bunext-dev-hmr",
|
||||
setup(build) {
|
||||
// Intercept absolute-path imports that already carry ?t= (our dynamic imports)
|
||||
build.onResolve({ filter: /\?t=\d+$/ }, (args) => {
|
||||
if (args.path.includes("node_modules")) return undefined;
|
||||
const cleanPath = args.path.replace(/\?t=\d+$/, "");
|
||||
const resolved = tryResolveSync(cleanPath);
|
||||
if (!resolved) return undefined;
|
||||
if (!SOURCE_EXTENSIONS.some((e) => resolved.endsWith(e)))
|
||||
return undefined;
|
||||
return {
|
||||
path: `${resolved}?t=${global.LAST_BUILD_TIME ?? 0}`,
|
||||
namespace: "bunext-dev",
|
||||
};
|
||||
});
|
||||
|
||||
// Intercept relative imports from within bunext-dev modules
|
||||
build.onResolve({ filter: /^\./ }, (args) => {
|
||||
if (!/\?t=\d+/.test(args.importer)) return undefined;
|
||||
// Strip "namespace:" prefix (e.g. "bunext-dev:") Bun prepends to importer
|
||||
const cleanImporter = args.importer
|
||||
.replace(/^[^/]+:(?=\/)/, "")
|
||||
.replace(/\?t=\d+$/, "");
|
||||
const base = resolve(dirname(cleanImporter), args.path);
|
||||
const resolved = tryResolveSync(base);
|
||||
if (!resolved) return undefined;
|
||||
if (!SOURCE_EXTENSIONS.some((e) => resolved.endsWith(e)))
|
||||
return undefined;
|
||||
return {
|
||||
path: `${resolved}?t=${global.LAST_BUILD_TIME ?? 0}`,
|
||||
namespace: "bunext-dev",
|
||||
};
|
||||
});
|
||||
|
||||
// Load files in the bunext-dev namespace from disk (async is fine in onLoad)
|
||||
build.onLoad({ filter: /.*/, namespace: "bunext-dev" }, async (args) => {
|
||||
const realPath = args.path.replace(/\?t=\d+$/, "");
|
||||
const source = await Bun.file(realPath).text();
|
||||
return { contents: source, loader: getLoader(realPath) };
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user