Skip to content

Commit e5f32b6

Browse files
dac09cannikin
andauthored
feat(og-gen): Implement middleware and hooks (redwoodjs#10469)
Co-authored-by: Rob Cameron <cannikin@fastmail.com>
1 parent a2a4a02 commit e5f32b6

File tree

32 files changed

+1135
-63
lines changed

32 files changed

+1135
-63
lines changed

.changesets/10469.md

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
- feat(og-gen): Implement middleware and hooks (#10469) by @dac09
2+
3+
The OG Gen saga continues with @cannikin and @dac09 ⚔️
4+
5+
This PR:
6+
- adds OgImageMiddleware and Hooks to `@redwoodjs/og-gen`, complete with tests
7+
8+
⚠️ Template changes:
9+
- updates entry.client template to pass in Routes to App
10+
- updates App to take children (i.e. Routes)
11+
12+
This is so that we can pass the OG component to be rendered _with_ your App's CSS setup.
13+
14+
15+
**How to use this?**
16+
17+
1. **Registering the middleware:**
18+
```ts
19+
import OgImageMiddleware from '@redwoodjs/ogimage-gen/middleware'
20+
21+
export const registerMiddleware = () => {
22+
const ogMw = new OgImageMiddleware({
23+
App,
24+
Document,
25+
})
26+
27+
return [ogMw]
28+
}
29+
```
30+
31+
2. Configure your `vite.config.ts`
32+
```ts
33+
import vitePluginOgImageGen from '@redwoodjs/ogimage-gen/plugin'
34+
35+
const viteConfig: UserConfig = {
36+
// 👇 so it builds your OG components
37+
plugins: [redwood(), vitePluginOgImageGen()],
38+
}
39+
40+
export default defineConfig(viteConfig)
41+
```
42+
3. Add your OG Image component next to the page it's for
43+
e.g. web/src/pages/AboutPage/AboutPage.png.tsx
44+
45+
4. Use hooks on AboutPage to generate the ogURL

.vscode/settings.json

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"Flightcontrol",
3232
"graphiql",
3333
"memfs",
34+
"OGIMAGE",
3435
"opentelemetry",
3536
"pino",
3637
"Pistorius",

__fixtures__/test-project/web/src/App.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web'
44
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
55

66
import FatalErrorPage from 'src/pages/FatalErrorPage'
7-
import Routes from 'src/Routes'
87

98
import { AuthProvider, useAuth } from './auth'
109

11-
import './scaffold.css'
1210
import './index.css'
11+
import './scaffold.css'
12+
1313
interface AppProps {
1414
children?: ReactNode
1515
}
@@ -19,7 +19,7 @@ const App = ({ children }: AppProps) => (
1919
<RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
2020
<AuthProvider>
2121
<RedwoodApolloProvider useAuth={useAuth}>
22-
{children ? children : <Routes />}
22+
{children}
2323
</RedwoodApolloProvider>
2424
</AuthProvider>
2525
</RedwoodProvider>

__fixtures__/test-project/web/src/entry.client.tsx

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { hydrateRoot, createRoot } from 'react-dom/client'
22

33
import App from './App'
4+
import Routes from './Routes'
5+
46
/**
57
* When `#redwood-app` isn't empty then it's very likely that you're using
68
* prerendering. So React attaches event listeners to the existing markup
@@ -16,8 +18,17 @@ if (!redwoodAppElement) {
1618
}
1719

1820
if (redwoodAppElement.children?.length > 0) {
19-
hydrateRoot(redwoodAppElement, <App />)
21+
hydrateRoot(
22+
redwoodAppElement,
23+
<App>
24+
<Routes />
25+
</App>
26+
)
2027
} else {
2128
const root = createRoot(redwoodAppElement)
22-
root.render(<App />)
29+
root.render(
30+
<App>
31+
<Routes />
32+
</App>
33+
)
2334
}

packages/cli/src/commands/experimental/templates/streamingSsr/entry.client.tsx.template

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { hydrateRoot, createRoot } from 'react-dom/client'
22

33
import App from './App'
44
import { Document } from './Document'
5+
import Routes from './Routes'
56

67
/**
78
* When `#redwood-app` isn't empty then it's very likely that you're using
@@ -15,14 +16,18 @@ if (redwoodAppElement.children?.length > 0) {
1516
hydrateRoot(
1617
document,
1718
<Document css={window.__assetMap?.()?.css}>
18-
<App />
19+
<App>
20+
<Routes />
21+
</App>
1922
</Document>
2023
)
2124
} else {
2225
const root = createRoot(document)
2326
root.render(
2427
<Document css={window.__assetMap?.()?.css}>
25-
<App />
28+
<App>
29+
<Routes />
30+
</App>
2631
</Document>
2732
)
2833
}

packages/cli/src/commands/experimental/templates/streamingSsr/entry.server.tsx.template

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { TagDescriptor } from '@redwoodjs/web'
22

33
import App from './App'
44
import { Document } from './Document'
5+
import Routes from './Routes'
56

67
interface Props {
78
css: string[]
@@ -11,7 +12,9 @@ interface Props {
1112
export const ServerEntry: React.FC<Props> = ({ css, meta }) => {
1213
return (
1314
<Document css={css} meta={meta}>
14-
<App />
15+
<App>
16+
<Routes />
17+
</App>
1518
</Document>
1619
)
1720
}

packages/cli/src/commands/setup/graphql/features/fragments/__codemod_tests__/appGqlConfigTransform.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ describe('fragments graphQLClientConfig', () => {
7070
import { RedwoodApolloProvider } from \"@redwoodjs/web/apollo\";
7171
7272
import FatalErrorPage from \"src/pages/FatalErrorPage\";
73-
import Routes from \"src/Routes\";
7473
7574
import { AuthProvider, useAuth } from \"./auth\";
7675
77-
import \"./scaffold.css\";
7876
import \"./index.css\";
77+
import \"./scaffold.css\";
78+
7979
interface AppProps {
8080
children?: ReactNode;
8181
}
@@ -94,7 +94,7 @@ describe('fragments graphQLClientConfig', () => {
9494
useAuth={useAuth}
9595
graphQLClientConfig={graphQLClientConfig}
9696
>
97-
{children ? children : <Routes />}
97+
{children}
9898
</RedwoodApolloProvider>
9999
</AuthProvider>
100100
</RedwoodProvider>

packages/cli/src/lib/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,8 @@ export const addScaffoldImport = () => {
441441
}
442442

443443
appJsContents = appJsContents.replace(
444-
"import Routes from 'src/Routes'\n",
445-
"import Routes from 'src/Routes'\n\nimport './scaffold.css'",
444+
"import './index.css'",
445+
"import './index.css'\nimport './scaffold.css'\n",
446446
)
447447
writeFile(appJsPath, appJsContents, { overwriteExisting: true })
448448

packages/core/config/webpack.common.js

+1
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ module.exports = (webpackEnv) => {
240240
'styled-components',
241241
),
242242
'~redwood-app-root': path.resolve(redwoodPaths.web.app),
243+
'~redwood-app-routes': path.resolve(redwoodPaths.web.routes),
243244
react: path.resolve(redwoodPaths.base, 'node_modules', 'react'),
244245
'react-hook-form': path.resolve(
245246
redwoodPaths.base,

packages/create-redwood-app/templates/js/web/src/App.jsx

+1-4
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@ import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web'
22
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
33

44
import FatalErrorPage from 'src/pages/FatalErrorPage'
5-
import Routes from 'src/Routes'
65

76
import './index.css'
87

98
const App = ({ children }) => (
109
<FatalErrorBoundary page={FatalErrorPage}>
1110
<RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
12-
<RedwoodApolloProvider>
13-
{children ? children : <Routes />}
14-
</RedwoodApolloProvider>
11+
<RedwoodApolloProvider>{children}</RedwoodApolloProvider>
1512
</RedwoodProvider>
1613
</FatalErrorBoundary>
1714
)

packages/create-redwood-app/templates/js/web/src/entry.client.jsx

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { hydrateRoot, createRoot } from 'react-dom/client'
22

33
import App from './App'
4+
import Routes from './Routes'
5+
46
/**
57
* When `#redwood-app` isn't empty then it's very likely that you're using
68
* prerendering. So React attaches event listeners to the existing markup
@@ -16,8 +18,17 @@ if (!redwoodAppElement) {
1618
}
1719

1820
if (redwoodAppElement.children?.length > 0) {
19-
hydrateRoot(redwoodAppElement, <App />)
21+
hydrateRoot(
22+
redwoodAppElement,
23+
<App>
24+
<Routes />
25+
</App>
26+
)
2027
} else {
2128
const root = createRoot(redwoodAppElement)
22-
root.render(<App />)
29+
root.render(
30+
<App>
31+
<Routes />
32+
</App>
33+
)
2334
}

packages/create-redwood-app/templates/ts/web/src/App.tsx

+1-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web'
44
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
55

66
import FatalErrorPage from 'src/pages/FatalErrorPage'
7-
import Routes from 'src/Routes'
87

98
import './index.css'
109
interface AppProps {
@@ -14,9 +13,7 @@ interface AppProps {
1413
const App = ({ children }: AppProps) => (
1514
<FatalErrorBoundary page={FatalErrorPage}>
1615
<RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
17-
<RedwoodApolloProvider>
18-
{children ? children : <Routes />}
19-
</RedwoodApolloProvider>
16+
<RedwoodApolloProvider>{children}</RedwoodApolloProvider>
2017
</RedwoodProvider>
2118
</FatalErrorBoundary>
2219
)

packages/create-redwood-app/templates/ts/web/src/entry.client.tsx

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { hydrateRoot, createRoot } from 'react-dom/client'
22

33
import App from './App'
4+
import Routes from './Routes'
5+
46
/**
57
* When `#redwood-app` isn't empty then it's very likely that you're using
68
* prerendering. So React attaches event listeners to the existing markup
@@ -16,8 +18,17 @@ if (!redwoodAppElement) {
1618
}
1719

1820
if (redwoodAppElement.children?.length > 0) {
19-
hydrateRoot(redwoodAppElement, <App />)
21+
hydrateRoot(
22+
redwoodAppElement,
23+
<App>
24+
<Routes />
25+
</App>
26+
)
2027
} else {
2128
const root = createRoot(redwoodAppElement)
22-
root.render(<App />)
29+
root.render(
30+
<App>
31+
<Routes />
32+
</App>
33+
)
2334
}

packages/internal/src/routes.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export interface RWRouteManifestItem {
7171
routeHooks: string | null
7272
bundle: string | null
7373
hasParams: boolean
74-
relativeFilePath: string | undefined
74+
relativeFilePath: string
7575
redirect: { to: string; permanent: boolean } | null
7676
// Probably want isNotFound here, so we can attach a separate 404 handler
7777
}
@@ -80,7 +80,7 @@ export interface RouteSpec extends RWRouteManifestItem {
8080
id: string
8181
isNotFound: boolean
8282
filePath: string | undefined
83-
relativeFilePath: string | undefined
83+
relativeFilePath: string
8484
}
8585

8686
export const getProjectRoutes = (): RouteSpec[] => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/* eslint-env node */
2+
3+
module.exports = require('../dist/hooks.js').default
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/* eslint-env node */
2+
3+
module.exports = require('../dist/OgImageMiddleware.js').default

packages/ogimage-gen/package.json

+14-4
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,20 @@
88
},
99
"license": "MIT",
1010
"exports": {
11-
".": {
12-
"default": "./dist/index.js"
13-
},
1411
"./plugin": {
1512
"import": "./dist/vite-plugin-ogimage-gen.js",
16-
"default": "./cjsWrappers/plugin.js"
13+
"default": "./cjsWrappers/plugin.js",
14+
"types": "./dist/vite-plugin-ogimage-gen.d.ts"
15+
},
16+
"./middleware": {
17+
"import": "./dist/OgImageMiddleware.js",
18+
"default": "./cjsWrappers/middleware.js",
19+
"types": "./dist/OgImageMiddleware.d.ts"
20+
},
21+
"./hooks": {
22+
"import": "./dist/hooks.js",
23+
"default": "./cjsWrappers/hooks.js",
24+
"types": "./dist/hooks.d.ts"
1725
}
1826
},
1927
"files": [
@@ -34,10 +42,12 @@
3442
"@redwoodjs/router": "workspace:*",
3543
"@redwoodjs/vite": "workspace:*",
3644
"fast-glob": "3.3.2",
45+
"lodash": "4.17.21",
3746
"react": "19.0.0-canary-cb151849e1-20240424",
3847
"react-dom": "19.0.0-canary-cb151849e1-20240424"
3948
},
4049
"devDependencies": {
50+
"@playwright/test": "1.42.1",
4151
"@redwoodjs/framework-tools": "workspace:*",
4252
"ts-toolbelt": "9.6.0",
4353
"tsx": "4.7.1",

0 commit comments

Comments
 (0)