From da1ef5062f360d5b85a3ee4a33356fc4a5830753 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 10 Dec 2019 09:08:42 -0600 Subject: [PATCH] Refactor next-server some for easier page checking (#9671) * De-dupe pagesManifest * Update handleApiRequest a bit * Simplify handleApiRequest a bit * Update packages/next/next-server/server/next-server.ts Co-authored-by: Joe Haddad --- packages/next/next-server/server/api-utils.ts | 27 ++-- .../next/next-server/server/next-server.ts | 117 +++++++++--------- packages/next/server/next-dev-server.ts | 34 ++--- 3 files changed, 85 insertions(+), 93 deletions(-) diff --git a/packages/next/next-server/server/api-utils.ts b/packages/next/next-server/server/api-utils.ts index b7664b7f33bf1..2de7ab56cd5b4 100644 --- a/packages/next/next-server/server/api-utils.ts +++ b/packages/next/next-server/server/api-utils.ts @@ -1,4 +1,4 @@ -import { IncomingMessage } from 'http' +import { IncomingMessage, ServerResponse } from 'http' import { NextApiResponse, NextApiRequest } from '../lib/utils' import { Stream } from 'stream' import getRawBody from 'raw-body' @@ -11,12 +11,15 @@ export type NextApiRequestCookies = { [key: string]: string } export type NextApiRequestQuery = { [key: string]: string | string[] } export async function apiResolver( - req: NextApiRequest, - res: NextApiResponse, + req: IncomingMessage, + res: ServerResponse, params: any, resolverModule: any, onError?: ({ err }: { err: any }) => Promise ) { + const apiReq = req as NextApiRequest + const apiRes = res as NextApiResponse + try { let config: PageConfig = {} let bodyParser = true @@ -33,32 +36,32 @@ export async function apiResolver( } } // Parsing of cookies - setLazyProp({ req }, 'cookies', getCookieParser(req)) + setLazyProp({ req: apiReq }, 'cookies', getCookieParser(req)) // Parsing query string - setLazyProp({ req, params }, 'query', getQueryParser(req)) + setLazyProp({ req: apiReq, params }, 'query', getQueryParser(req)) // // Parsing of body if (bodyParser) { - req.body = await parseBody( - req, + apiReq.body = await parseBody( + apiReq, config.api && config.api.bodyParser && config.api.bodyParser.sizeLimit ? config.api.bodyParser.sizeLimit : '1mb' ) } - res.status = statusCode => sendStatusCode(res, statusCode) - res.send = data => sendData(res, data) - res.json = data => sendJson(res, data) + apiRes.status = statusCode => sendStatusCode(apiRes, statusCode) + apiRes.send = data => sendData(apiRes, data) + apiRes.json = data => sendJson(apiRes, data) const resolver = interopDefault(resolverModule) resolver(req, res) } catch (err) { if (err instanceof ApiError) { - sendError(res, err.statusCode, err.message) + sendError(apiRes, err.statusCode, err.message) } else { console.error(err) if (onError) await onError({ err }) - sendError(res, 500, 'Internal Server Error') + sendError(apiRes, 500, 'Internal Server Error') } } } diff --git a/packages/next/next-server/server/next-server.ts b/packages/next/next-server/server/next-server.ts index 1c43d8d803868..307ba5a6d5de7 100644 --- a/packages/next/next-server/server/next-server.ts +++ b/packages/next/next-server/server/next-server.ts @@ -76,7 +76,8 @@ export default class Server { pagesDir?: string publicDir: string hasStaticDir: boolean - pagesManifest: string + serverBuildDir: string + pagesManifest?: { [name: string]: string } buildId: string renderOpts: { poweredByHeader: boolean @@ -114,13 +115,6 @@ export default class Server { this.distDir = join(this.dir, this.nextConfig.distDir) this.publicDir = join(this.dir, CLIENT_PUBLIC_FILES_PATH) this.hasStaticDir = fs.existsSync(join(this.dir, 'static')) - this.pagesManifest = join( - this.distDir, - this.nextConfig.target === 'server' - ? SERVER_DIRECTORY - : SERVERLESS_DIRECTORY, - PAGES_MANIFEST - ) // Only serverRuntimeConfig needs the default // publicRuntimeConfig gets it's default in client/index.js @@ -164,19 +158,26 @@ export default class Server { }) } + this.serverBuildDir = join( + this.distDir, + this._isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY + ) + const pagesManifestPath = join(this.serverBuildDir, PAGES_MANIFEST) + + if (!dev) { + this.pagesManifest = require(pagesManifestPath) + } + this.router = new Router(this.generateRoutes()) this.setAssetPrefix(assetPrefix) // call init-server middleware, this is also handled // individually in serverless bundles when deployed if (!dev && this.nextConfig.experimental.plugins) { - const serverPath = join( - this.distDir, - this._isLikeServerless ? 'serverless' : 'server' - ) - const initServer = require(join(serverPath, 'init-server.js')).default + const initServer = require(join(this.serverBuildDir, 'init-server.js')) + .default this.onErrorMiddleware = require(join( - serverPath, + this.serverBuildDir, 'on-error-server.js' )).default initServer() @@ -495,6 +496,24 @@ export default class Server { return routes } + private async getPagePath(pathname: string) { + return getPagePath( + pathname, + this.distDir, + this._isLikeServerless, + this.renderOpts.dev + ) + } + + protected async hasPage(pathname: string): Promise { + let found = false + try { + found = !!(await this.getPagePath(pathname)) + } catch (_) {} + + return found + } + protected async _beforeCatchAllRender( _req: IncomingMessage, _res: ServerResponse, @@ -504,6 +523,9 @@ export default class Server { return false } + // Used to build API page in development + protected async ensureApiPage(pathname: string) {} + /** * Resolves `API` request, in development builds on demand * @param req http request @@ -511,77 +533,53 @@ export default class Server { * @param pathname path of request */ private async handleApiRequest( - req: NextApiRequest, - res: NextApiResponse, + req: IncomingMessage, + res: ServerResponse, pathname: string ) { + let page = pathname let params: Params | boolean = false - let resolverFunction: any - - try { - resolverFunction = await this.resolveApiRequest(pathname) - } catch (err) {} + let pageFound = await this.hasPage(page) - if ( - this.dynamicRoutes && - this.dynamicRoutes.length > 0 && - !resolverFunction - ) { + if (!pageFound && this.dynamicRoutes) { for (const dynamicRoute of this.dynamicRoutes) { params = dynamicRoute.match(pathname) if (params) { - resolverFunction = await this.resolveApiRequest(dynamicRoute.page) + page = dynamicRoute.page + pageFound = true break } } } - if (!resolverFunction) { + if (!pageFound) { return this.render404(req, res) } + // Make sure the page is built before getting the path + // or else it won't be in the manifest yet + await this.ensureApiPage(page) + + const builtPagePath = await this.getPagePath(page) + const pageModule = require(builtPagePath) if (!this.renderOpts.dev && this._isLikeServerless) { - const mod = require(resolverFunction) - if (typeof mod.default === 'function') { - return mod.default(req, res) + if (typeof pageModule.default === 'function') { + return pageModule.default(req, res) } } - await apiResolver( - req, - res, - params, - resolverFunction ? require(resolverFunction) : undefined, - this.onErrorMiddleware - ) - } - - /** - * Resolves path to resolver function - * @param pathname path of request - */ - protected async resolveApiRequest(pathname: string): Promise { - return getPagePath( - pathname, - this.distDir, - this._isLikeServerless, - this.renderOpts.dev - ) + await apiResolver(req, res, params, pageModule, this.onErrorMiddleware) } protected generatePublicRoutes(): Route[] { const routes: Route[] = [] const publicFiles = recursiveReadDirSync(this.publicDir) - const serverBuildPath = join( - this.distDir, - this._isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY - ) - const pagesManifest = require(join(serverBuildPath, PAGES_MANIFEST)) publicFiles.forEach(path => { const unixPath = path.replace(/\\/g, '/') // Only include public files that will not replace a page path - if (!pagesManifest[unixPath]) { + // this should not occur now that we check this during build + if (!this.pagesManifest![unixPath]) { routes.push({ match: route(unixPath), type: 'route', @@ -601,8 +599,9 @@ export default class Server { } protected getDynamicRoutes() { - const manifest = require(this.pagesManifest) - const dynamicRoutedPages = Object.keys(manifest).filter(isDynamicRoute) + const dynamicRoutedPages = Object.keys(this.pagesManifest!).filter( + isDynamicRoute + ) return getSortedRoutes(dynamicRoutedPages).map(page => ({ page, match: getRouteMatcher(getRouteRegex(page)), diff --git a/packages/next/server/next-dev-server.ts b/packages/next/server/next-dev-server.ts index ed0ab2ac9470d..2fc0d8a974bbb 100644 --- a/packages/next/server/next-dev-server.ts +++ b/packages/next/server/next-dev-server.ts @@ -246,6 +246,15 @@ export default class DevServer extends Server { } } + protected async hasPage(pathname: string): Promise { + const pageFile = await findPageFile( + this.pagesDir!, + normalizePagePath(pathname), + this.nextConfig.pageExtensions + ) + return !!pageFile + } + protected async _beforeCatchAllRender( req: IncomingMessage, res: ServerResponse, @@ -257,13 +266,7 @@ export default class DevServer extends Server { // check for a public file, throwing error if there's a // conflicting page if (await this.hasPublicFile(pathname!)) { - const pageFile = await findPageFile( - this.pagesDir!, - normalizePagePath(pathname!), - this.nextConfig.pageExtensions - ) - - if (pageFile) { + if (await this.hasPage(pathname!)) { const err = new Error( `A conflicting public file and page file was found for path ${pathname} https://err.sh/zeit/next.js/conflicting-public-file-page` ) @@ -381,21 +384,8 @@ export default class DevServer extends Server { return !snippet.includes('data-amp-development-mode-only') } - /** - * Check if resolver function is build or request new build for this function - * @param {string} pathname - */ - protected async resolveApiRequest(pathname: string): Promise { - try { - await this.hotReloader!.ensurePage(pathname) - } catch (err) { - // API route dosn't exist => return 404 - if (err.code === 'ENOENT') { - return null - } - } - const resolvedPath = await super.resolveApiRequest(pathname) - return resolvedPath + protected async ensureApiPage(pathname: string) { + return this.hotReloader!.ensurePage(pathname) } async renderToHTML(