Skip to content
This repository has been archived by the owner on Nov 23, 2023. It is now read-only.

Commit

Permalink
Merge pull request #45 from fastify/react-ts
Browse files Browse the repository at this point in the history
feat(react): TypeScript support, starter template
  • Loading branch information
galvez authored Aug 4, 2022
2 parents 2e2b689 + 6bd34ac commit f40758a
Show file tree
Hide file tree
Showing 35 changed files with 891 additions and 9 deletions.
2 changes: 1 addition & 1 deletion devinstall.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

const { name: example } = path.parse(process.cwd())
const exRoot = path.resolve(__dirname, 'starters', example)
const command = process.argv.slice(5)
const command = process.argv.slice(process.argv.findIndex(_ => _ === '--') + 1)

if (!fs.existsSync(exRoot)) {
console.log('Must be called from a directory under starters/.')
Expand Down
1 change: 1 addition & 0 deletions packages/fastify-dx-react/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export function createHtmlFunction (source, scope, config) {
head: headTemplate({ ...context, head }),
footer: () => footerTemplate({
...context,
hydration: '',
// Decide whether or not to include the hydration script
...!context.serverOnly && {
hydration: (
Expand Down
6 changes: 5 additions & 1 deletion packages/fastify-dx-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
"type": "module",
"main": "index.js",
"name": "fastify-dx-react",
"version": "0.0.4",
"version": "0.0.5",
"files": [
"virtual/create.jsx",
"virtual/create.tsx",
"virtual/root.jsx",
"virtual/root.tsx",
"virtual/layouts.js",
"virtual/layouts/default.jsx",
"virtual/context.js",
"virtual/context.ts",
"virtual/mount.js",
"virtual/mount.ts",
"virtual/resource.js",
"virtual/core.jsx",
"virtual/routes.js",
Expand Down
7 changes: 6 additions & 1 deletion packages/fastify-dx-react/plugin.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@ const { fileURLToPath } = require('url')
function viteReactFastifyDX (config = {}) {
const prefix = /^\/?dx:/
const routing = Object.assign({
globPattern: '/pages/**/*.jsx',
globPattern: '/pages/**/*.(jsx|tsx)',
paramPattern: /\[(\w+)\]/,
}, config)
const virtualRoot = resolve(__dirname, 'virtual')
const virtualModules = [
'mount.js',
'mount.ts',
'resource.js',
'resource.ts',
'routes.js',
'layouts.js',
'create.jsx',
'create.tsx',
'root.jsx',
'root.tsx',
'layouts/',
'context.js',
'context.ts',
'core.jsx'
]
virtualModules.includes = function (virtual) {
Expand Down
4 changes: 4 additions & 0 deletions packages/fastify-dx-react/virtual/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This file serves as a placeholder
// if no context.js file is provided

export default () => {}
2 changes: 1 addition & 1 deletion packages/fastify-dx-react/virtual/create.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import Root from '/dx:root.jsx'

export default function create ({ url, ...serverInit }) {
return (
<Root url={url} serverInit={serverInit} />
<Root url={url} {...serverInit} />
)
}
7 changes: 7 additions & 0 deletions packages/fastify-dx-react/virtual/create.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Root from '/dx:root.tsx'

export default function create ({ url, ...serverInit }) {
return (
<Root url={url} {...serverInit} />
)
}
47 changes: 47 additions & 0 deletions packages/fastify-dx-react/virtual/mount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Head from 'unihead/client'
import { createRoot, hydrateRoot } from 'react-dom/client'

import create from '/dx:create.tsx'
import routesPromise from '/dx:routes.js'

mount('main')

async function mount (target) {
if (typeof target === 'string') {
target = document.querySelector(target)
}
const context = await import('/dx:context.ts')
const ctxHydration = await extendContext(window.route, context)
const head = new Head(window.route.head, window.document)
const resolvedRoutes = await routesPromise
const routeMap = Object.fromEntries(
resolvedRoutes.map((route) => [route.path, route]),
)

const app = create({
head,
ctxHydration,
routes: window.routes,
routeMap,
})
if (ctxHydration.clientOnly) {
createRoot(target).render(app)
} else {
hydrateRoot(target, app)
}
}

async function extendContext (ctx, {
// The route context initialization function
default: setter,
// We destructure state here just to discard it from extra
state,
// Other named exports from context.js
...extra
}) {
Object.assign(ctx, extra)
if (setter) {
await setter(ctx)
}
return ctx
}
68 changes: 68 additions & 0 deletions packages/fastify-dx-react/virtual/resource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@

const fetchMap = new Map()
const resourceMap = new Map()

export function waitResource (path, id, promise) {
const resourceId = `${path}:${id}`
const loader = resourceMap.get(resourceId)
if (loader) {
if (loader.error) {
throw loader.error
}
if (loader.suspended) {
throw loader.promise
}
resourceMap.delete(resourceId)

return loader.result
} else {
const loader = {
suspended: true,
error: null,
result: null,
promise: null,
}
loader.promise = promise()
.then((result) => { loader.result = result })
.catch((loaderError) => { loader.error = loaderError })
.finally(() => { loader.suspended = false })

resourceMap.set(resourceId, loader)

return waitResource(path, id)
}
}

export function waitFetch (path) {
const loader = fetchMap.get(path)
if (loader) {
if (loader.error || loader.data?.statusCode === 500) {
if (loader.data?.statusCode === 500) {
throw new Error(loader.data.message)
}
throw loader.error
}
if (loader.suspended) {
throw loader.promise
}
fetchMap.delete(path)

return loader.data
} else {
const loader = {
suspended: true,
error: null,
data: null,
promise: null,
}
loader.promise = fetch(`/-/data${path}`)
.then((response) => response.json())
.then((loaderData) => { loader.data = loaderData })
.catch((loaderError) => { loader.error = loaderError })
.finally(() => { loader.suspended = false })

fetchMap.set(path, loader)

return waitFetch(path)
}
}
25 changes: 21 additions & 4 deletions packages/fastify-dx-react/virtual/root.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
import { Suspense } from 'react'
import { DXApp } from '/dx:core.jsx'
import { Routes, Route } from 'react-router-dom'
import { Router, DXRoute } from '/dx:core.jsx'

export default function Root ({ url, serverInit }) {
export default function Root ({ url, routes, head, ctxHydration, routeMap }) {
return (
<Suspense>
<DXApp url={url} {...serverInit} />
<Router location={url}>
<Routes>{
routes.map(({ path, component: Component }) =>
<Route
key={path}
path={path}
element={
<DXRoute
head={head}
ctxHydration={ctxHydration}
ctx={routeMap[path]}>
<Component />
</DXRoute>
} />,
)
}</Routes>
</Router>
</Suspense>
)
}
}
27 changes: 27 additions & 0 deletions packages/fastify-dx-react/virtual/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Suspense } from 'react'
import { Routes, Route } from 'react-router-dom'
import { Router, DXRoute } from '/dx:core.jsx'

export default function Root ({ url, routes, head, ctxHydration, routeMap }) {
return (
<Suspense>
<Router location={url}>
<Routes>{
routes.map(({ path, component: Component }) =>
<Route
key={path}
path={path}
element={
<DXRoute
head={head}
ctxHydration={ctxHydration}
ctx={routeMap[path]}>
<Component />
</DXRoute>
} />,
)
}</Routes>
</Router>
</Suspense>
)
}
1 change: 1 addition & 0 deletions starters/react-ts/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
34 changes: 34 additions & 0 deletions starters/react-ts/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
parser: '@typescript-eslint/parser',
plugins: ['react'],
extends: [
'eslint:recommended',
'standard-with-typescript',
'plugin:react/recommended',
],
parserOptions: {
project: './tsconfig.json',
requireConfigFile: false,
ecmaVersion: 2021,
sourceType: 'module',
babelOptions: {
presets: ['@babel/preset-react'],
},
ecmaFeatures: {
jsx: true,
},
},
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/strict-boolean-expressions': 'off',
'comma-dangle': ['error', 'always-multiline'],
'react/prop-types': 'off',
'import/no-absolute-path': 'off',
'react/react-in-jsx-scope': 'off',
},
settings: {
react: {
version: '18.0',
},
},
}
84 changes: 84 additions & 0 deletions starters/react-ts/client/assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit f40758a

Please sign in to comment.