From 95079095edbd264e30207b431a6d925e727c7226 Mon Sep 17 00:00:00 2001 From: daishi Date: Tue, 27 Feb 2024 21:22:06 +0900 Subject: [PATCH 1/6] rename src/lib/middleware to src/lib/old-wrappers --- packages/waku/src/cli.ts | 4 ++-- packages/waku/src/dev.ts | 7 +++---- packages/waku/src/lib/builder/serve-aws-lambda.ts | 2 +- packages/waku/src/lib/builder/serve-cloudflare.ts | 2 +- packages/waku/src/lib/builder/serve-deno.ts | 2 +- packages/waku/src/lib/builder/serve-netlify.ts | 2 +- packages/waku/src/lib/builder/serve-partykit.ts | 2 +- packages/waku/src/lib/builder/serve-vercel.ts | 2 +- .../src/lib/{middleware => old-wrappers}/connect-dev.ts | 0 .../src/lib/{middleware => old-wrappers}/connect-prd.ts | 0 .../src/lib/{middleware => old-wrappers}/connect-utils.ts | 0 .../waku/src/lib/{middleware => old-wrappers}/hono-dev.ts | 0 .../waku/src/lib/{middleware => old-wrappers}/hono-prd.ts | 0 .../src/lib/{middleware => old-wrappers}/hono-utils.ts | 0 packages/waku/src/prd.ts | 6 ++---- 15 files changed, 13 insertions(+), 16 deletions(-) rename packages/waku/src/lib/{middleware => old-wrappers}/connect-dev.ts (100%) rename packages/waku/src/lib/{middleware => old-wrappers}/connect-prd.ts (100%) rename packages/waku/src/lib/{middleware => old-wrappers}/connect-utils.ts (100%) rename packages/waku/src/lib/{middleware => old-wrappers}/hono-dev.ts (100%) rename packages/waku/src/lib/{middleware => old-wrappers}/hono-prd.ts (100%) rename packages/waku/src/lib/{middleware => old-wrappers}/hono-utils.ts (100%) diff --git a/packages/waku/src/cli.ts b/packages/waku/src/cli.ts index 92a43f409..fe70dacd8 100644 --- a/packages/waku/src/cli.ts +++ b/packages/waku/src/cli.ts @@ -10,8 +10,8 @@ import * as dotenv from 'dotenv'; import type { Config } from './config.js'; import { resolveConfig } from './lib/config.js'; -import { honoMiddleware as honoDevMiddleware } from './lib/middleware/hono-dev.js'; -import { honoMiddleware as honoPrdMiddleware } from './lib/middleware/hono-prd.js'; +import { honoMiddleware as honoDevMiddleware } from './lib/old-wrappers/hono-dev.js'; +import { honoMiddleware as honoPrdMiddleware } from './lib/old-wrappers/hono-prd.js'; import { build } from './lib/builder/build.js'; const require = createRequire(new URL('.', import.meta.url)); diff --git a/packages/waku/src/dev.ts b/packages/waku/src/dev.ts index b8f16fc03..2e717dc2e 100644 --- a/packages/waku/src/dev.ts +++ b/packages/waku/src/dev.ts @@ -1,5 +1,4 @@ -export { honoMiddleware as unstable_honoMiddleware } from './lib/middleware/hono-dev.js'; -export { connectMiddleware as unstable_connectMiddleware } from './lib/middleware/connect-dev.js'; +export { honoMiddleware as unstable_honoMiddleware } from './lib/old-wrappers/hono-dev.js'; +export { connectMiddleware as unstable_connectMiddleware } from './lib/old-wrappers/connect-dev.js'; -export { createHandler as unstable_createHandler } from './lib/handlers/handler-dev.js'; -export { build } from './lib/builder/build.js'; +export { build as unstable_build } from './lib/builder/build.js'; diff --git a/packages/waku/src/lib/builder/serve-aws-lambda.ts b/packages/waku/src/lib/builder/serve-aws-lambda.ts index 85443f261..b2ff65fa9 100644 --- a/packages/waku/src/lib/builder/serve-aws-lambda.ts +++ b/packages/waku/src/lib/builder/serve-aws-lambda.ts @@ -2,7 +2,7 @@ import { Hono } from 'hono'; import { handle } from 'hono/aws-lambda'; import { serveStatic } from '@hono/node-server/serve-static'; -import { honoMiddleware } from '../middleware/hono-prd.js'; +import { honoMiddleware } from '../old-wrappers/hono-prd.js'; import path from 'node:path'; import { existsSync, readFileSync } from 'node:fs'; diff --git a/packages/waku/src/lib/builder/serve-cloudflare.ts b/packages/waku/src/lib/builder/serve-cloudflare.ts index 8e3b9a347..f231af10d 100644 --- a/packages/waku/src/lib/builder/serve-cloudflare.ts +++ b/packages/waku/src/lib/builder/serve-cloudflare.ts @@ -4,7 +4,7 @@ import { serveStatic } from 'hono/cloudflare-workers'; // eslint-disable-next-line import/no-unresolved import manifest from '__STATIC_CONTENT_MANIFEST'; -import { honoMiddleware } from '../middleware/hono-prd.js'; +import { honoMiddleware } from '../old-wrappers/hono-prd.js'; const ssr = !!import.meta.env.WAKU_BUILD_SSR; const loadEntries = () => import(import.meta.env.WAKU_ENTRIES_FILE!); diff --git a/packages/waku/src/lib/builder/serve-deno.ts b/packages/waku/src/lib/builder/serve-deno.ts index b767dd5a6..183ab9b9b 100644 --- a/packages/waku/src/lib/builder/serve-deno.ts +++ b/packages/waku/src/lib/builder/serve-deno.ts @@ -5,7 +5,7 @@ import { Hono } from 'https://deno.land/x/hono/mod.ts'; // @ts-expect-error no types import { serveStatic } from 'https://deno.land/x/hono/middleware.ts'; -import { honoMiddleware } from '../middleware/hono-prd.js'; +import { honoMiddleware } from '../old-wrappers/hono-prd.js'; declare const Deno: any; diff --git a/packages/waku/src/lib/builder/serve-netlify.ts b/packages/waku/src/lib/builder/serve-netlify.ts index 6d9458e9a..7e1a33639 100644 --- a/packages/waku/src/lib/builder/serve-netlify.ts +++ b/packages/waku/src/lib/builder/serve-netlify.ts @@ -1,7 +1,7 @@ import { Hono } from 'hono'; import type { Context } from '@netlify/functions'; -import { honoMiddleware } from '../middleware/hono-prd.js'; +import { honoMiddleware } from '../old-wrappers/hono-prd.js'; const ssr = !!import.meta.env.WAKU_BUILD_SSR; const loadEntries = () => import(import.meta.env.WAKU_ENTRIES_FILE!); diff --git a/packages/waku/src/lib/builder/serve-partykit.ts b/packages/waku/src/lib/builder/serve-partykit.ts index bfcbdbb3a..bdbe838ad 100644 --- a/packages/waku/src/lib/builder/serve-partykit.ts +++ b/packages/waku/src/lib/builder/serve-partykit.ts @@ -1,6 +1,6 @@ import { Hono } from 'hono'; -import { honoMiddleware } from '../middleware/hono-prd.js'; +import { honoMiddleware } from '../old-wrappers/hono-prd.js'; const ssr = !!import.meta.env.WAKU_BUILD_SSR; const loadEntries = () => import(import.meta.env.WAKU_ENTRIES_FILE!); diff --git a/packages/waku/src/lib/builder/serve-vercel.ts b/packages/waku/src/lib/builder/serve-vercel.ts index fb8e33e5f..74cd704fb 100644 --- a/packages/waku/src/lib/builder/serve-vercel.ts +++ b/packages/waku/src/lib/builder/serve-vercel.ts @@ -4,7 +4,7 @@ import type { IncomingMessage, ServerResponse } from 'node:http'; import { Hono } from 'hono'; import { getRequestListener } from '@hono/node-server'; -import { honoMiddleware } from '../middleware/hono-prd.js'; +import { honoMiddleware } from '../old-wrappers/hono-prd.js'; const ssr = !!import.meta.env.WAKU_BUILD_SSR; const distDir = import.meta.env.WAKU_CONFIG_DIST_DIR!; diff --git a/packages/waku/src/lib/middleware/connect-dev.ts b/packages/waku/src/lib/old-wrappers/connect-dev.ts similarity index 100% rename from packages/waku/src/lib/middleware/connect-dev.ts rename to packages/waku/src/lib/old-wrappers/connect-dev.ts diff --git a/packages/waku/src/lib/middleware/connect-prd.ts b/packages/waku/src/lib/old-wrappers/connect-prd.ts similarity index 100% rename from packages/waku/src/lib/middleware/connect-prd.ts rename to packages/waku/src/lib/old-wrappers/connect-prd.ts diff --git a/packages/waku/src/lib/middleware/connect-utils.ts b/packages/waku/src/lib/old-wrappers/connect-utils.ts similarity index 100% rename from packages/waku/src/lib/middleware/connect-utils.ts rename to packages/waku/src/lib/old-wrappers/connect-utils.ts diff --git a/packages/waku/src/lib/middleware/hono-dev.ts b/packages/waku/src/lib/old-wrappers/hono-dev.ts similarity index 100% rename from packages/waku/src/lib/middleware/hono-dev.ts rename to packages/waku/src/lib/old-wrappers/hono-dev.ts diff --git a/packages/waku/src/lib/middleware/hono-prd.ts b/packages/waku/src/lib/old-wrappers/hono-prd.ts similarity index 100% rename from packages/waku/src/lib/middleware/hono-prd.ts rename to packages/waku/src/lib/old-wrappers/hono-prd.ts diff --git a/packages/waku/src/lib/middleware/hono-utils.ts b/packages/waku/src/lib/old-wrappers/hono-utils.ts similarity index 100% rename from packages/waku/src/lib/middleware/hono-utils.ts rename to packages/waku/src/lib/old-wrappers/hono-utils.ts diff --git a/packages/waku/src/prd.ts b/packages/waku/src/prd.ts index 2b50b66e4..2c430f663 100644 --- a/packages/waku/src/prd.ts +++ b/packages/waku/src/prd.ts @@ -1,4 +1,2 @@ -export { honoMiddleware as unstable_honoMiddleware } from './lib/middleware/hono-prd.js'; -export { connectMiddleware as unstable_connectMiddleware } from './lib/middleware/connect-prd.js'; - -export { createHandler as unstable_createHandler } from './lib/handlers/handler-prd.js'; +export { honoMiddleware as unstable_honoMiddleware } from './lib/old-wrappers/hono-prd.js'; +export { connectMiddleware as unstable_connectMiddleware } from './lib/old-wrappers/connect-prd.js'; From 1c2de97eb145ff24738c1b1099b17dcbafba059f Mon Sep 17 00:00:00 2001 From: daishi Date: Tue, 27 Feb 2024 23:40:55 +0900 Subject: [PATCH 2/6] wip: middleware interface and runner --- packages/waku/package.json | 4 ++ packages/waku/src/cli.ts | 31 ++++++++---- packages/waku/src/lib/hono/runner.ts | 57 +++++++++++++++++++++++ packages/waku/src/lib/middleware/rsc.ts | 55 ++++++++++++++++++++++ packages/waku/src/lib/middleware/types.ts | 35 ++++++++++++++ packages/waku/src/lib/utils/stream.ts | 10 ++++ packages/waku/src/middleware.ts | 1 + 7 files changed, 184 insertions(+), 9 deletions(-) create mode 100644 packages/waku/src/lib/hono/runner.ts create mode 100644 packages/waku/src/lib/middleware/rsc.ts create mode 100644 packages/waku/src/lib/middleware/types.ts create mode 100644 packages/waku/src/middleware.ts diff --git a/packages/waku/package.json b/packages/waku/package.json index 607f8a2f1..ff2422203 100644 --- a/packages/waku/package.json +++ b/packages/waku/package.json @@ -25,6 +25,10 @@ "types": "./dist/config.d.ts", "default": "./dist/config.js" }, + "./middleware": { + "types": "./dist/middleware.d.ts", + "default": "./dist/middleware.js" + }, "./prd": { "types": "./dist/prd.d.ts", "default": "./dist/prd.js" diff --git a/packages/waku/src/cli.ts b/packages/waku/src/cli.ts index fe70dacd8..e1755590d 100644 --- a/packages/waku/src/cli.ts +++ b/packages/waku/src/cli.ts @@ -12,6 +12,7 @@ import type { Config } from './config.js'; import { resolveConfig } from './lib/config.js'; import { honoMiddleware as honoDevMiddleware } from './lib/old-wrappers/hono-dev.js'; import { honoMiddleware as honoPrdMiddleware } from './lib/old-wrappers/hono-prd.js'; +import { runner } from './lib/hono/runner.js'; import { build } from './lib/builder/build.js'; const require = createRequire(new URL('.', import.meta.url)); @@ -139,15 +140,27 @@ async function runStart(options: { ssr: boolean }) { import(pathToFileURL(path.resolve(distDir, entriesJs)).toString()); const app = new Hono(); app.use('*', serveStatic({ root: path.join(distDir, publicDir) })); - app.use( - '*', - honoPrdMiddleware({ - ...options, - config, - loadEntries, - env: process.env as any, - }), - ); + if (process.env.WAKU_WIP_MIDDLEWARE) { + app.use( + '*', + runner({ + config, + env: process.env as any, + cmd: 'start', + loadEntries, + }), + ); + } else { + app.use( + '*', + honoPrdMiddleware({ + ...options, + config, + loadEntries, + env: process.env as any, + }), + ); + } if (!options.ssr) { // history api fallback app.use( diff --git a/packages/waku/src/lib/hono/runner.ts b/packages/waku/src/lib/hono/runner.ts new file mode 100644 index 000000000..8c7443e0c --- /dev/null +++ b/packages/waku/src/lib/hono/runner.ts @@ -0,0 +1,57 @@ +import type { MiddlewareHandler } from 'hono'; + +import type { HandlerContext, MiddlewareOptions } from '../middleware/types.js'; + +const createEmptyReadableStream = () => + new ReadableStream({ + start(controller) { + controller.close(); + }, + }); + +export const runner = (options: MiddlewareOptions): MiddlewareHandler => { + const middlewareList = [import('waku/middleware').then((mod) => mod.rsc)]; + const handlerList = Promise.all( + middlewareList.map(async (middleware) => (await middleware)(options)), + ); + return async (c, next) => { + const ctx: HandlerContext = { + req: { + body: c.req.raw.body || createEmptyReadableStream(), + url: new URL(c.req.url), + method: c.req.method, + headers: Object.fromEntries( + Array.from(c.req.raw.headers.entries()).map(([k, v]) => [k, v]), + ), + }, + res: {}, + context: {}, + }; + const handlers = await handlerList; + const run = async (index: number) => { + if (index >= handlers.length) { + return next(); + } + let alreadyCalled = false; + await handlers[index]!(ctx, async () => { + if (!alreadyCalled) { + alreadyCalled = true; + await run(index + 1); + } + }); + }; + await run(0); + if ('status' in ctx.res) { + c.status(ctx.res.status as any); + } + if ('headers' in ctx.res) { + for (const [k, v] of Object.entries(ctx.res.headers)) { + c.header(k, v); + } + } + if ('body' in ctx.res) { + return c.body(ctx.res.body); + } + return c.body(null); + }; +}; diff --git a/packages/waku/src/lib/middleware/rsc.ts b/packages/waku/src/lib/middleware/rsc.ts new file mode 100644 index 000000000..9cde274e7 --- /dev/null +++ b/packages/waku/src/lib/middleware/rsc.ts @@ -0,0 +1,55 @@ +import { resolveConfig } from '../config.js'; +import { decodeInput, hasStatusCode } from '../renderers/utils.js'; +import { renderRsc } from '../renderers/rsc-renderer.js'; +import type { Middleware } from './types.js'; + +export const CLIENT_PREFIX = 'client/'; + +export const rsc: Middleware = (options) => { + const { config, cmd } = options; + if (cmd === 'dev') { + throw new Error('not implemented yet'); + } + + (globalThis as any).__WAKU_PRIVATE_ENV__ = options.env || {}; + const configPromise = resolveConfig(config || {}); + const entries = options.loadEntries(); + + return async (ctx, next) => { + const config = await configPromise; + const basePrefix = config.basePath + config.rscPath + '/'; + if (ctx.req.url.pathname.startsWith(basePrefix)) { + const { method, headers } = ctx.req; + if (method !== 'GET' && method !== 'POST') { + throw new Error(`Unsupported method '${method}'`); + } + try { + const input = decodeInput( + ctx.req.url.pathname.slice(basePrefix.length), + ); + const readable = await renderRsc({ + config, + input, + searchParams: ctx.req.url.searchParams, + method, + context: ctx.context, + body: ctx.req.body, + contentType: headers['content-type'] || '', + isDev: false, + entries: await entries, + }); + ctx.res.body = readable; + return; + } catch (err) { + if (hasStatusCode(err)) { + ctx.res.status = err.statusCode; + } else { + console.info('Cannot render RSC', err); + ctx.res.status = 500; + } + return; + } + } + await next(); + }; +}; diff --git a/packages/waku/src/lib/middleware/types.ts b/packages/waku/src/lib/middleware/types.ts new file mode 100644 index 000000000..855ffcb9e --- /dev/null +++ b/packages/waku/src/lib/middleware/types.ts @@ -0,0 +1,35 @@ +import type { Config } from '../../config.js'; +import type { EntriesPrd } from '../../server.js'; + +export type HandlerReq = { + readonly body: ReadableStream; + readonly url: URL; + readonly method: string; + readonly headers: Record; +}; + +export type HandlerRes = { + body?: ReadableStream; + headers?: Record; + status?: number; +}; + +export type RscContext = Record; + +export type HandlerContext = { + readonly req: HandlerReq; + readonly res: HandlerRes; + readonly context: RscContext; +}; + +export type Handler = ( + ctx: HandlerContext, + next: () => Promise, +) => Promise; + +export type MiddlewareOptions = { + config?: Config; + env?: Record; +} & ({ cmd: 'dev' } | { cmd: 'start'; loadEntries: () => Promise }); + +export type Middleware = (options: MiddlewareOptions) => Handler; diff --git a/packages/waku/src/lib/utils/stream.ts b/packages/waku/src/lib/utils/stream.ts index a72b5b130..93259366c 100644 --- a/packages/waku/src/lib/utils/stream.ts +++ b/packages/waku/src/lib/utils/stream.ts @@ -37,3 +37,13 @@ export const streamToString = async ( outs.push(decoder.decode()); return outs.join(''); }; + +export const stringToStream = (str: string): ReadableStream => { + const encoder = new TextEncoder(); + return new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode(str)); + controller.close(); + }, + }); +}; diff --git a/packages/waku/src/middleware.ts b/packages/waku/src/middleware.ts new file mode 100644 index 000000000..8fc73fff7 --- /dev/null +++ b/packages/waku/src/middleware.ts @@ -0,0 +1 @@ +export { rsc } from './lib/middleware/rsc.js'; From 23bda53d840ee879eb990b630f3c8fb425b6a286 Mon Sep 17 00:00:00 2001 From: daishi Date: Wed, 28 Feb 2024 08:32:07 +0900 Subject: [PATCH 3/6] remove unused --- packages/waku/src/lib/middleware/rsc.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/waku/src/lib/middleware/rsc.ts b/packages/waku/src/lib/middleware/rsc.ts index 9cde274e7..5b6a1c6ce 100644 --- a/packages/waku/src/lib/middleware/rsc.ts +++ b/packages/waku/src/lib/middleware/rsc.ts @@ -3,8 +3,6 @@ import { decodeInput, hasStatusCode } from '../renderers/utils.js'; import { renderRsc } from '../renderers/rsc-renderer.js'; import type { Middleware } from './types.js'; -export const CLIENT_PREFIX = 'client/'; - export const rsc: Middleware = (options) => { const { config, cmd } = options; if (cmd === 'dev') { From 7a998bc5e530432aeb818a0b99fe464dc8260a40 Mon Sep 17 00:00:00 2001 From: daishi Date: Wed, 28 Feb 2024 10:56:20 +0900 Subject: [PATCH 4/6] fallback middleware --- packages/waku/src/lib/builder/build.ts | 1 + packages/waku/src/lib/hono/runner.ts | 5 ++++- packages/waku/src/lib/middleware/fallback.ts | 20 ++++++++++++++++++++ packages/waku/src/lib/middleware/rsc.ts | 9 ++++----- packages/waku/src/middleware.ts | 1 + packages/waku/src/server.ts | 1 + 6 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 packages/waku/src/lib/middleware/fallback.ts diff --git a/packages/waku/src/lib/builder/build.ts b/packages/waku/src/lib/builder/build.ts index 1b88cfe8f..2463def54 100644 --- a/packages/waku/src/lib/builder/build.ts +++ b/packages/waku/src/lib/builder/build.ts @@ -586,6 +586,7 @@ const emitHtmlFiles = async ( const dynamicHtmlPaths = Array.from(dynamicHtmlPathMap); const code = ` export const dynamicHtmlPaths= ${JSON.stringify(dynamicHtmlPaths)}; +export const publicIndexHtml= ${JSON.stringify(publicIndexHtml)}; `; await appendFile(distEntriesFile, code); }; diff --git a/packages/waku/src/lib/hono/runner.ts b/packages/waku/src/lib/hono/runner.ts index 8c7443e0c..776a14ee6 100644 --- a/packages/waku/src/lib/hono/runner.ts +++ b/packages/waku/src/lib/hono/runner.ts @@ -10,7 +10,10 @@ const createEmptyReadableStream = () => }); export const runner = (options: MiddlewareOptions): MiddlewareHandler => { - const middlewareList = [import('waku/middleware').then((mod) => mod.rsc)]; + const middlewareList = [ + import('waku/middleware').then((mod) => mod.rsc), + import('waku/middleware').then((mod) => mod.fallback), + ]; const handlerList = Promise.all( middlewareList.map(async (middleware) => (await middleware)(options)), ); diff --git a/packages/waku/src/lib/middleware/fallback.ts b/packages/waku/src/lib/middleware/fallback.ts new file mode 100644 index 000000000..3df72cef7 --- /dev/null +++ b/packages/waku/src/lib/middleware/fallback.ts @@ -0,0 +1,20 @@ +import { stringToStream } from '../utils/stream.js'; +import type { Middleware } from './types.js'; + +export const fallback: Middleware = (options) => { + if (options.cmd === 'dev') { + // pass through in dev command + return async (_ctx, next) => next(); + } + + const entriesPromise = options.loadEntries(); + + return async (ctx, _next) => { + const entries = await entriesPromise; + ctx.res.body = stringToStream(entries.publicIndexHtml); + ctx.res.headers = { + ...ctx.res.headers, + 'content-type': 'text/html; charset=utf-8', + }; + }; +}; diff --git a/packages/waku/src/lib/middleware/rsc.ts b/packages/waku/src/lib/middleware/rsc.ts index 5b6a1c6ce..62aa473ce 100644 --- a/packages/waku/src/lib/middleware/rsc.ts +++ b/packages/waku/src/lib/middleware/rsc.ts @@ -4,14 +4,13 @@ import { renderRsc } from '../renderers/rsc-renderer.js'; import type { Middleware } from './types.js'; export const rsc: Middleware = (options) => { - const { config, cmd } = options; - if (cmd === 'dev') { + if (options.cmd === 'dev') { throw new Error('not implemented yet'); } (globalThis as any).__WAKU_PRIVATE_ENV__ = options.env || {}; - const configPromise = resolveConfig(config || {}); - const entries = options.loadEntries(); + const configPromise = resolveConfig(options.config || {}); + const entriesPromise = options.loadEntries(); return async (ctx, next) => { const config = await configPromise; @@ -34,7 +33,7 @@ export const rsc: Middleware = (options) => { body: ctx.req.body, contentType: headers['content-type'] || '', isDev: false, - entries: await entries, + entries: await entriesPromise, }); ctx.res.body = readable; return; diff --git a/packages/waku/src/middleware.ts b/packages/waku/src/middleware.ts index 8fc73fff7..5599a8d92 100644 --- a/packages/waku/src/middleware.ts +++ b/packages/waku/src/middleware.ts @@ -1 +1,2 @@ export { rsc } from './lib/middleware/rsc.js'; +export { fallback } from './lib/middleware/fallback.js'; diff --git a/packages/waku/src/server.ts b/packages/waku/src/server.ts index faf320b78..1906ffe89 100644 --- a/packages/waku/src/server.ts +++ b/packages/waku/src/server.ts @@ -57,6 +57,7 @@ export type EntriesDev = { export type EntriesPrd = EntriesDev & { loadModule: (id: string) => Promise; dynamicHtmlPaths: [pathSpec: PathSpec, htmlHead: string][]; + publicIndexHtml: string; }; export function getEnv(key: string): string | undefined { From 38f40a0194d98a9cf1728e984d4ec7c3500190a6 Mon Sep 17 00:00:00 2001 From: daishi Date: Wed, 28 Feb 2024 21:34:27 +0900 Subject: [PATCH 5/6] add ssr middleware --- packages/waku/src/lib/hono/runner.ts | 3 +- packages/waku/src/lib/middleware/rsc.ts | 9 ++- packages/waku/src/lib/middleware/ssr.ts | 78 +++++++++++++++++++++++++ packages/waku/src/middleware.ts | 1 + 4 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 packages/waku/src/lib/middleware/ssr.ts diff --git a/packages/waku/src/lib/hono/runner.ts b/packages/waku/src/lib/hono/runner.ts index 776a14ee6..7e7eebf6a 100644 --- a/packages/waku/src/lib/hono/runner.ts +++ b/packages/waku/src/lib/hono/runner.ts @@ -11,8 +11,9 @@ const createEmptyReadableStream = () => export const runner = (options: MiddlewareOptions): MiddlewareHandler => { const middlewareList = [ + import('waku/middleware').then((mod) => mod.ssr), import('waku/middleware').then((mod) => mod.rsc), - import('waku/middleware').then((mod) => mod.fallback), + // import('waku/middleware').then((mod) => mod.fallback), ]; const handlerList = Promise.all( middlewareList.map(async (middleware) => (await middleware)(options)), diff --git a/packages/waku/src/lib/middleware/rsc.ts b/packages/waku/src/lib/middleware/rsc.ts index 62aa473ce..61403cd66 100644 --- a/packages/waku/src/lib/middleware/rsc.ts +++ b/packages/waku/src/lib/middleware/rsc.ts @@ -13,7 +13,10 @@ export const rsc: Middleware = (options) => { const entriesPromise = options.loadEntries(); return async (ctx, next) => { - const config = await configPromise; + const [config, entries] = await Promise.all([ + configPromise, + entriesPromise, + ]); const basePrefix = config.basePath + config.rscPath + '/'; if (ctx.req.url.pathname.startsWith(basePrefix)) { const { method, headers } = ctx.req; @@ -33,7 +36,7 @@ export const rsc: Middleware = (options) => { body: ctx.req.body, contentType: headers['content-type'] || '', isDev: false, - entries: await entriesPromise, + entries, }); ctx.res.body = readable; return; @@ -41,7 +44,7 @@ export const rsc: Middleware = (options) => { if (hasStatusCode(err)) { ctx.res.status = err.statusCode; } else { - console.info('Cannot render RSC', err); + console.info('Cannot process RSC', err); ctx.res.status = 500; } return; diff --git a/packages/waku/src/lib/middleware/ssr.ts b/packages/waku/src/lib/middleware/ssr.ts new file mode 100644 index 000000000..c5fc5fe83 --- /dev/null +++ b/packages/waku/src/lib/middleware/ssr.ts @@ -0,0 +1,78 @@ +import { resolveConfig } from '../config.js'; +import { getPathMapping } from '../utils/path.js'; +import { renderHtml } from '../renderers/html-renderer.js'; +import { hasStatusCode } from '../renderers/utils.js'; +import { renderRsc, getSsrConfig } from '../renderers/rsc-renderer.js'; +import type { Middleware } from './types.js'; + +export const CLIENT_PREFIX = 'client/'; + +export const ssr: Middleware = (options) => { + if (options.cmd === 'dev') { + throw new Error('not implemented yet'); + } + + (globalThis as any).__WAKU_PRIVATE_ENV__ = options.env || {}; + const configPromise = resolveConfig(options.config || {}); + const entriesPromise = options.loadEntries(); + + return async (ctx, next) => { + const [config, entries] = await Promise.all([ + configPromise, + entriesPromise, + ]); + try { + const { dynamicHtmlPaths } = entries; + const htmlHead = dynamicHtmlPaths.find(([pathSpec]) => + getPathMapping(pathSpec, ctx.req.url.pathname), + )?.[1]; + if (htmlHead) { + const readable = await renderHtml({ + config, + pathname: ctx.req.url.pathname, + searchParams: ctx.req.url.searchParams, + htmlHead, + // TODO refactor: avoid this and try using next() instead + renderRscForHtml: (input, searchParams) => + renderRsc({ + entries, + config, + input, + searchParams, + method: 'GET', + context: ctx.context, + isDev: false, + }), + getSsrConfigForHtml: (pathname, searchParams) => + getSsrConfig({ + config, + pathname, + searchParams, + isDev: false, + entries, + }), + loadClientModule: (key) => entries.loadModule(CLIENT_PREFIX + key), + isDev: false, + loadModule: entries.loadModule, + }); + if (readable) { + ctx.res.headers = { + ...ctx.res.headers, + 'content-type': 'text/html; charset=utf-8', + }; + ctx.res.body = readable; + return; + } + } + } catch (err) { + if (hasStatusCode(err)) { + ctx.res.status = err.statusCode; + } else { + console.info('Cannot process SSR', err); + ctx.res.status = 500; + } + return; + } + await next(); + }; +}; diff --git a/packages/waku/src/middleware.ts b/packages/waku/src/middleware.ts index 5599a8d92..7c01580cd 100644 --- a/packages/waku/src/middleware.ts +++ b/packages/waku/src/middleware.ts @@ -1,2 +1,3 @@ export { rsc } from './lib/middleware/rsc.js'; +export { ssr } from './lib/middleware/ssr.js'; export { fallback } from './lib/middleware/fallback.js'; From 253d16ab548a104c86abacdf84ddb930fed5a4d6 Mon Sep 17 00:00:00 2001 From: daishi Date: Thu, 29 Feb 2024 00:18:18 +0900 Subject: [PATCH 6/6] refactor --- packages/waku/src/lib/hono/runner.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/waku/src/lib/hono/runner.ts b/packages/waku/src/lib/hono/runner.ts index 7e7eebf6a..41b1332f7 100644 --- a/packages/waku/src/lib/hono/runner.ts +++ b/packages/waku/src/lib/hono/runner.ts @@ -15,7 +15,7 @@ export const runner = (options: MiddlewareOptions): MiddlewareHandler => { import('waku/middleware').then((mod) => mod.rsc), // import('waku/middleware').then((mod) => mod.fallback), ]; - const handlerList = Promise.all( + const handlersPromise = Promise.all( middlewareList.map(async (middleware) => (await middleware)(options)), ); return async (c, next) => { @@ -31,7 +31,7 @@ export const runner = (options: MiddlewareOptions): MiddlewareHandler => { res: {}, context: {}, }; - const handlers = await handlerList; + const handlers = await handlersPromise; const run = async (index: number) => { if (index >= handlers.length) { return next();