Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstract out native filesystem usage from the base server #33226

Merged
merged 3 commits into from Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/next/lib/load-custom-routes.ts
@@ -1,6 +1,7 @@
import type { NextConfig } from '../server/config'

import chalk from 'next/dist/compiled/chalk'
import { parse as parseUrl } from 'url'
import { NextConfig } from '../server/config'
import * as pathToRegexp from 'next/dist/compiled/path-to-regexp'
import { escapeStringRegexp } from '../shared/lib/escape-regexp'
import {
Expand Down
136 changes: 23 additions & 113 deletions packages/next/server/base-server.ts
Expand Up @@ -60,10 +60,7 @@ import {
} from './api-utils'
import { isTargetLikeServerless } from './config'
import pathMatch from '../shared/lib/router/utils/path-match'
import { loadComponents } from './load-components'
import { normalizePagePath } from './normalize-page-path'
import { renderToHTML } from './render'
import { getPagePath, requireFontManifest } from './require'
import Router, { replaceBasePath, route } from './router'
import {
compileNonPath,
Expand All @@ -90,7 +87,6 @@ import { PreviewData } from 'next/types'
import ResponseCache from './response-cache'
import { parseNextUrl } from '../shared/lib/router/utils/parse-next-url'
import isError, { getProperError } from '../lib/is-error'
import { getMiddlewareInfo } from './require'
import { MIDDLEWARE_ROUTE } from '../lib/constants'
import { run } from './web/sandbox'
import { addRequestMeta, getRequestMeta } from './request-meta'
Expand Down Expand Up @@ -193,7 +189,7 @@ export default abstract class Server {
basePath: string
optimizeFonts: boolean
images: string
fontManifest: FontManifest
fontManifest?: FontManifest
optimizeImages: boolean
disableOptimizedLoading?: boolean
optimizeCss: any
Expand All @@ -220,7 +216,21 @@ export default abstract class Server {
protected abstract getPagesManifest(): PagesManifest | undefined
protected abstract getBuildId(): string
protected abstract generatePublicRoutes(): Route[]
protected abstract generateImageRoutes(): Route[]
protected abstract getFilesystemPaths(): Set<string>
protected abstract findPageComponents(
pathname: string,
query?: NextParsedUrlQuery,
params?: Params | null
): Promise<FindComponentsResult | null>
protected abstract getMiddlewareInfo(params: {
dev?: boolean
distDir: string
page: string
serverless: boolean
}): { name: string; paths: string[]; env: string[] }
protected abstract getPagePath(pathname: string, locales?: string[]): string
protected abstract getFontManifest(): FontManifest | undefined

public constructor({
dir = '.',
Expand Down Expand Up @@ -272,8 +282,8 @@ export default abstract class Server {
optimizeFonts: !!this.nextConfig.optimizeFonts && !dev,
fontManifest:
this.nextConfig.optimizeFonts && !dev
? requireFontManifest(this.distDir, this._isLikeServerless)
: null,
? this.getFontManifest()
: undefined,
optimizeImages: !!this.nextConfig.experimental.optimizeImages,
optimizeCss: this.nextConfig.experimental.optimizeCss,
disableOptimizedLoading:
Expand Down Expand Up @@ -652,7 +662,7 @@ export default abstract class Server {
): Promise<boolean> {
try {
return (
getMiddlewareInfo({
this.getMiddlewareInfo({
dev: this.renderOpts.dev,
distDir: this.distDir,
page: pathname,
Expand Down Expand Up @@ -715,7 +725,7 @@ export default abstract class Server {

await this.ensureMiddleware(middleware.page, middleware.ssr)

const middlewareInfo = getMiddlewareInfo({
const middlewareInfo = this.getMiddlewareInfo({
dev: this.renderOpts.dev,
distDir: this.distDir,
page: middleware.page,
Expand Down Expand Up @@ -792,8 +802,8 @@ export default abstract class Server {
dynamicRoutes: DynamicRoutes | undefined
locales: string[]
} {
const server: Server = this
const publicRoutes = this.generatePublicRoutes()
const imageRoutes = this.generateImageRoutes()

const staticFilesRoute = this.hasStaticDir
? [
Expand Down Expand Up @@ -926,32 +936,7 @@ export default abstract class Server {
}
},
},
{
match: route('/_next/image'),
type: 'route',
name: '_next/image catchall',
fn: (req, res, _params, parsedUrl) => {
if (this.minimalMode) {
res.statusCode = 400
res.end('Bad Request')
return {
finished: true,
}
}
const { imageOptimizer } =
require('./image-optimizer') as typeof import('./image-optimizer')

return imageOptimizer(
server,
req,
res,
parsedUrl,
server.nextConfig,
server.distDir,
this.renderOpts.dev
)
},
},
...imageRoutes,
{
match: route('/_next/:path*'),
type: 'route',
Expand Down Expand Up @@ -1442,26 +1427,10 @@ export default abstract class Server {
}
}

private async getPagePath(
pathname: string,
locales?: string[]
): Promise<string> {
return getPagePath(
pathname,
this.distDir,
this._isLikeServerless,
this.renderOpts.dev,
locales
)
}

protected async hasPage(pathname: string): Promise<boolean> {
let found = false
try {
found = !!(await this.getPagePath(
pathname,
this.nextConfig.i18n?.locales
))
found = !!this.getPagePath(pathname, this.nextConfig.i18n?.locales)
} catch (_) {}

return found
Expand Down Expand Up @@ -1515,7 +1484,7 @@ export default abstract class Server {

let builtPagePath
try {
builtPagePath = await this.getPagePath(page)
builtPagePath = this.getPagePath(page)
} catch (err) {
if (isError(err) && err.code === 'ENOENT') {
return false
Expand Down Expand Up @@ -1715,65 +1684,6 @@ export default abstract class Server {
})
}

protected async findPageComponents(
pathname: string,
query: NextParsedUrlQuery = {},
params: Params | null = null
): Promise<FindComponentsResult | null> {
let paths = [
// try serving a static AMP version first
query.amp ? normalizePagePath(pathname) + '.amp' : null,
pathname,
].filter(Boolean)

if (query.__nextLocale) {
paths = [
...paths.map(
(path) => `/${query.__nextLocale}${path === '/' ? '' : path}`
),
...paths,
]
}

for (const pagePath of paths) {
try {
const components = await loadComponents(
this.distDir,
pagePath!,
!this.renderOpts.dev && this._isLikeServerless
)

if (
query.__nextLocale &&
typeof components.Component === 'string' &&
!pagePath?.startsWith(`/${query.__nextLocale}`)
) {
// if loading an static HTML file the locale is required
// to be present since all HTML files are output under their locale
continue
}

return {
components,
query: {
...(components.getStaticProps
? ({
amp: query.amp,
_nextDataReq: query._nextDataReq,
__nextLocale: query.__nextLocale,
__nextDefaultLocale: query.__nextDefaultLocale,
} as NextParsedUrlQuery)
: query),
...(params || {}),
},
}
} catch (err) {
if (isError(err) && err.code !== 'ENOENT') throw err
}
}
return null
}

protected async getStaticPaths(pathname: string): Promise<{
staticPaths: string[] | undefined
fallbackMode: 'static' | 'blocking' | false
Expand Down
127 changes: 124 additions & 3 deletions packages/next/server/next-server.ts
@@ -1,13 +1,20 @@
import type { Route } from './router'
import type { Route, Params } from './router'
import type { CacheFs } from '../shared/lib/utils'
import type { NextParsedUrlQuery } from './request-meta'
import type { FontManifest } from './font-utils'

import fs from 'fs'
import { join, relative } from 'path'
import { PAGES_MANIFEST, BUILD_ID_FILE } from '../shared/lib/constants'
import { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin'
import { recursiveReadDirSync } from './lib/recursive-readdir-sync'
import { route } from './router'
import BaseServer from './base-server'
import BaseServer, { FindComponentsResult } from './base-server'
import { getMiddlewareInfo } from './require'
import { loadComponents } from './load-components'
import isError from '../lib/is-error'
import { normalizePagePath } from './normalize-page-path'
import { getPagePath, requireFontManifest } from './require'
import { BUILD_ID_FILE, PAGES_MANIFEST } from '../shared/lib/constants'

export * from './base-server'

Expand Down Expand Up @@ -36,6 +43,38 @@ export default class NextNodeServer extends BaseServer {
}
}

protected generateImageRoutes(): Route[] {
const server = this
return [
{
match: route('/_next/image'),
type: 'route',
name: '_next/image catchall',
fn: (req, res, _params, parsedUrl) => {
if (this.minimalMode) {
res.statusCode = 400
res.end('Bad Request')
return {
finished: true,
}
}
const { imageOptimizer } =
require('./image-optimizer') as typeof import('./image-optimizer')

return imageOptimizer(
server,
req,
res,
parsedUrl,
server.nextConfig,
server.distDir,
this.renderOpts.dev
)
},
},
]
}

protected generatePublicRoutes(): Route[] {
if (!fs.existsSync(this.publicDir)) return []

Expand Down Expand Up @@ -135,6 +174,79 @@ export default class NextNodeServer extends BaseServer {
]))
}

protected getPagePath(pathname: string, locales?: string[]): string {
return getPagePath(
pathname,
this.distDir,
this._isLikeServerless,
this.renderOpts.dev,
locales
)
}

protected async findPageComponents(
pathname: string,
query: NextParsedUrlQuery = {},
params: Params | null = null
): Promise<FindComponentsResult | null> {
let paths = [
// try serving a static AMP version first
query.amp ? normalizePagePath(pathname) + '.amp' : null,
pathname,
].filter(Boolean)

if (query.__nextLocale) {
paths = [
...paths.map(
(path) => `/${query.__nextLocale}${path === '/' ? '' : path}`
),
...paths,
]
}

for (const pagePath of paths) {
try {
const components = await loadComponents(
this.distDir,
pagePath!,
!this.renderOpts.dev && this._isLikeServerless
)

if (
query.__nextLocale &&
typeof components.Component === 'string' &&
!pagePath?.startsWith(`/${query.__nextLocale}`)
) {
// if loading an static HTML file the locale is required
// to be present since all HTML files are output under their locale
continue
}

return {
components,
query: {
...(components.getStaticProps
? ({
amp: query.amp,
_nextDataReq: query._nextDataReq,
__nextLocale: query.__nextLocale,
__nextDefaultLocale: query.__nextDefaultLocale,
} as NextParsedUrlQuery)
: query),
...(params || {}),
},
}
} catch (err) {
if (isError(err) && err.code !== 'ENOENT') throw err
}
}
return null
}

protected getFontManifest(): FontManifest {
return requireFontManifest(this.distDir, this._isLikeServerless)
}

protected getCacheFilesystem(): CacheFs {
return {
readFile: (f) => fs.promises.readFile(f, 'utf8'),
Expand All @@ -144,4 +256,13 @@ export default class NextNodeServer extends BaseServer {
stat: (f) => fs.promises.stat(f),
}
}

protected getMiddlewareInfo(params: {
dev?: boolean
distDir: string
page: string
serverless: boolean
}) {
return getMiddlewareInfo(params)
}
}