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 })