Skip to content

Commit 8804beb

Browse files
authored
Merge pull request #13072 from github/repo-sync
repo sync
2 parents 6e9d021 + b55856b commit 8804beb

80 files changed

Lines changed: 1158 additions & 1009 deletions

File tree

Some content is hidden

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

.github/workflows/link-check-dotcom.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ name: 'Link Checker: Dotcom'
66

77
on:
88
workflow_dispatch:
9-
push:
10-
branches:
11-
- main
129
pull_request:
1310

1411
permissions:

.github/workflows/link-check-ghae.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ name: 'Link Checker: GitHub AE'
66

77
on:
88
workflow_dispatch:
9-
push:
10-
branches:
11-
- main
129
pull_request:
1310

1411
permissions:

.github/workflows/link-check-ghec.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ name: 'Link Checker: Enterprise Cloud'
66

77
on:
88
workflow_dispatch:
9-
push:
10-
branches:
11-
- main
129
pull_request:
1310

1411
permissions:

.github/workflows/link-check-ghes.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ name: 'Link Checker: Enterprise Server'
66

77
on:
88
workflow_dispatch:
9-
push:
10-
branches:
11-
- main
129
pull_request:
1310

1411
permissions:

middleware/render-page.js

Lines changed: 118 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,124 @@
11
import { get } from 'lodash-es'
2+
import QuickLRU from 'quick-lru'
3+
24
import patterns from '../lib/patterns.js'
35
import getMiniTocItems from '../lib/get-mini-toc-items.js'
46
import Page from '../lib/page.js'
57
import statsd from '../lib/statsd.js'
68
import { isConnectionDropped } from './halt-on-dropped-connection.js'
79
import { nextApp, nextHandleRequest } from './next.js'
810

