Skip to content

Commit

Permalink
Abstract out native filesystem usage from the base server (#33226)
Browse files Browse the repository at this point in the history
This PR moves `require` and `fs` usage from the base server to the node server. Closes #32322.

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `yarn lint`
  • Loading branch information
shuding committed Jan 12, 2022
1 parent 6c8808e commit 5572eb3
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 118 deletions.
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)
}
}

0 comments on commit 5572eb3

Please sign in to comment.