mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Add 'unified' SSR compilation target
This commit is contained in:
parent
d2ef34429c
commit
94460eee55
|
@ -1,7 +1,7 @@
|
||||||
import findUp from 'find-up'
|
import findUp from 'find-up'
|
||||||
import {CONFIG_FILE} from 'next-server/constants'
|
import {CONFIG_FILE} from 'next-server/constants'
|
||||||
|
|
||||||
const targets = ['server', 'serverless']
|
const targets = ['server', 'serverless', 'unified']
|
||||||
|
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
webpack: null,
|
webpack: null,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {join} from 'path'
|
||||||
import {stringify} from 'querystring'
|
import {stringify} from 'querystring'
|
||||||
import {PAGES_DIR_ALIAS, DOT_NEXT_ALIAS} from '../lib/constants'
|
import {PAGES_DIR_ALIAS, DOT_NEXT_ALIAS} from '../lib/constants'
|
||||||
import {ServerlessLoaderQuery} from './webpack/loaders/next-serverless-loader'
|
import {ServerlessLoaderQuery} from './webpack/loaders/next-serverless-loader'
|
||||||
|
import {UnifiedLoaderQuery} from './webpack/loaders/next-unified-loader'
|
||||||
|
|
||||||
type PagesMapping = {
|
type PagesMapping = {
|
||||||
[page: string]: string
|
[page: string]: string
|
||||||
|
@ -30,7 +31,7 @@ type Entrypoints = {
|
||||||
server: WebpackEntrypoints
|
server: WebpackEntrypoints
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEntrypoints(pages: PagesMapping, target: 'server'|'serverless', buildId: string, config: any): Entrypoints {
|
export function createEntrypoints(pages: PagesMapping, target: 'server'|'serverless'|'unified', buildId: string, config: any): Entrypoints {
|
||||||
const client: WebpackEntrypoints = {}
|
const client: WebpackEntrypoints = {}
|
||||||
const server: WebpackEntrypoints = {}
|
const server: WebpackEntrypoints = {}
|
||||||
|
|
||||||
|
@ -60,6 +61,22 @@ export function createEntrypoints(pages: PagesMapping, target: 'server'|'serverl
|
||||||
client[bundlePath] = `next-client-pages-loader?${stringify({page, absolutePagePath})}!`
|
client[bundlePath] = `next-client-pages-loader?${stringify({page, absolutePagePath})}!`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if(target === 'unified') {
|
||||||
|
const pagesArray: Array<String> = []
|
||||||
|
const absolutePagePaths: Array<String> = []
|
||||||
|
|
||||||
|
Object.keys(pages).forEach((page) => {
|
||||||
|
if(page === '/_document') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pagesArray.push(page)
|
||||||
|
absolutePagePaths.push(pages[page])
|
||||||
|
});
|
||||||
|
|
||||||
|
const unifiedLoaderOptions: UnifiedLoaderQuery = {pages: pagesArray.join(','), absolutePagePaths: absolutePagePaths.join(','), ...defaultServerlessOptions};
|
||||||
|
server['index.js'] = `next-unified-loader?${stringify(unifiedLoaderOptions)}!`
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
client,
|
client,
|
||||||
server
|
server
|
||||||
|
|
|
@ -47,8 +47,8 @@ export default async function build (dir: string, conf = null): Promise<void> {
|
||||||
])
|
])
|
||||||
|
|
||||||
let result: CompilerResult = {warnings: [], errors: []}
|
let result: CompilerResult = {warnings: [], errors: []}
|
||||||
if (config.target === 'serverless') {
|
if (config.target === 'serverless' || config.target === 'unified') {
|
||||||
if (config.publicRuntimeConfig) throw new Error('Cannot use publicRuntimeConfig with target=serverless https://err.sh/zeit/next.js/serverless-publicRuntimeConfig')
|
if (config.publicRuntimeConfig) throw new Error(`Cannot use publicRuntimeConfig with target=${config.target} https://err.sh/zeit/next.js/serverless-publicRuntimeConfig`)
|
||||||
|
|
||||||
const clientResult = await runCompiler([configs[0]])
|
const clientResult = await runCompiler([configs[0]])
|
||||||
// Fail build if clientResult contains errors
|
// Fail build if clientResult contains errors
|
||||||
|
|
|
@ -26,7 +26,7 @@ function externalsConfig (isServer, target) {
|
||||||
|
|
||||||
// When the serverless target is used all node_modules will be compiled into the output bundles
|
// When the serverless target is used all node_modules will be compiled into the output bundles
|
||||||
// So that the serverless bundles have 0 runtime dependencies
|
// So that the serverless bundles have 0 runtime dependencies
|
||||||
if (!isServer || target === 'serverless') {
|
if (!isServer || target === 'serverless' || target === 'unified') {
|
||||||
return externals
|
return externals
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ function optimizationConfig ({ dev, isServer, totalPages, target }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isServer && target === 'serverless') {
|
if (isServer && (target === 'serverless' || target === 'unified')) {
|
||||||
return {
|
return {
|
||||||
splitChunks: false,
|
splitChunks: false,
|
||||||
minimizer: [
|
minimizer: [
|
||||||
|
@ -166,7 +166,12 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
||||||
.filter((p) => !!p)
|
.filter((p) => !!p)
|
||||||
|
|
||||||
const distDir = path.join(dir, config.distDir)
|
const distDir = path.join(dir, config.distDir)
|
||||||
const outputDir = target === 'serverless' ? 'serverless' : SERVER_DIRECTORY
|
let outputDir = SERVER_DIRECTORY
|
||||||
|
if (target === 'serverless') {
|
||||||
|
outputDir = 'serverless'
|
||||||
|
} else if (target === 'unified') {
|
||||||
|
outputDir = 'unified'
|
||||||
|
}
|
||||||
const outputPath = path.join(distDir, isServer ? outputDir : '')
|
const outputPath = path.join(distDir, isServer ? outputDir : '')
|
||||||
const totalPages = Object.keys(entrypoints).length
|
const totalPages = Object.keys(entrypoints).length
|
||||||
const clientEntries = !isServer ? {
|
const clientEntries = !isServer ? {
|
||||||
|
@ -307,10 +312,10 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
||||||
!isServer && dev && new webpack.DefinePlugin({
|
!isServer && dev && new webpack.DefinePlugin({
|
||||||
'process.env.__NEXT_DIST_DIR': JSON.stringify(distDir)
|
'process.env.__NEXT_DIST_DIR': JSON.stringify(distDir)
|
||||||
}),
|
}),
|
||||||
target !== 'serverless' && isServer && new PagesManifestPlugin(),
|
target !== 'serverless' && target !== 'unified' && isServer && new PagesManifestPlugin(),
|
||||||
!isServer && new BuildManifestPlugin(),
|
!isServer && new BuildManifestPlugin(),
|
||||||
isServer && new NextJsSsrImportPlugin(),
|
isServer && new NextJsSsrImportPlugin(),
|
||||||
target !== 'serverless' && isServer && new NextJsSSRModuleCachePlugin({outputPath}),
|
target !== 'serverless' && target !== 'unified' && isServer && new NextJsSSRModuleCachePlugin({ outputPath }),
|
||||||
!dev && new webpack.IgnorePlugin({
|
!dev && new webpack.IgnorePlugin({
|
||||||
checkResource: (resource) => {
|
checkResource: (resource) => {
|
||||||
return /react-is/.test(resource)
|
return /react-is/.test(resource)
|
||||||
|
|
135
packages/next/build/webpack/loaders/next-unified-loader.ts
Normal file
135
packages/next/build/webpack/loaders/next-unified-loader.ts
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
import { loader } from 'webpack';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { parse } from 'querystring';
|
||||||
|
import { BUILD_MANIFEST, REACT_LOADABLE_MANIFEST } from 'next-server/constants';
|
||||||
|
|
||||||
|
export type UnifiedLoaderQuery = {
|
||||||
|
pages: string
|
||||||
|
distDir: string
|
||||||
|
absolutePagePaths: string
|
||||||
|
absoluteAppPath: string
|
||||||
|
absoluteDocumentPath: string
|
||||||
|
absoluteErrorPath: string
|
||||||
|
buildId: string
|
||||||
|
assetPrefix: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextUnifiedLoader: loader.Loader = function() {
|
||||||
|
const {distDir, absolutePagePaths, pages, buildId, assetPrefix, absoluteAppPath, absoluteDocumentPath, absoluteErrorPath}: UnifiedLoaderQuery =
|
||||||
|
typeof this.query === 'string' ? parse(this.query.substr(1)) : this.query
|
||||||
|
const buildManifest = join(distDir, BUILD_MANIFEST).replace(/\\/g, '/')
|
||||||
|
const reactLoadableManifest = join(distDir, REACT_LOADABLE_MANIFEST).replace(/\\/g, '/')
|
||||||
|
const parsedPagePaths = absolutePagePaths.split(',')
|
||||||
|
const parsedPages = pages.split(',')
|
||||||
|
return `
|
||||||
|
import {renderToHTML} from 'next-server/dist/server/render'
|
||||||
|
import buildManifest from '${buildManifest}'
|
||||||
|
import reactLoadableManifest from '${reactLoadableManifest}'
|
||||||
|
import Document from '${absoluteDocumentPath}'
|
||||||
|
import Error from '${absoluteErrorPath}'
|
||||||
|
import App from '${absoluteAppPath}'
|
||||||
|
${parsedPagePaths
|
||||||
|
.map(
|
||||||
|
(absolutePagePath, index) =>
|
||||||
|
`import page${index} from '${absolutePagePath}'`,
|
||||||
|
)
|
||||||
|
.join('\n')}
|
||||||
|
|
||||||
|
const errorPage = '/_error'
|
||||||
|
const routes = ${JSON.stringify(
|
||||||
|
Object.assign(
|
||||||
|
{},
|
||||||
|
...parsedPages.map((page, index) => ({ [page]: `page${index}` })),
|
||||||
|
),
|
||||||
|
).replace(/"(page\d+)"/g, '$1')}
|
||||||
|
|
||||||
|
function matchRoute(url) {
|
||||||
|
let page = '/index'
|
||||||
|
if (url === '/' && routes.hasOwnProperty(page)) {
|
||||||
|
return [page, routes[page]]
|
||||||
|
}
|
||||||
|
if (routes.hasOwnProperty(url)) {
|
||||||
|
return [url, routes[url]]
|
||||||
|
}
|
||||||
|
|
||||||
|
const splitUrl = url.split('/');
|
||||||
|
for (let i = splitUrl.length; i > 0; i--) {
|
||||||
|
const currentPrefix = splitUrl.slice(0, i).join('/')
|
||||||
|
|
||||||
|
if (routes.hasOwnProperty(currentPrefix)) {
|
||||||
|
return [currentPrefix, routes[currentPrefix]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [errorPage, routes[errorPage]]
|
||||||
|
};
|
||||||
|
|
||||||
|
const errorResponse = { status: 500, body: 'Internal Server Error', headers: {} }
|
||||||
|
|
||||||
|
export async function render(url, query = {}, reqHeaders = {}) {
|
||||||
|
const req = {
|
||||||
|
headers: reqHeaders,
|
||||||
|
method: 'GET',
|
||||||
|
url
|
||||||
|
}
|
||||||
|
const [page, Component] = matchRoute(url)
|
||||||
|
const headers = {}
|
||||||
|
let body = ''
|
||||||
|
const res = {
|
||||||
|
statusCode: 200,
|
||||||
|
finished: false,
|
||||||
|
headersSent: false,
|
||||||
|
setHeader(name, value) {
|
||||||
|
headers[name.toLowerCase()] = value
|
||||||
|
},
|
||||||
|
getHeader(name) {
|
||||||
|
return headers[name.toLowerCase()]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const options = {
|
||||||
|
App,
|
||||||
|
Document,
|
||||||
|
buildManifest,
|
||||||
|
reactLoadableManifest,
|
||||||
|
buildId: ${JSON.stringify(buildId)},
|
||||||
|
assetPrefix: ${JSON.stringify(assetPrefix)},
|
||||||
|
Component
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (page === '/_error') {
|
||||||
|
res.statusCode = 404
|
||||||
|
}
|
||||||
|
body = await renderToHTML(req, res, page, query, Object.assign({}, options))
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === 'ENOENT') {
|
||||||
|
res.statusCode = 404
|
||||||
|
body = await renderToHTML(req, res, "/_error", query, Object.assign({}, options, {
|
||||||
|
Component: Error
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
console.error(err)
|
||||||
|
try {
|
||||||
|
res.statusCode = 500
|
||||||
|
body = await renderToHTML(req, res, "/_error", query, Object.assign({}, options, {
|
||||||
|
Component: Error,
|
||||||
|
err
|
||||||
|
}))
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return errorResponse; // non-html fatal/fallback error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: res.statusCode,
|
||||||
|
headers: Object.assign({
|
||||||
|
'content-type': 'text/html; charset=utf-8',
|
||||||
|
'content-length': Buffer.byteLength(body)
|
||||||
|
}, headers),
|
||||||
|
body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextUnifiedLoader;
|
Loading…
Reference in a new issue