From 9c2b27a1655fbc7bc7c425e0f0c31a9f1a939665 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 10 Jun 2024 15:33:33 +0900 Subject: [PATCH 1/3] feat: dynamic import server routes glob --- .../react-server/src/entry/react-server.tsx | 3 - .../router/__snapshots__/server.test.tsx.snap | 99 ++----------------- .../src/features/router/server.test.tsx | 2 +- .../src/features/router/server.tsx | 32 ++++-- 4 files changed, 32 insertions(+), 104 deletions(-) diff --git a/packages/react-server/src/entry/react-server.tsx b/packages/react-server/src/entry/react-server.tsx index 511d8bbc5..99bc15836 100644 --- a/packages/react-server/src/entry/react-server.tsx +++ b/packages/react-server/src/entry/react-server.tsx @@ -163,9 +163,6 @@ function createRouter() { // for now hard code /src/routes as convention const glob = import.meta.glob( "/src/routes/**/(page|layout|error).(js|jsx|ts|tsx)", - { - eager: true, - }, ); const tree = generateRouteModuleTree( objectMapKeys(glob, (_v, k) => k.slice("/src/routes".length)), diff --git a/packages/react-server/src/features/router/__snapshots__/server.test.tsx.snap b/packages/react-server/src/features/router/__snapshots__/server.test.tsx.snap index c689c8b71..9fd1104f7 100644 --- a/packages/react-server/src/features/router/__snapshots__/server.test.tsx.snap +++ b/packages/react-server/src/features/router/__snapshots__/server.test.tsx.snap @@ -9,32 +9,20 @@ exports[`generateRouteModuleTree > basic 1`] = ` "children": { "y": { "value": { - "layout": { - "default": "/X/Y/LAYOUT.TSX", - }, - "page": { - "default": "/X/Y/PAGE.TSX", - }, + "layout": [Function], + "page": [Function], }, }, }, "value": { - "error": { - "default": "/X/ERROR.TSX", - }, - "page": { - "default": "/X/PAGE.TSX", - }, + "error": [Function], + "page": [Function], }, }, }, "value": { - "layout": { - "default": "/LAYOUT.TSX", - }, - "page": { - "default": "/PAGE.TSX", - }, + "layout": [Function], + "page": [Function], }, }, }, @@ -88,30 +76,7 @@ exports[`generateRouteModuleTree > basic 2`] = ` , }, "pages": { - "/x": , + "/x": Promise {}, }, } `; @@ -193,30 +158,7 @@ exports[`generateRouteModuleTree > basic 3`] = ` , }, "pages": { - "/x/y": , + "/x/y": Promise {}, }, } `; @@ -264,30 +206,7 @@ exports[`generateRouteModuleTree > basic 4`] = ` , }, "pages": { - "/z": , + "/z": Promise {}, }, } `; diff --git a/packages/react-server/src/features/router/server.test.tsx b/packages/react-server/src/features/router/server.test.tsx index 85e2a348e..cee0f1230 100644 --- a/packages/react-server/src/features/router/server.test.tsx +++ b/packages/react-server/src/features/router/server.test.tsx @@ -12,7 +12,7 @@ describe(generateRouteModuleTree, () => { "/x/y/page.tsx", ]; const input = Object.fromEntries( - files.map((k) => [k, { default: k.toUpperCase() }]), + files.map((k) => [k, async () => ({ default: k.toUpperCase() })]), ); const tree = generateRouteModuleTree(input); expect(tree).toMatchSnapshot(); diff --git a/packages/react-server/src/features/router/server.tsx b/packages/react-server/src/features/router/server.tsx index d84b99280..34e7bd790 100644 --- a/packages/react-server/src/features/router/server.tsx +++ b/packages/react-server/src/features/router/server.tsx @@ -1,19 +1,20 @@ +import { objectHas, tinyassert } from "@hiogawa/utils"; import React from "react"; import { type ReactServerErrorContext, createError } from "../../lib/error"; import { type TreeNode, createFsRouteTree, matchRouteTree } from "./tree"; // cf. https://nextjs.org/docs/app/building-your-application/routing#file-conventions interface RouteEntry { - page?: { + page?: () => Promise<{ default: React.FC; - }; - layout?: { + }>; + layout?: () => Promise<{ default: React.FC; - }; - error?: { + }>; + error?: () => Promise<{ // TODO: warn if no "use client" default: React.FC; - }; + }>; } type RouteModuleNode = TreeNode; @@ -27,8 +28,8 @@ function importRuntimeClient(): Promise { return import("@hiogawa/react-server/runtime-client" as string); } -function renderPage(node: RouteModuleNode, props: PageProps) { - const Page = node.value?.page?.default ?? ThrowNotFound; +async function renderPage(node: RouteModuleNode, props: PageProps) { + const Page = (await importDefault(node.value?.page)) ?? ThrowNotFound; return ; } @@ -43,11 +44,11 @@ async function renderLayout( let acc = ; acc = {acc}; - const ErrorPage = node.value?.error?.default; + const ErrorPage = await importDefault(node.value?.error); if (ErrorPage) { acc = {acc}; } - const Layout = node.value?.layout?.default; + const Layout = await importDefault(node.value?.layout); if (Layout) { acc = ( @@ -131,3 +132,14 @@ export interface ErrorPageProps { serverError?: ReactServerErrorContext; reset: () => void; } + +async function importDefault( + lazyMod?: () => Promise<{ default: T }>, +): Promise { + if (lazyMod) { + const mod = await lazyMod(); + tinyassert(objectHas(mod, "default"), `no deafult export found`); + return mod.default; + } + return; +} From b54273b9e29e23b054501b41d6a1c212ae521365 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 10 Jun 2024 15:35:30 +0900 Subject: [PATCH 2/3] fix: fix renderPage --- .../router/__snapshots__/server.test.tsx.snap | 75 ++++++++++++++++++- .../src/features/router/server.tsx | 2 +- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/packages/react-server/src/features/router/__snapshots__/server.test.tsx.snap b/packages/react-server/src/features/router/__snapshots__/server.test.tsx.snap index 9fd1104f7..0959d3f1c 100644 --- a/packages/react-server/src/features/router/__snapshots__/server.test.tsx.snap +++ b/packages/react-server/src/features/router/__snapshots__/server.test.tsx.snap @@ -76,7 +76,30 @@ exports[`generateRouteModuleTree > basic 2`] = ` , }, "pages": { - "/x": Promise {}, + "/x": , }, } `; @@ -158,7 +181,30 @@ exports[`generateRouteModuleTree > basic 3`] = ` , }, "pages": { - "/x/y": Promise {}, + "/x/y": , }, } `; @@ -206,7 +252,30 @@ exports[`generateRouteModuleTree > basic 4`] = ` , }, "pages": { - "/z": Promise {}, + "/z": , }, } `; diff --git a/packages/react-server/src/features/router/server.tsx b/packages/react-server/src/features/router/server.tsx index 34e7bd790..5953a0f37 100644 --- a/packages/react-server/src/features/router/server.tsx +++ b/packages/react-server/src/features/router/server.tsx @@ -81,7 +81,7 @@ export async function renderRouteMap( if (m.type === "layout") { layouts[m.prefix] = await renderLayout(m.node, props, m.prefix); } else if (m.type === "page") { - pages[m.prefix] = renderPage(m.node, props); + pages[m.prefix] = await renderPage(m.node, props); } else { m.type satisfies never; } From 56d76930595d180535b295eee3626101ceaec0aa Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 10 Jun 2024 16:06:06 +0900 Subject: [PATCH 3/3] fix: fix SERVER_CSS_PROXY invalidation --- packages/react-server/src/entry/server.tsx | 5 +++-- packages/react-server/src/features/assets/plugin.ts | 9 ++++----- packages/react-server/src/features/assets/shared.ts | 2 ++ packages/react-server/src/plugin/index.ts | 6 ++---- 4 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 packages/react-server/src/features/assets/shared.ts diff --git a/packages/react-server/src/entry/server.tsx b/packages/react-server/src/entry/server.tsx index 556d8a0bb..9047f0485 100644 --- a/packages/react-server/src/entry/server.tsx +++ b/packages/react-server/src/entry/server.tsx @@ -3,6 +3,7 @@ import { createMemoryHistory } from "@tanstack/history"; import ReactDOMServer from "react-dom/server.edge"; import type { ModuleNode, ViteDevServer } from "vite"; import type { SsrAssetsType } from "../features/assets/plugin"; +import { DEV_SSR_CSS, SERVER_CSS_PROXY } from "../features/assets/shared"; import { LayoutRoot, LayoutStateContext, @@ -135,8 +136,8 @@ export async function renderHtml( if (import.meta.env.DEV) { // ensure latest css - invalidateModule($__global.dev.server, "\0virtual:react-server-css.js"); - invalidateModule($__global.dev.server, "\0virtual:dev-ssr-css.css?direct"); + invalidateModule($__global.dev.server, `\0${SERVER_CSS_PROXY}`); + invalidateModule($__global.dev.server, `\0${DEV_SSR_CSS}?direct`); } const assets: SsrAssetsType = (await import("virtual:ssr-assets" as string)) .default; diff --git a/packages/react-server/src/features/assets/plugin.ts b/packages/react-server/src/features/assets/plugin.ts index 14e13698d..1ada1fa91 100644 --- a/packages/react-server/src/features/assets/plugin.ts +++ b/packages/react-server/src/features/assets/plugin.ts @@ -11,14 +11,13 @@ import { createVirtualPlugin, } from "../../plugin/utils"; import { collectStyle, collectStyleUrls } from "./css"; +import { DEV_SSR_CSS, SERVER_CSS_PROXY } from "./shared"; export interface SsrAssetsType { bootstrapModules: string[]; head: string; } -export const SERVER_CSS_PROXY = "virtual:server-css-proxy.js"; - export function vitePluginServerAssets({ manager, }: { manager: PluginStateManager }): Plugin[] { @@ -41,7 +40,7 @@ export function vitePluginServerAssets({