diff --git a/example/build-watch.ts b/example/build-watch.ts
index ea94fbc..fcd8d1a 100644
--- a/example/build-watch.ts
+++ b/example/build-watch.ts
@@ -1,4 +1,4 @@
import { watchBuild } from "bun-react-ssr/watch";
import { doBuild } from "./build";
-watchBuild(doBuild, ["./hydrate.ts", "./pages"]);
+watchBuild(doBuild, ["./hydrate.ts", "./pages", "./components"]);
diff --git a/example/components/Clock.tsx b/example/components/Clock.tsx
new file mode 100644
index 0000000..03c938b
--- /dev/null
+++ b/example/components/Clock.tsx
@@ -0,0 +1,3 @@
+export function Clock({ time }: { time: Date }) {
+ return
Server time: {time.toISOString()}
;
+}
diff --git a/example/pages/index.tsx b/example/pages/index.tsx
index 34844c6..d24740b 100644
--- a/example/pages/index.tsx
+++ b/example/pages/index.tsx
@@ -1,5 +1,6 @@
import { Link, ReloadContext, useLoadingEffect } from "bun-react-ssr/router";
import { useContext } from "react";
+import { Clock } from "../components/Clock";
export default function Index({ time }: { time: Date }) {
const reload = useContext(ReloadContext);
@@ -8,7 +9,7 @@ export default function Index({ time }: { time: Date }) {
});
return (
-
time {time.toISOString()}
+
index
reload()}>reload
diff --git a/example/routes.ts b/example/routes.ts
index 8983dfb..46e7ecb 100644
--- a/example/routes.ts
+++ b/example/routes.ts
@@ -1,3 +1,12 @@
import { StaticRouters } from "bun-react-ssr";
+import { watch } from "node:fs";
export const router = new StaticRouters(import.meta.dir);
+
+if (Bun.env.NODE_ENV !== "production") {
+ const watcher = watch("./.build/.meta.json");
+ watcher.on("change", () => {
+ console.log("reload");
+ router.reload();
+ });
+}
diff --git a/index.tsx b/index.tsx
index b5ca483..0f48441 100644
--- a/index.tsx
+++ b/index.tsx
@@ -1,25 +1,43 @@
import { FileSystemRouter } from "bun";
import { NJSON } from "next-json";
-import { statSync } from "node:fs";
+import { readFileSync, statSync } from "node:fs";
import { join, relative } from "node:path";
-import { preloadModule } from "react-dom";
import { renderToReadableStream } from "react-dom/server";
import { ClientOnlyError } from "./client";
import { MetaContext, PreloadModule } from "./preload";
export class StaticRouters {
- readonly server: FileSystemRouter;
- readonly client: FileSystemRouter;
- readonly #routes: Map;
- readonly #routes_dump: string;
- readonly #dependencies: Record;
- readonly #hashed: Record;
+ server!: FileSystemRouter;
+ client!: FileSystemRouter;
+ #routes!: Map;
+ #routes_dump!: string;
+ #dependencies!: Record;
+ #hashed!: Record;
+ #cached = new Set();
constructor(
public baseDir: string,
public buildDir = ".build",
public pageDir = "pages"
) {
+ this.reload();
+ }
+
+ reload(excludes: RegExp[] = []) {
+ const { baseDir, pageDir, buildDir } = this;
+ const metafile = Bun.fileURLToPath(
+ import.meta.resolve(join(baseDir, buildDir, ".meta.json"))
+ );
+ delete require.cache[metafile];
+ if (this.#cached.size) {
+ for (const cached of this.#cached) {
+ delete require.cache[cached];
+ for (const dep of scanCacheDependencies(cached, excludes)) {
+ delete require.cache[dep];
+ }
+ }
+ this.#cached.clear();
+ }
this.server = new FileSystemRouter({
dir: join(baseDir, pageDir),
style: "nextjs",
@@ -28,7 +46,7 @@ export class StaticRouters {
dir: join(baseDir, buildDir, pageDir),
style: "nextjs",
});
- const parsed = require(join(baseDir, buildDir, ".meta.json"));
+ const parsed = require(metafile);
this.#hashed = parsed.hashed;
this.#dependencies = parsed.dependencies;
this.#routes = new Map(
@@ -83,7 +101,8 @@ export class StaticRouters {
"No client-side script found for server-side component: " +
serverSide.filePath
);
- const module = await import(serverSide.filePath);
+ const module = require(serverSide.filePath);
+ this.#cached.add(serverSide.filePath);
const result = await module.getServerSideProps?.({
params: serverSide.params,
req: request,
@@ -156,32 +175,36 @@ export class StaticRouters {
}
}
-function DirectPreloadModule({
- target,
- dependencies,
-}: {
- target: string;
- dependencies: Record;
-}) {
- preloadModule(target, { as: "script" });
- preloadModule(target, { as: "script" });
- for (const dep of walkDependencies(target, dependencies)) {
- preloadModule(dep, { as: "script" });
- preloadModule(dep, { as: "script" });
- }
- return null;
-}
-
-function* walkDependencies(
+function* scanCacheDependencies(
target: string,
- dependencies: Record
+ excludes: RegExp[] = []
): Generator {
- if (dependencies[target]) {
- for (const dep of dependencies[target]) {
- yield dep;
- yield* walkDependencies(dep, dependencies);
+ try {
+ const imports = new Bun.Transpiler({
+ loader: target.endsWith(".tsx")
+ ? "tsx"
+ : target.endsWith(".ts")
+ ? "ts"
+ : "jsx",
+ }).scanImports(readFileSync(target));
+ for (const imp of imports) {
+ if (imp.kind === "import-statement") {
+ const path = Bun.fileURLToPath(import.meta.resolve(imp.path, target));
+ if (
+ path.includes("/node_modules/") ||
+ excludes.some((x) => path.match(x))
+ )
+ continue;
+ const resolved = Object.keys(require.cache).find((x) =>
+ x.startsWith(path)
+ );
+ if (resolved) {
+ yield resolved;
+ yield* scanCacheDependencies(resolved, excludes);
+ }
+ }
}
- }
+ } catch {}
}
export async function serveFromDir(config: {