Skip to content

Commit 02bea8b

Browse files
authored
release: @fastify/[email protected] (#254)
1 parent c0f8631 commit 02bea8b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1323
-84
lines changed

packages/fastify-react/context.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { createHead } from '@unhead/react/server'
2+
13
const routeContextInspect = Symbol.for('nodejs.util.inspect.custom')
24

35
export default class RouteContext {
@@ -15,10 +17,12 @@ export default class RouteContext {
1517
}
1618

1719
constructor(server, req, reply, route) {
20+
this.app = null
1821
this.server = server
1922
this.req = req
2023
this.reply = reply
2124
this.head = {}
25+
this.useHead = createHead()
2226
this.actionData = {}
2327
this.state = null
2428
this.data = route.data
@@ -46,6 +50,7 @@ export default class RouteContext {
4650
actionData: this.actionData,
4751
state: this.state,
4852
data: this.data,
53+
head: this.head,
4954
layout: this.layout,
5055
getMeta: this.getMeta,
5156
getData: this.getData,

packages/fastify-react/package.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"main": "index.js",
77
"name": "@fastify/react",
88
"description": "The official @fastify/vite renderer for React",
9-
"version": "1.0.2",
9+
"version": "1.1.0",
1010
"files": [
1111
"plugin/index.js",
1212
"plugin/parsers.js",
@@ -24,6 +24,16 @@
2424
"virtual/resource.js",
2525
"virtual/root.jsx",
2626
"virtual/routes.js",
27+
"virtual-ts/layouts/default.tsx",
28+
"virtual-ts/context.ts",
29+
"virtual-ts/core.tsx",
30+
"virtual-ts/create.tsx",
31+
"virtual-ts/index.ts",
32+
"virtual-ts/layouts.ts",
33+
"virtual-ts/mount.ts",
34+
"virtual-ts/resource.ts",
35+
"virtual-ts/root.tsx",
36+
"virtual-ts/routes.ts",
2737
"client.js",
2838
"context.js",
2939
"index.js",
@@ -41,6 +51,7 @@
4151
},
4252
"dependencies": {
4353
"@fastify/vite": "workspace:^",
54+
"@unhead/react": "^2.0.8",
4455
"acorn": "^8.14.1",
4556
"acorn-strip-function": "^1.2.0",
4657
"acorn-walk": "^8.3.4",
@@ -52,7 +63,6 @@
5263
"react": "^19.1.0",
5364
"react-dom": "^19.1.0",
5465
"react-router": "^7.5.0",
55-
"unihead": "latest",
5666
"valtio": "latest",
5767
"youch": "^3.3.4"
5868
},

packages/fastify-react/plugin/index.js

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,18 @@ import { closeBundle } from './preload.js'
1212
import { parseStateKeys } from './parsers.js'
1313
import { generateStores } from './stores.js'
1414

15-
export default function viteFastifyReactPlugin () {
15+
export default function viteFastifyReactPlugin ({ ts } = {}) {
1616
const context = {
1717
root: null,
1818
}
1919
return [viteFastify({
20-
clientModule: '$app/index.js'
20+
clientModule: ts ? '$app/index.ts' : '$app/index.js'
2121
}), {
22-
name: 'vite-plugin-fastify-react',
22+
// https://vite.dev/guide/api-plugin#conventions
23+
name: 'vite-plugin-react-fastify',
2324
config,
2425
configResolved: configResolved.bind(context),
2526
resolveId: resolveId.bind(context),
26-
configEnvironment (name, config, { mode }) {
27-
if (mode === 'production') {
28-
config.build.minify = true
29-
config.build.sourcemap = false
30-
}
31-
if (name === 'ssr') {
32-
config.build.manifest = false
33-
}
34-
},
3527
async load (id) {
3628
if (id.includes('?server') && !this.environment.config.build?.ssr) {
3729
const source = loadSource(id)
@@ -44,14 +36,6 @@ export default function viteFastifyReactPlugin () {
4436
if (prefix.test(id)) {
4537
const [, virtual] = id.split(prefix)
4638
if (virtual) {
47-
if (virtual === 'stores') {
48-
const contextPath = join(context.root, 'context.js')
49-
if (existsSync(contextPath)) {
50-
const keys = parseStateKeys(readFileSync(contextPath, 'utf8'))
51-
return generateStores(keys)
52-
}
53-
return
54-
}
5539
return loadVirtualModule(virtual)
5640
}
5741
}

packages/fastify-react/plugin/preload.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'
2-
import { join, parse as parsePath } from 'node:path'
2+
import { join, isAbsolute, parse as parsePath } from 'node:path'
33
import { HTMLRewriter } from 'html-rewriter-wasm'
44

55
const imageFileRE = /\.((png)|(jpg)|(svg)|(webp)|(gif))$/
@@ -10,13 +10,18 @@ export async function closeBundle(resolvedBundle) {
1010
}
1111
const { assetsInlineLimit } = this.environment.config.build
1212
const { root, base } = this.environment.config
13-
const distDir = join(root, this.environment.config.build.outDir)
13+
let distDir
14+
if (isAbsolute(this.environment.config.build.outDir)) {
15+
distDir = this.environment.config.build.outDir
16+
} else {
17+
distDir = join(root, this.environment.config.build.outDir)
18+
}
1419
const indexHtml = readFileSync(join(distDir, 'index.html'), 'utf8')
1520
const pages = Object.fromEntries(
1621
Object.entries(resolvedBundle ?? {})
1722
.filter(([id, meta]) => {
1823
if (meta.facadeModuleId?.includes('/pages/')) {
19-
meta.htmlPath = meta.facadeModuleId.replace(/.*pages\/(.*)\.jsx$/, 'html/$1.html')
24+
meta.htmlPath = meta.facadeModuleId.replace(/.*pages\/(.*)\.(j|t)sx$/, 'html/$1.html')
2025
return true
2126
}
2227
})

packages/fastify-react/plugin/virtual.js

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { findExports } from 'mlly'
66
const __dirname = dirname(fileURLToPath(import.meta.url))
77

88
const virtualRoot = resolve(__dirname, '..', 'virtual')
9+
const virtualRootTS = resolve(__dirname, '..', 'virtual-ts')
910

1011
const virtualModules = [
1112
'mount.js',
@@ -20,6 +21,19 @@ const virtualModules = [
2021
'index.js',
2122
]
2223

24+
const virtualModulesTS = [
25+
'mount.ts',
26+
'resource.ts',
27+
'routes.ts',
28+
'layouts.ts',
29+
'create.tsx',
30+
'root.tsx',
31+
'layouts/',
32+
'context.ts',
33+
'core.tsx',
34+
'index.ts',
35+
]
36+
2337
export const prefix = /^\/?\$app\//
2438

2539
export async function resolveId (id) {
@@ -42,19 +56,34 @@ export async function resolveId (id) {
4256

4357
export function loadVirtualModule (virtualInput) {
4458
let virtual = virtualInput
45-
if (!/\.((mc)?ts)|((mc)?js)|(jsx)$/.test(virtual)) {
46-
virtual += '.js'
47-
}
48-
if (!virtualModules.includes(virtual)) {
59+
if (!virtualModules.includes(virtual) && !virtualModulesTS.includes(virtual)) {
4960
return
5061
}
51-
const code = readFileSync(resolve(virtualRoot, virtual), 'utf8')
62+
let virtualRootDir = virtualRoot
63+
if (virtualInput.match(/\.tsx?$/)) {
64+
virtualRootDir = virtualRootTS
65+
}
66+
const codePath = resolve(virtualRootDir, virtual)
5267
return {
53-
code,
68+
code: readFileSync(codePath, 'utf8'),
5469
map: null,
5570
}
5671
}
5772

73+
74+
virtualModulesTS.includes = function (virtual) {
75+
if (!virtual) {
76+
return false
77+
}
78+
for (const entry of this) {
79+
if (virtual.startsWith(entry)) {
80+
return true
81+
}
82+
}
83+
return false
84+
}
85+
86+
5887
virtualModules.includes = function (virtual) {
5988
if (!virtual) {
6089
return false
@@ -67,11 +96,16 @@ virtualModules.includes = function (virtual) {
6796
return false
6897
}
6998

70-
function loadVirtualModuleOverride (viteProjectRoot, virtual) {
71-
if (!virtualModules.includes(virtual)) {
99+
function loadVirtualModuleOverride (viteProjectRoot, virtualInput) {
100+
let virtual = virtualInput
101+
if (!virtualModules.includes(virtual) && !virtualModulesTS.includes(virtual)) {
72102
return
73103
}
74-
const overridePath = resolve(viteProjectRoot, virtual)
104+
let overridePath = resolve(viteProjectRoot, virtual)
105+
if (existsSync(overridePath)) {
106+
return overridePath
107+
}
108+
overridePath = overridePath.replace('.js', '.ts')
75109
if (existsSync(overridePath)) {
76110
return overridePath
77111
}

packages/fastify-react/rendering.js

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Minipass } from 'minipass'
66
// which enables the combination of React.lazy() and Suspense
77
import { renderToPipeableStream } from 'react-dom/server'
88
import * as devalue from 'devalue'
9-
import Head from 'unihead'
9+
import { transformHtmlTemplate } from '@unhead/react/server'
1010
import { createHtmlTemplates } from './templating.js'
1111

1212
// Helper function to get an AsyncIterable (via PassThrough)
@@ -46,6 +46,7 @@ export function onAllReady(app) {
4646
export async function createRenderFunction ({ routes, create }) {
4747
// Used when hydrating React Router on the client
4848
const routeMap = Object.fromEntries(routes.map(_ => [_.path, _]))
49+
4950
// Registered as reply.render()
5051
return function () {
5152
if (this.request.route.streaming) {
@@ -80,6 +81,7 @@ export async function createHtmlFunction (source, _, config) {
8081
return async function () {
8182
const { routes, context, body } = await this.render()
8283

84+
context.useHead.push(context.head)
8385
this.type('text/html')
8486

8587
// Use template with client module import removed
@@ -116,24 +118,35 @@ export async function createHtmlFunction (source, _, config) {
116118
}
117119
}
118120

119-
export function sendClientOnlyShell (templates, context, body) {
120-
context.head = new Head(context.head).render()
121-
return `${
122-
templates.beforeElement(context)
123-
}${
124-
templates.afterElement(context)
125-
}`
121+
export async function sendClientOnlyShell (templates, context) {
122+
return await transformHtmlTemplate(
123+
context.useHead,
124+
`${
125+
templates.beforeElement(context)
126+
}${
127+
templates.afterElement(context)
128+
}`
129+
)
126130
}
127131

128132
export function streamShell (templates, context, body) {
129-
context.head = new Head(context.head).render()
130133
return Readable.from(createShellStream(templates, context, body))
131134
}
132135

133136
async function * createShellStream (templates, context, body) {
134-
yield templates.beforeElement(context)
137+
yield await transformHtmlTemplate(
138+
context.useHead,
139+
templates.beforeElement(context)
140+
)
141+
135142
for await (const chunk of body) {
136-
yield chunk
143+
yield await transformHtmlTemplate(
144+
context.useHead,
145+
chunk.toString()
146+
)
137147
}
138-
yield templates.afterElement(context)
148+
yield await transformHtmlTemplate(
149+
context.useHead,
150+
templates.afterElement(context)
151+
)
139152
}

packages/fastify-react/routing.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { readFileSync } from 'node:fs'
2-
import { join } from 'node:path'
2+
import { join, isAbsolute } from 'node:path'
33
import Youch from 'youch'
44
import RouteContext from './context.js'
55
import { createHtmlFunction } from './rendering.js'
@@ -111,8 +111,12 @@ export async function createRoute ({ client, errorHandler, route }, scope, confi
111111
handler = (_, reply) => reply.html()
112112
} else {
113113
const { id } = route
114-
const htmlPath = id.replace(/pages\/(.*?)\.jsx/, 'html/$1.html')
115-
const htmlSource = readFileSync(join(config.vite.root, config.vite.build.outDir, htmlPath), 'utf8')
114+
const htmlPath = id.replace(/pages\/(.*?)\.(j|t)sx/, 'html/$1.html')
115+
let distDir = config.vite.build.outDir
116+
if (!isAbsolute(config.vite.build.outDir)) {
117+
distDir = join(config.vite.root, distDir)
118+
}
119+
const htmlSource = readFileSync(join(distDir, htmlPath), 'utf8')
116120
const htmlFunction = await createHtmlFunction(htmlSource, scope, config)
117121
handler = (_, reply) => htmlFunction.call(reply)
118122
}

packages/fastify-react/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export function prepareServer(server) {
3939
})
4040
}
4141

42-
export async function createRoutes (fromPromise, { param } = { param: /\[([\.\w]+\+?)\]/ }) {
42+
export async function createRoutes (fromPromise, { param } = { param: /\[([.\w]+\+?)\]/ }) {
4343
const { default: from } = await fromPromise
4444
const importPaths = Object.keys(from)
4545
const promises = []
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// This file serves as a placeholder
2+
// if no context.js file is provided
3+
4+
export default () => {}

0 commit comments

Comments
 (0)