Skip to content

Commit

Permalink
Fix issues surrounding file serving. Types.
Browse files Browse the repository at this point in the history
  • Loading branch information
GirlBossRush committed Oct 19, 2023
1 parent fb6dfc8 commit 3ee3989
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 54 deletions.
38 changes: 7 additions & 31 deletions cli/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@
* @see LICENSE.md in the project root for further licensing information.
*/

import * as http from 'node:http'
import { Socket } from 'node:net'
import * as path from 'node:path'
import type { Argv } from 'yargs'
import { KeyworkResourceError, Status } from '../errors/index.js'
import { KeyworkLogger } from '../logging/index.js'
import {
NodeStaticFileRouter,
applyNodeKeyworkPolyfills,
createNodeServerHandler,
createNodeKeyworkServer,
projectRootPathBuilder,
} from '../node/index.js'
import { FetcherModuleExports, RequestRouter } from '../router/index.js'
Expand Down Expand Up @@ -80,54 +78,32 @@ export async function serveBuilder({ port, host, ...serveArgs }: ServeArgs) {
)
}

const fetcher = scriptModule.default
const router = scriptModule.default

if (!fetcher || !RequestRouter.assertIsInstanceOf(fetcher)) {
if (!router || !RequestRouter.assertIsInstanceOf(router)) {
throw new KeyworkResourceError(
`Router script ${absoluteScriptPath} does not export a valid request router.`,
Status.InternalServerError
)
}

fetcher.get(
router.get(
'/dist/*',
new NodeStaticFileRouter({
filesDirectoryPath: projectRootPathBuilder('dist'),
mountPath: '/dist',
}).fetchStaticFile
)

fetcher.use(
router.use(
new NodeStaticFileRouter({
filesDirectoryPath: absolutePublicDirPath,
})
)

fetcher.$prettyPrintRoutes()
router.$prettyPrintRoutes()

// And then wrap the router with `createServerHandler`
const server = http.createServer(createNodeServerHandler(fetcher))

const connections = new Set<Socket>()
server.on('connection', (connection) => {
connections.add(connection)
connection.on('close', () => connections.delete(connection))
})

server.on('close', () => fetcher.dispose('Shutting down...'))

const onProcessEnd = () => {
for (const connection of connections) {
connection.destroy()
}

connections.clear()

server.close(() => process.exit(0))
}

process.on('SIGINT', onProcessEnd)
process.on('SIGTERM', onProcessEnd)
const server = createNodeKeyworkServer(router)

logger.info('Starting...')

Expand Down
6 changes: 4 additions & 2 deletions events/FetchEventProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* @see LICENSE.md in the project root for further licensing information.
*/

import { IsomorphicFetchEvent } from '../events/index.js'
import { IsomorphicFetchEvent, SSRDocumentContext } from '../events/index.js'
import { RequestContext } from '../http/index.js'
import { KeyworkLogger, KeyworkLoggerContext } from '../logging/index.js'
import { URLPatternResultContext } from '../uri/index.js'
Expand Down Expand Up @@ -50,7 +50,9 @@ export const FetchEventProvider: React.FC<FetchEventProviderProps> = ({ logger,
<KeyworkLoggerContext.Provider value={logger}>
<EnvironmentContext.Provider value={event.env}>
<RequestContext.Provider value={event.request}>
<URLPatternResultContext.Provider value={event.match}>{children}</URLPatternResultContext.Provider>
<URLPatternResultContext.Provider value={event.match}>
<SSRDocumentContext.Provider value={event.document}>{children}</SSRDocumentContext.Provider>
</URLPatternResultContext.Provider>
</RequestContext.Provider>
</EnvironmentContext.Provider>
</KeyworkLoggerContext.Provider>
Expand Down
15 changes: 13 additions & 2 deletions events/SSRDocument.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* @see LICENSE.md in the project root for further licensing information.
*/

import { HtmlHTMLAttributes } from 'react'
import { HtmlHTMLAttributes, createContext, useContext } from 'react'
import { ImportMap } from '../files/index.js'

/**
Expand Down Expand Up @@ -111,7 +111,8 @@ export interface SSRDocument {

/**
* Whether to omit the React hydration script from the document.
* This is useful when you want to render a static HTML page.
* This is useful when you want to render a static HTML page,
* or use your own hydration script.
*
* @default false
*/
Expand All @@ -123,3 +124,13 @@ export interface DocumentImage {
height?: number
url: string
}

/**
* @internal
*/
export const SSRDocumentContext = createContext<SSRDocument>(undefined as any)
SSRDocumentContext.displayName = 'SSRDocumentContext'

export function useSSRDocument() {
return useContext(SSRDocumentContext)
}
5 changes: 5 additions & 0 deletions files/extensions/Images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ export const PNG = {
extension: 'png',
mimeType: 'image/png',
} as const

export const WEBP = {
extension: 'webp',
mimeType: 'image/webp',
} as const
18 changes: 18 additions & 0 deletions files/extensions/Styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @file This file is part of the Keywork project.
* @copyright Nirrius, LLC. All rights reserved.
* @author Teffen Ellis, et al.
* @license AGPL-3.0
*
* @remarks Keywork is free software for non-commercial purposes.
* You can be released from the requirements of the license by purchasing a commercial license.
* Buying such a license is mandatory as soon as you develop commercial activities
* involving the Keywork software without disclosing the source code of your own applications.
*
* @see LICENSE.md in the project root for further licensing information.
*/

export const CSS = {
extension: 'css',
mimeType: 'text/css; charset=utf-8',
} as const
1 change: 1 addition & 0 deletions files/extensions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export * from './Forms.js'
export * from './Images.js'
export * from './JavaScript.js'
export * from './PlainText.js'
export * from './Styles.js'
export * from './XML.js'
41 changes: 38 additions & 3 deletions node/createServerHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
* @see LICENSE.md in the project root for further licensing information.
*/

import type { IncomingMessage, ServerResponse } from 'node:http'
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'
import type { Socket } from 'node:net'
import type { RequestRouter } from '../router/index.js'
import { respondWithRouter } from './respondWithRouter.js'

Expand All @@ -38,6 +39,40 @@ export type ServerHandler = (req: IncomingMessage, res: ServerResponse) => void
* @see {respondWithRouter}
* @beta Node support is currently experimental and may change in the near future.
*/
export function createNodeServerHandler<BoundAliases = {}>(router: RequestRouter<BoundAliases>): ServerHandler {
return (req, res) => respondWithRouter<BoundAliases>(router, req, res)
export function createNodeServerHandler<BoundAliases = {}>(
router: RequestRouter<BoundAliases>,
env?: BoundAliases
): ServerHandler {
return (req, res) => respondWithRouter<BoundAliases>(router, req, res, env)
}

/**
* Given a `RequestRouter`, creates a Node-compatible server.
*/
export function createNodeKeyworkServer<BoundAliases = {}>(
router: RequestRouter<BoundAliases>,
server = createServer(createNodeServerHandler(router))
) {
const connections = new Set<Socket>()
server.on('connection', (connection) => {
connections.add(connection)
connection.on('close', () => connections.delete(connection))
})

server.on('close', () => router.dispose('Shutting down...'))

const onProcessEnd = () => {
for (const connection of connections) {
connection.destroy()
}

connections.clear()

server.close(() => process.exit(0))
}

process.on('SIGINT', onProcessEnd)
process.on('SIGTERM', onProcessEnd)

return server
}
4 changes: 2 additions & 2 deletions node/respondWithRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ function readNodeEnv<BoundAliases = {}>(): BoundAliases {
export async function respondWithRouter<BoundAliases = {}>(
router: RequestRouter<BoundAliases>,
incomingMessage: IncomingMessage,
serverResponse: ServerResponse
serverResponse: ServerResponse,
env = readNodeEnv<BoundAliases>()
): Promise<void> {
const request = transformIncomingMessageToRequest(incomingMessage)
const env = readNodeEnv<BoundAliases>()
const event = new IsomorphicFetchEvent('fetch', { request, originalURL: request.url, env })

const fetchResponse = await router.fetch(request, env, event)
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "keywork",
"version": "8.1.14",
"version": "8.1.17",
"license": "AGPL-3.0",
"author": "Teffen Ellis <[email protected]>",
"description": "A batteries-included, magic-free, library for building web apps in V8 Isolates.",
Expand Down Expand Up @@ -89,7 +89,7 @@
"react": ">=18.2",
"react-dom": ">=18.2",
"undici": ">=5.25.2",
"urlpattern-polyfill": "^5.0.5"
"urlpattern-polyfill": "^9.0.0"
},
"peerDependenciesMeta": {
"react": {
Expand Down
3 changes: 2 additions & 1 deletion router/MiddlewareFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/
/* eslint-disable no-restricted-globals */

import type { IsomorphicFetchEvent } from '../events/IsomorphicFetchEvent.js'
import type { RouteMatch } from './RouteMatch.js'

/** @ignore */
Expand Down Expand Up @@ -51,7 +52,7 @@ export interface MiddlewareFetch<BoundAliases = {}, ExpectedReturn extends Middl
* @see {IsomorphicFetchEvent}
* @see {ExecutionContext}
*/
eventLike?: unknown,
eventLike?: IsomorphicFetchEvent<BoundAliases, any, any>,
/**
* When invoked, will execute a route handler defined after the current.
*
Expand Down
13 changes: 10 additions & 3 deletions router/RequestRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ import {
import { IDisposable } from '../lifecycle/index.js'
import { KeyworkLogger } from '../logging/index.js'
import { ReactRendererOptions, renderReactStream } from '../ssr/index.js'
import { PatternRouteComponentMap, URLPatternLike, isKeyworkRouteComponent, normalizeURLPattern } from '../uri/index.js'
import {
PatternRouteComponentMap,
URLPatternLike,
isKeyworkRouteComponent,
normalizeURLPattern,
normalizeURLPatternInput,
} from '../uri/index.js'
import { Fetcher } from './Fetcher.js'
import { FetcherLike } from './FetcherLike.js'
import { MiddlewareFetch } from './MiddlewareFetch.js'
Expand Down Expand Up @@ -489,7 +495,7 @@ export class RequestRouter<BoundAliases = {}> implements Fetcher<BoundAliases>,
parsedRoutes: ParsedRoute<BoundAliases>[],
matchingAgainst: URLPatternLike
): RouteMatch<BoundAliases>[] {
const matchInput = normalizeURLPattern(matchingAgainst)
const matchInput = normalizeURLPatternInput(matchingAgainst)
const matchedRoutes: RouteMatch<BoundAliases>[] = []

for (const parsedRoute of parsedRoutes) {
Expand Down Expand Up @@ -554,6 +560,7 @@ export class RequestRouter<BoundAliases = {}> implements Fetcher<BoundAliases>,
const pathnameGroups = match.pathname.groups['0']

if (pathnameGroups) {
console.log('>>>> Normalizing', normalizedURL.pathname, pathnameGroups)
normalizedURL.pathname = pathnameGroups
}
// Update the URL params...
Expand Down Expand Up @@ -590,7 +597,7 @@ export class RequestRouter<BoundAliases = {}> implements Fetcher<BoundAliases>,
)
try {
if (parsedRoute.kind === 'routeHandler') {
possibleResponse = await parsedRoute.fetch(event, next as any)
possibleResponse = await parsedRoute.fetch(event, next)
} else {
possibleResponse = await parsedRoute.fetcher.fetch(event.request, env, event, next)
}
Expand Down
22 changes: 14 additions & 8 deletions ssr/RequestDocument.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* @see LICENSE.md in the project root for further licensing information.
*/

import { SSRPropsByPath, SSRPropsProvider } from '../client/SSRPropsProvider.js'
import { FetchEventProvider, IsomorphicFetchEvent } from '../events/index.js'
import { ImportMap } from '../files/index.js'
import { PageElementComponent } from '../http/index.js'
Expand Down Expand Up @@ -54,15 +55,20 @@ export const RequestDocument: React.FC<RequestDocumentProps> = ({
}) => {
const staticProps = pageElement.props

const initialNavigatorURL = new URL(event.request.url)
const initialPropsByPath: SSRPropsByPath = new Map([[initialNavigatorURL.pathname, staticProps]])

const appDocument = (
<FetchEventProvider event={event} logger={logger}>
<Providers>
<DocumentComponent event={event} importMap={importMap}>
{pageElement}
<KeyworkSSREmbed eventInit={event.toJSON()} staticProps={staticProps} />
</DocumentComponent>
</Providers>
</FetchEventProvider>
<SSRPropsProvider initialLocation={initialNavigatorURL} initialPropsByPath={initialPropsByPath}>
<FetchEventProvider event={event} logger={logger}>
<Providers>
<DocumentComponent event={event} importMap={importMap}>
{pageElement}
<KeyworkSSREmbed eventInit={event.toJSON()} staticProps={staticProps} />
</DocumentComponent>
</Providers>
</FetchEventProvider>
</SSRPropsProvider>
)

return appDocument
Expand Down

0 comments on commit 3ee3989

Please sign in to comment.