1
0
Fork 0
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:
Kegan Myers 2019-02-17 23:46:51 -06:00
parent d2ef34429c
commit 94460eee55
5 changed files with 166 additions and 9 deletions

View file

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

View file

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

View file

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

View file

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

View 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;