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:
parent
cd0a1767f4
commit
32451e979e
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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[]
|
||||||
}
|
}
|
||||||
|
|
21
packages/next-server/server/load-components.ts
Normal file
21
packages/next-server/server/load-components.ts
Normal 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}
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue