Skip to content

Commit 38c1f73

Browse files
authored
Allow useDeno and ssr.props access Request (#404)
* Update types * Allow `useDeno` and `ssr.props` access `Request` (close #22, #364, #401) * Rename
1 parent 1de0a35 commit 38c1f73

File tree

11 files changed

+149
-76
lines changed

11 files changed

+149
-76
lines changed

examples/hello-world-ssr/app.tsx examples/hello-world-isr/app.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import React, { ComponentType } from 'react'
1+
import React, { FC } from 'react'
22

3-
export default function App({ Page, pageProps }: { Page: ComponentType<any>, pageProps: any }) {
3+
export default function App({ Page, pageProps }: { Page: FC, pageProps: any }) {
44
return (
55
<main>
66
<head>
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { SSROptions } from 'aleph/types.d.ts'
2+
import React, { FC } from 'react'
3+
4+
type Props = {
5+
serverTime: number
6+
ua: string | null
7+
}
8+
9+
export const ssr: SSROptions<Props> = {
10+
props: req => ({
11+
$revalidate: 1, // revalidate props after 1 second
12+
serverTime: Date.now(),
13+
ua: req.headers.get('User-Agent'),
14+
})
15+
}
16+
17+
const Page: FC<Props> = (props) => {
18+
return (
19+
<>
20+
<p>Now: {props.serverTime}</p>
21+
<p>UA: {props.ua}</p>
22+
</>
23+
)
24+
}
25+
26+
export default Page

examples/hello-world-ssr/pages/index.tsx

-26
This file was deleted.

framework/react/context.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { createContext, ReactNode } from 'https://esm.sh/[email protected]'
21
import type { RouterURL } from '../../types.d.ts'
2+
import type { RendererStore } from './renderer.ts'
3+
import { createContext, ReactNode } from 'https://esm.sh/[email protected]'
34
import { createBlankRouterURL } from '../core/routing.ts'
45
import { createNamedContext } from './helper.ts'
5-
import type { RendererStore } from './renderer.ts'
66

77
export const RouterContext = createNamedContext<RouterURL>(createBlankRouterURL(), 'RouterContext')
88
export const FallbackContext = createNamedContext<{ to: ReactNode }>({ to: null }, 'FallbackContext')
99
export const SSRContext = createContext<RendererStore>({
10+
request: new Request('http://localhost/'),
11+
dataCache: {},
1012
headElements: new Map(),
1113
inlineStyles: new Map(),
1214
scripts: new Map(),

framework/react/hooks.ts

+20-16
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { useContext, useMemo } from 'https://esm.sh/[email protected]'
21
import type { RouterURL } from '../../types.d.ts'
2+
import { useContext, useMemo } from 'https://esm.sh/[email protected]'
3+
import util from '../../shared/util.ts'
34
import events from '../core/events.ts'
4-
import { RouterContext } from './context.ts'
5+
import { RouterContext, SSRContext } from './context.ts'
56
import { inDeno } from './helper.ts'
67

78
export class AsyncUseDenoError extends Error { }
@@ -30,40 +31,43 @@ export function useRouter(): RouterURL {
3031
* }
3132
* ```
3233
*/
33-
export function useDeno<T = any>(callback: () => (T | Promise<T>), options?: { key?: string | number, revalidate?: number }): T {
34+
export function useDeno<T = any>(callback: (request: Request) => (T | Promise<T>), options?: { key?: string | number, revalidate?: number | boolean | { date: number } }): T {
3435
const { key, revalidate } = options || {}
3536
const uuid = arguments[2] // generated by compiler
36-
const router = useRouter()
3737
const id = useMemo(() => [uuid, key].filter(Boolean).join('-'), [key])
38+
const { request, dataCache } = useContext(SSRContext)
39+
const router = useRouter()
3840

3941
return useMemo(() => {
40-
const global = window as any
4142
const href = router.toString()
4243
const dataUrl = `pagedata://${href}`
4344

4445
if (inDeno) {
45-
const renderingData = global[`rendering-${dataUrl}`]
46-
47-
if (renderingData && id in renderingData) {
48-
return renderingData[id] // 2+ pass
46+
if (id in dataCache) {
47+
return dataCache[id] // 2+ pass
4948
}
5049

51-
const value = callback()
52-
const expires = typeof revalidate === 'number' && !isNaN(revalidate) ? Date.now() + revalidate * 1000 : 0
50+
let expires = 0
51+
if (util.isNumber(revalidate)) {
52+
expires = Date.now() + Math.round(revalidate * 1000)
53+
} else if (revalidate === true) {
54+
expires = Date.now() - 1000
55+
} else if (util.isPlainObject(revalidate) && util.isNumber(revalidate.date)) {
56+
expires = revalidate.date
57+
}
58+
const value = callback(request)
5359
events.emit(`useDeno-${dataUrl}`, { id, value, expires })
5460

5561
// thow an `AsyncUseDenoError` to break current rendering
5662
if (value instanceof Promise) {
5763
throw new AsyncUseDenoError()
5864
}
5965

60-
if (renderingData) {
61-
renderingData[id] = value
62-
}
66+
dataCache[id] = value
6367
return value
6468
}
6569

66-
const data = global[`${dataUrl}#${id}`]
70+
const data = (window as any)[`${dataUrl}#${id}`]
6771
return data?.value
68-
}, [id, router])
72+
}, [id, router, dataCache, request])
6973
}

framework/react/renderer.ts

+16-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import type { FrameworkRenderResult } from '../../server/renderer.ts'
2+
import type { RouterURL } from '../../types.d.ts'
13
import { createElement, ComponentType, ReactElement } from 'https://esm.sh/[email protected]'
24
import { renderToString } from 'https://esm.sh/[email protected]/server'
35
import util from '../../shared/util.ts'
4-
import type { FrameworkRenderResult } from '../../server/renderer.ts'
5-
import type { RouterURL } from '../../types.d.ts'
66
import events from '../core/events.ts'
77
import { RouterContext, SSRContext } from './context.ts'
88
import { E400MissingComponent, E404Page } from './components/ErrorBoundary.ts'
@@ -11,12 +11,15 @@ import { isLikelyReactComponent } from './helper.ts'
1111
import { createPageProps } from './pageprops.ts'
1212

1313
export type RendererStore = {
14+
request: Request
15+
dataCache: Record<string, any>
1416
headElements: Map<string, { type: string, props: Record<string, any> }>
1517
inlineStyles: Map<string, string>
1618
scripts: Map<string, { props: Record<string, any> }>
1719
}
1820

1921
export async function render(
22+
request: Request,
2023
url: RouterURL,
2124
App: ComponentType<any> | undefined,
2225
nestedPageComponents: { specifier: string, Component?: any, props?: Record<string, any> }[],
@@ -30,6 +33,8 @@ export async function render(
3033
data: null,
3134
}
3235
const rendererStore: RendererStore = {
36+
request: request,
37+
dataCache: {},
3338
headElements: new Map(),
3439
inlineStyles: new Map(),
3540
scripts: new Map(),
@@ -38,7 +43,6 @@ export async function render(
3843
const dataKey = 'rendering-' + dataUrl
3944
const asyncCalls: Array<[string, number, Promise<any>]> = []
4045
const data: Record<string, any> = {}
41-
const renderingData: Record<string, any> = {}
4246
const pageProps = createPageProps(nestedPageComponents)
4347
const defer = async () => {
4448
Reflect.deleteProperty(global, dataKey)
@@ -47,9 +51,14 @@ export async function render(
4751

4852
nestedPageComponents.forEach(({ specifier, props }) => {
4953
if (util.isPlainObject(props)) {
54+
const { $revalidate } = props
5055
let expires = 0
51-
if (typeof props.$revalidate === 'number' && props.$revalidate > 0) {
52-
expires = Date.now() + Math.round(props.$revalidate * 1000)
56+
if (util.isNumber($revalidate)) {
57+
expires = Date.now() + Math.round($revalidate * 1000)
58+
} else if ($revalidate === true) {
59+
expires = Date.now() - 1000
60+
} else if (util.isPlainObject($revalidate) && util.isNumber($revalidate.date)) {
61+
expires = $revalidate.date
5362
}
5463
data[`props-${btoa(specifier)}`] = {
5564
value: props,
@@ -58,9 +67,6 @@ export async function render(
5867
}
5968
})
6069

61-
// share rendering data
62-
global[dataKey] = renderingData
63-
6470
// listen `useDeno-*` events to get hooks callback result.
6571
events.on('useDeno-' + dataUrl, ({ id, value, expires }: { id: string, value: any, expires: number }) => {
6672
if (value instanceof Promise) {
@@ -96,12 +102,12 @@ export async function render(
96102
const datas = await Promise.all(calls.map(a => a[2]))
97103
calls.forEach(([id, expires], i) => {
98104
const value = datas[i]
99-
renderingData[id] = value
105+
rendererStore.dataCache[id] = value
100106
data[id] = { value, expires }
101107
})
102108
}
109+
Object.values(rendererStore).forEach(v => v instanceof Map && v.clear())
103110
try {
104-
Object.values(rendererStore).forEach(map => map.clear())
105111
ret.body = renderToString(createElement(
106112
SSRContext.Provider,
107113
{ value: rendererStore },

server/aleph.ts

+10-9
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ export class Aleph implements IAleph {
546546
}
547547

548548
/** get ssr data by the given location(page), return `null` if no data defined */
549-
async getSSRData(loc: { pathname: string, search?: string }): Promise<Record<string, SSRData> | null> {
549+
async getSSRData(request: Request, loc: { pathname: string, search?: string }): Promise<Record<string, SSRData> | null> {
550550
const [router, nestedModules] = this.#pageRouting.createRouter(loc)
551551
const { routePath } = router
552552
if (routePath === '' || !this.#isSSRable(router.pathname)) {
@@ -566,7 +566,7 @@ export class Aleph implements IAleph {
566566

567567
const path = loc.pathname + (loc.search || '')
568568
const [_, data] = await this.#renderer.cache(routePath, path, async () => {
569-
return await this.#renderPage(router, nestedModules)
569+
return await this.#renderPage(request, router, nestedModules)
570570
})
571571
return data
572572
}
@@ -600,7 +600,7 @@ export class Aleph implements IAleph {
600600
}
601601

602602
/** render page to HTML by the given location */
603-
async renderPage(loc: { pathname: string, search?: string }): Promise<[number, string]> {
603+
async renderPage(request: Request, loc: { pathname: string, search?: string }): Promise<[number, string]> {
604604
const [router, nestedModules] = this.#pageRouting.createRouter(loc)
605605
const { routePath } = router
606606
const path = loc.pathname + (loc.search || '')
@@ -615,19 +615,19 @@ export class Aleph implements IAleph {
615615
if (routePath === '') {
616616
const [html] = await this.#renderer.cache('404', path, async () => {
617617
const [_, nestedModules] = this.#pageRouting.createRouter({ pathname: '/404' })
618-
return await this.#renderPage(router, nestedModules.slice(0, 1))
618+
return await this.#renderPage(request, router, nestedModules.slice(0, 1))
619619
})
620620
return [404, html]
621621
}
622622

623623
const [html] = await this.#renderer.cache(routePath, path, async () => {
624-
return await this.#renderPage(router, nestedModules)
624+
return await this.#renderPage(request, router, nestedModules)
625625
})
626626
return [200, html]
627627
}
628628

629-
async #renderPage(url: RouterURL, nestedModules: string[]): Promise<[string, Record<string, SSRData> | null]> {
630-
let [html, data] = await this.#renderer.renderPage(url, nestedModules)
629+
async #renderPage(request: Request, url: RouterURL, nestedModules: string[]): Promise<[string, Record<string, SSRData> | null]> {
630+
let [html, data] = await this.#renderer.renderPage(request, url, nestedModules)
631631
for (const callback of this.#renderListeners) {
632632
await callback({ path: url.toString(), html, data })
633633
}
@@ -1534,12 +1534,13 @@ export class Aleph implements IAleph {
15341534
}
15351535

15361536
// render route pages
1537+
const req = new Request('http://localhost/')
15371538
await Promise.all(Array.from(paths).map(loc => ([loc, ...locales.map(locale => ({ ...loc, pathname: '/' + locale + loc.pathname }))])).flat().map(async ({ pathname, search }) => {
15381539
if (this.#isSSRable(pathname)) {
15391540
const [router, nestedModules] = this.#pageRouting.createRouter({ pathname, search })
15401541
if (router.routePath !== '') {
15411542
const href = router.toString()
1542-
const [html, data] = await this.#renderPage(router, nestedModules)
1543+
const [html, data] = await this.#renderPage(req, router, nestedModules)
15431544
await ensureTextFile(join(outputDir, pathname, 'index.html' + (search || '')), html)
15441545
if (data) {
15451546
const dataFile = join(
@@ -1559,7 +1560,7 @@ export class Aleph implements IAleph {
15591560
if (nestedModules.length > 0) {
15601561
await this.compile(nestedModules[0])
15611562
}
1562-
const [html] = await this.#renderPage(router, nestedModules.slice(0, 1))
1563+
const [html] = await this.#renderPage(req, router, nestedModules.slice(0, 1))
15631564
await ensureTextFile(join(outputDir, '404.html'), html)
15641565
}
15651566
}

0 commit comments

Comments
 (0)