Skip to content

Commit

Permalink
feat: multiple clients, optional server in vite plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
TylorS committed Jun 11, 2024
1 parent ccc613b commit 60771ce
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 37 deletions.
3 changes: 2 additions & 1 deletion examples/realworld/src/ui/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 })),
Expand Down
4 changes: 3 additions & 1 deletion examples/realworld/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
})
Expand Down
44 changes: 30 additions & 14 deletions packages/core/src/Platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,20 @@ export class GuardsNotMatched extends Data.TaggedError("GuardsNotMatched")<{
export type LayoutParams<Content extends Fx.Fx<RenderEvent | null, any, any>> = {
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
}

/**
Expand All @@ -81,6 +85,7 @@ export function toHttpRouter<
>(
matcher: Router.RouteMatcher<M>,
options?: {
clientEntry?: string
layout?: LayoutTemplate<
Fx.Fx<RenderEvent | null, Router.RouteMatch.RouteMatch.Error<M>, Router.RouteMatch.RouteMatch.Context<M>>,
E2,
Expand All @@ -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
Expand Down Expand Up @@ -365,6 +375,7 @@ export function toServerRouter<
>(
matcher: Router.RouteMatcher<M>,
options?: {
clientEntry?: string
layout?: LayoutTemplate<
Fx.Fx<RenderEvent | null, Router.RouteMatch.RouteMatch.Error<M>, Router.RouteMatch.RouteMatch.Context<M>>,
E2,
Expand All @@ -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)) {
Expand All @@ -407,8 +423,8 @@ const fromMatches = <R extends Route.Route.Any>(
baseRoute: Route.Route.Any,
route: R,
matches: Array.NonEmptyArray<Router.RouteMatch.RouteMatch.Any>,
head: ReturnType<typeof getHeadAndScript>["head"],
script: ReturnType<typeof getHeadAndScript>["script"],
head: ReturnType<typeof getHeadAndScript>["head"] | null,
script: ReturnType<typeof getHeadAndScript>["script"] | null,
options?: {
layout?: LayoutTemplate<
Fx.Fx<RenderEvent | null, any, any>,
Expand Down
4 changes: 2 additions & 2 deletions packages/vite-plugin-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>
export const serverEntry: string | null
export const relativeServerToClientOutputDirectory: string
export const assetDirectory: string
}
55 changes: 36 additions & 19 deletions packages/vite-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import type { TypedOptions } from "./types.js"
* @since 1.0.0
*/
export interface TypedPluginOptions {
readonly clientEntry: string
readonly clientEntries?: Record<string, string>
readonly clientOutputDirectory?: string

readonly serverEntry: string
readonly serverEntry?: string
readonly serverOutputDirectory?: string

readonly rootDir?: string
Expand All @@ -38,8 +38,10 @@ export function makeTypedPlugin(pluginOptions: TypedPluginOptions): Array<Plugin
: resolve(rootDir, "dist/server")
const tsconfig = resolve(rootDir, pluginOptions.tsconfig ?? "tsconfig.json")
const options: TypedOptions = {
clientEntry: relative(rootDir, resolve(rootDir, pluginOptions.clientEntry)),
serverEntry: relative(rootDir, resolve(rootDir, pluginOptions.serverEntry)),
clientEntries: pluginOptions.clientEntries ?
mapObject(pluginOptions.clientEntries, (value) => relative(rootDir, resolve(rootDir, value)))
: {},
serverEntry: pluginOptions.serverEntry ? relative(rootDir, resolve(rootDir, pluginOptions.serverEntry)) : null,
relativeServerToClientOutputDirectory: relative(serverOutputDirectory, clientOutputDirectory),
assetDirectory: "assets"
}
Expand Down Expand Up @@ -72,7 +74,7 @@ export function makeTypedPlugin(pluginOptions: TypedPluginOptions): Array<Plugin
config: {
build: {
outDir: clientOutputDirectory,
rollupOptions: { input: options.clientEntry }
rollupOptions: { input: options.clientEntries }
},
plugins: [
compression(),
Expand All @@ -84,25 +86,29 @@ export function makeTypedPlugin(pluginOptions: TypedPluginOptions): Array<Plugin
]
}
},
{
name: "server",
config: {
build: {
ssr: true,
outDir: serverOutputDirectory,
rollupOptions: { input: options.serverEntry }
...options.serverEntry ?
[{
name: "server",
config: {
build: {
ssr: true,
outDir: serverOutputDirectory,
rollupOptions: { input: options.serverEntry }
}
}
}
}
}] :
[]
]
}
}
},
tsconfigPaths({ projects: [tsconfig] }),
vavite({
serverEntry: options.serverEntry,
serveClientAssetsInDev: true
}),
...options.serverEntry ?
[vavite({
serverEntry: options.serverEntry,
serveClientAssetsInDev: true
})] :
[],
exposeAssetManifest(clientOutputDirectory),
exposeTypedOptions(options)
]
Expand Down Expand Up @@ -172,10 +178,21 @@ function exposeTypedOptions(options: TypedOptions): Plugin {
if (id === "virtual:typed-options") {
const entries = Object.entries(options)
const lines = entries.map(([key, value]) => `
export const ${key} = "${value}"`)
export const ${key} = ${JSON.stringify(value, null, 2)}`)

return lines.join("\n") + "\n"
}
}
}
}

function mapObject<T, U>(obj: Record<string, T>, fn: (value: T, key: string) => U): Record<string, U> {
const entries = Object.entries(obj)
const result: Record<string, U> = Object.create(null)

for (const [key, value] of entries) {
result[key] = fn(value, key)
}

return result
}

0 comments on commit 60771ce

Please sign in to comment.