11+
function cacheOnReq(fn, minSize = 1024, lruMaxSize = 1000) {
12+
const cache = new QuickLRU({ maxSize: lruMaxSize })
13+
14+
return async function (req) {
15+
const path = req.pagePath || req.path
16+
17+
// Is the request for the GraphQL Explorer page?
18+
const isGraphQLExplorer =
19+
req.context.currentPathWithoutLanguage === '/graphql/overview/explorer'
20+
21+
// Serve from the cache if possible
22+
const isCacheable =
23+
// Skip for HTTP methods other than GET
24+
req.method === 'GET' &&
25+
// Skip for JSON debugging info requests
26+
!('json' in req.query) &&
27+
// Skip for the GraphQL Explorer page
28+
!isGraphQLExplorer
29+
30+
if (isCacheable && cache.has(path)) {
31+
return cache.get(path)
32+
}
33+
const result = await fn(req)
34+
35+
if (result && isCacheable && result.length > minSize) {
36+
cache.set(path, result)
37+
}
38+
return result
39+
}
40+
}
41+
42+
async function buildRenderedPage(req) {
43+
const { context } = req
44+
const { page } = context
45+
const path = req.pagePath || req.path
46+
47+
const pageRenderTimed = statsd.asyncTimer(page.render, 'middleware.render_page', [`path:${path}`])
48+
49+
const renderedPage = await pageRenderTimed(context)
50+
51+
// handle special-case prerendered GraphQL objects page
52+
if (path.endsWith('graphql/reference/objects')) {
53+
return renderedPage + context.graphql.prerenderedObjectsForCurrentVersion.html
54+
}
55+
56+
// handle special-case prerendered GraphQL input objects page
57+
if (path.endsWith('graphql/reference/input-objects')) {
58+
return renderedPage + context.graphql.prerenderedInputObjectsForCurrentVersion.html
59+
}
60+
61+
// handle special-case prerendered GraphQL mutations page
62+
if (path.endsWith('graphql/reference/mutations')) {
63+
return renderedPage + context.graphql.prerenderedMutationsForCurrentVersion.html
64+
}
65+
66+
return renderedPage
67+
}
68+
69+
async function buildMiniTocItems(req) {
70+
const { context } = req
71+
const { page } = context
72+
const path = req.pagePath || req.path
73+
74+
// get mini TOC items on articles
75+
if (!page.showMiniToc) {
76+
return
77+
}
78+
79+
const miniTocItems = getMiniTocItems(context.renderedPage, page.miniTocMaxHeadingLevel)
80+
81+
// handle special-case prerendered GraphQL objects page
82+
if (path.endsWith('graphql/reference/objects')) {
83+
// concat the markdown source miniToc items and the prerendered miniToc items
84+
return miniTocItems.concat(context.graphql.prerenderedObjectsForCurrentVersion.miniToc)
85+
}
86+
87+
// handle special-case prerendered GraphQL input objects page
88+
if (path.endsWith('graphql/reference/input-objects')) {
89+
// concat the markdown source miniToc items and the prerendered miniToc items
90+
return miniTocItems.concat(context.graphql.prerenderedInputObjectsForCurrentVersion.miniToc)
91+
}
92+
93+
// handle special-case prerendered GraphQL mutations page
94+
if (path.endsWith('graphql/reference/mutations')) {
95+
// concat the markdown source miniToc items and the prerendered miniToc items
96+
return miniTocItems.concat(context.graphql.prerenderedMutationsForCurrentVersion.miniToc)
97+
}
98+
99+
return miniTocItems
100+
}
101+
102+
// The avergage size of buildRenderedPage() is about 22KB.
103+
// The median in 7KB. By only caching those larger than 10KB we avoid
104+
// putting too much into the cache.
105+
const wrapRenderedPage = cacheOnReq(buildRenderedPage, 10 * 1024)
106+
// const wrapMiniTocItems = cacheOnReq(buildMiniTocItems)
107+
9108
export default async function renderPage(req, res, next) {
10-
if (req.path.startsWith('/storybook')) {
109+
const { context } = req
110+
const { page } = context
111+
const path = req.pagePath || req.path
112+
113+
if (path.startsWith('/storybook')) {
11114
return nextHandleRequest(req, res)
12115
}
13116

14-
const page = req.context.page
15117
// render a 404 page
16118
if (!page) {
17-
if (process.env.NODE_ENV !== 'test' && req.context.redirectNotFound) {
119+
if (process.env.NODE_ENV !== 'test' && context.redirectNotFound) {
18120
console.error(
19-
`\nTried to redirect to ${req.context.redirectNotFound}, but that page was not found.\n`
121+
`\nTried to redirect to ${context.redirectNotFound}, but that page was not found.\n`
20122
)
21123
}
22124
return nextApp.render404(req, res)
@@ -27,79 +129,38 @@ export default async function renderPage(req, res, next) {
27129
return res.status(200).end()
28130
}
29131

30-
// Is the request for JSON debugging info?
31-
const isRequestingJsonForDebugging = 'json' in req.query && process.env.NODE_ENV !== 'production'
32-
33-
// add page context
34-
const context = Object.assign({}, req.context, { page })
35-
36132
// Updating the Last-Modified header for substantive changes on a page for engineering
37133
// Docs Engineering Issue #945
38-
if (context.page.effectiveDate) {
134+
if (page.effectiveDate) {
39135
// Note that if a page has an invalidate `effectiveDate` string value,
40136
// it would be caught prior to this usage and ultimately lead to
41137
// 500 error.
42-
res.setHeader('Last-Modified', new Date(context.page.effectiveDate).toUTCString())
138+
res.setHeader('Last-Modified', new Date(page.effectiveDate).toUTCString())
43139
}
44140

45141
// collect URLs for variants of this page in all languages
46-
context.page.languageVariants = Page.getLanguageVariants(req.pagePath)
142+
page.languageVariants = Page.getLanguageVariants(path)
143+
47144
// Stop processing if the connection was already dropped
48145
if (isConnectionDropped(req, res)) return
49146

50-
// render page
51-
const pageRenderTimed = statsd.asyncTimer(page.render, 'middleware.render_page', [
52-
`path:${req.pagePath || req.path}`,
53-
])
54-
context.renderedPage = await pageRenderTimed(context)
147+
req.context.renderedPage = await wrapRenderedPage(req)
148+
req.context.miniTocItems = await buildMiniTocItems(req)
55149

56150
// Stop processing if the connection was already dropped
57151
if (isConnectionDropped(req, res)) return
58152

59-
// get mini TOC items on articles
60-
if (page.showMiniToc) {
61-
context.miniTocItems = getMiniTocItems(context.renderedPage, page.miniTocMaxHeadingLevel)
62-
}
63-
64-
// handle special-case prerendered GraphQL objects page
65-
if (req.pagePath.endsWith('graphql/reference/objects')) {
66-
// concat the markdown source miniToc items and the prerendered miniToc items
67-
context.miniTocItems = context.miniTocItems.concat(
68-
req.context.graphql.prerenderedObjectsForCurrentVersion.miniToc
69-
)
70-
context.renderedPage =
71-
context.renderedPage + req.context.graphql.prerenderedObjectsForCurrentVersion.html
72-
}
73-
74-
// handle special-case prerendered GraphQL input objects page
75-
if (req.pagePath.endsWith('graphql/reference/input-objects')) {
76-
// concat the markdown source miniToc items and the prerendered miniToc items
77-
context.miniTocItems = context.miniTocItems.concat(
78-
req.context.graphql.prerenderedInputObjectsForCurrentVersion.miniToc
79-
)
80-
context.renderedPage =
81-
context.renderedPage + req.context.graphql.prerenderedInputObjectsForCurrentVersion.html
82-
}
83-
84-
// handle special-case prerendered GraphQL mutations page
85-
if (req.pagePath.endsWith('graphql/reference/mutations')) {
86-
// concat the markdown source miniToc items and the prerendered miniToc items
87-
context.miniTocItems = context.miniTocItems.concat(
88-
req.context.graphql.prerenderedMutationsForCurrentVersion.miniToc
89-
)
90-
context.renderedPage =
91-
context.renderedPage + req.context.graphql.prerenderedMutationsForCurrentVersion.html
92-
}
93-
94153
// Create string for <title> tag
95-
context.page.fullTitle = context.page.titlePlainText
154+
page.fullTitle = page.titlePlainText
96155

97156
// add localized ` - GitHub Docs` suffix to <title> tag (except for the homepage)
98-
if (!patterns.homepagePath.test(req.pagePath)) {
99-
context.page.fullTitle =
100-
context.page.fullTitle + ' - ' + context.site.data.ui.header.github_docs
157+
if (!patterns.homepagePath.test(path)) {
158+
page.fullTitle = page.fullTitle + ' - ' + context.site.data.ui.header.github_docs
101159
}
102160

161+
// Is the request for JSON debugging info?
162+
const isRequestingJsonForDebugging = 'json' in req.query && process.env.NODE_ENV !== 'production'
163+
103164
// `?json` query param for debugging request context
104165
if (isRequestingJsonForDebugging) {
105166
if (req.query.json.length > 1) {
@@ -115,8 +176,5 @@ export default async function renderPage(req, res, next) {
115176
}
116177
}
117178

118-
// Hand rendering over to NextJS
119-
req.context.renderedPage = context.renderedPage
120-
req.context.miniTocItems = context.miniTocItems
121179
return nextHandleRequest(req, res)
122180
}

package-lock.json

Lines changed: 26 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"node-fetch": "^3.1.0",
6363
"parse5": "^6.0.1",
6464
"port-used": "^2.0.8",
65+
"quick-lru": "6.0.2",
6566
"rate-limit-redis": "^2.1.0",
6667
"react": "^17.0.2",
6768
"react-dom": "^17.0.2",

0 commit comments

Comments
 (0)