diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df27a01cb..31b7271d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,11 @@ jobs: - run: pnpm -C packages/react-server/examples/prerender test-e2e-preview - run: pnpm -C packages/react-server/examples/prerender cf-build - run: pnpm -C packages/react-server/examples/prerender vc-build - + - run: pnpm -C packages/react-server/examples/custom-out-dir test-e2e + - run: pnpm -C packages/react-server/examples/custom-out-dir build + - run: pnpm -C packages/react-server/examples/custom-out-dir test-e2e-preview + - run: pnpm -C packages/react-server/examples/custom-out-dir cf-test-e2e-preview + test-react-server-package: runs-on: ubuntu-latest steps: diff --git a/packages/react-server-next/src/vite/adapters/cloudflare/build.ts b/packages/react-server-next/src/vite/adapters/cloudflare/build.ts index ddfa383b2..30ed7fad7 100644 --- a/packages/react-server-next/src/vite/adapters/cloudflare/build.ts +++ b/packages/react-server-next/src/vite/adapters/cloudflare/build.ts @@ -3,23 +3,23 @@ import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; import { join } from "node:path"; import type { PrerenderManifest } from "@hiogawa/react-server/plugin"; -export async function build() { - const buildDir = join(process.cwd(), "dist"); - const outDir = join(process.cwd(), "dist/cloudflare"); +export async function build({ outDir }: { outDir: string }) { + const buildDir = join(process.cwd(), outDir); + const adapterOutDir = join(process.cwd(), outDir, "cloudflare"); // clean - await rm(outDir, { recursive: true, force: true }); - await mkdir(outDir, { recursive: true }); + await rm(adapterOutDir, { recursive: true, force: true }); + await mkdir(adapterOutDir, { recursive: true }); // assets - await cp(join(buildDir, "client"), outDir, { + await cp(join(buildDir, "client"), adapterOutDir, { recursive: true, }); // worker routes // https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file await writeFile( - join(outDir, "_routes.json"), + join(adapterOutDir, "_routes.json"), JSON.stringify( { version: 1, @@ -39,7 +39,7 @@ export async function build() { // headers // https://developers.cloudflare.com/pages/configuration/headers/ await writeFile( - join(outDir, "_headers"), + join(adapterOutDir, "_headers"), `\ /favicon.ico Cache-Control: public, max-age=3600, s-maxage=3600 @@ -53,7 +53,7 @@ export async function build() { const esbuild = await import("esbuild"); const result = await esbuild.build({ entryPoints: [join(buildDir, "server/index.js")], - outfile: join(outDir, "_worker.js"), + outfile: join(adapterOutDir, "_worker.js"), bundle: true, minify: true, metafile: true, diff --git a/packages/react-server-next/src/vite/adapters/index.ts b/packages/react-server-next/src/vite/adapters/index.ts index eddd28ae3..1987323bb 100644 --- a/packages/react-server-next/src/vite/adapters/index.ts +++ b/packages/react-server-next/src/vite/adapters/index.ts @@ -4,6 +4,7 @@ export type AdapterType = "node" | "vercel" | "vercel-edge" | "cloudflare"; export function adapterPlugin(options: { adapter?: AdapterType; + outDir: string; }): Plugin[] { const adapter = options.adapter ?? autoSelectAdapter(); if (adapter === "node") { @@ -31,15 +32,15 @@ export function adapterPlugin(options: { console.log(`▶▶▶ ADAPTER: ${adapter}`); if (adapter === "cloudflare") { const { build } = await import("./cloudflare/build"); - await build(); + await build({ outDir: options.outDir }); } if (adapter === "vercel") { const { build } = await import("./vercel/build"); - await build({ runtime: "node" }); + await build({ runtime: "node", outDir: options.outDir }); } if (adapter === "vercel-edge") { const { build } = await import("./vercel/build"); - await build({ runtime: "edge" }); + await build({ runtime: "edge", outDir: options.outDir }); } }, }, diff --git a/packages/react-server-next/src/vite/adapters/vercel/build.ts b/packages/react-server-next/src/vite/adapters/vercel/build.ts index 8c02c946b..bdabeb35c 100644 --- a/packages/react-server-next/src/vite/adapters/vercel/build.ts +++ b/packages/react-server-next/src/vite/adapters/vercel/build.ts @@ -38,13 +38,19 @@ const vcConfig = { type VercelRuntime = "node" | "edge"; -export async function build({ runtime }: { runtime: VercelRuntime }) { - const buildDir = join(process.cwd(), "dist"); - const outDir = join(process.cwd(), ".vercel/output"); +export async function build({ + runtime, + outDir, +}: { + runtime: VercelRuntime; + outDir: string; +}) { + const buildDir = join(process.cwd(), outDir); + const adapterOutDir = join(process.cwd(), ".vercel/output"); // clean - await rm(outDir, { recursive: true, force: true }); - await mkdir(outDir, { recursive: true }); + await rm(adapterOutDir, { recursive: true, force: true }); + await mkdir(adapterOutDir, { recursive: true }); // overrides for ssg html // https://vercel.com/docs/build-output-api/v3/configuration#overrides @@ -69,20 +75,20 @@ export async function build({ runtime }: { runtime: VercelRuntime }) { // config await writeFile( - join(outDir, "config.json"), + join(adapterOutDir, "config.json"), JSON.stringify(configJson, null, 2), ); // static - await mkdir(join(outDir, "static"), { recursive: true }); - await cp(join(buildDir, "client"), join(outDir, "static"), { + await mkdir(join(adapterOutDir, "static"), { recursive: true }); + await cp(join(buildDir, "client"), join(adapterOutDir, "static"), { recursive: true, }); // function config - await mkdir(join(outDir, "functions/index.func"), { recursive: true }); + await mkdir(join(adapterOutDir, "functions/index.func"), { recursive: true }); await writeFile( - join(outDir, "functions/index.func/.vc-config.json"), + join(adapterOutDir, "functions/index.func/.vc-config.json"), JSON.stringify(vcConfig[runtime], null, 2), ); @@ -90,7 +96,7 @@ export async function build({ runtime }: { runtime: VercelRuntime }) { const esbuild = await import("esbuild"); const result = await esbuild.build({ entryPoints: [join(buildDir, "server/index.js")], - outfile: join(outDir, "functions/index.func/index.mjs"), + outfile: join(adapterOutDir, "functions/index.func/index.mjs"), metafile: true, bundle: true, minify: true, diff --git a/packages/react-server-next/src/vite/index.ts b/packages/react-server-next/src/vite/index.ts index f4d9f0c52..b3f08c623 100644 --- a/packages/react-server-next/src/vite/index.ts +++ b/packages/react-server-next/src/vite/index.ts @@ -20,6 +20,7 @@ export type ReactServerNextPluginOptions = { export default function vitePluginReactServerNext( options?: ReactServerPluginOptions & ReactServerNextPluginOptions, ): PluginOption { + const outDir = options?.outDir ?? "dist"; return [ react(), nextJsxPlugin(), @@ -32,9 +33,9 @@ export default function vitePluginReactServerNext( vitePluginLogger(), vitePluginSsrMiddleware({ entry: "next/vite/entry-ssr", - preview: path.resolve("./dist/server/index.js"), + preview: path.resolve(outDir, "server", "index.js"), }), - adapterPlugin({ adapter: options?.adapter }), + adapterPlugin({ adapter: options?.adapter, outDir }), appFaviconPlugin(), { name: "next-exclude-optimize", diff --git a/packages/react-server/examples/custom-out-dir/.gitignore b/packages/react-server/examples/custom-out-dir/.gitignore new file mode 100644 index 000000000..b8a118880 --- /dev/null +++ b/packages/react-server/examples/custom-out-dir/.gitignore @@ -0,0 +1,4 @@ +custom-out-dir/ +.vercel/ +.wrangler/ +dist-*/ \ No newline at end of file diff --git a/packages/react-server/examples/custom-out-dir/app/page.tsx b/packages/react-server/examples/custom-out-dir/app/page.tsx new file mode 100644 index 000000000..34cc3da60 --- /dev/null +++ b/packages/react-server/examples/custom-out-dir/app/page.tsx @@ -0,0 +1,11 @@ +function page() { + const outDir = import.meta.env.VITE_E2E_OUT_DIR; + return ( +
+

Hello from custom out dir!

+ {outDir &&
{outDir}
} +
+ ); +} + +export default page; diff --git a/packages/react-server/examples/custom-out-dir/e2e/basic.test.ts b/packages/react-server/examples/custom-out-dir/e2e/basic.test.ts new file mode 100644 index 000000000..f6c8d7a1e --- /dev/null +++ b/packages/react-server/examples/custom-out-dir/e2e/basic.test.ts @@ -0,0 +1,23 @@ +import fs from "node:fs"; +import { expect, test } from "@playwright/test"; +test("custom outDir app can be visited", async ({ page }) => { + const res = await page.goto("/"); + expect(res?.status()).toBe(200); + expect(await page.textContent("h1")).toBe("Hello from custom out dir!"); + if (process.env.VITE_E2E_OUT_DIR) { + expect(await page.textContent("pre")).toBe(process.env.VITE_E2E_OUT_DIR); + } +}); + +test("custom outDir is created", async () => { + test.skip( + !process.env.E2E_PREVIEW && !process.env.E2E_CF, + "outDir is not available in preview", + ); + const outDir = process.env.VITE_E2E_OUT_DIR || "custom-out-dir"; + expect(fs.existsSync(outDir)).toBe(true); +}); + +test("default outDir is not created", async () => { + expect(fs.existsSync("dist")).toBe(false); +}); diff --git a/packages/react-server/examples/custom-out-dir/package.json b/packages/react-server/examples/custom-out-dir/package.json new file mode 100644 index 000000000..c551968c4 --- /dev/null +++ b/packages/react-server/examples/custom-out-dir/package.json @@ -0,0 +1,30 @@ +{ + "name": "@hiogawa/react-server-example-custom-out-dir", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "next dev", + "start": "next start", + "build": "next build", + "cf-build": "CF_PAGES=1 pnpm build", + "cf-preview": "wrangler pages dev ./dist-cf/cloudflare --compatibility-date=2024-01-01 --compatibility-flags=nodejs_compat", + "vc-build": "VERCEL=1 pnpm build", + "test-e2e": "playwright test", + "test-e2e-preview": "E2E_PREVIEW=1 playwright test", + "cf-test-e2e-preview": "VITE_E2E_OUT_DIR=dist-cf pnpm cf-build && VITE_E2E_OUT_DIR=dist-cf E2E_CF=1 pnpm test-e2e" + }, + "dependencies": { + "@hiogawa/react-server": "workspace:*", + "next": "link:../../../react-server-next", + "react": "rc", + "react-dom": "rc", + "react-server-dom-webpack": "rc" + }, + "devDependencies": { + "@playwright/test": "^1.45.1", + "@types/react": "latest", + "@types/react-dom": "latest", + "vite": "latest" + } +} diff --git a/packages/react-server/examples/custom-out-dir/playwright.config.ts b/packages/react-server/examples/custom-out-dir/playwright.config.ts new file mode 100644 index 000000000..a9b8be342 --- /dev/null +++ b/packages/react-server/examples/custom-out-dir/playwright.config.ts @@ -0,0 +1,35 @@ +import { defineConfig, devices } from "@playwright/test"; + +const port = Number(process.env.E2E_PORT || 6174); +const isCloudflarePages = Boolean(process.env.CF_PAGES); +const isPreview = Boolean(process.env.E2E_PREVIEW); +const command = isPreview + ? `pnpm start --port ${port} --strict-port` + : `pnpm dev --port ${port} --strict-port`; + +const cfPreview = `pnpm run cf-preview --port ${port}`; + +export default defineConfig({ + testDir: "e2e", + use: { + trace: "on-first-retry", + }, + projects: [ + { + name: "chromium", + use: { + ...devices["Desktop Chrome"], + viewport: null, + deviceScaleFactor: undefined, + }, + }, + ], + webServer: { + command: isCloudflarePages ? cfPreview : command, + port, + }, + grepInvert: isPreview ? /@dev/ : /@build/, + forbidOnly: !!process.env["CI"], + retries: process.env["CI"] ? 2 : 0, + reporter: "list", +}); diff --git a/packages/react-server/examples/custom-out-dir/tsconfig.json b/packages/react-server/examples/custom-out-dir/tsconfig.json new file mode 100644 index 000000000..51a93621c --- /dev/null +++ b/packages/react-server/examples/custom-out-dir/tsconfig.json @@ -0,0 +1,17 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "skipLibCheck": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "moduleResolution": "Bundler", + "module": "ESNext", + "target": "ESNext", + "lib": ["ESNext", "DOM"], + "types": ["next", "vitest/globals"], + "jsx": "react-jsx" + } +} diff --git a/packages/react-server/examples/custom-out-dir/vite.config.ts b/packages/react-server/examples/custom-out-dir/vite.config.ts new file mode 100644 index 000000000..65e519d6e --- /dev/null +++ b/packages/react-server/examples/custom-out-dir/vite.config.ts @@ -0,0 +1,10 @@ +import next from "next/vite"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + next({ + outDir: process.env.VITE_E2E_OUT_DIR || "custom-out-dir", + }), + ], +}); diff --git a/packages/react-server/src/entry/ssr.tsx b/packages/react-server/src/entry/ssr.tsx index 12d974285..c2f75e7c9 100644 --- a/packages/react-server/src/entry/ssr.tsx +++ b/packages/react-server/src/entry/ssr.tsx @@ -81,7 +81,7 @@ export async function importReactServer(): Promise { if (import.meta.env.DEV) { return $__global.dev.reactServer.ssrLoadModule(ENTRY_SERVER_WRAPPER) as any; } else { - return import("/dist/rsc/index.js" as string); + return import("virtual:import-react-server"); } } diff --git a/packages/react-server/src/features/assets/plugin.ts b/packages/react-server/src/features/assets/plugin.ts index 300c28641..6cf5f55cc 100644 --- a/packages/react-server/src/features/assets/plugin.ts +++ b/packages/react-server/src/features/assets/plugin.ts @@ -65,7 +65,7 @@ export function vitePluginServerAssets({ // TODO: (refactor) use RouteManifest? const manifest: Manifest = JSON.parse( await fs.promises.readFile( - "dist/client/.vite/manifest.json", + path.join(manager.outDir, "client", ".vite", "manifest.json"), "utf-8", ), ); @@ -138,8 +138,8 @@ export function vitePluginServerAssets({ if (manager.buildType === "browser") { for (const file of manager.serverAssets) { await fs.promises.cp( - path.join("dist/rsc", file), - path.join("dist/client", file), + path.join(manager.outDir, "rsc", file), + path.join(manager.outDir, "client", file), ); } } @@ -150,7 +150,9 @@ export function vitePluginServerAssets({ export function serverAssertsPluginServer({ manager, -}: { manager: PluginStateManager }): Plugin[] { +}: { + manager: PluginStateManager; +}): Plugin[] { // 0. track server assets during server build (this plugin) // 1. copy all server assets to browser build (copy-build plugin) // 2. out of those, inject links automatically (ssr-assets virtual plugin) diff --git a/packages/react-server/src/features/next/plugin.ts b/packages/react-server/src/features/next/plugin.ts index b2e8e08ba..b67fb9309 100644 --- a/packages/react-server/src/features/next/plugin.ts +++ b/packages/react-server/src/features/next/plugin.ts @@ -1,4 +1,5 @@ import { mkdir, writeFile } from "node:fs/promises"; +import path from "node:path"; import type { Rollup } from "vite"; // ensure `.js` extension even if project root is cjs @@ -8,7 +9,7 @@ export const OUTPUT_SERVER_JS_EXT = { chunkFileNames: "assets/[name]-[hash].js", } satisfies Rollup.OutputOptions; -export async function createServerPackageJson() { - await mkdir("dist", { recursive: true }); - await writeFile("dist/package.json", `{ "type": "module" }`); +export async function createServerPackageJson(outDir: string) { + await mkdir(outDir, { recursive: true }); + await writeFile(path.join(outDir, "package.json"), `{ "type": "module" }`); } diff --git a/packages/react-server/src/features/prerender/plugin.ts b/packages/react-server/src/features/prerender/plugin.ts index 3b275d2d2..7abc7541d 100644 --- a/packages/react-server/src/features/prerender/plugin.ts +++ b/packages/react-server/src/features/prerender/plugin.ts @@ -39,7 +39,7 @@ export function prerenderPlugin({ writeBundle: { sequential: true, handler() { - return processPrerender(prerender); + return processPrerender(prerender, manager.outDir); }, }, }, @@ -66,10 +66,13 @@ function urlPathToHtmlPath(pathname: string) { return pathname + (pathname.endsWith("/") ? "index.html" : ".html"); } -async function processPrerender(getPrerenderRoutes: PrerenderFn) { +async function processPrerender( + getPrerenderRoutes: PrerenderFn, + outDir: string, +) { console.log("▶▶▶ PRERENDER"); const entry: typeof import("../../entry/ssr") = await import( - path.resolve("dist/server/__entry_ssr.js") + path.resolve(path.join(outDir, "server", "__entry_ssr.js")) ); const { router } = await entry.importReactServer(); const presets = createPrerenderPresets(router.manifest); @@ -87,11 +90,11 @@ async function processPrerender(getPrerenderRoutes: PrerenderFn) { const data = Readable.from(stream as any); const htmlFile = urlPathToHtmlPath(route); const dataFile = route + RSC_PATH; - await mkdir(path.dirname(path.join("dist/client", htmlFile)), { + await mkdir(path.dirname(path.join(outDir, "client", htmlFile)), { recursive: true, }); - await writeFile(path.join("dist/client", htmlFile), html); - await writeFile(path.join("dist/client", dataFile), data); + await writeFile(path.join(outDir, "client", htmlFile), html); + await writeFile(path.join(outDir, "client", dataFile), data); manifest.entries.push({ route, html: htmlFile, @@ -99,7 +102,7 @@ async function processPrerender(getPrerenderRoutes: PrerenderFn) { }); } await writeFile( - "dist/client/__prerender.json", + path.join(outDir, "client", "__prerender.json"), JSON.stringify(manifest, null, 2), ); } diff --git a/packages/react-server/src/features/router/plugin.ts b/packages/react-server/src/features/router/plugin.ts index 204a4b2d5..c058e7e60 100644 --- a/packages/react-server/src/features/router/plugin.ts +++ b/packages/react-server/src/features/router/plugin.ts @@ -24,7 +24,10 @@ import { createFsRouteTree } from "./tree"; export function routeManifestPluginServer({ manager, routeDir, -}: { manager: PluginStateManager; routeDir: string }): Plugin[] { +}: { + manager: PluginStateManager; + routeDir: string; +}): Plugin[] { return [ { name: "server-route-manifest", @@ -60,7 +63,9 @@ export function routeManifestPluginServer({ export function routeManifestPluginClient({ manager, -}: { manager: PluginStateManager }): Plugin[] { +}: { + manager: PluginStateManager; +}): Plugin[] { return [ { name: routeManifestPluginClient.name + ":bundle", @@ -96,7 +101,10 @@ export function routeManifestPluginClient({ const source = `${JSON.stringify(data, null, 2)}`; const sourceHash = hashString(source).slice(0, 8); const url = `/assets/route-manifest-${sourceHash}.js`; - writeFileSync(`dist/client${url}`, `export default ${source}`); + writeFileSync( + path.join(manager.outDir, `client${url}`), + `export default ${source}`, + ); // give asset url and manifest to ssr return `export default ${JSON.stringify( diff --git a/packages/react-server/src/plugin/index.ts b/packages/react-server/src/plugin/index.ts index 48c88babf..5f57e9256 100644 --- a/packages/react-server/src/plugin/index.ts +++ b/packages/react-server/src/plugin/index.ts @@ -75,6 +75,8 @@ class PluginStateManager { config!: ResolvedConfig; configEnv!: ConfigEnv; + outDir!: string; + buildType?: "scan" | "server" | "browser" | "ssr"; routeToClientReferences: Record = {}; @@ -123,6 +125,7 @@ export type ReactServerPluginOptions = { entryBrowser?: string; entryServer?: string; routeDir?: string; + outDir?: string; noAsyncLocalStorage?: boolean; }; @@ -134,6 +137,7 @@ export function vitePluginReactServer( const entryServer = options?.entryServer ?? "@hiogawa/react-server/entry/server"; const routeDir = options?.routeDir ?? "src/routes"; + const outDir = options?.outDir ?? "dist"; const reactServerViteConfig: InlineConfig = { customLogger: createLogger(undefined, { @@ -149,7 +153,6 @@ export function vitePluginReactServer( }, plugins: [ ...(options?.plugins ?? []), - vitePluginSilenceDirectiveBuildWarning(), // expose server reference to react-server itself @@ -168,28 +171,28 @@ export function vitePluginReactServer( createVirtualPlugin("server-routes", () => { return ` - const glob = import.meta.glob( - "/${routeDir}/**/(page|layout|error|not-found|loading|template|route).(js|jsx|ts|tsx)", - { eager: true }, - ); - export default Object.fromEntries( - Object.entries(glob).map( - ([k, v]) => [k.slice("/${routeDir}".length), v] - ) - ); - - const globMiddleware = import.meta.glob("/middleware.(js|jsx|ts|tsx)", { eager: true }); - export const middleware = Object.values(globMiddleware)[0]; - `; + const glob = import.meta.glob( + "/${routeDir}/**/(page|layout|error|not-found|loading|template|route).(js|jsx|ts|tsx)", + { eager: true }, + ); + export default Object.fromEntries( + Object.entries(glob).map( + ([k, v]) => [k.slice("/${routeDir}".length), v] + ) + ); + + const globMiddleware = import.meta.glob("/middleware.(js|jsx|ts|tsx)", { eager: true }); + export const middleware = Object.values(globMiddleware)[0]; + `; }), createVirtualPlugin( ENTRY_SERVER_WRAPPER.slice("virtual:".length), () => ` - import "virtual:inject-async-local-storage"; - export { handler } from "${entryServer}"; - export { router } from "@hiogawa/react-server/entry/server"; - `, + import "virtual:inject-async-local-storage"; + export { handler } from "${entryServer}"; + export { router } from "@hiogawa/react-server/entry/server"; + `, ), // make `AsyncLocalStorage` available globally for React.cache from edge build @@ -199,9 +202,9 @@ export function vitePluginReactServer( return "export {}"; } return ` - import { AsyncLocalStorage } from "node:async_hooks"; - Object.assign(globalThis, { AsyncLocalStorage }); - `; + import { AsyncLocalStorage } from "node:async_hooks"; + Object.assign(globalThis, { AsyncLocalStorage }); + `; }), validateImportPlugin({ @@ -243,7 +246,7 @@ export function vitePluginReactServer( ssr: true, manifest: true, ssrEmitAssets: true, - outDir: "dist/rsc", + outDir: path.join(outDir, "rsc"), rollupOptions: { input: { index: ENTRY_SERVER_WRAPPER, @@ -285,7 +288,7 @@ export function vitePluginReactServer( }, build: { manifest: true, - outDir: env.isSsrBuild ? "dist/server" : "dist/client", + outDir: path.join(outDir, env.isSsrBuild ? "server" : "client"), rollupOptions: env.isSsrBuild ? { input: options?.prerender @@ -303,6 +306,7 @@ export function vitePluginReactServer( }, configResolved(config) { manager.config = config; + manager.outDir = outDir; }, async configureServer(server) { manager.server = server; @@ -373,7 +377,7 @@ export function vitePluginReactServer( apply: "build", async buildStart(_options) { if (!manager.buildType) { - await createServerPackageJson(); + await createServerPackageJson(manager.outDir); console.log("▶▶▶ REACT SERVER BUILD (scan) [1/4]"); manager.buildType = "scan"; await build( @@ -426,6 +430,13 @@ export function vitePluginReactServer( "client-only": true, "server-only": `'server-only' is included in client build`, }), + + createVirtualPlugin("import-react-server", () => { + return ` + export * from "/${outDir}/rsc/index.js"; + `; + }), + createVirtualPlugin(ENTRY_BROWSER_WRAPPER.slice("virtual:".length), () => { // dev if (!manager.buildType) { diff --git a/packages/react-server/src/plugin/virtual.d.ts b/packages/react-server/src/plugin/virtual.d.ts index e3cf4f693..d25e6f1ff 100644 --- a/packages/react-server/src/plugin/virtual.d.ts +++ b/packages/react-server/src/plugin/virtual.d.ts @@ -6,3 +6,7 @@ declare module "virtual:server-routes" { | import("../features/next/middleware").MiddlewareModule | undefined; } + +declare module "virtual:import-react-server" { + export const { handler, router }: typeof import("../entry/server"); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bba7a4690..5d65a50ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -234,6 +234,37 @@ importers: specifier: ^5.3.3 version: 5.3.3(@types/node@20.14.10)(terser@5.31.2) + packages/react-server/examples/custom-out-dir: + dependencies: + '@hiogawa/react-server': + specifier: workspace:* + version: link:../.. + next: + specifier: link:../../../react-server-next + version: link:../../../react-server-next + react: + specifier: 19.0.0-rc-df5f2736-20240712 + version: 19.0.0-rc-df5f2736-20240712 + react-dom: + specifier: 19.0.0-rc-df5f2736-20240712 + version: 19.0.0-rc-df5f2736-20240712(react@19.0.0-rc-df5f2736-20240712) + react-server-dom-webpack: + specifier: 19.0.0-rc-df5f2736-20240712 + version: 19.0.0-rc-df5f2736-20240712(react-dom@19.0.0-rc-df5f2736-20240712(react@19.0.0-rc-df5f2736-20240712))(react@19.0.0-rc-df5f2736-20240712)(webpack@5.93.0(@swc/core@1.6.13(@swc/helpers@0.5.12))(esbuild@0.23.0)) + devDependencies: + '@playwright/test': + specifier: ^1.45.1 + version: 1.45.1 + '@types/react': + specifier: 18.3.3 + version: 18.3.3 + '@types/react-dom': + specifier: 18.3.0 + version: 18.3.0 + vite: + specifier: ^5.3.3 + version: 5.3.3(@types/node@20.14.10)(terser@5.31.2) + packages/react-server/examples/minimal: dependencies: '@hiogawa/react-server':