Port next.js to TypeScript (#51438)
This commit is contained in:
@@ -50,8 +50,8 @@ import productExamples from './context/product-examples'
|
||||
import productGroups from './context/product-groups'
|
||||
import featuredLinks from '@/landings/middleware/featured-links'
|
||||
import learningTrack from '@/learning-track/middleware/learning-track'
|
||||
import next from './next.js'
|
||||
import renderPage from './render-page.js'
|
||||
import next from './next'
|
||||
import renderPage from './render-page'
|
||||
import assetPreprocessing from '@/assets/middleware/asset-preprocessing'
|
||||
import archivedAssetRedirects from '@/archives/middleware/archived-asset-redirects'
|
||||
import favicons from './favicons'
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import next from 'next'
|
||||
|
||||
import type { Response, NextFunction } from 'express'
|
||||
|
||||
import type { ExtendedRequest } from '@/types'
|
||||
|
||||
const { NODE_ENV } = process.env
|
||||
const isDevelopment = NODE_ENV === 'development'
|
||||
|
||||
@@ -7,7 +11,7 @@ export const nextApp = next({ dev: isDevelopment })
|
||||
export const nextHandleRequest = nextApp.getRequestHandler()
|
||||
await nextApp.prepare()
|
||||
|
||||
function renderPageWithNext(req, res, next) {
|
||||
function renderPageWithNext(req: ExtendedRequest, res: Response, next: NextFunction) {
|
||||
if (req.path.startsWith('/_next') && !req.path.startsWith('/_next/data')) {
|
||||
return nextHandleRequest(req, res)
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
import http from 'http'
|
||||
|
||||
import { get } from 'lodash-es'
|
||||
import type { Response } from 'express'
|
||||
import type { Failbot } from '@github/failbot'
|
||||
|
||||
import FailBot from '#src/observability/lib/failbot.js'
|
||||
import patterns from '#src/frame/lib/patterns.js'
|
||||
import getMiniTocItems from '#src/frame/lib/get-mini-toc-items.js'
|
||||
import { pathLanguagePrefixed } from '#src/languages/lib/languages.js'
|
||||
import statsd from '#src/observability/lib/statsd.js'
|
||||
import { allVersions } from '#src/versions/lib/all-versions.js'
|
||||
import type { ExtendedRequest } from '@/types'
|
||||
import FailBot from '@/observability/lib/failbot.js'
|
||||
import patterns from '@/frame/lib/patterns.js'
|
||||
import getMiniTocItems from '@/frame/lib/get-mini-toc-items.js'
|
||||
import { pathLanguagePrefixed } from '@/languages/lib/languages.js'
|
||||
import statsd from '@/observability/lib/statsd.js'
|
||||
import { allVersions } from '@/versions/lib/all-versions.js'
|
||||
import { isConnectionDropped } from './halt-on-dropped-connection'
|
||||
import { nextHandleRequest } from './next.js'
|
||||
import { defaultCacheControl } from './cache-control.js'
|
||||
@@ -16,37 +19,41 @@ import { minimumNotFoundHtml } from '../lib/constants.js'
|
||||
const STATSD_KEY_RENDER = 'middleware.render_page'
|
||||
const STATSD_KEY_404 = 'middleware.render_404'
|
||||
|
||||
async function buildRenderedPage(req) {
|
||||
async function buildRenderedPage(req: ExtendedRequest): Promise<string> {
|
||||
const { context } = req
|
||||
if (!context) throw new Error('request not contextualized')
|
||||
const { page } = context
|
||||
if (!page) throw new Error('page not set in context')
|
||||
const path = req.pagePath || req.path
|
||||
|
||||
const pageRenderTimed = statsd.asyncTimer(page.render, STATSD_KEY_RENDER, [`path:${path}`])
|
||||
|
||||
return await pageRenderTimed(context)
|
||||
return (await pageRenderTimed(context)) as string
|
||||
}
|
||||
|
||||
async function buildMiniTocItems(req) {
|
||||
async function buildMiniTocItems(req: ExtendedRequest): Promise<string | undefined> {
|
||||
const { context } = req
|
||||
if (!context) throw new Error('request not contextualized')
|
||||
const { page } = context
|
||||
|
||||
// get mini TOC items on articles
|
||||
if (!page.showMiniToc) {
|
||||
if (!page || !page.showMiniToc) {
|
||||
return
|
||||
}
|
||||
|
||||
return getMiniTocItems(context.renderedPage, '')
|
||||
return getMiniTocItems(context.renderedPage, 0)
|
||||
}
|
||||
|
||||
export default async function renderPage(req, res) {
|
||||
export default async function renderPage(req: ExtendedRequest, res: Response) {
|
||||
const { context } = req
|
||||
|
||||
// This is a contextualizing the request so that when this `req` is
|
||||
// ultimately passed into the `Error.getInitialProps` function,
|
||||
// which NextJS executes at runtime on errors, so that we can
|
||||
// from there send the error to Failbot.
|
||||
req.FailBot = FailBot
|
||||
req.FailBot = FailBot as Failbot
|
||||
|
||||
if (!context) throw new Error('request not contextualized')
|
||||
const { page } = context
|
||||
const path = req.pagePath || req.path
|
||||
|
||||
@@ -95,7 +102,7 @@ export default async function renderPage(req, res) {
|
||||
// src/pages/404.tsx) but control the status code (and the Cache-Control).
|
||||
//
|
||||
// Create a new request for a real one.
|
||||
const tempReq = new http.IncomingMessage(req)
|
||||
const tempReq = new http.IncomingMessage(req as any) as ExtendedRequest
|
||||
tempReq.method = 'GET'
|
||||
// There is a `src/pages/_notfound.txt`. That's why this will render
|
||||
// a working and valid React component.
|
||||
@@ -129,6 +136,7 @@ export default async function renderPage(req, res) {
|
||||
// Stop processing if the connection was already dropped
|
||||
if (isConnectionDropped(req, res)) return
|
||||
|
||||
if (!req.context) throw new Error('request not contextualized')
|
||||
req.context.renderedPage = await buildRenderedPage(req)
|
||||
req.context.miniTocItems = await buildMiniTocItems(req)
|
||||
|
||||
@@ -142,11 +150,11 @@ export default async function renderPage(req, res) {
|
||||
if (!patterns.homepagePath.test(path)) {
|
||||
if (
|
||||
req.context.currentVersion === 'free-pro-team@latest' ||
|
||||
!allVersions[req.context.currentVersion]
|
||||
!allVersions[req.context.currentVersion!]
|
||||
) {
|
||||
page.fullTitle += ' - ' + context.site.data.ui.header.github_docs
|
||||
page.fullTitle += ' - ' + context.site!.data.ui.header.github_docs
|
||||
} else {
|
||||
const { versionTitle } = allVersions[req.context.currentVersion]
|
||||
const { versionTitle } = allVersions[req.context.currentVersion!]
|
||||
page.fullTitle += ' - '
|
||||
// Some plans don't have the word "GitHub" in them.
|
||||
// E.g. "Enterprise Server 3.5"
|
||||
@@ -163,9 +171,15 @@ export default async function renderPage(req, res) {
|
||||
|
||||
// `?json` query param for debugging request context
|
||||
if (isRequestingJsonForDebugging) {
|
||||
if (req.query.json.length > 1) {
|
||||
const json = req.query.json
|
||||
if (Array.isArray(json)) {
|
||||
// e.g. ?json=page.permalinks&json=currentPath
|
||||
throw new Error("'json' query string can only be 1")
|
||||
}
|
||||
|
||||
if (json) {
|
||||
// deep reference: ?json=page.permalinks
|
||||
return res.json(get(context, req.query.json))
|
||||
return res.json(get(context, req.query.json as string))
|
||||
} else {
|
||||
// dump all the keys: ?json
|
||||
return res.json({
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test, vi } from 'vitest'
|
||||
|
||||
import { get } from '#src/tests/helpers/e2etest.js'
|
||||
import { get } from '@/tests/helpers/e2etest.js'
|
||||
|
||||
describe('bad requests', () => {
|
||||
vi.setConfig({ testTimeout: 60 * 1000 })
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Request } from 'express'
|
||||
import type { Failbot } from '@github/failbot'
|
||||
|
||||
import type enterpriseServerReleases from '@/versions/lib/enterprise-server-releases.d.ts'
|
||||
|
||||
@@ -11,7 +12,7 @@ export type ExtendedRequest = Request & {
|
||||
context?: Context
|
||||
language?: string
|
||||
userLanguage?: string
|
||||
// Add more properties here as needed
|
||||
FailBot?: Failbot
|
||||
}
|
||||
|
||||
// TODO: Make this type from inference using AJV based on the schema.
|
||||
@@ -164,6 +165,8 @@ export type Context = Features & {
|
||||
productGroups?: ProductGroup[]
|
||||
featuredLinks?: FeaturedLinksExpanded
|
||||
currentLearningTrack?: LearningTrack | null
|
||||
renderedPage?: string
|
||||
miniTocItems?: string | undefined
|
||||
}
|
||||
export type LearningTracks = {
|
||||
[group: string]: {
|
||||
@@ -349,6 +352,10 @@ export type Page = {
|
||||
autogenerated?: string
|
||||
featuredLinks?: FeaturedLinksExpanded
|
||||
redirect_from?: string[]
|
||||
showMiniToc?: boolean
|
||||
effectiveDate?: string
|
||||
fullTitle?: string
|
||||
render: (context: Context) => Promise<string>
|
||||
}
|
||||
|
||||
type ChangeLog = {
|
||||
|
||||
Reference in New Issue
Block a user