diff --git a/examples/realworld/src/ui/server.ts b/examples/realworld/src/ui/server.ts index 13eab6b86..f02400f94 100644 --- a/examples/realworld/src/ui/server.ts +++ b/examples/realworld/src/ui/server.ts @@ -4,7 +4,8 @@ import { document } from "./document" import { router } from "./router" // Convert our UI router to a ServerRouter and provide a layout to construct a full HTML document. -export const UiServer = toServerRouter(router, { layout: document }).pipe( +// `clientEntry` must correspond to the name provided in your vite configuration within `clientEntries`. +export const UiServer = toServerRouter(router, { clientEntry: "client", layout: document }).pipe( // Handle UI errors ServerRouter.catchTag("RedirectError", (error) => ServerResponse.seeOther(error.path.toString())), ServerRouter.catchTag("Unprocessable", (_) => ServerResponse.json(_, { status: 422 })), diff --git a/examples/realworld/vite.config.mts b/examples/realworld/vite.config.mts index c460d8e95..2b9a34b7a 100644 --- a/examples/realworld/vite.config.mts +++ b/examples/realworld/vite.config.mts @@ -16,7 +16,9 @@ export default defineConfig({ }, plugins: [ makeTypedPlugin({ - clientEntry: "./src/client.ts", + clientEntries: { + client: "./src/client.ts" + }, serverEntry: "./src/server.ts", tsconfig: "tsconfig.build.json" }) diff --git a/packages/core/src/Platform.ts b/packages/core/src/Platform.ts index 7232238da..0a5f0d05e 100644 --- a/packages/core/src/Platform.ts +++ b/packages/core/src/Platform.ts @@ -48,16 +48,20 @@ export class GuardsNotMatched extends Data.TaggedError("GuardsNotMatched")<{ export type LayoutParams> = { readonly content: Content readonly request: ServerRequest - readonly head: Fx.Fx< - RenderEvent | null, - never, - RenderContext.RenderContext | RenderQueue.RenderQueue | RenderTemplate | Scope.Scope - > - readonly script: Fx.Fx< - RenderEvent | null, - never, - RenderContext.RenderContext | RenderQueue.RenderQueue | RenderTemplate | Scope.Scope - > + readonly head: + | Fx.Fx< + RenderEvent | null, + never, + RenderContext.RenderContext | RenderQueue.RenderQueue | RenderTemplate | Scope.Scope + > + | null + readonly script: + | Fx.Fx< + RenderEvent | null, + never, + RenderContext.RenderContext | RenderQueue.RenderQueue | RenderTemplate | Scope.Scope + > + | null } /** @@ -81,6 +85,7 @@ export function toHttpRouter< >( matcher: Router.RouteMatcher, options?: { + clientEntry?: string layout?: LayoutTemplate< Fx.Fx, Router.RouteMatch.RouteMatch.Context>, E2, @@ -103,7 +108,12 @@ export function toHttpRouter< const withoutQuery = route.path.split("?")[0] return withoutQuery.endsWith("\\") ? withoutQuery.slice(0, -1) : withoutQuery }) - const { head, script } = getHeadAndScript(typedOptions.clientEntry, assetManifest) + const { head, script } = options?.clientEntry ? + getHeadAndScript(typedOptions.clientEntries[options.clientEntry], assetManifest) : + { + head: null, + script: null + } const baseRoute = Route.parse(options?.base ?? "/") for (const [path, matches] of Object.entries(guardsByPath)) { const route = matches[0].route @@ -365,6 +375,7 @@ export function toServerRouter< >( matcher: Router.RouteMatcher, options?: { + clientEntry?: string layout?: LayoutTemplate< Fx.Fx, Router.RouteMatch.RouteMatch.Context>, E2, @@ -386,7 +397,12 @@ export function toServerRouter< const withoutQuery = route.path.split("?")[0] return withoutQuery.endsWith("\\") ? withoutQuery.slice(0, -1) : withoutQuery }) - const { head, script } = getHeadAndScript(typedOptions.clientEntry, assetManifest) + const { head, script } = options?.clientEntry ? + getHeadAndScript(typedOptions.clientEntries[options.clientEntry], assetManifest) : + { + head: null, + script: null + } const baseRoute = Route.parse(options?.base ?? "/") for (const [path, matches] of Object.entries(guardsByPath)) { @@ -407,8 +423,8 @@ const fromMatches = ( baseRoute: Route.Route.Any, route: R, matches: Array.NonEmptyArray, - head: ReturnType["head"], - script: ReturnType["script"], + head: ReturnType["head"] | null, + script: ReturnType["script"] | null, options?: { layout?: LayoutTemplate< Fx.Fx, diff --git a/packages/vite-plugin-types/index.d.ts b/packages/vite-plugin-types/index.d.ts index fdf41fa89..76b38dd96 100644 --- a/packages/vite-plugin-types/index.d.ts +++ b/packages/vite-plugin-types/index.d.ts @@ -6,8 +6,8 @@ declare module "virtual:asset-manifest" { } declare module "virtual:typed-options" { - export const clientEntry: string - export const serverEntry: string + export const clientEntries: Record + export const serverEntry: string | null export const relativeServerToClientOutputDirectory: string export const assetDirectory: string } diff --git a/packages/vite-plugin/src/plugin.ts b/packages/vite-plugin/src/plugin.ts index 6cc37bb57..a9ee2b23c 100644 --- a/packages/vite-plugin/src/plugin.ts +++ b/packages/vite-plugin/src/plugin.ts @@ -15,10 +15,10 @@ import type { TypedOptions } from "./types.js" * @since 1.0.0 */ export interface TypedPluginOptions { - readonly clientEntry: string + readonly clientEntries?: Record readonly clientOutputDirectory?: string - readonly serverEntry: string + readonly serverEntry?: string readonly serverOutputDirectory?: string readonly rootDir?: string @@ -38,8 +38,10 @@ export function makeTypedPlugin(pluginOptions: TypedPluginOptions): Array relative(rootDir, resolve(rootDir, value))) + : {}, + serverEntry: pluginOptions.serverEntry ? relative(rootDir, resolve(rootDir, pluginOptions.serverEntry)) : null, relativeServerToClientOutputDirectory: relative(serverOutputDirectory, clientOutputDirectory), assetDirectory: "assets" } @@ -72,7 +74,7 @@ export function makeTypedPlugin(pluginOptions: TypedPluginOptions): Array ` - export const ${key} = "${value}"`) + export const ${key} = ${JSON.stringify(value, null, 2)}`) return lines.join("\n") + "\n" } } } } + +function mapObject(obj: Record, fn: (value: T, key: string) => U): Record { + const entries = Object.entries(obj) + const result: Record = Object.create(null) + + for (const [key, value] of entries) { + result[key] = fn(value, key) + } + + return result +}