1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

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.
This commit is contained in:
Tim Neutkens 2018-12-18 17:12:49 +01:00 committed by GitHub
parent cd0a1767f4
commit 32451e979e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 70 additions and 32 deletions

View file

@ -5,7 +5,7 @@ export type ManifestItem = {
publicPath: string publicPath: string
} }
type Manifest = {[moduleId: string]: ManifestItem[]} export type Manifest = {[moduleId: string]: ManifestItem[]}
type DynamicImportBundles = Set<ManifestItem> type DynamicImportBundles = Set<ManifestItem>

View file

@ -1,6 +1,7 @@
import {normalizePagePath} from './require' import {normalizePagePath} from './require'
type BuildManifest = { export type BuildManifest = {
devFiles: string[],
pages: { pages: {
[page: string]: string[] [page: string]: string[]
} }

View file

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

View file

@ -10,9 +10,10 @@ import {serveStatic} from './serve-static'
import Router, {route, Route} from './router' import Router, {route, Route} from './router'
import { isInternalUrl, isBlockedPage } from './utils' import { isInternalUrl, isBlockedPage } from './utils'
import loadConfig from 'next-server/next-config' 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 asset from '../lib/asset'
import * as envConfig from '../lib/runtime-config' import * as envConfig from '../lib/runtime-config'
import {loadComponents} from './load-components'
type NextConfig = any type NextConfig = any
@ -79,6 +80,11 @@ export default class Server {
return PHASE_PRODUCTION_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<void> { private handleRequest (req: IncomingMessage, res: ServerResponse, parsedUrl?: UrlWithParsedQuery): Promise<void> {
// Parse url if parsedUrl not provided // Parse url if parsedUrl not provided
if (!parsedUrl || typeof parsedUrl !== 'object') { if (!parsedUrl || typeof parsedUrl !== 'object') {
@ -94,7 +100,7 @@ export default class Server {
res.statusCode = 200 res.statusCode = 200
return this.run(req, res, parsedUrl) return this.run(req, res, parsedUrl)
.catch((err) => { .catch((err) => {
if (!this.quiet) console.error(err) this.logError(err)
res.statusCode = 500 res.statusCode = 500
res.end('Internal Server Error') res.end('Internal Server Error')
}) })
@ -224,17 +230,22 @@ export default class Server {
return this.sendHTML(req, res, html) 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<string|null> { public async renderToHTML (req: IncomingMessage, res: ServerResponse, pathname: string, query: ParsedUrlQuery = {}): Promise<string|null> {
try { try {
// To make sure the try/catch is executed // 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 return html
} catch (err) { } catch (err) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
res.statusCode = 404 res.statusCode = 404
return this.renderErrorToHTML(null, req, res, pathname, query) return this.renderErrorToHTML(null, req, res, pathname, query)
} else { } else {
if (!this.quiet) console.error(err) this.logError(err)
res.statusCode = 500 res.statusCode = 500
return this.renderErrorToHTML(err, req, res, pathname, query) 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 = {}) { 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<void> { public async render404 (req: IncomingMessage, res: ServerResponse, parsedUrl?: UrlWithParsedQuery): Promise<void> {

View file

@ -3,15 +3,14 @@ import { ParsedUrlQuery } from 'querystring'
import { join } from 'path' import { join } from 'path'
import React from 'react' import React from 'react'
import { renderToString, renderToStaticMarkup } from 'react-dom/server' import { renderToString, renderToStaticMarkup } from 'react-dom/server'
import {requirePage} from './require'
import Router from '../lib/router/router' import Router from '../lib/router/router'
import { loadGetInitialProps, isResSent } from '../lib/utils' import { loadGetInitialProps, isResSent } from '../lib/utils'
import Head, { defaultHead } from '../lib/head' import Head, { defaultHead } from '../lib/head'
import Loadable from '../lib/loadable' import Loadable from '../lib/loadable'
import LoadableCapture from '../lib/loadable-capture' import LoadableCapture from '../lib/loadable-capture'
import { BUILD_MANIFEST, REACT_LOADABLE_MANIFEST, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH } from 'next-server/constants' import { SERVER_DIRECTORY } from 'next-server/constants'
import {getDynamicImportBundles, ManifestItem} from './get-dynamic-import-bundles' import {getDynamicImportBundles, Manifest as ReactLoadableManifest, ManifestItem} from './get-dynamic-import-bundles'
import {getPageFiles} from './get-page-files' import {getPageFiles, BuildManifest} from './get-page-files'
type Enhancer = (Component: React.ComponentType) => React.ComponentType type Enhancer = (Component: React.ComponentType) => React.ComponentType
type ComponentsEnhancer = {enhanceApp?: Enhancer, enhanceComponent?: Enhancer}|Enhancer 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<any>) => string, element: React.ReactElement<any>): {html: string, head: any} { function render(renderElementToString: (element: React.ReactElement<any>) => string, element: React.ReactElement<any>): {html: string, head: any} {
let html let html
let head let head
@ -59,7 +54,13 @@ type RenderOpts = {
assetPrefix?: string, assetPrefix?: string,
err?: Error|null, err?: Error|null,
nextExport?: boolean, 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, { 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<string|null> { export async function renderToHTML (req: IncomingMessage, res: ServerResponse, pathname: string, query: ParsedUrlQuery, renderOpts: RenderOpts): Promise<string|null> {
const { const {
err, err,
buildId,
distDir,
dev = false, dev = false,
staticMarkup = false staticMarkup = false,
App,
Document,
Component,
buildManifest,
reactLoadableManifest,
ErrorDebug
} = renderOpts } = 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 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 reactLoadableModules: string[] = []
const renderPage = (options: ComponentsEnhancer = {}): {html: string, head: any} => { const renderPage = (options: ComponentsEnhancer = {}): {html: string, head: any} => {
const {App: EnhancedApp, Component: EnhancedComponent} = enhanceComponents(options, App, Component)
const renderElementToString = staticMarkup ? renderToStaticMarkup : renderToString const renderElementToString = staticMarkup ? renderToStaticMarkup : renderToString
if(err && dev) { if(err && ErrorDebug) {
const ErrorDebug = require(join(distDir, SERVER_DIRECTORY, 'error-debug')).default
return render(renderElementToString, <ErrorDebug error={err} />) return render(renderElementToString, <ErrorDebug error={err} />)
} }
const {App: EnhancedApp, Component: EnhancedComponent} = enhanceComponents(options, App, Component)
return render(renderElementToString, return render(renderElementToString,
<LoadableCapture report={(moduleName) => reactLoadableModules.push(moduleName)}> <LoadableCapture report={(moduleName) => reactLoadableModules.push(moduleName)}>
<EnhancedApp <EnhancedApp

View file

@ -136,6 +136,8 @@ export default async function (dir, options, configuration) {
env: process.env env: process.env
}) })
worker.send({ worker.send({
distDir,
buildId,
exportPaths: chunk.paths, exportPaths: chunk.paths,
exportPathMap: chunk.pathMap, exportPathMap: chunk.pathMap,
outDir, outDir,

View file

@ -7,10 +7,13 @@ const mkdirp = require('mkdirp-then')
const { renderToHTML } = require('next-server/dist/server/render') const { renderToHTML } = require('next-server/dist/server/render')
const { writeFile } = require('fs') const { writeFile } = require('fs')
const Sema = require('async-sema') const Sema = require('async-sema')
const {loadComponents} = require('next-server/dist/server/load-components')
process.on( process.on(
'message', 'message',
async ({ async ({
distDir,
buildId,
exportPaths, exportPaths,
exportPathMap, exportPathMap,
outDir, outDir,
@ -37,7 +40,8 @@ process.on(
const htmlFilepath = join(outDir, htmlFilename) const htmlFilepath = join(outDir, htmlFilename)
await mkdirp(baseDir) await mkdirp(baseDir)
const html = await renderToHTML(req, res, page, query, renderOpts) const components = await loadComponents(distDir, buildId, page)
const html = await renderToHTML(req, res, page, query, {...components, ...renderOpts})
await new Promise((resolve, reject) => await new Promise((resolve, reject) =>
writeFile( writeFile(
htmlFilepath, htmlFilepath,

View file

@ -3,11 +3,13 @@ import { join } from 'path'
import HotReloader from './hot-reloader' import HotReloader from './hot-reloader'
import {route} from 'next-server/dist/server/router' import {route} from 'next-server/dist/server/router'
import {PHASE_DEVELOPMENT_SERVER} from 'next-server/constants' import {PHASE_DEVELOPMENT_SERVER} from 'next-server/constants'
import ErrorDebug from './error-debug'
export default class DevServer extends Server { export default class DevServer extends Server {
constructor (options) { constructor (options) {
super(options) super(options)
this.renderOpts.dev = true this.renderOpts.dev = true
this.renderOpts.ErrorDebug = ErrorDebug
this.devReady = new Promise(resolve => { this.devReady = new Promise(resolve => {
this.setDevReady = resolve this.setDevReady = resolve
}) })