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 {CONFIG_FILE} from 'next-server/constants'
|
||||
|
||||
const targets = ['server', 'serverless']
|
||||
const targets = ['server', 'serverless', 'unified']
|
||||
|
||||
const defaultConfig = {
|
||||
webpack: null,
|
||||
|
|
|
@ -2,6 +2,7 @@ import {join} from 'path'
|
|||
import {stringify} from 'querystring'
|
||||
import {PAGES_DIR_ALIAS, DOT_NEXT_ALIAS} from '../lib/constants'
|
||||
import {ServerlessLoaderQuery} from './webpack/loaders/next-serverless-loader'
|
||||
import {UnifiedLoaderQuery} from './webpack/loaders/next-unified-loader'
|
||||
|
||||
type PagesMapping = {
|
||||
[page: string]: string
|
||||
|
@ -30,7 +31,7 @@ type Entrypoints = {
|
|||
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 server: WebpackEntrypoints = {}
|
||||
|
||||
|
@ -60,6 +61,22 @@ export function createEntrypoints(pages: PagesMapping, target: 'server'|'serverl
|
|||
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 {
|
||||
client,
|
||||
server
|
||||
|
|
|
@ -47,8 +47,8 @@ export default async function build (dir: string, conf = null): Promise<void> {
|
|||
])
|
||||
|
||||
let result: CompilerResult = {warnings: [], errors: []}
|
||||
if (config.target === 'serverless') {
|
||||
if (config.publicRuntimeConfig) throw new Error('Cannot use publicRuntimeConfig with target=serverless https://err.sh/zeit/next.js/serverless-publicRuntimeConfig')
|
||||
if (config.target === 'serverless' || config.target === 'unified') {
|
||||
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]])
|
||||
// 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
|
||||
// So that the serverless bundles have 0 runtime dependencies
|
||||
if (!isServer || target === 'serverless') {
|
||||
if (!isServer || target === 'serverless' || target === 'unified') {
|
||||
return externals
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,7 @@ function optimizationConfig ({ dev, isServer, totalPages, target }) {
|
|||
}
|
||||
}
|
||||
|
||||
if (isServer && target === 'serverless') {
|
||||
if (isServer && (target === 'serverless' || target === 'unified')) {
|
||||
return {
|
||||
splitChunks: false,
|
||||
minimizer: [
|
||||
|
@ -166,7 +166,12 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
|||
.filter((p) => !!p)
|
||||
|
||||
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 totalPages = Object.keys(entrypoints).length
|
||||
const clientEntries = !isServer ? {
|
||||
|
@ -307,10 +312,10 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
|||
!isServer && dev && new webpack.DefinePlugin({
|
||||
'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 NextJsSsrImportPlugin(),
|
||||
target !== 'serverless' && isServer && new NextJsSSRModuleCachePlugin({outputPath}),
|
||||
target !== 'serverless' && target !== 'unified' && isServer && new NextJsSSRModuleCachePlugin({ outputPath }),
|
||||
!dev && new webpack.IgnorePlugin({
|
||||
checkResource: (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