Skip to content

Commit edc25dd

Browse files
author
Peter Bengtsson
authored
re-read content on every request (github#30646)
1 parent c1feb44 commit edc25dd

File tree

5 files changed

+146
-5
lines changed

5 files changed

+146
-5
lines changed

contributing/development.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ npm run build
2222
npm start
2323
```
2424

25-
You should now have a running server! Visit [localhost:4000](http://localhost:4000) in your browser. It will automatically restart as you make changes to site content.
25+
You should now have a running server! Visit [localhost:4000](http://localhost:4000) in your browser.
2626

2727
When you're ready to stop your local server, type <kbd>Ctrl</kbd>+<kbd>C</kbd> in your terminal window.
2828

middleware/find-page.js

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
1-
export default function findPage(req, res, next) {
2-
const page = req.context.pages[req.pagePath]
1+
import { fileURLToPath } from 'url'
2+
import path from 'path'
3+
import { existsSync } from 'fs'
4+
5+
import Page from '../lib/page.js'
6+
import { languageKeys } from '../lib/languages.js'
7+
8+
const languagePrefixRegex = new RegExp(`^/(${languageKeys.join('|')})(/|$)`)
9+
const englishPrefixRegex = /^\/en(\/|$)/
10+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
11+
const CONTENT_ROOT = path.posix.join(__dirname, '..', 'content')
12+
13+
export default async function findPage(
14+
req,
15+
res,
16+
next,
17+
// Express won't execute these but it makes it easier to unit test
18+
// the middleware.
19+
{ isDev = process.env.NODE_ENV === 'development', contentRoot = CONTENT_ROOT } = {}
20+
) {
21+
// Filter out things like `/will/redirect` or `/_next/data/...`
22+
if (!languagePrefixRegex.test(req.pagePath)) {
23+
return next()
24+
}
25+
26+
const page =
27+
isDev && englishPrefixRegex.test(req.pagePath)
28+
? await rereadByPath(req.pagePath, contentRoot, req.context.currentVersion)
29+
: req.context.pages[req.pagePath]
330

431
if (page) {
532
req.context.page = page
@@ -8,3 +35,27 @@ export default function findPage(req, res, next) {
835

936
return next()
1037
}
38+
39+
async function rereadByPath(uri, contentRoot, currentVersion) {
40+
const languageCode = uri.match(languagePrefixRegex)[1]
41+
const withoutLanguage = uri.replace(languagePrefixRegex, '/')
42+
const withoutVersion = withoutLanguage.replace(`/${currentVersion}/`, '/')
43+
// TODO: Support loading translations the same way.
44+
// NOTE: No one is going to test translations like this in development
45+
// but perhaps one day we can always and only do these kinds of lookups
46+
// at runtime.
47+
const possible = path.join(contentRoot, withoutVersion)
48+
const filePath = existsSync(possible) ? path.join(possible, 'index.md') : possible + '.md'
49+
const relativePath = path.relative(contentRoot, filePath)
50+
const basePath = contentRoot
51+
52+
// Remember, the Page.init() can return a Promise that resolves to falsy
53+
// if it can't read the file in from disk. E.g. a request for /en/non/existent.
54+
// In other words, it's fine if it can't be read from disk. It'll get
55+
// handled and turned into a nice 404 message.
56+
return await Page.init({
57+
basePath,
58+
relativePath,
59+
languageCode,
60+
})
61+
}

middleware/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ export default function (app) {
222222
app.use(instrument(handleRedirects, './redirects/handle-redirects')) // Must come before contextualizers
223223

224224
// *** Config and context for rendering ***
225-
app.use(instrument(findPage, './find-page')) // Must come before archived-enterprise-versions, breadcrumbs, featured-links, products, render-page
225+
app.use(asyncMiddleware(instrument(findPage, './find-page'))) // Must come before archived-enterprise-versions, breadcrumbs, featured-links, products, render-page
226226
app.use(instrument(blockRobots, './block-robots'))
227227

228228
// Check for a dropped connection before proceeding

nodemon.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"script",
66
"translations",
77
"stylesheets",
8-
"tests"
8+
"tests",
9+
"content"
910
]
1011
}

tests/unit/find-page-middleware.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { fileURLToPath } from 'url'
2+
import path from 'path'
3+
import http from 'http'
4+
5+
import { expect, describe, test } from '@jest/globals'
6+
7+
import Page from '../../lib/page.js'
8+
import findPage from '../../middleware/find-page.js'
9+
10+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
11+
12+
function makeRequestResponse(url, currentVersion = 'free-pro-team@latest') {
13+
const req = new http.IncomingMessage(null)
14+
req.method = 'GET'
15+
req.url = url
16+
req.path = url
17+
req.cookies = {}
18+
req.headers = {}
19+
20+
// Custom keys on the request
21+
req.pagePath = url
22+
req.context = {}
23+
req.context.currentVersion = currentVersion
24+
req.context.pages = {}
25+
26+
return [req, new http.ServerResponse(req)]
27+
}
28+
29+
describe('find page middleware', () => {
30+
test('attaches page on req.context', async () => {
31+
const url = '/en/foo/bar'
32+
const [req, res] = makeRequestResponse(url)
33+
req.context.pages = {
34+
'/en/foo/bar': await Page.init({
35+
relativePath: 'page-with-redirects.md',
36+
basePath: path.join(__dirname, '../fixtures'),
37+
languageCode: 'en',
38+
}),
39+
}
40+
41+
let nextCalls = 0
42+
await findPage(req, res, () => {
43+
nextCalls++
44+
})
45+
expect(nextCalls).toBe(1)
46+
expect(req.context.page).toBeInstanceOf(Page)
47+
})
48+
49+
test('does not attach page on req.context if not found', async () => {
50+
const [req, res] = makeRequestResponse('/en/foo/bar')
51+
52+
let nextCalls = 0
53+
await findPage(req, res, () => {
54+
nextCalls++
55+
})
56+
expect(nextCalls).toBe(1)
57+
expect(req.context.page).toBe(undefined)
58+
})
59+
60+
test('re-reads from disk if in development mode', async () => {
61+
const [req, res] = makeRequestResponse('/en/page-with-redirects')
62+
63+
await findPage(req, res, () => {}, {
64+
isDev: true,
65+
contentRoot: path.join(__dirname, '../fixtures'),
66+
})
67+
expect(req.context.page).toBeInstanceOf(Page)
68+
})
69+
70+
test('finds it for non-fpt version URLS', async () => {
71+
const [req, res] = makeRequestResponse('/en/page-with-redirects', 'enterprise-cloud@latest')
72+
73+
await findPage(req, res, () => {}, {
74+
isDev: true,
75+
contentRoot: path.join(__dirname, '../fixtures'),
76+
})
77+
expect(req.context.page).toBeInstanceOf(Page)
78+
})
79+
80+
test('re-reads from disk if in development mode and finds nothing', async () => {
81+
const [req, res] = makeRequestResponse('/en/never/heard/of')
82+
83+
await findPage(req, res, () => {}, {
84+
isDev: true,
85+
contentRoot: path.join(__dirname, '../fixtures'),
86+
})
87+
expect(req.context.page).toBe(undefined)
88+
})
89+
})

0 commit comments

Comments
 (0)