From 32451e979e04ac563d4c9eadaf8db8dc97e95b3a Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Tue, 18 Dec 2018 17:12:49 +0100 Subject: [PATCH] Move out requires from renderToHTML (#5915) This brings us one step closer to outputting serverless functions as renderToHTML now renders the passed components, which allows us to bundle the renderToHTML function together with statically imported components in webpack. --- .../server/get-dynamic-import-bundles.ts | 2 +- packages/next-server/server/get-page-files.ts | 3 +- .../next-server/server/load-components.ts | 21 +++++++++ packages/next-server/server/next-server.ts | 21 ++++++--- packages/next-server/server/render.tsx | 45 +++++++++---------- packages/next/export/index.js | 2 + packages/next/export/worker.js | 6 ++- packages/next/server/next-dev-server.js | 2 + 8 files changed, 70 insertions(+), 32 deletions(-) create mode 100644 packages/next-server/server/load-components.ts diff --git a/packages/next-server/server/get-dynamic-import-bundles.ts b/packages/next-server/server/get-dynamic-import-bundles.ts index 30c6aaf2..81ae8300 100644 --- a/packages/next-server/server/get-dynamic-import-bundles.ts +++ b/packages/next-server/server/get-dynamic-import-bundles.ts @@ -5,7 +5,7 @@ export type ManifestItem = { publicPath: string } -type Manifest = {[moduleId: string]: ManifestItem[]} +export type Manifest = {[moduleId: string]: ManifestItem[]} type DynamicImportBundles = Set diff --git a/packages/next-server/server/get-page-files.ts b/packages/next-server/server/get-page-files.ts index ae2df6ce..2621c027 100644 --- a/packages/next-server/server/get-page-files.ts +++ b/packages/next-server/server/get-page-files.ts @@ -1,6 +1,7 @@ import {normalizePagePath} from './require' -type BuildManifest = { +export type BuildManifest = { + devFiles: string[], pages: { [page: string]: string[] } diff --git a/packages/next-server/server/load-components.ts b/packages/next-server/server/load-components.ts new file mode 100644 index 00000000..df5ff8bd --- /dev/null +++ b/packages/next-server/server/load-components.ts @@ -0,0 +1,21 @@ +import {join} from 'path' +import {CLIENT_STATIC_FILES_PATH, BUILD_MANIFEST, REACT_LOADABLE_MANIFEST, SERVER_DIRECTORY} from 'next-server/constants' +import {requirePage} from './require' + +function interopDefault (mod: any) { + return mod.default || mod +} + +export async function loadComponents (distDir: string, buildId: string, pathname: string) { + const documentPath = join(distDir, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH, buildId, 'pages', '_document') + const appPath = join(distDir, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH, buildId, 'pages', '_app') + let [buildManifest, reactLoadableManifest, Component, Document, App] = await Promise.all([ + require(join(distDir, BUILD_MANIFEST)), + require(join(distDir, REACT_LOADABLE_MANIFEST)), + interopDefault(requirePage(pathname, distDir)), + interopDefault(require(documentPath)), + interopDefault(require(appPath)) + ]) + + return {buildManifest, reactLoadableManifest, Component, Document, App} +} diff --git a/packages/next-server/server/next-server.ts b/packages/next-server/server/next-server.ts index 9bef61a9..96bb4ff9 100644 --- a/packages/next-server/server/next-server.ts +++ b/packages/next-server/server/next-server.ts @@ -10,9 +10,10 @@ import {serveStatic} from './serve-static' import Router, {route, Route} from './router' import { isInternalUrl, isBlockedPage } from './utils' import loadConfig from 'next-server/next-config' -import {PHASE_PRODUCTION_SERVER, BUILD_ID_FILE, CLIENT_STATIC_FILES_PATH, CLIENT_STATIC_FILES_RUNTIME} from 'next-server/constants' +import {PHASE_PRODUCTION_SERVER, BUILD_ID_FILE, CLIENT_STATIC_FILES_PATH, CLIENT_STATIC_FILES_RUNTIME, BUILD_MANIFEST, REACT_LOADABLE_MANIFEST, SERVER_DIRECTORY} from 'next-server/constants' import * as asset from '../lib/asset' import * as envConfig from '../lib/runtime-config' +import {loadComponents} from './load-components' type NextConfig = any @@ -79,6 +80,11 @@ export default class Server { return PHASE_PRODUCTION_SERVER } + private logError(...args: any): void { + if(this.quiet) return + console.error(...args) + } + private handleRequest (req: IncomingMessage, res: ServerResponse, parsedUrl?: UrlWithParsedQuery): Promise { // Parse url if parsedUrl not provided if (!parsedUrl || typeof parsedUrl !== 'object') { @@ -94,7 +100,7 @@ export default class Server { res.statusCode = 200 return this.run(req, res, parsedUrl) .catch((err) => { - if (!this.quiet) console.error(err) + this.logError(err) res.statusCode = 500 res.end('Internal Server Error') }) @@ -224,17 +230,22 @@ export default class Server { return this.sendHTML(req, res, html) } + private async renderToHTMLWithComponents(req: IncomingMessage, res: ServerResponse, pathname: string, query: ParsedUrlQuery = {}, opts: any) { + const result = await loadComponents(this.distDir, this.buildId, pathname) + return renderToHTML(req, res, pathname, query, {...result, ...opts}) + } + public async renderToHTML (req: IncomingMessage, res: ServerResponse, pathname: string, query: ParsedUrlQuery = {}): Promise { try { // To make sure the try/catch is executed - const html = await renderToHTML(req, res, pathname, query, this.renderOpts) + const html = await this.renderToHTMLWithComponents(req, res, pathname, query, this.renderOpts) return html } catch (err) { if (err.code === 'ENOENT') { res.statusCode = 404 return this.renderErrorToHTML(null, req, res, pathname, query) } else { - if (!this.quiet) console.error(err) + this.logError(err) res.statusCode = 500 return this.renderErrorToHTML(err, req, res, pathname, query) } @@ -251,7 +262,7 @@ export default class Server { } public async renderErrorToHTML (err: Error|null, req: IncomingMessage, res: ServerResponse, _pathname: string, query: ParsedUrlQuery = {}) { - return renderToHTML(req, res, '/_error', query, {...this.renderOpts, err}) + return this.renderToHTMLWithComponents(req, res, '/_error', query, {...this.renderOpts, err}) } public async render404 (req: IncomingMessage, res: ServerResponse, parsedUrl?: UrlWithParsedQuery): Promise { diff --git a/packages/next-server/server/render.tsx b/packages/next-server/server/render.tsx index aa8c9c9c..b2cc2c75 100644 --- a/packages/next-server/server/render.tsx +++ b/packages/next-server/server/render.tsx @@ -3,15 +3,14 @@ import { ParsedUrlQuery } from 'querystring' import { join } from 'path' import React from 'react' import { renderToString, renderToStaticMarkup } from 'react-dom/server' -import {requirePage} from './require' import Router from '../lib/router/router' import { loadGetInitialProps, isResSent } from '../lib/utils' import Head, { defaultHead } from '../lib/head' import Loadable from '../lib/loadable' import LoadableCapture from '../lib/loadable-capture' -import { BUILD_MANIFEST, REACT_LOADABLE_MANIFEST, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH } from 'next-server/constants' -import {getDynamicImportBundles, ManifestItem} from './get-dynamic-import-bundles' -import {getPageFiles} from './get-page-files' +import { SERVER_DIRECTORY } from 'next-server/constants' +import {getDynamicImportBundles, Manifest as ReactLoadableManifest, ManifestItem} from './get-dynamic-import-bundles' +import {getPageFiles, BuildManifest} from './get-page-files' type Enhancer = (Component: React.ComponentType) => React.ComponentType type ComponentsEnhancer = {enhanceApp?: Enhancer, enhanceComponent?: Enhancer}|Enhancer @@ -34,10 +33,6 @@ function enhanceComponents(options: ComponentsEnhancer, App: React.ComponentType } } -function interoptDefault(mod: any) { - return mod.default || mod -} - function render(renderElementToString: (element: React.ReactElement) => string, element: React.ReactElement): {html: string, head: any} { let html let head @@ -59,7 +54,13 @@ type RenderOpts = { assetPrefix?: string, err?: Error|null, nextExport?: boolean, - dev?: boolean + dev?: boolean, + buildManifest: BuildManifest, + reactLoadableManifest: ReactLoadableManifest, + Component: React.ComponentType, + Document: React.ComponentType, + App: React.ComponentType, + ErrorDebug?: React.ComponentType<{error: Error}> } function renderDocument(Document: React.ComponentType, { @@ -114,20 +115,16 @@ function renderDocument(Document: React.ComponentType, { export async function renderToHTML (req: IncomingMessage, res: ServerResponse, pathname: string, query: ParsedUrlQuery, renderOpts: RenderOpts): Promise { const { err, - buildId, - distDir, dev = false, - staticMarkup = false + staticMarkup = false, + App, + Document, + Component, + buildManifest, + reactLoadableManifest, + ErrorDebug } = renderOpts - const documentPath = join(distDir, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH, buildId, 'pages', '_document') - const appPath = join(distDir, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH, buildId, 'pages', '_app') - let [buildManifest, reactLoadableManifest, Component, Document, App] = await Promise.all([ - require(join(distDir, BUILD_MANIFEST)), - require(join(distDir, REACT_LOADABLE_MANIFEST)), - interoptDefault(requirePage(pathname, distDir)), - interoptDefault(require(documentPath)), - interoptDefault(require(appPath)) - ]) + await Loadable.preloadAll() // Make sure all dynamic imports are loaded @@ -165,14 +162,14 @@ export async function renderToHTML (req: IncomingMessage, res: ServerResponse, p const reactLoadableModules: string[] = [] const renderPage = (options: ComponentsEnhancer = {}): {html: string, head: any} => { - const {App: EnhancedApp, Component: EnhancedComponent} = enhanceComponents(options, App, Component) const renderElementToString = staticMarkup ? renderToStaticMarkup : renderToString - if(err && dev) { - const ErrorDebug = require(join(distDir, SERVER_DIRECTORY, 'error-debug')).default + if(err && ErrorDebug) { return render(renderElementToString, ) } + const {App: EnhancedApp, Component: EnhancedComponent} = enhanceComponents(options, App, Component) + return render(renderElementToString, reactLoadableModules.push(moduleName)}> writeFile( htmlFilepath, diff --git a/packages/next/server/next-dev-server.js b/packages/next/server/next-dev-server.js index 09652da1..8a4b630a 100644 --- a/packages/next/server/next-dev-server.js +++ b/packages/next/server/next-dev-server.js @@ -3,11 +3,13 @@ import { join } from 'path' import HotReloader from './hot-reloader' import {route} from 'next-server/dist/server/router' import {PHASE_DEVELOPMENT_SERVER} from 'next-server/constants' +import ErrorDebug from './error-debug' export default class DevServer extends Server { constructor (options) { super(options) this.renderOpts.dev = true + this.renderOpts.ErrorDebug = ErrorDebug this.devReady = new Promise(resolve => { this.setDevReady = resolve })