Skip to content

Commit 339da2c

Browse files
author
Jason Gao
committed
unthunk hooks on every render
1 parent 702857d commit 339da2c

File tree

4 files changed

+84
-36
lines changed

4 files changed

+84
-36
lines changed

packages/fastify-renderer/src/node/Plugin.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { ReactRenderer, ReactRendererOptions } from './renderers/react/ReactRend
99
import { RenderableRoute, Renderer } from './renderers/Renderer'
1010
import './types' // necessary to make sure that the fastify types are augmented
1111
import { FastifyRendererHook, ServerEntrypointManifest, ViteClientManifest } from './types'
12-
import { unthunk } from './utils'
1312

1413
export interface FastifyRendererOptions {
1514
renderer?: ReactRendererOptions
@@ -31,7 +30,7 @@ export class FastifyRendererPlugin {
3130
clientOutDir: string
3231
serverOutDir: string
3332
assetsHost: string
34-
hooks: FastifyRendererHook[]
33+
hooks: (FastifyRendererHook | (() => FastifyRendererHook))[]
3534
clientManifest?: ViteClientManifest
3635
serverEntrypointManifest?: ServerEntrypointManifest
3736
routes: RenderableRoute[] = []
@@ -43,7 +42,7 @@ export class FastifyRendererPlugin {
4342
this.vite.base ??= '/.vite/'
4443
this.viteBase = this.vite.base
4544
this.assetsHost = incomingOptions.assetsHost || ''
46-
this.hooks = (incomingOptions.hooks || []).map(unthunk)
45+
this.hooks = incomingOptions.hooks || []
4746

4847
const outDir = incomingOptions.outDir || path.join(process.cwd(), 'dist')
4948
this.clientOutDir = path.join(outDir, 'client', this.viteBase)

packages/fastify-renderer/src/node/renderers/react/ReactRenderer.tsx

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { normalizePath } from 'vite/dist/node'
77
import { FastifyRendererPlugin } from '../../Plugin'
88
import { RenderBus } from '../../RenderBus'
99
import { wrap } from '../../tracing'
10-
import { mapFilepathToEntrypointName } from '../../utils'
10+
import { FastifyRendererHook } from '../../types'
11+
import { mapFilepathToEntrypointName, unthunk } from '../../utils'
1112
import { Render, RenderableRoute, Renderer } from '../Renderer'
1213

1314
const CLIENT_ENTRYPOINT_PREFIX = '/@fstr!entrypoint:'
@@ -79,6 +80,7 @@ export class ReactRenderer implements Renderer {
7980
/** Renders a given request and sends the resulting HTML document out with the `reply`. */
8081
private wrappedRender = wrap('fastify-renderer.render', async <Props,>(render: Render<Props>): Promise<void> => {
8182
const bus = this.startRenderBus(render)
83+
const hooks = this.plugin.hooks.map(unthunk)
8284

8385
try {
8486
const url = this.entrypointRequirePathForServer(render)
@@ -97,16 +99,16 @@ export class ReactRenderer implements Renderer {
9799
</RenderBusContext.Provider>
98100
)
99101

100-
for (const hook of this.plugin.hooks) {
102+
for (const hook of hooks) {
101103
if (hook.transform) {
102104
app = hook.transform(app)
103105
}
104106
}
105107

106108
if (this.options.mode == 'streaming') {
107-
await render.reply.send(this.renderStreamingTemplate(app, bus, ReactDOMServer, render))
109+
await render.reply.send(this.renderStreamingTemplate(app, bus, ReactDOMServer, render, hooks))
108110
} else {
109-
await render.reply.send(this.renderSynchronousTemplate(app, bus, ReactDOMServer, render))
111+
await render.reply.send(this.renderSynchronousTemplate(app, bus, ReactDOMServer, render, hooks))
110112
}
111113
} catch (error: unknown) {
112114
this.devServer?.ssrFixStacktrace(error as Error)
@@ -203,14 +205,20 @@ export class ReactRenderer implements Renderer {
203205
return bus
204206
}
205207

206-
private renderStreamingTemplate<Props>(app: JSX.Element, bus: RenderBus, ReactDOMServer: any, render: Render<Props>) {
207-
this.runHeadHooks(bus)
208+
private renderStreamingTemplate<Props>(
209+
app: JSX.Element,
210+
bus: RenderBus,
211+
ReactDOMServer: any,
212+
render: Render<Props>,
213+
hooks: FastifyRendererHook[]
214+
) {
215+
this.runHeadHooks(bus, hooks)
208216
// There are not postRenderHead hooks for streaming templates
209217
// so let's end the head stack
210218
bus.push('head', null)
211219
const contentStream = ReactDOMServer.renderToNodeStream(app)
212220
contentStream.on('end', () => {
213-
this.runTailHooks(bus)
221+
this.runTailHooks(bus, hooks)
214222
})
215223

216224
return render.document({
@@ -225,12 +233,13 @@ export class ReactRenderer implements Renderer {
225233
app: JSX.Element,
226234
bus: RenderBus,
227235
ReactDOMServer: any,
228-
render: Render<Props>
236+
render: Render<Props>,
237+
hooks: FastifyRendererHook[]
229238
) {
230-
this.runHeadHooks(bus)
239+
this.runHeadHooks(bus, hooks)
231240
const content = ReactDOMServer.renderToString(app)
232-
this.runPostRenderHeadHooks(bus)
233-
this.runTailHooks(bus)
241+
this.runPostRenderHeadHooks(bus, hooks)
242+
this.runTailHooks(bus, hooks)
234243

235244
return render.document({
236245
content,
@@ -240,28 +249,28 @@ export class ReactRenderer implements Renderer {
240249
})
241250
}
242251

243-
private runPostRenderHeadHooks(bus: RenderBus) {
252+
private runPostRenderHeadHooks(bus: RenderBus, hooks: FastifyRendererHook[]) {
244253
// Run any heads hooks that might want to push something after the content
245-
for (const hook of this.plugin.hooks) {
254+
for (const hook of hooks) {
246255
if (hook.postRenderHeads) {
247256
bus.push('head', hook.postRenderHeads())
248257
}
249258
}
250259
bus.push('head', null)
251260
}
252261

253-
private runHeadHooks(bus: RenderBus) {
262+
private runHeadHooks(bus: RenderBus, hooks: FastifyRendererHook[]) {
254263
// Run any heads hooks that might want to push something before the content
255-
for (const hook of this.plugin.hooks) {
264+
for (const hook of hooks) {
256265
if (hook.heads) {
257266
bus.push('head', hook.heads())
258267
}
259268
}
260269
}
261270

262-
private runTailHooks(bus: RenderBus) {
271+
private runTailHooks(bus: RenderBus, hooks: FastifyRendererHook[]) {
263272
// when we're done rendering the content, run any hooks that might want to push more stuff after the content
264-
for (const hook of this.plugin.hooks) {
273+
for (const hook of hooks) {
265274
if (hook.tails) {
266275
bus.push('tail', hook.tails())
267276
}

packages/fastify-renderer/test/renderers/ReactRenderer.spec.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,6 @@ describe('ReactRenderer', () => {
5252
test('should call postRenderHooks after dom render', async () => {
5353
const renderer = newReactRenderer()
5454
const callOrder: string[] = []
55-
renderer.plugin.hooks = [
56-
{
57-
heads: () => {
58-
callOrder.push('heads')
59-
return 'heads'
60-
},
61-
postRenderHeads: () => {
62-
callOrder.push('postRenderHeads')
63-
return 'postRenderHeads'
64-
},
65-
},
66-
]
6755

6856
renderer['renderSynchronousTemplate'](
6957
React.createElement(testLayoutComponent, {}),
@@ -74,7 +62,19 @@ describe('ReactRenderer', () => {
7462
return 'test'
7563
},
7664
},
77-
getMockRender({})
65+
getMockRender({}),
66+
[
67+
{
68+
heads: () => {
69+
callOrder.push('heads')
70+
return 'heads'
71+
},
72+
postRenderHeads: () => {
73+
callOrder.push('postRenderHeads')
74+
return 'postRenderHeads'
75+
},
76+
},
77+
]
7878
)
7979

8080
expect(callOrder).toEqual(['heads', 'render', 'postRenderHeads'])

packages/fastify-renderer/test/routes.spec.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import path from 'path'
22
import FastifyRenderer from '../src/node'
3+
import { unthunk } from '../src/node/utils'
34
import { newFastify } from './helpers'
45

56
const testComponent = require.resolve(path.join(__dirname, 'fixtures', 'test-module.tsx'))
67
const testLayoutComponent = require.resolve(path.join(__dirname, 'fixtures', 'test-layout.tsx'))
8+
let thunkId = 0
9+
710
const options = {
811
vite: { root: __dirname, logLevel: (process.env.LOG_LEVEL ?? 'info') as any },
912
devMode: true,
@@ -23,6 +26,21 @@ const options = {
2326
return 'postRenderHead'
2427
},
2528
},
29+
() => {
30+
const id = thunkId++
31+
32+
return {
33+
heads: () => {
34+
return `<style>#${id} {}</style>`
35+
},
36+
transform: (app) => {
37+
return app
38+
},
39+
postRenderHeads: () => {
40+
return ''
41+
},
42+
}
43+
},
2644
],
2745
}
2846

@@ -55,6 +73,10 @@ describe('FastifyRenderer', () => {
5573
await server.ready()
5674
})
5775

76+
beforeEach(() => {
77+
thunkId = 0
78+
})
79+
5880
test('should return the route props if content-type is application/json', async () => {
5981
const response = await server.inject({
6082
method: 'GET',
@@ -96,15 +118,16 @@ describe('FastifyRenderer', () => {
96118

97119
test('should call hooks in correct order', async () => {
98120
const callOrder: string[] = []
99-
jest.spyOn(options.hooks[0], 'heads').mockImplementation(() => {
121+
const hook = unthunk(options.hooks[0])
122+
jest.spyOn(hook, 'heads').mockImplementation(() => {
100123
callOrder.push('heads')
101124
return 'head'
102125
})
103-
jest.spyOn(options.hooks[0], 'transform').mockImplementation((app) => {
126+
jest.spyOn(hook, 'transform').mockImplementation((app) => {
104127
callOrder.push('transforms')
105128
return app
106129
})
107-
jest.spyOn(options.hooks[0], 'postRenderHeads').mockImplementation(() => {
130+
jest.spyOn(hook, 'postRenderHeads').mockImplementation(() => {
108131
callOrder.push('postRenders')
109132
return 'postRender'
110133
})
@@ -117,4 +140,21 @@ describe('FastifyRenderer', () => {
117140

118141
expect(callOrder).toEqual(['transforms', 'heads', 'postRenders'])
119142
})
143+
144+
test('should unthunk hooks on every render', async () => {
145+
const firstResponse = await server.inject({
146+
method: 'GET',
147+
url: '/render-test',
148+
headers: { Accept: 'text/html' },
149+
})
150+
151+
const secondResponse = await server.inject({
152+
method: 'GET',
153+
url: '/render-test',
154+
headers: { Accept: 'text/html' },
155+
})
156+
157+
expect(firstResponse.body).toMatch('<style>#0 {}</style>')
158+
expect(secondResponse.body).toMatch('<style>#1 {}</style>')
159+
})
120160
})

0 commit comments

Comments
 (0)