From d3b061a01293117a8f7bc87719c6ccc4bb59a5fb Mon Sep 17 00:00:00 2001 From: rtritto Date: Mon, 21 Oct 2024 15:07:02 +0200 Subject: [PATCH 01/13] feat: init uWebSocket.js support --- .../vike-node/src/runtime/frameworks/uws.ts | 36 +++++++++++++ .../src/runtime/handler-web-and-node-uws.ts | 53 +++++++++++++++++++ .../src/runtime/handler-web-only-uws.ts | 11 ++++ packages/vike-node/src/runtime/types.ts | 7 +++ pnpm-lock.yaml | 23 ++++++-- test/vike-node/package.json | 1 + test/vike-node/vite.config.ts | 4 +- 7 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 packages/vike-node/src/runtime/frameworks/uws.ts create mode 100644 packages/vike-node/src/runtime/handler-web-and-node-uws.ts create mode 100644 packages/vike-node/src/runtime/handler-web-only-uws.ts diff --git a/packages/vike-node/src/runtime/frameworks/uws.ts b/packages/vike-node/src/runtime/frameworks/uws.ts new file mode 100644 index 0000000..474117b --- /dev/null +++ b/packages/vike-node/src/runtime/frameworks/uws.ts @@ -0,0 +1,36 @@ +export { vike } + +import type { TemplatedApp, HttpRequest } from 'uWebSockets.js' +import { createHandler } from '../handler-web-and-node-uws.js' +import type { PlatformRequestUws, VikeOptions } from '../types.js' + +/** + * Creates an uWebSockets.js plugin to handle Vike requests. + * + * @param {VikeOptions} [options] - Configuration options for Vike. + * + * @returns {TemplatedApp} An uWebSockets.js plugin that handles all GET requests and processes them with Vike. + * + * @description + * The plugin: + * 1. Set up a catch-all GET route handler that processes requests using Vike's handler. + * 2. Catch internal errors. + * + * @example + * ```js + * import { App } from 'uWebSockets.js' + * import { vike } from 'vike-node/uws' + * + * const app = vike(App()) + * app.listen(3000) + * ``` + */ +function vike(app: TemplatedApp, options?: VikeOptions): TemplatedApp { + const handler = createHandler(options) + return app.get('*', (response, request) => + handler({ response, request, platformRequest: request as PlatformRequestUws }).catch((error: Error) => { + console.error(error) + response.writeStatus('500').end('Internal Server Error: ' + error.message) + }) + ) +} \ No newline at end of file diff --git a/packages/vike-node/src/runtime/handler-web-and-node-uws.ts b/packages/vike-node/src/runtime/handler-web-and-node-uws.ts new file mode 100644 index 0000000..521d4f9 --- /dev/null +++ b/packages/vike-node/src/runtime/handler-web-and-node-uws.ts @@ -0,0 +1,53 @@ +import type { HttpRequest, HttpResponse } from 'uWebSockets.js' +import { isNodeLike } from '../utils/isNodeLike.js' +import { connectToWeb } from './adapters/connectToWeb.js' +import { createHandler as createHandlerNode } from './handler-node-only-uws.js' +import { createHandler as createHandlerWeb } from'./handler-web-only-uws.js' +import type { PlatformRequestUws, VikeOptions } from './types.js' + +const getHeaders = (req: HttpRequest): [string, string][] => { + const headers: [string, string][] = [] + + req.forEach((key, value) => { + headers.push([key, value]) + }) + + return headers +} + +type Handler = (params: { + response: HttpResponse + request: HttpRequest + platformRequest: PlatformRequestUws +}) => Promise + +export function createHandler(options: VikeOptions = {}): Handler { + return async function handler({ response, request, platformRequest }) { + response.onAborted(() => { + response.isAborted = true + }) + + if (request.getMethod() !== 'get') { + response.writeStatus('405').end() + return + } + + platformRequest.url = request.getUrl() + platformRequest.headers = getHeaders(request) + + if (await isNodeLike()) { + const nodeOnlyHandler = createHandlerNode(options) + const nodeHandler: Handler = ({ request, platformRequest }) => { + const connectedHandler = connectToWeb((req, res) => + nodeOnlyHandler({ req, res, platformRequest }) + ) + return connectedHandler(request) + } + + await nodeHandler({ request, platformRequest }) + } else { + const webHandler: Handler = createHandlerWeb(options) + await webHandler({ platformRequest }) + } + } +} \ No newline at end of file diff --git a/packages/vike-node/src/runtime/handler-web-only-uws.ts b/packages/vike-node/src/runtime/handler-web-only-uws.ts new file mode 100644 index 0000000..e93113c --- /dev/null +++ b/packages/vike-node/src/runtime/handler-web-only-uws.ts @@ -0,0 +1,11 @@ +import type { HttpRequest } from 'uWebSockets.js' +import type { PlatformRequestUws, VikeOptions } from './types.js' +import { renderPageWebUws } from './vike-handler-uws.js' + +export function createHandler(options: VikeOptions = {}): Promise { + return async function handler({ platformRequest }: { + platformRequest: PlatformRequestUws + }) { + await renderPageWebUws({ url: platformRequest.url, headers: platformRequest.headers, platformRequest, options }) + } +} diff --git a/packages/vike-node/src/runtime/types.ts b/packages/vike-node/src/runtime/types.ts index 957544c..e190982 100644 --- a/packages/vike-node/src/runtime/types.ts +++ b/packages/vike-node/src/runtime/types.ts @@ -1,4 +1,5 @@ import type { IncomingMessage, ServerResponse } from 'http' +import type { HttpRequest, HttpResponse } from 'uWebSockets.js' export type HeadersProvided = Record | Headers export type VikeHttpResponse = Awaited>['httpResponse'] @@ -13,4 +14,10 @@ export type ConnectMiddleware< PlatformRequest extends IncomingMessage = IncomingMessage, PlatformResponse extends ServerResponse = ServerResponse > = (req: PlatformRequest, res: PlatformResponse, next: NextFunction) => void +export type ConnectMiddlewareUws = (req: HttpRequest, res: HttpResponse) => void export type WebHandler = (request: Request) => Response | undefined | Promise +export type WebHandlerUws = (request: HttpRequest) => Promise +export type PlatformRequestUws = HttpRequest & { + url: string + headers: [string, string][] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 509a8f7..40cd6a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -201,7 +201,7 @@ importers: version: 4.28.1 h3: specifier: ^1.12.0 - version: 1.12.0 + version: 1.12.0(uWebSockets.js@https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700) hono: specifier: ^4.5.5 version: 4.5.5 @@ -255,7 +255,7 @@ importers: version: 4.28.1 h3: specifier: ^1.12.0 - version: 1.12.0 + version: 1.12.0(uWebSockets.js@https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700) hono: specifier: ^4.5.5 version: 4.5.5 @@ -277,6 +277,9 @@ importers: typescript: specifier: ^5.5.4 version: 5.5.4 + uWebSockets.js: + specifier: github:uNetworking/uWebSockets.js#v20.49.0 + version: https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700 vike: specifier: ^0.4.193 version: 0.4.193(react-streaming@0.3.43(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.0(@types/node@20.14.15)) @@ -1863,6 +1866,7 @@ packages: crossws@0.2.4: resolution: {integrity: sha512-DAxroI2uSOgUKLz00NX6A8U/8EE3SZHmIND+10jkVSaypvyt57J5JEOxAQOL6lQxyzi/wZbTIwssU1uy69h5Vg==} + version: 0.2.4 peerDependencies: uWebSockets.js: '*' peerDependenciesMeta: @@ -2180,6 +2184,7 @@ packages: h3@1.12.0: resolution: {integrity: sha512-Zi/CcNeWBXDrFNlV0hUBJQR9F7a96RjMeAZweW/ZWkR9fuXrMcvKnSA63f/zZ9l0GgQOZDVHGvXivNN9PWOwhA==} + version: 1.12.0 handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} @@ -3040,6 +3045,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + uWebSockets.js@https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700: + resolution: {tarball: https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700} + version: 20.49.0 + ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} @@ -4542,7 +4551,9 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crossws@0.2.4: {} + crossws@0.2.4(uWebSockets.js@https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700): + optionalDependencies: + uWebSockets.js: https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700 csstype@3.1.3: {} @@ -4959,10 +4970,10 @@ snapshots: graceful-fs@4.2.11: {} - h3@1.12.0: + h3@1.12.0(uWebSockets.js@https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700): dependencies: cookie-es: 1.2.2 - crossws: 0.2.4 + crossws: 0.2.4(uWebSockets.js@https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700) defu: 6.1.4 destr: 2.0.3 iron-webcrypto: 1.2.1 @@ -5788,6 +5799,8 @@ snapshots: typescript@5.5.4: {} + uWebSockets.js@https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700: {} + ufo@1.5.4: {} uglify-js@3.19.2: diff --git a/test/vike-node/package.json b/test/vike-node/package.json index 6293aff..5361e61 100644 --- a/test/vike-node/package.json +++ b/test/vike-node/package.json @@ -26,6 +26,7 @@ "sharp": "^0.33.4", "telefunc": "^0.1.76", "typescript": "^5.5.4", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.49.0", "vike": "^0.4.193", "vike-node": "link:../../packages/vike-node", "vite": "^5.4.0" diff --git a/test/vike-node/vite.config.ts b/test/vike-node/vite.config.ts index 217980e..cc30734 100644 --- a/test/vike-node/vite.config.ts +++ b/test/vike-node/vite.config.ts @@ -13,5 +13,7 @@ export default { telefunc() ], // Make test more interesting: avoid vite-plugin-server-entry from [finding the server entry by searching for the dist/ directory](https://github.com/brillout/vite-plugin-server-entry/blob/240f59b4849a3fdfd84448117a3aaf4fbe95a8a0/src/runtime/crawlServerEntry.ts) - build: { outDir: 'build' } + build: { outDir: 'build' }, + // Disable CORS for the test of uWebSockets.js where the cors middleware isn't compatible with uWebSockets.js + server: { cors: false } } From a4d36868a8235383c6fb30bcdf2cd8a77572d686 Mon Sep 17 00:00:00 2001 From: rtritto Date: Mon, 21 Oct 2024 16:25:23 +0200 Subject: [PATCH 02/13] feat: add writeHttpResponseUws and fix types --- .../vike-node/src/runtime/frameworks/uws.ts | 2 +- .../src/runtime/handler-web-and-node-uws.ts | 8 +-- .../src/runtime/handler-web-only-uws.ts | 12 ++-- packages/vike-node/src/runtime/types.ts | 4 ++ .../src/runtime/utils/writeHttpResponse.ts | 41 +++++++++++- .../vike-node/src/runtime/vike-handler-uws.ts | 62 +++++++++++++++++++ 6 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 packages/vike-node/src/runtime/vike-handler-uws.ts diff --git a/packages/vike-node/src/runtime/frameworks/uws.ts b/packages/vike-node/src/runtime/frameworks/uws.ts index 474117b..4b7c9ba 100644 --- a/packages/vike-node/src/runtime/frameworks/uws.ts +++ b/packages/vike-node/src/runtime/frameworks/uws.ts @@ -33,4 +33,4 @@ function vike(app: TemplatedApp, options?: VikeOptions): TemplatedA response.writeStatus('500').end('Internal Server Error: ' + error.message) }) ) -} \ No newline at end of file +} diff --git a/packages/vike-node/src/runtime/handler-web-and-node-uws.ts b/packages/vike-node/src/runtime/handler-web-and-node-uws.ts index 521d4f9..4a8e6b1 100644 --- a/packages/vike-node/src/runtime/handler-web-and-node-uws.ts +++ b/packages/vike-node/src/runtime/handler-web-and-node-uws.ts @@ -3,7 +3,7 @@ import { isNodeLike } from '../utils/isNodeLike.js' import { connectToWeb } from './adapters/connectToWeb.js' import { createHandler as createHandlerNode } from './handler-node-only-uws.js' import { createHandler as createHandlerWeb } from'./handler-web-only-uws.js' -import type { PlatformRequestUws, VikeOptions } from './types.js' +import type { HandlerUws, PlatformRequestUws, VikeOptions } from './types.js' const getHeaders = (req: HttpRequest): [string, string][] => { const headers: [string, string][] = [] @@ -37,7 +37,7 @@ export function createHandler(options: VikeOptions = { if (await isNodeLike()) { const nodeOnlyHandler = createHandlerNode(options) - const nodeHandler: Handler = ({ request, platformRequest }) => { + const nodeHandler: HandlerUws = ({ request, platformRequest }) => { const connectedHandler = connectToWeb((req, res) => nodeOnlyHandler({ req, res, platformRequest }) ) @@ -46,8 +46,8 @@ export function createHandler(options: VikeOptions = { await nodeHandler({ request, platformRequest }) } else { - const webHandler: Handler = createHandlerWeb(options) + const webHandler: HandlerUws = createHandlerWeb(options) await webHandler({ platformRequest }) } } -} \ No newline at end of file +} diff --git a/packages/vike-node/src/runtime/handler-web-only-uws.ts b/packages/vike-node/src/runtime/handler-web-only-uws.ts index e93113c..70566c5 100644 --- a/packages/vike-node/src/runtime/handler-web-only-uws.ts +++ b/packages/vike-node/src/runtime/handler-web-only-uws.ts @@ -1,11 +1,9 @@ import type { HttpRequest } from 'uWebSockets.js' -import type { PlatformRequestUws, VikeOptions } from './types.js' -import { renderPageWebUws } from './vike-handler-uws.js' +import type { HandlerUws, PlatformRequestUws, VikeOptions } from './types.js' +import { renderPageWeb } from './vike-handler-uws.js' -export function createHandler(options: VikeOptions = {}): Promise { - return async function handler({ platformRequest }: { - platformRequest: PlatformRequestUws - }) { - await renderPageWebUws({ url: platformRequest.url, headers: platformRequest.headers, platformRequest, options }) +export function createHandler(options: VikeOptions = {}): HandlerUws { + return async function handler({ res, platformRequest }) { + return renderPageWeb({ res, url: platformRequest.url, headers: platformRequest.headers, platformRequest, options }) } } diff --git a/packages/vike-node/src/runtime/types.ts b/packages/vike-node/src/runtime/types.ts index e190982..61a0c9c 100644 --- a/packages/vike-node/src/runtime/types.ts +++ b/packages/vike-node/src/runtime/types.ts @@ -21,3 +21,7 @@ export type PlatformRequestUws = HttpRequest & { url: string headers: [string, string][] } +export type HandlerUws = (params: { + res: HttpResponse + platformRequest: PlatformRequestUws +}) => Promise diff --git a/packages/vike-node/src/runtime/utils/writeHttpResponse.ts b/packages/vike-node/src/runtime/utils/writeHttpResponse.ts index 488cec2..c6df320 100644 --- a/packages/vike-node/src/runtime/utils/writeHttpResponse.ts +++ b/packages/vike-node/src/runtime/utils/writeHttpResponse.ts @@ -1,5 +1,6 @@ -export { writeHttpResponse } +export { writeHttpResponse, writeHttpResponseUws } +import type { HttpResponse } from 'uWebSockets.js' import type { ServerResponse } from 'http' import { assert } from '../../utils/assert.js' import type { VikeHttpResponse } from '../types.js' @@ -16,3 +17,41 @@ async function writeHttpResponse(httpResponse: VikeHttpResponse, res: ServerResp res.once('close', resolve) }) } + +async function writeHttpResponseUws(httpResponse: VikeHttpResponse, res: HttpResponse) { + assert(httpResponse) + res.writeStatus(httpResponse.statusCode + '') + + for (const header in httpResponse.headers) { + res.writeHeader(header[0]!, header[1]!) + } + + const readableWebStream = httpResponse.getReadableWebStream() + + res.end(await readableStreamToArrayBuffer(readableWebStream)) +} + +/** + * Convert Readable Web Stream to ArrayBuffer + */ +async function readableStreamToArrayBuffer(readableStream: ReadableStream): Promise { + const reader = readableStream.getReader() + const chunks: Uint8Array[] = [] + let done = false + while (!done) { + const { value, done: doneReading } = await reader.read() + if (value) { + chunks.push(value) + } + done = doneReading + } + + const arrayBuffer = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0)) + let offset = 0 + for (const chunk of chunks) { + arrayBuffer.set(chunk, offset) + offset += chunk.length + } + + return arrayBuffer +} diff --git a/packages/vike-node/src/runtime/vike-handler-uws.ts b/packages/vike-node/src/runtime/vike-handler-uws.ts new file mode 100644 index 0000000..5085fca --- /dev/null +++ b/packages/vike-node/src/runtime/vike-handler-uws.ts @@ -0,0 +1,62 @@ +export { renderPage, renderPageWeb } + +import type { HttpResponse } from 'uWebSockets.js' +import { renderPage as _renderPage } from 'vike/server' +import type { VikeHttpResponse, VikeOptions } from './types.js' +import { writeHttpResponseUws } from './utils/writeHttpResponse.js' + +async function renderPage({ + url, + headers, + options, + platformRequest +}: { + url: string + headers: [string, string][] + options: VikeOptions + platformRequest: PlatformRequest +}): Promise { + async function getPageContext(platformRequest: PlatformRequest): Promise> { + return typeof options.pageContext === 'function' ? options.pageContext(platformRequest) : options.pageContext ?? {} + } + + const pageContext = await _renderPage({ + urlOriginal: url, + headersOriginal: headers, + ...(await getPageContext(platformRequest)) + }) + + if (pageContext.errorWhileRendering) { + options.onError?.(pageContext.errorWhileRendering) + } + + return pageContext.httpResponse +} + +async function renderPageWeb({ + res, + url, + headers, + platformRequest, + options +}: { + res: HttpResponse, + url: string + headers: [string, string][] + platformRequest: PlatformRequest + options: VikeOptions +}): Promise { + const httpResponse = await renderPage({ + url, + headers, + platformRequest, + options + }) + + if (!httpResponse) { + res.writeStatus('404').end('Not found') + return + } + + await writeHttpResponseUws(httpResponse, res) +} From 5290698e6bd5411461175e6c80e5adb5bc1f2ba0 Mon Sep 17 00:00:00 2001 From: rtritto Date: Mon, 21 Oct 2024 18:23:43 +0200 Subject: [PATCH 03/13] feat: add uws index test --- test/vike-node/server/index-uws.ts | 46 ++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/vike-node/server/index-uws.ts diff --git a/test/vike-node/server/index-uws.ts b/test/vike-node/server/index-uws.ts new file mode 100644 index 0000000..0daef9c --- /dev/null +++ b/test/vike-node/server/index-uws.ts @@ -0,0 +1,46 @@ +import { telefunc } from 'telefunc' +import { App, type HttpResponse } from 'uWebSockets.js' +import vike from 'vike-node/uws' +import { init } from '../database/todoItems' + +const getBodyJson = (res: HttpResponse): Promise => { + return new Promise((resolve, reject) => { + let data = Buffer.from('') + res.onData((chunk, isLast) => { + data = Buffer.concat([data, Buffer.from(chunk)]) + if (isLast) { + try { + resolve(data.toString()) + } catch (error) { + reject(error) + } + } + }) + }) +} + +startServer() + +async function startServer() { + await init() + const app = vike(App()) + + app.post('/_telefunc', async (res, req) => { + const context = {} + const httpResponse = await telefunc({ + url: req.getUrl(), + method: req.getMethod(), + body: await getBodyJson(res), + context + }) + res.writeStatus(httpResponse.statusCode + '') + res.writeHeader('content-type', httpResponse.contentType) + res.end(httpResponse.body) + }) + + // TODO + // res.writeHeader('x-test', 'test') + + const port = process.env.PORT || 3000 + app.listen(+port, () => console.log(`Server running at http://localhost:${port}`)) +} From a94e9fcde6866e2ebdc810453984b1ecf2f6cfd4 Mon Sep 17 00:00:00 2001 From: rtritto Date: Mon, 21 Oct 2024 22:16:26 +0200 Subject: [PATCH 04/13] feat: fix import and types --- .../vike-node/src/runtime/handler-web-and-node-uws.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/vike-node/src/runtime/handler-web-and-node-uws.ts b/packages/vike-node/src/runtime/handler-web-and-node-uws.ts index 4a8e6b1..50ddd37 100644 --- a/packages/vike-node/src/runtime/handler-web-and-node-uws.ts +++ b/packages/vike-node/src/runtime/handler-web-and-node-uws.ts @@ -1,6 +1,6 @@ import type { HttpRequest, HttpResponse } from 'uWebSockets.js' import { isNodeLike } from '../utils/isNodeLike.js' -import { connectToWeb } from './adapters/connectToWeb.js' +import { connectToWeb } from './adapters/connectToWebUws.js' import { createHandler as createHandlerNode } from './handler-node-only-uws.js' import { createHandler as createHandlerWeb } from'./handler-web-only-uws.js' import type { HandlerUws, PlatformRequestUws, VikeOptions } from './types.js' @@ -37,17 +37,17 @@ export function createHandler(options: VikeOptions = { if (await isNodeLike()) { const nodeOnlyHandler = createHandlerNode(options) - const nodeHandler: HandlerUws = ({ request, platformRequest }) => { + const nodeHandler: HandlerUws = ({ res: response, platformRequest }) => { const connectedHandler = connectToWeb((req, res) => nodeOnlyHandler({ req, res, platformRequest }) ) - return connectedHandler(request) + return connectedHandler(platformRequest) } - await nodeHandler({ request, platformRequest }) + await nodeHandler({ res: response, platformRequest }) } else { const webHandler: HandlerUws = createHandlerWeb(options) - await webHandler({ platformRequest }) + await webHandler({ res: response, platformRequest }) } } } From 7f766e0d56f2aba1dc80dbc38242b8cc514eec20 Mon Sep 17 00:00:00 2001 From: rtritto Date: Tue, 22 Oct 2024 17:09:25 +0200 Subject: [PATCH 05/13] feat: update handlers --- .../src/runtime/adapters/connectToWebUws.ts | 64 ++++++++++ .../adapters/createServerResponseUws.ts | 76 +++++++++++ .../src/runtime/handler-node-only-uws.ts | 118 ++++++++++++++++++ .../src/runtime/handler-web-and-node-uws.ts | 8 +- packages/vike-node/src/runtime/types.ts | 4 +- .../src/runtime/utils/writeHttpResponse.ts | 4 +- 6 files changed, 266 insertions(+), 8 deletions(-) create mode 100644 packages/vike-node/src/runtime/adapters/connectToWebUws.ts create mode 100644 packages/vike-node/src/runtime/adapters/createServerResponseUws.ts create mode 100644 packages/vike-node/src/runtime/handler-node-only-uws.ts diff --git a/packages/vike-node/src/runtime/adapters/connectToWebUws.ts b/packages/vike-node/src/runtime/adapters/connectToWebUws.ts new file mode 100644 index 0000000..237e120 --- /dev/null +++ b/packages/vike-node/src/runtime/adapters/connectToWebUws.ts @@ -0,0 +1,64 @@ +export { connectToWeb } + +import { Readable } from 'node:stream' +import type { HttpResponse } from 'uWebSockets.js' +import type { ConnectMiddlewareUws, PlatformRequestUws, WebHandlerUws } from '../types.js' +import { createServerResponse } from './createServerResponseUws.js' +import { DUMMY_BASE_URL } from '../constants.js' + +const statusCodesWithoutBody = new Set([ + 100, // Continue + 101, // Switching Protocols + 102, // Processing (WebDAV) + 103, // Early Hints + 204, // No Content + 205, // Reset Content + 304 // Not Modified +]) as ReadonlySet + +/** + * Converts a Connect-style middleware to a web-compatible request handler. + * + * @param {ConnectMiddlewareUws} handler - The Connect-style middleware function to be converted. + * @returns {WebHandlerUws} A function that handles web requests and returns a Response or undefined. + */ +function connectToWeb(handler: ConnectMiddlewareUws): WebHandlerUws { + return async (response: HttpResponse, platformRequest: PlatformRequestUws): Promise => { + const { res, onReadable } = createServerResponse(enrichResponse(response, platformRequest)) + + return new Promise((resolve) => { + onReadable(({ readable, headers, statusCode }) => { + const responseBody = statusCodesWithoutBody.has(statusCode) + ? '' + : (Readable.toWeb(readable) as ReadableStream) + + res.writeStatus(statusCode.toString()) + for (const [key, value] of headers) { + res.writeHeader(key, value) + } + res.end(responseBody) + resolve() + }) + + Promise.resolve(handler(res, platformRequest)) + }) + } +} + +/** + * Update the HttpResponse object from a web HttpRequest. + * + * @param {HttpResponse} res - The web Request object. + * @param {PlatformRequestUws} platformRequest + * @returns {HttpResponse} An IncomingMessage-like object compatible with Node.js HTTP module. + */ +function enrichResponse(res: HttpResponse, platformRequest: PlatformRequestUws): HttpResponse { + const parsedUrl = new URL(platformRequest.url, DUMMY_BASE_URL) + const pathnameAndQuery = (parsedUrl.pathname || '') + (parsedUrl.search || '') + // (?) TODO const body = platformRequest.body ? Readable.fromWeb(platformRequest.body as any) : Readable.from([]) + res.url = pathnameAndQuery + res.method = 'GET' + res.headers = Object.fromEntries(platformRequest.headers) + + return res +} diff --git a/packages/vike-node/src/runtime/adapters/createServerResponseUws.ts b/packages/vike-node/src/runtime/adapters/createServerResponseUws.ts new file mode 100644 index 0000000..7d5c1fe --- /dev/null +++ b/packages/vike-node/src/runtime/adapters/createServerResponseUws.ts @@ -0,0 +1,76 @@ +export { createServerResponse } + +import type { OutgoingHttpHeader, OutgoingHttpHeaders } from 'node:http' +import { PassThrough, Readable } from 'node:stream' +import type { HttpResponse } from 'uWebSockets.js' + +type CreatedServerReponse = { + res: HttpResponse + onReadable: (cb: (result: { readable: Readable; headers: OutgoingHttpHeaders; statusCode: number }) => void) => void +} + +/** + * Creates a custom ServerResponse object that allows for intercepting and streaming the response. + * + * @param {HttpResponse} res - The incoming HTTP request message. + * @returns {CreatedServerReponse} + * An object containing: + * - res: The custom ServerResponse object. + * - onReadable: A function that takes a callback. The callback is invoked when the response is readable, + * providing an object with the readable stream, headers, and status code. + */ +function createServerResponse(res: HttpResponse): CreatedServerReponse { + const passThrough = new PassThrough() + let handled = false + + const onReadable = ( + cb: (result: { readable: Readable; headers: OutgoingHttpHeaders; statusCode: number }) => void + ) => { + const handleReadable = () => { + if (handled) return + handled = true + cb({ readable: Readable.from(passThrough), headers: res.getHeaders(), statusCode: res.statusCode }) + } + + passThrough.once('readable', handleReadable) + passThrough.once('end', handleReadable) + } + + passThrough.once('finish', () => { + res.emit('finish') + }) + passThrough.once('close', () => { + res.destroy() + res.emit('close') + }) + passThrough.on('drain', () => { + res.emit('drain') + }) + + res.write = passThrough.write.bind(passThrough) + res.end = (passThrough as any).end.bind(passThrough) + + res.writeHead = function writeHead( + statusCode: number, + statusMessage?: string | OutgoingHttpHeaders | OutgoingHttpHeader[], + headers?: OutgoingHttpHeaders | OutgoingHttpHeader[] + ): void { + res.writeStatus(statusCode + '') + if (typeof statusMessage === 'object') { + headers = statusMessage + statusMessage = undefined + } + if (headers) { + Object.entries(headers).forEach(([key, value]) => { + if (value !== undefined) { + res.writeHeader(key, value) + } + }) + } + } + + return { + res, + onReadable + } +} diff --git a/packages/vike-node/src/runtime/handler-node-only-uws.ts b/packages/vike-node/src/runtime/handler-node-only-uws.ts new file mode 100644 index 0000000..99553b8 --- /dev/null +++ b/packages/vike-node/src/runtime/handler-node-only-uws.ts @@ -0,0 +1,118 @@ +import type { IncomingMessage, ServerResponse } from 'node:http' +// import { dirname, isAbsolute, join } from 'node:path' +// import { fileURLToPath } from 'node:url' +import type { HttpResponse } from 'uWebSockets.js' + +import { assert } from '../utils/assert.js' +import { globalStore } from './globalStore.js' +import type { ConnectMiddleware, PlatformRequestUws, VikeOptions } from './types.js' +import { writeHttpResponseUws } from './utils/writeHttpResponse.js' +import { renderPage } from './vike-handler.js' +// import { isVercel } from '../utils/isVercel.js' + +export function createHandler(options: VikeOptions = {}) { + // TODO + // const staticConfig = resolveStaticConfig(options.static) + // const shouldCache = staticConfig && staticConfig.cache + // const compressionType = options.compress ?? !isVercel() + // let staticMiddleware: ConnectMiddleware | undefined + // let compressMiddleware: ConnectMiddleware | undefined + + return async function handler({ res, platformRequest }: { + res: HttpResponse + platformRequest: PlatformRequestUws + }): Promise { + if (globalStore.isPluginLoaded) { + const handled = await handleViteDevServer(res, platformRequest) + if (handled) { + res.end() + return + } + } else { + // TODO + // const isAsset = platformRequest.url?.startsWith('/assets/') + // const shouldCompressResponse = compressionType === true || (compressionType === 'static' && isAsset) + // if (shouldCompressResponse) { + // await applyCompression(req, res, shouldCache) + // } + + // if (staticConfig) { + // const handled = await serveStaticFiles(req, res, staticConfig) + // if (handled) return true + // } + } + + const httpResponse = await renderPage({ + url: platformRequest.url, + headers: platformRequest.headers, + platformRequest, + options + }) + if (!httpResponse) { + res.writeStatus('404').end() + return + } + await writeHttpResponseUws(httpResponse, res) + return + } + + // TODO + // async function applyCompression(req: IncomingMessage, res: ServerResponse, shouldCache: boolean) { + // if (!compressMiddleware) { + // const { default: shrinkRay } = await import('@nitedani/shrink-ray-current') + // compressMiddleware = shrinkRay({ cacheSize: shouldCache ? '128mB' : false }) as ConnectMiddleware + // } + // compressMiddleware(req, res, () => {}) + // } + + // TODO + // async function serveStaticFiles( + // req: IncomingMessage, + // res: ServerResponse, + // config: { root: string; cache: boolean } + // ): Promise { + // if (!staticMiddleware) { + // const { default: sirv } = await import('sirv') + // staticMiddleware = sirv(config.root, { etag: true }) + // } + + // return new Promise((resolve) => { + // res.once('close', () => resolve(true)) + // staticMiddleware!(req, res, () => resolve(false)) + // }) + // } +} + +function handleViteDevServer(res: HttpResponse, platformRequest: PlatformRequestUws): Promise { + return new Promise((resolve) => { + res.once('close', () => resolve(true)) + assert(globalStore.viteDevServer) + globalStore.viteDevServer.middlewares(platformRequest as unknown as IncomingMessage, res as unknown as ServerResponse, () => resolve(false)) + }) +} + +// TODO +// function resolveStaticConfig(static_: VikeOptions['static']): false | { root: string; cache: boolean } { +// // Disable static file serving for Vercel +// // Vercel will serve static files on its own +// // See vercel.json > outputDirectory +// if (isVercel()) return false +// if (static_ === false) return false + +// const argv1 = process.argv[1] +// const entrypointDirAbs = argv1 +// ? dirname(isAbsolute(argv1) ? argv1 : join(process.cwd(), argv1)) +// : dirname(fileURLToPath(import.meta.url)) +// const defaultStaticDir = join(entrypointDirAbs, '..', 'client') + +// if (static_ === true || static_ === undefined) { +// return { root: defaultStaticDir, cache: true } +// } +// if (typeof static_ === 'string') { +// return { root: static_, cache: true } +// } +// return { +// root: static_.root ?? defaultStaticDir, +// cache: static_.cache ?? true +// } +// } diff --git a/packages/vike-node/src/runtime/handler-web-and-node-uws.ts b/packages/vike-node/src/runtime/handler-web-and-node-uws.ts index 50ddd37..81acdd2 100644 --- a/packages/vike-node/src/runtime/handler-web-and-node-uws.ts +++ b/packages/vike-node/src/runtime/handler-web-and-node-uws.ts @@ -37,11 +37,11 @@ export function createHandler(options: VikeOptions = { if (await isNodeLike()) { const nodeOnlyHandler = createHandlerNode(options) - const nodeHandler: HandlerUws = ({ res: response, platformRequest }) => { - const connectedHandler = connectToWeb((req, res) => - nodeOnlyHandler({ req, res, platformRequest }) + const nodeHandler: HandlerUws = ({ platformRequest }) => { + const connectedHandler = connectToWeb((res, platformRequest) => + nodeOnlyHandler({ res, platformRequest }) ) - return connectedHandler(platformRequest) + return connectedHandler(response, platformRequest) } await nodeHandler({ res: response, platformRequest }) diff --git a/packages/vike-node/src/runtime/types.ts b/packages/vike-node/src/runtime/types.ts index 61a0c9c..2190e53 100644 --- a/packages/vike-node/src/runtime/types.ts +++ b/packages/vike-node/src/runtime/types.ts @@ -14,9 +14,9 @@ export type ConnectMiddleware< PlatformRequest extends IncomingMessage = IncomingMessage, PlatformResponse extends ServerResponse = ServerResponse > = (req: PlatformRequest, res: PlatformResponse, next: NextFunction) => void -export type ConnectMiddlewareUws = (req: HttpRequest, res: HttpResponse) => void +export type ConnectMiddlewareUws = (res: HttpResponse, platformRequest: PlatformRequestUws) => void export type WebHandler = (request: Request) => Response | undefined | Promise -export type WebHandlerUws = (request: HttpRequest) => Promise +export type WebHandlerUws = (response: HttpResponse, platformRequest: PlatformRequestUws) => Promise export type PlatformRequestUws = HttpRequest & { url: string headers: [string, string][] diff --git a/packages/vike-node/src/runtime/utils/writeHttpResponse.ts b/packages/vike-node/src/runtime/utils/writeHttpResponse.ts index c6df320..27aebed 100644 --- a/packages/vike-node/src/runtime/utils/writeHttpResponse.ts +++ b/packages/vike-node/src/runtime/utils/writeHttpResponse.ts @@ -22,8 +22,8 @@ async function writeHttpResponseUws(httpResponse: VikeHttpResponse, res: HttpRes assert(httpResponse) res.writeStatus(httpResponse.statusCode + '') - for (const header in httpResponse.headers) { - res.writeHeader(header[0]!, header[1]!) + for (const [key, value] of httpResponse.headers) { + res.writeHeader(key!, value!) } const readableWebStream = httpResponse.getReadableWebStream() From 1191237c673dbc15b44438df87d0a751e82b9c27 Mon Sep 17 00:00:00 2001 From: rtritto Date: Tue, 22 Oct 2024 17:55:57 +0200 Subject: [PATCH 06/13] feat: improve headers --- .../adapters/createServerResponseUws.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/vike-node/src/runtime/adapters/createServerResponseUws.ts b/packages/vike-node/src/runtime/adapters/createServerResponseUws.ts index 7d5c1fe..7851780 100644 --- a/packages/vike-node/src/runtime/adapters/createServerResponseUws.ts +++ b/packages/vike-node/src/runtime/adapters/createServerResponseUws.ts @@ -4,9 +4,15 @@ import type { OutgoingHttpHeader, OutgoingHttpHeaders } from 'node:http' import { PassThrough, Readable } from 'node:stream' import type { HttpResponse } from 'uWebSockets.js' +type OnReadable = (cb: (result: { + readable: Readable + headers: [string, string][] + statusCode: number +}) => void) => void + type CreatedServerReponse = { res: HttpResponse - onReadable: (cb: (result: { readable: Readable; headers: OutgoingHttpHeaders; statusCode: number }) => void) => void + onReadable: OnReadable } /** @@ -23,13 +29,15 @@ function createServerResponse(res: HttpResponse): CreatedServerReponse { const passThrough = new PassThrough() let handled = false - const onReadable = ( - cb: (result: { readable: Readable; headers: OutgoingHttpHeaders; statusCode: number }) => void - ) => { + const onReadable: OnReadable = (cb) => { const handleReadable = () => { if (handled) return handled = true - cb({ readable: Readable.from(passThrough), headers: res.getHeaders(), statusCode: res.statusCode }) + cb({ + readable: Readable.from(passThrough), + headers: res.headers as [string, string][], + statusCode: res.statusCode as number + }) } passThrough.once('readable', handleReadable) @@ -63,7 +71,7 @@ function createServerResponse(res: HttpResponse): CreatedServerReponse { if (headers) { Object.entries(headers).forEach(([key, value]) => { if (value !== undefined) { - res.writeHeader(key, value) + res.writeHeader(key, value.toString()) } }) } From 456ba8d4e83d3dc540fed57ca3e2da3fe51d780e Mon Sep 17 00:00:00 2001 From: rtritto Date: Tue, 22 Oct 2024 18:28:18 +0200 Subject: [PATCH 07/13] feat: improve ReadableStream conversion to Buffer --- .../src/runtime/adapters/connectToWebUws.ts | 13 +++++---- .../src/runtime/utils/writeHttpResponse.ts | 28 +++++++------------ 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/packages/vike-node/src/runtime/adapters/connectToWebUws.ts b/packages/vike-node/src/runtime/adapters/connectToWebUws.ts index 237e120..90f04a9 100644 --- a/packages/vike-node/src/runtime/adapters/connectToWebUws.ts +++ b/packages/vike-node/src/runtime/adapters/connectToWebUws.ts @@ -5,6 +5,7 @@ import type { HttpResponse } from 'uWebSockets.js' import type { ConnectMiddlewareUws, PlatformRequestUws, WebHandlerUws } from '../types.js' import { createServerResponse } from './createServerResponseUws.js' import { DUMMY_BASE_URL } from '../constants.js' +import { readableStreamToBuffer } from '../utils/writeHttpResponse.js' const statusCodesWithoutBody = new Set([ 100, // Continue @@ -27,16 +28,16 @@ function connectToWeb(handler: ConnectMiddlewareUws): WebHandlerUws { const { res, onReadable } = createServerResponse(enrichResponse(response, platformRequest)) return new Promise((resolve) => { - onReadable(({ readable, headers, statusCode }) => { - const responseBody = statusCodesWithoutBody.has(statusCode) - ? '' - : (Readable.toWeb(readable) as ReadableStream) - + onReadable(async ({ readable, headers, statusCode }) => { res.writeStatus(statusCode.toString()) for (const [key, value] of headers) { res.writeHeader(key, value) } - res.end(responseBody) + if(statusCodesWithoutBody.has(statusCode)) { + res.end() + } else { + res.end(await readableStreamToBuffer(Readable.toWeb(readable) as ReadableStream)) + } resolve() }) diff --git a/packages/vike-node/src/runtime/utils/writeHttpResponse.ts b/packages/vike-node/src/runtime/utils/writeHttpResponse.ts index 27aebed..74570f9 100644 --- a/packages/vike-node/src/runtime/utils/writeHttpResponse.ts +++ b/packages/vike-node/src/runtime/utils/writeHttpResponse.ts @@ -1,4 +1,4 @@ -export { writeHttpResponse, writeHttpResponseUws } +export { writeHttpResponse, writeHttpResponseUws, readableStreamToBuffer } import type { HttpResponse } from 'uWebSockets.js' import type { ServerResponse } from 'http' @@ -28,30 +28,22 @@ async function writeHttpResponseUws(httpResponse: VikeHttpResponse, res: HttpRes const readableWebStream = httpResponse.getReadableWebStream() - res.end(await readableStreamToArrayBuffer(readableWebStream)) + res.end(await readableStreamToBuffer(readableWebStream)) } /** - * Convert Readable Web Stream to ArrayBuffer + * Convert Readable Web Stream to Buffer */ -async function readableStreamToArrayBuffer(readableStream: ReadableStream): Promise { +async function readableStreamToBuffer(readableStream: ReadableStream): Promise { const reader = readableStream.getReader() const chunks: Uint8Array[] = [] - let done = false - while (!done) { - const { value, done: doneReading } = await reader.read() - if (value) { - chunks.push(value) - } - done = doneReading - } - const arrayBuffer = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0)) - let offset = 0 - for (const chunk of chunks) { - arrayBuffer.set(chunk, offset) - offset += chunk.length + let result = await reader.read() + while (!result.done) { + chunks.push(result.value) + result = await reader.read() } - return arrayBuffer + // Using Buffer.concat directly to handle chunk concatenation, improving performance. + return Buffer.concat(chunks.map(chunk => Buffer.from(chunk))) } From 9962d8f33c4e22dd57571ee6526ea5150d9c5723 Mon Sep 17 00:00:00 2001 From: rtritto Date: Tue, 22 Oct 2024 18:38:40 +0200 Subject: [PATCH 08/13] feat: add uws entrypoint --- packages/vike-node/src/uws.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/vike-node/src/uws.ts diff --git a/packages/vike-node/src/uws.ts b/packages/vike-node/src/uws.ts new file mode 100644 index 0000000..2bf15c0 --- /dev/null +++ b/packages/vike-node/src/uws.ts @@ -0,0 +1 @@ +export { vike, vike as default } from './runtime/frameworks/uws.js' From 6a0f80ae5b8bab5e8bf0aeec51b5a318298709b3 Mon Sep 17 00:00:00 2001 From: rtritto Date: Tue, 22 Oct 2024 18:40:02 +0200 Subject: [PATCH 09/13] Format --- .../src/runtime/adapters/connectToWebUws.ts | 2 +- .../src/runtime/adapters/createServerResponseUws.ts | 12 +++++++----- packages/vike-node/src/runtime/frameworks/uws.ts | 6 +++--- .../vike-node/src/runtime/handler-node-only-uws.ts | 12 +++++++++--- .../src/runtime/handler-web-and-node-uws.ts | 6 ++---- .../vike-node/src/runtime/utils/writeHttpResponse.ts | 2 +- packages/vike-node/src/runtime/vike-handler-uws.ts | 2 +- 7 files changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/vike-node/src/runtime/adapters/connectToWebUws.ts b/packages/vike-node/src/runtime/adapters/connectToWebUws.ts index 90f04a9..7e1ed5b 100644 --- a/packages/vike-node/src/runtime/adapters/connectToWebUws.ts +++ b/packages/vike-node/src/runtime/adapters/connectToWebUws.ts @@ -33,7 +33,7 @@ function connectToWeb(handler: ConnectMiddlewareUws): WebHandlerUws { for (const [key, value] of headers) { res.writeHeader(key, value) } - if(statusCodesWithoutBody.has(statusCode)) { + if (statusCodesWithoutBody.has(statusCode)) { res.end() } else { res.end(await readableStreamToBuffer(Readable.toWeb(readable) as ReadableStream)) diff --git a/packages/vike-node/src/runtime/adapters/createServerResponseUws.ts b/packages/vike-node/src/runtime/adapters/createServerResponseUws.ts index 7851780..f3d3f49 100644 --- a/packages/vike-node/src/runtime/adapters/createServerResponseUws.ts +++ b/packages/vike-node/src/runtime/adapters/createServerResponseUws.ts @@ -4,11 +4,13 @@ import type { OutgoingHttpHeader, OutgoingHttpHeaders } from 'node:http' import { PassThrough, Readable } from 'node:stream' import type { HttpResponse } from 'uWebSockets.js' -type OnReadable = (cb: (result: { - readable: Readable - headers: [string, string][] - statusCode: number -}) => void) => void +type OnReadable = ( + cb: (result: { + readable: Readable + headers: [string, string][] + statusCode: number + }) => void +) => void type CreatedServerReponse = { res: HttpResponse diff --git a/packages/vike-node/src/runtime/frameworks/uws.ts b/packages/vike-node/src/runtime/frameworks/uws.ts index 4b7c9ba..c9b1708 100644 --- a/packages/vike-node/src/runtime/frameworks/uws.ts +++ b/packages/vike-node/src/runtime/frameworks/uws.ts @@ -27,10 +27,10 @@ import type { PlatformRequestUws, VikeOptions } from '../types.js' */ function vike(app: TemplatedApp, options?: VikeOptions): TemplatedApp { const handler = createHandler(options) - return app.get('*', (response, request) => + return app.get('*', (response, request) => handler({ response, request, platformRequest: request as PlatformRequestUws }).catch((error: Error) => { - console.error(error) - response.writeStatus('500').end('Internal Server Error: ' + error.message) + console.error(error) + response.writeStatus('500').end('Internal Server Error: ' + error.message) }) ) } diff --git a/packages/vike-node/src/runtime/handler-node-only-uws.ts b/packages/vike-node/src/runtime/handler-node-only-uws.ts index 99553b8..828987f 100644 --- a/packages/vike-node/src/runtime/handler-node-only-uws.ts +++ b/packages/vike-node/src/runtime/handler-node-only-uws.ts @@ -18,7 +18,10 @@ export function createHandler(options: VikeOptions = {}) { // let staticMiddleware: ConnectMiddleware | undefined // let compressMiddleware: ConnectMiddleware | undefined - return async function handler({ res, platformRequest }: { + return async function handler({ + res, + platformRequest + }: { res: HttpResponse platformRequest: PlatformRequestUws }): Promise { @@ -35,7 +38,6 @@ export function createHandler(options: VikeOptions = {}) { // if (shouldCompressResponse) { // await applyCompression(req, res, shouldCache) // } - // if (staticConfig) { // const handled = await serveStaticFiles(req, res, staticConfig) // if (handled) return true @@ -87,7 +89,11 @@ function handleViteDevServer(res: HttpResponse, platformRequest: PlatformRequest return new Promise((resolve) => { res.once('close', () => resolve(true)) assert(globalStore.viteDevServer) - globalStore.viteDevServer.middlewares(platformRequest as unknown as IncomingMessage, res as unknown as ServerResponse, () => resolve(false)) + globalStore.viteDevServer.middlewares( + platformRequest as unknown as IncomingMessage, + res as unknown as ServerResponse, + () => resolve(false) + ) }) } diff --git a/packages/vike-node/src/runtime/handler-web-and-node-uws.ts b/packages/vike-node/src/runtime/handler-web-and-node-uws.ts index 81acdd2..cac614b 100644 --- a/packages/vike-node/src/runtime/handler-web-and-node-uws.ts +++ b/packages/vike-node/src/runtime/handler-web-and-node-uws.ts @@ -2,7 +2,7 @@ import type { HttpRequest, HttpResponse } from 'uWebSockets.js' import { isNodeLike } from '../utils/isNodeLike.js' import { connectToWeb } from './adapters/connectToWebUws.js' import { createHandler as createHandlerNode } from './handler-node-only-uws.js' -import { createHandler as createHandlerWeb } from'./handler-web-only-uws.js' +import { createHandler as createHandlerWeb } from './handler-web-only-uws.js' import type { HandlerUws, PlatformRequestUws, VikeOptions } from './types.js' const getHeaders = (req: HttpRequest): [string, string][] => { @@ -38,9 +38,7 @@ export function createHandler(options: VikeOptions = { if (await isNodeLike()) { const nodeOnlyHandler = createHandlerNode(options) const nodeHandler: HandlerUws = ({ platformRequest }) => { - const connectedHandler = connectToWeb((res, platformRequest) => - nodeOnlyHandler({ res, platformRequest }) - ) + const connectedHandler = connectToWeb((res, platformRequest) => nodeOnlyHandler({ res, platformRequest })) return connectedHandler(response, platformRequest) } diff --git a/packages/vike-node/src/runtime/utils/writeHttpResponse.ts b/packages/vike-node/src/runtime/utils/writeHttpResponse.ts index 74570f9..9f8c5d3 100644 --- a/packages/vike-node/src/runtime/utils/writeHttpResponse.ts +++ b/packages/vike-node/src/runtime/utils/writeHttpResponse.ts @@ -45,5 +45,5 @@ async function readableStreamToBuffer(readableStream: ReadableStream): Promise Buffer.from(chunk))) + return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))) } diff --git a/packages/vike-node/src/runtime/vike-handler-uws.ts b/packages/vike-node/src/runtime/vike-handler-uws.ts index 5085fca..2d75b0a 100644 --- a/packages/vike-node/src/runtime/vike-handler-uws.ts +++ b/packages/vike-node/src/runtime/vike-handler-uws.ts @@ -40,7 +40,7 @@ async function renderPageWeb({ platformRequest, options }: { - res: HttpResponse, + res: HttpResponse url: string headers: [string, string][] platformRequest: PlatformRequest From f2a44ad3b59a8ff0f3117712e5d026cc83d6bc0a Mon Sep 17 00:00:00 2001 From: rtritto Date: Tue, 22 Oct 2024 18:55:06 +0200 Subject: [PATCH 10/13] feat: add uws test --- test/vike-node/.dev-uws.test.ts | 4 ++++ test/vike-node/.prod-uws.test.ts | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 test/vike-node/.dev-uws.test.ts create mode 100644 test/vike-node/.prod-uws.test.ts diff --git a/test/vike-node/.dev-uws.test.ts b/test/vike-node/.dev-uws.test.ts new file mode 100644 index 0000000..10c4001 --- /dev/null +++ b/test/vike-node/.dev-uws.test.ts @@ -0,0 +1,4 @@ +process.env.VIKE_NODE_FRAMEWORK = 'uws' + +import { testRun } from './.testRun' +testRun('npm run dev') diff --git a/test/vike-node/.prod-uws.test.ts b/test/vike-node/.prod-uws.test.ts new file mode 100644 index 0000000..6455d1a --- /dev/null +++ b/test/vike-node/.prod-uws.test.ts @@ -0,0 +1,4 @@ +process.env.VIKE_NODE_FRAMEWORK = 'uws' + +import { testRun } from './.testRun' +testRun('npm run prod') From 760ca3e9c098733f494185899de6d58bbe1011a3 Mon Sep 17 00:00:00 2001 From: rtritto Date: Tue, 22 Oct 2024 18:57:39 +0200 Subject: [PATCH 11/13] feat: add uws exports and dev deps --- packages/vike-node/package.json | 2 ++ pnpm-lock.yaml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/packages/vike-node/package.json b/packages/vike-node/package.json index 252ed39..172dd8c 100644 --- a/packages/vike-node/package.json +++ b/packages/vike-node/package.json @@ -10,6 +10,7 @@ "./h3": "./dist/h3.js", "./hono": "./dist/hono.js", "./elysia": "./dist/elysia.js", + "./uws": "./dist/uws.js", "./plugin": "./dist/plugin/index.js", ".": "./dist/index.js", "./__handler": { @@ -50,6 +51,7 @@ "h3": "^1.12.0", "hono": "^4.5.5", "typescript": "^5.5.4", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.49.0", "vike": "^0.4.193", "vite": "^5.4.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40cd6a1..81b42e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -208,6 +208,9 @@ importers: typescript: specifier: ^5.5.4 version: 5.5.4 + uWebSockets.js: + specifier: github:uNetworking/uWebSockets.js#v20.49.0 + version: https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700 vike: specifier: ^0.4.193 version: 0.4.193(react-streaming@0.3.43(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@5.4.0(@types/node@20.14.15)) From 9a62cda3b660ac5a92c49451aa9b5937defa4d26 Mon Sep 17 00:00:00 2001 From: rtritto Date: Thu, 24 Oct 2024 21:55:48 +0200 Subject: [PATCH 12/13] fix: promisify app.listen in test --- test/vike-node/server/index-uws.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/vike-node/server/index-uws.ts b/test/vike-node/server/index-uws.ts index 0daef9c..673eb1b 100644 --- a/test/vike-node/server/index-uws.ts +++ b/test/vike-node/server/index-uws.ts @@ -42,5 +42,14 @@ async function startServer() { // res.writeHeader('x-test', 'test') const port = process.env.PORT || 3000 - app.listen(+port, () => console.log(`Server running at http://localhost:${port}`)) + await new Promise((resolve, reject) => { + app.listen(+port, (listenSocket) => { + if (listenSocket) { + console.log(`Server running at http://localhost:${port}`) + resolve() + } else { + reject(`Failed to listen to port ${port}`) + } + }) + }) } From 383640a45ee27fd49cd2e9a660b018c191010ecb Mon Sep 17 00:00:00 2001 From: rtritto Date: Thu, 24 Oct 2024 23:28:07 +0200 Subject: [PATCH 13/13] fix: add loader file to esbuild for .node files --- test/vike-node/vite.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/vike-node/vite.config.ts b/test/vike-node/vite.config.ts index cc30734..9ed270a 100644 --- a/test/vike-node/vite.config.ts +++ b/test/vike-node/vite.config.ts @@ -14,6 +14,7 @@ export default { ], // Make test more interesting: avoid vite-plugin-server-entry from [finding the server entry by searching for the dist/ directory](https://github.com/brillout/vite-plugin-server-entry/blob/240f59b4849a3fdfd84448117a3aaf4fbe95a8a0/src/runtime/crawlServerEntry.ts) build: { outDir: 'build' }, + esbuild: { loader: { '.node': 'file' } }, // Disable CORS for the test of uWebSockets.js where the cors middleware isn't compatible with uWebSockets.js server: { cors: false } }