Skip to content

Commit

Permalink
Add required server files manifest (vercel#20035)
Browse files Browse the repository at this point in the history
This keeps track of required server files in a manifest file
  • Loading branch information
ijjk authored Dec 16, 2020
1 parent 5d5383b commit e819e00
Show file tree
Hide file tree
Showing 10 changed files with 705 additions and 157 deletions.
69 changes: 64 additions & 5 deletions packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,21 @@ import { loadEnvConfig } from '@next/env'
import { recursiveDelete } from '../lib/recursive-delete'
import { verifyTypeScriptSetup } from '../lib/verifyTypeScriptSetup'
import {
BUILD_ID_FILE,
BUILD_MANIFEST,
CLIENT_STATIC_FILES_PATH,
EXPORT_DETAIL,
EXPORT_MARKER,
FONT_MANIFEST,
IMAGES_MANIFEST,
PAGES_MANIFEST,
PHASE_PRODUCTION_BUILD,
PRERENDER_MANIFEST,
REACT_LOADABLE_MANIFEST,
ROUTES_MANIFEST,
SERVERLESS_DIRECTORY,
SERVER_DIRECTORY,
SERVER_FILES_MANIFEST,
} from '../next-server/lib/constants'
import {
getRouteRegex,
Expand Down Expand Up @@ -343,6 +347,39 @@ export default async function build(
'utf8'
)

const manifestPath = path.join(
distDir,
isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY,
PAGES_MANIFEST
)

const requiredServerFiles = {
version: 1,
config: {
...config,
compress: false,
configFile: undefined,
},
files: [
ROUTES_MANIFEST,
path.relative(distDir, manifestPath),
BUILD_MANIFEST,
PRERENDER_MANIFEST,
REACT_LOADABLE_MANIFEST,
path.join(
isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY,
FONT_MANIFEST
),
BUILD_ID_FILE,
].map((file) => path.join(config.distDir, file)),
ignore: [
path.relative(
dir,
path.join(path.dirname(require.resolve('sharp')), '**/*')
),
],
}

const configs = await Promise.all([
getBaseWebpackConfig(dir, {
tracer,
Expand Down Expand Up @@ -465,11 +502,6 @@ export default async function build(
prefixText: `${Log.prefixes.info} Collecting page data`,
})

const manifestPath = path.join(
distDir,
isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY,
PAGES_MANIFEST
)
const buildManifestPath = path.join(distDir, BUILD_MANIFEST)

const ssgPages = new Set<string>()
Expand Down Expand Up @@ -515,6 +547,8 @@ export default async function build(
false
))

let hasSsrAmpPages = false

const analysisBegin = process.hrtime()
await Promise.all(
pageKeys.map(async (page) => {
Expand Down Expand Up @@ -575,6 +609,13 @@ export default async function build(
config.i18n?.defaultLocale
)

if (
workerResult.isStatic === false &&
(workerResult.isHybridAmp || workerResult.isAmpOnly)
) {
hasSsrAmpPages = true
}

if (workerResult.isHybridAmp) {
isHybridAmp = true
hybridAmpPages.add(page)
Expand Down Expand Up @@ -637,6 +678,18 @@ export default async function build(
)
staticCheckWorkers.end()

if (!hasSsrAmpPages) {
requiredServerFiles.ignore.push(
path.relative(
dir,
path.join(
path.dirname(require.resolve('@ampproject/toolbox-optimizer')),
'**/*'
)
)
)
}

if (serverPropsPages.size > 0 || ssgPages.size > 0) {
// We update the routes manifest after the build with the
// data routes since we can't determine these until after build
Expand Down Expand Up @@ -712,6 +765,12 @@ export default async function build(

await writeBuildId(distDir, buildId)

await promises.writeFile(
path.join(distDir, SERVER_FILES_MANIFEST),
JSON.stringify(requiredServerFiles),
'utf8'
)

const finalPrerenderRoutes: { [route: string]: SsgRoute } = {}
const tbdPrerenderRoutes: string[] = []
let ssgNotFoundPaths: string[] = []
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { parse as parseQs } from 'querystring'
import { IncomingMessage, ServerResponse } from 'http'
import { parse as parseUrl, format as formatUrl, UrlWithParsedQuery } from 'url'
import { isResSent } from '../../../../next-server/lib/utils'
Expand All @@ -14,7 +13,6 @@ import {
} from '../../../../next-server/server/api-utils'
import { getRedirectStatus } from '../../../../lib/load-custom-routes'
import getRouteNoAssetPath from '../../../../next-server/lib/router/utils/get-route-from-asset-path'
import { getRouteMatcher } from '../../../../next-server/lib/router/utils/route-matcher'
import { PERMANENT_REDIRECT_STATUS } from '../../../../next-server/lib/constants'

export function getPageHandler(ctx: ServerlessHandlerCtx) {
Expand Down Expand Up @@ -56,7 +54,10 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) {
handleBasePath,
defaultRouteRegex,
dynamicRouteMatcher,
interpolateDynamicPath,
getParamsFromRouteMatches,
normalizeDynamicRouteParams,
normalizeVercelUrl,
} = getUtils(ctx)

async function renderReqToHTML(
Expand Down Expand Up @@ -222,116 +223,24 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) {
!hasValidParams &&
req.headers?.['x-now-route-matches']
) {
nowParams = getRouteMatcher(
(function () {
const { groups, routeKeys } = defaultRouteRegex!

return {
re: {
// Simulate a RegExp match from the \`req.url\` input
exec: (str: string) => {
const obj = parseQs(str)

// favor named matches if available
const routeKeyNames = Object.keys(routeKeys || {})

const filterLocaleItem = (val: string | string[]) => {
if (i18n) {
// locale items can be included in route-matches
// for fallback SSG pages so ensure they are
// filtered
const isCatchAll = Array.isArray(val)
const _val = isCatchAll ? val[0] : val

if (
typeof _val === 'string' &&
i18n.locales.some((item) => {
if (item.toLowerCase() === _val.toLowerCase()) {
detectedLocale = item
renderOpts.locale = detectedLocale
return true
}
return false
})
) {
// remove the locale item from the match
if (isCatchAll) {
;(val as string[]).splice(0, 1)
}

// the value is only a locale item and
// shouldn't be added
return isCatchAll ? val.length === 0 : true
}
}
return false
}

if (routeKeyNames.every((name) => obj[name])) {
return routeKeyNames.reduce((prev, keyName) => {
const paramName = routeKeys?.[keyName]

if (paramName && !filterLocaleItem(obj[keyName])) {
prev[groups[paramName].pos] = obj[keyName]
}
return prev
}, {} as any)
}

return Object.keys(obj).reduce((prev, key) => {
if (!filterLocaleItem(obj[key])) {
return Object.assign(prev, {
[key]: obj[key],
})
}
return prev
}, {})
},
},
groups,
}
})() as any
)(req.headers['x-now-route-matches'] as string)
nowParams = getParamsFromRouteMatches(req, renderOpts, detectedLocale)
}

// make sure to set renderOpts to the correct params e.g. _params
// if provided from worker or params if we're parsing them here
renderOpts.params = _params || params

// make sure to normalize req.url on Vercel to strip dynamic params
// from the query which are added during routing
if (pageIsDynamic && trustQuery && defaultRouteRegex) {
const _parsedUrl = parseUrl(req.url!, true)
delete (_parsedUrl as any).search

for (const param of Object.keys(defaultRouteRegex.groups)) {
delete _parsedUrl.query[param]
}
req.url = formatUrl(_parsedUrl)
}
normalizeVercelUrl(req, !!trustQuery)

// normalize request URL/asPath for fallback/revalidate pages since the
// proxy sets the request URL to the output's path for fallback pages
if (pageIsDynamic && nowParams && defaultRouteRegex) {
const _parsedUrl = parseUrl(req.url!)

for (const param of Object.keys(defaultRouteRegex.groups)) {
const { optional, repeat } = defaultRouteRegex.groups[param]
let builtParam = `[${repeat ? '...' : ''}${param}]`

if (optional) {
builtParam = `[${builtParam}]`
}

const paramIdx = _parsedUrl.pathname!.indexOf(builtParam)

if (paramIdx > -1) {
_parsedUrl.pathname =
_parsedUrl.pathname!.substr(0, paramIdx) +
encodeURI((nowParams as any)[param] || '') +
_parsedUrl.pathname!.substr(paramIdx + builtParam.length)
}
}
_parsedUrl.pathname = interpolateDynamicPath(
_parsedUrl.pathname!,
nowParams
)
parsedUrl.pathname = _parsedUrl.pathname
req.url = formatUrl(_parsedUrl)
}
Expand Down
Loading

0 comments on commit e819e00

Please sign in to comment.