2019-04-10 18:37:13 +02:00
import chalk from 'chalk'
2019-10-18 18:40:47 +02:00
import ciEnvironment from 'ci-info'
2019-11-15 08:19:41 +01:00
import findUp from 'find-up'
2019-08-06 00:26:20 +02:00
import fs from 'fs'
2019-10-01 04:08:01 +02:00
import Worker from 'jest-worker'
2019-08-06 00:26:20 +02:00
import mkdirpOrig from 'mkdirp'
2019-10-01 04:08:01 +02:00
import nanoid from 'next/dist/compiled/nanoid/index.js'
import path from 'path'
2019-11-27 22:48:28 +01:00
import { pathToRegexp } from 'path-to-regexp'
2019-10-01 04:08:01 +02:00
import { promisify } from 'util'
import formatWebpackMessages from '../client/dev/error-overlay/format-webpack-messages'
2020-01-01 13:47:58 +01:00
import checkCustomRoutes , {
2020-01-14 19:28:48 +01:00
getRedirectStatus ,
2020-01-01 13:47:58 +01:00
RouteType ,
Redirect ,
Rewrite ,
Header ,
} from '../lib/check-custom-routes'
2020-02-01 15:47:42 +01:00
import {
PUBLIC_DIR_MIDDLEWARE_CONFLICT ,
PAGES_404_GET_INITIAL_PROPS_ERROR ,
} from '../lib/constants'
2019-10-01 04:08:01 +02:00
import { findPagesDir } from '../lib/find-pages-dir'
import { recursiveDelete } from '../lib/recursive-delete'
import { recursiveReadDir } from '../lib/recursive-readdir'
import { verifyTypeScriptSetup } from '../lib/verifyTypeScriptSetup'
2019-04-24 11:04:36 +02:00
import {
2019-09-19 18:16:51 +02:00
BUILD_MANIFEST ,
2019-12-13 20:30:22 +01:00
EXPORT_DETAIL ,
EXPORT_MARKER ,
2019-10-01 04:08:01 +02:00
PAGES_MANIFEST ,
2019-04-24 11:04:36 +02:00
PHASE_PRODUCTION_BUILD ,
2019-08-06 22:26:01 +02:00
PRERENDER_MANIFEST ,
2019-11-15 08:19:41 +01:00
ROUTES_MANIFEST ,
2019-08-06 00:26:20 +02:00
SERVERLESS_DIRECTORY ,
2019-12-12 17:20:24 +01:00
SERVER_DIRECTORY ,
2019-09-04 16:00:54 +02:00
} from '../next-server/lib/constants'
2019-11-09 01:51:19 +01:00
import {
getRouteRegex ,
getSortedRoutes ,
2019-11-15 08:19:41 +01:00
isDynamicRoute ,
2019-11-09 01:51:19 +01:00
} from '../next-server/lib/router/utils'
2019-08-06 00:26:20 +02:00
import loadConfig , {
isTargetLikeServerless ,
2019-09-04 16:00:54 +02:00
} from '../next-server/server/config'
2019-08-29 18:43:06 +02:00
import {
2019-10-10 19:18:07 +02:00
eventBuildDuration ,
eventBuildOptimize ,
eventNextPlugins ,
eventVersion ,
2019-08-29 18:43:06 +02:00
} from '../telemetry/events'
2019-10-10 19:18:07 +02:00
import { Telemetry } from '../telemetry/storage'
2019-04-10 18:37:13 +02:00
import { CompilerResult , runCompiler } from './compiler'
import { createEntrypoints , createPagesMapping } from './entries'
2019-02-17 12:56:48 +01:00
import { generateBuildId } from './generate-build-id'
import { isWriteable } from './is-writeable'
2019-10-01 04:08:01 +02:00
import createSpinner from './spinner'
2019-04-10 18:41:59 +02:00
import {
2020-01-25 00:41:00 +01:00
isPageStatic ,
2019-04-10 18:41:59 +02:00
collectPages ,
2019-05-14 17:11:22 +02:00
getPageSizeInKb ,
2019-05-22 18:36:53 +02:00
hasCustomAppGetInitialProps ,
2019-08-06 00:26:20 +02:00
PageInfo ,
2019-11-26 10:33:47 +01:00
printCustomRoutes ,
2019-11-27 22:48:28 +01:00
printTreeView ,
2019-04-10 18:41:59 +02:00
} from './utils'
2019-04-10 18:37:13 +02:00
import getBaseWebpackConfig from './webpack-config'
import { writeBuildId } from './write-build-id'
2020-01-27 23:50:59 +01:00
import escapeStringRegexp from 'escape-string-regexp'
2019-05-22 18:36:53 +02:00
2019-10-18 18:40:47 +02:00
const fsAccess = promisify ( fs . access )
2019-05-22 18:36:53 +02:00
const fsUnlink = promisify ( fs . unlink )
const fsRmdir = promisify ( fs . rmdir )
2019-09-16 23:06:30 +02:00
const fsStat = promisify ( fs . stat )
2019-05-22 18:36:53 +02:00
const fsMove = promisify ( fs . rename )
const fsReadFile = promisify ( fs . readFile )
const fsWriteFile = promisify ( fs . writeFile )
const mkdirp = promisify ( mkdirpOrig )
2019-04-06 19:11:38 +02:00
2019-09-24 10:50:04 +02:00
const staticCheckWorker = require . resolve ( './utils' )
2019-06-26 04:54:28 +02:00
2020-01-15 21:57:07 +01:00
export type SsgRoute = {
2019-09-24 10:50:04 +02:00
initialRevalidateSeconds : number | false
2019-10-02 15:28:38 +02:00
srcRoute : string | null
2019-10-01 04:08:01 +02:00
dataRoute : string
}
2020-01-15 21:57:07 +01:00
export type DynamicSsgRoute = {
2019-10-01 04:08:01 +02:00
routeRegex : string
dataRoute : string
dataRouteRegex : string
2019-09-24 10:50:04 +02:00
}
export type PrerenderManifest = {
version : number
2020-01-15 21:57:07 +01:00
routes : { [ route : string ] : SsgRoute }
dynamicRoutes : { [ route : string ] : DynamicSsgRoute }
2019-08-06 22:26:01 +02:00
}
2019-04-24 21:16:30 +02:00
export default async function build ( dir : string , conf = null ) : Promise < void > {
2019-02-17 12:56:48 +01:00
if ( ! ( await isWriteable ( dir ) ) ) {
throw new Error (
'> Build directory is not writeable. https://err.sh/zeit/next.js/build-dir-not-writeable'
)
2018-12-03 14:18:52 +01:00
}
const config = loadConfig ( PHASE_PRODUCTION_BUILD , dir , conf )
2019-06-06 15:57:42 +02:00
const { target } = config
2019-07-01 19:13:06 +02:00
const buildId = await generateBuildId ( config . generateBuildId , nanoid )
2019-04-03 02:22:04 +02:00
const distDir = path . join ( dir , config . distDir )
2020-01-01 13:47:58 +01:00
const rewrites : Rewrite [ ] = [ ]
const redirects : Redirect [ ] = [ ]
const headers : Header [ ] = [ ]
2019-11-09 23:34:53 +01:00
if ( typeof config . experimental . redirects === 'function' ) {
redirects . push ( . . . ( await config . experimental . redirects ( ) ) )
2019-12-10 15:54:56 +01:00
checkCustomRoutes ( redirects , 'redirect' )
2019-11-09 23:34:53 +01:00
}
if ( typeof config . experimental . rewrites === 'function' ) {
rewrites . push ( . . . ( await config . experimental . rewrites ( ) ) )
2019-12-10 15:54:56 +01:00
checkCustomRoutes ( rewrites , 'rewrite' )
2019-11-09 23:34:53 +01:00
}
2020-01-01 13:47:58 +01:00
if ( typeof config . experimental . headers === 'function' ) {
headers . push ( . . . ( await config . experimental . headers ( ) ) )
checkCustomRoutes ( headers , 'header' )
}
2019-10-10 19:18:07 +02:00
2019-10-18 18:40:47 +02:00
if ( ciEnvironment . isCI ) {
const cacheDir = path . join ( distDir , 'cache' )
const hasCache = await fsAccess ( cacheDir )
. then ( ( ) = > true )
. catch ( ( ) = > false )
if ( ! hasCache ) {
// Intentionally not piping to stderr in case people fail in CI when
// stderr is detected.
console . log (
chalk . bold . yellow ( ` Warning: ` ) +
chalk . bold (
` No build cache found. Please configure build caching for faster rebuilds. Read more: https://err.sh/next.js/no-cache `
)
)
console . log ( '' )
}
}
const buildSpinner = createSpinner ( {
prefixText : 'Creating an optimized production build' ,
} )
2019-10-10 19:18:07 +02:00
const telemetry = new Telemetry ( { distDir } )
2019-09-16 23:06:30 +02:00
const publicDir = path . join ( dir , 'public' )
2019-09-24 17:15:14 +02:00
const pagesDir = findPagesDir ( dir )
2019-09-16 23:06:30 +02:00
let publicFiles : string [ ] = [ ]
2019-10-06 13:44:03 +02:00
let hasPublicDir = false
2019-09-16 23:06:30 +02:00
2019-12-03 17:18:58 +01:00
telemetry . record (
eventVersion ( {
cliCommand : 'build' ,
isSrcDir : path.relative ( dir , pagesDir ! ) . startsWith ( 'src' ) ,
hasNowJson : ! ! ( await findUp ( 'now.json' , { cwd : dir } ) ) ,
isCustomServer : null ,
} )
2019-10-03 16:21:15 +02:00
)
2019-12-03 17:18:58 +01:00
eventNextPlugins ( path . resolve ( dir ) ) . then ( events = > telemetry . record ( events ) )
2019-09-24 17:15:14 +02:00
await verifyTypeScriptSetup ( dir , pagesDir )
2019-10-06 13:44:03 +02:00
try {
await fsStat ( publicDir )
hasPublicDir = true
} catch ( _ ) { }
if ( hasPublicDir ) {
2019-09-16 23:06:30 +02:00
publicFiles = await recursiveReadDir ( publicDir , /.*/ )
}
2019-04-03 02:22:04 +02:00
2019-08-18 21:45:39 +02:00
let tracer : any = null
if ( config . experimental . profiling ) {
const { createTrace } = require ( './profiler/profiler.js' )
tracer = createTrace ( path . join ( distDir , ` profile-events.json ` ) )
tracer . profiler . startProfiling ( )
}
2019-08-06 00:26:20 +02:00
const isLikeServerless = isTargetLikeServerless ( target )
2019-08-20 17:07:57 +02:00
const pagePaths : string [ ] = await collectPages (
pagesDir ,
config . pageExtensions
)
2019-04-10 18:41:59 +02:00
2019-05-22 18:36:53 +02:00
// needed for static exporting since we want to replace with HTML
2019-08-20 17:07:57 +02:00
// files
2019-05-22 18:36:53 +02:00
const allStaticPages = new Set < string > ( )
let allPageInfos = new Map < string , PageInfo > ( )
2019-04-10 18:41:59 +02:00
2019-03-27 16:51:05 +01:00
const mappedPages = createPagesMapping ( pagePaths , config . pageExtensions )
2019-08-20 17:07:57 +02:00
const entrypoints = createEntrypoints ( mappedPages , target , buildId , config )
2019-12-23 22:20:17 +01:00
const pageKeys = Object . keys ( mappedPages )
const dynamicRoutes = pageKeys . filter ( page = > isDynamicRoute ( page ) )
2019-09-16 23:06:30 +02:00
const conflictingPublicFiles : string [ ] = [ ]
2020-01-20 15:10:24 +01:00
const hasCustomErrorPage = mappedPages [ '/_error' ] . startsWith (
'private-next-pages'
)
2020-02-01 15:47:42 +01:00
const hasPages404 =
config . experimental . pages404 &&
mappedPages [ '/404' ] &&
mappedPages [ '/404' ] . startsWith ( 'private-next-pages' )
2019-09-16 23:06:30 +02:00
2019-10-06 13:44:03 +02:00
if ( hasPublicDir ) {
try {
await fsStat ( path . join ( publicDir , '_next' ) )
throw new Error ( PUBLIC_DIR_MIDDLEWARE_CONFLICT )
} catch ( err ) { }
}
2019-09-16 23:06:30 +02:00
for ( let file of publicFiles ) {
file = file
. replace ( /\\/g , '/' )
. replace ( /\/index$/ , '' )
. split ( publicDir )
. pop ( ) !
if ( mappedPages [ file ] ) {
conflictingPublicFiles . push ( file )
}
}
const numConflicting = conflictingPublicFiles . length
if ( numConflicting ) {
throw new Error (
` Conflicting public and page file ${
numConflicting === 1 ? ' was' : 's were'
} found . https : //err.sh/zeit/next.js/conflicting-public-file-page\n${conflictingPublicFiles.join(
'\n'
) } `
)
}
2019-12-23 22:20:17 +01:00
const buildCustomRoute = (
r : {
source : string
statusCode? : number
} ,
2020-01-01 13:47:58 +01:00
type : RouteType
2019-12-23 22:20:17 +01:00
) = > {
const keys : any [ ] = [ ]
const routeRegex = pathToRegexp ( r . source , keys , {
strict : true ,
sensitive : false ,
delimiter : '/' , // default is `/#?`, but Next does not pass query info
} )
return {
. . . r ,
2020-01-01 13:47:58 +01:00
. . . ( type === 'redirect'
2019-12-23 22:20:17 +01:00
? {
2020-01-14 19:28:48 +01:00
statusCode : getRedirectStatus ( r as Redirect ) ,
permanent : undefined ,
2019-12-23 22:20:17 +01:00
}
: { } ) ,
regex : routeRegex.source ,
}
}
2020-01-27 23:50:59 +01:00
const routesManifestPath = path . join ( distDir , ROUTES_MANIFEST )
const routesManifest : any = {
version : 1 ,
2020-02-01 15:47:42 +01:00
pages404 : ! ! hasPages404 ,
2020-01-27 23:50:59 +01:00
basePath : config.experimental.basePath ,
redirects : redirects.map ( r = > buildCustomRoute ( r , 'redirect' ) ) ,
rewrites : rewrites.map ( r = > buildCustomRoute ( r , 'rewrite' ) ) ,
headers : headers.map ( r = > buildCustomRoute ( r , 'header' ) ) ,
dynamicRoutes : getSortedRoutes ( dynamicRoutes ) . map ( page = > ( {
page ,
regex : getRouteRegex ( page ) . re . source ,
} ) ) ,
}
2019-12-23 22:20:17 +01:00
await mkdirp ( distDir )
2020-01-27 23:50:59 +01:00
// We need to write the manifest with rewrites before build
// so serverless can import the manifest
await fsWriteFile ( routesManifestPath , JSON . stringify ( routesManifest ) , 'utf8' )
2019-12-23 22:20:17 +01:00
2019-03-10 15:46:50 +01:00
const configs = await Promise . all ( [
getBaseWebpackConfig ( dir , {
2019-08-18 21:45:39 +02:00
tracer ,
2019-02-17 12:56:48 +01:00
buildId ,
isServer : false ,
config ,
2019-04-24 20:32:15 +02:00
target ,
2019-09-24 17:15:14 +02:00
pagesDir ,
2019-02-17 12:56:48 +01:00
entrypoints : entrypoints.client ,
} ) ,
2019-03-10 15:46:50 +01:00
getBaseWebpackConfig ( dir , {
2019-08-18 21:45:39 +02:00
tracer ,
2019-02-17 12:56:48 +01:00
buildId ,
isServer : true ,
config ,
2019-04-24 20:32:15 +02:00
target ,
2019-09-24 17:15:14 +02:00
pagesDir ,
2019-02-17 12:56:48 +01:00
entrypoints : entrypoints.server ,
} ) ,
2019-03-10 15:46:50 +01:00
] )
2018-12-03 14:18:52 +01:00
2019-08-13 05:13:12 +02:00
const clientConfig = configs [ 0 ]
if (
clientConfig . optimization &&
( clientConfig . optimization . minimize !== true ||
( clientConfig . optimization . minimizer &&
clientConfig . optimization . minimizer . length === 0 ) )
) {
console . warn (
chalk . bold . yellow ( ` Warning: ` ) +
chalk . bold (
` Production code optimization has been disabled in your project. Read more: https://err.sh/zeit/next.js/minification-disabled `
)
)
}
2019-08-29 18:43:06 +02:00
const webpackBuildStart = process . hrtime ( )
2019-02-17 12:56:48 +01:00
let result : CompilerResult = { warnings : [ ] , errors : [ ] }
2019-08-06 00:26:20 +02:00
// TODO: why do we need this?? https://github.com/zeit/next.js/issues/8253
if ( isLikeServerless ) {
2019-08-13 05:13:12 +02:00
const clientResult = await runCompiler ( clientConfig )
2019-01-10 22:10:50 +01:00
// Fail build if clientResult contains errors
2019-02-17 12:56:48 +01:00
if ( clientResult . errors . length > 0 ) {
result = {
warnings : [ . . . clientResult . warnings ] ,
errors : [ . . . clientResult . errors ] ,
}
2019-01-10 22:10:50 +01:00
} else {
2019-03-10 15:46:50 +01:00
const serverResult = await runCompiler ( configs [ 1 ] )
2019-02-17 12:56:48 +01:00
result = {
warnings : [ . . . clientResult . warnings , . . . serverResult . warnings ] ,
errors : [ . . . clientResult . errors , . . . serverResult . errors ] ,
}
2019-01-10 22:10:50 +01:00
}
2018-12-11 21:46:23 +01:00
} else {
result = await runCompiler ( configs )
}
2019-08-29 18:43:06 +02:00
const webpackBuildEnd = process . hrtime ( webpackBuildStart )
2019-09-16 17:37:00 +02:00
if ( buildSpinner ) {
buildSpinner . stopAndPersist ( )
}
console . log ( )
2019-08-29 18:43:06 +02:00
2019-02-17 12:56:48 +01:00
result = formatWebpackMessages ( result )
2018-12-03 14:18:52 +01:00
if ( result . errors . length > 0 ) {
2019-02-17 12:56:48 +01:00
// Only keep the first error. Others are often indicative
// of the same problem, but confuse the reader with noise.
if ( result . errors . length > 1 ) {
result . errors . length = 1
}
2019-03-17 13:13:29 +01:00
const error = result . errors . join ( '\n\n' )
2019-02-17 12:56:48 +01:00
console . error ( chalk . red ( 'Failed to compile.\n' ) )
2019-07-15 17:16:35 +02:00
if (
error . indexOf ( 'private-next-pages' ) > - 1 &&
error . indexOf ( 'does not contain a default export' ) > - 1
) {
2019-11-11 04:24:53 +01:00
const page_name_regex = /'private-next-pages\/(?<page_name>[^']*)'/
2019-07-15 17:16:35 +02:00
const parsed = page_name_regex . exec ( error )
const page_name = parsed && parsed . groups && parsed . groups . page_name
throw new Error (
` webpack build failed: found page without a React Component as default export in pages/ ${ page_name } \ n \ nSee https://err.sh/zeit/next.js/page-without-valid-component for more info. `
)
}
2019-03-17 13:13:29 +01:00
console . error ( error )
2019-02-17 12:56:48 +01:00
console . error ( )
2019-03-17 13:13:29 +01:00
2019-11-02 02:00:56 +01:00
if (
error . indexOf ( 'private-next-pages' ) > - 1 ||
error . indexOf ( '__next_polyfill__' ) > - 1
) {
2019-03-27 16:51:05 +01:00
throw new Error (
'> webpack config.resolve.alias was incorrectly overriden. https://err.sh/zeit/next.js/invalid-resolve-alias'
)
2019-03-17 13:13:29 +01:00
}
2018-12-03 14:18:52 +01:00
throw new Error ( '> Build failed because of webpack errors' )
2019-02-17 12:56:48 +01:00
} else if ( result . warnings . length > 0 ) {
console . warn ( chalk . yellow ( 'Compiled with warnings.\n' ) )
console . warn ( result . warnings . join ( '\n\n' ) )
console . warn ( )
} else {
console . log ( chalk . green ( 'Compiled successfully.\n' ) )
2019-12-03 17:18:58 +01:00
telemetry . record (
eventBuildDuration ( {
totalPageCount : pagePaths.length ,
durationInSeconds : webpackBuildEnd [ 0 ] ,
} )
2019-08-29 18:43:06 +02:00
)
2018-12-03 14:18:52 +01:00
}
2019-09-16 17:37:00 +02:00
const postBuildSpinner = createSpinner ( {
prefixText : 'Automatically optimizing pages' ,
} )
2019-05-29 13:57:26 +02:00
const manifestPath = path . join (
distDir ,
2019-08-06 00:26:20 +02:00
isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY ,
2019-05-29 13:57:26 +02:00
PAGES_MANIFEST
)
2019-09-19 18:16:51 +02:00
const buildManifestPath = path . join ( distDir , BUILD_MANIFEST )
2019-05-22 18:36:53 +02:00
2020-01-15 21:57:07 +01:00
const ssgPages = new Set < string > ( )
2019-05-22 18:36:53 +02:00
const staticPages = new Set < string > ( )
2019-06-14 02:08:19 +02:00
const invalidPages = new Set < string > ( )
2019-08-12 03:56:57 +02:00
const hybridAmpPages = new Set < string > ( )
2020-01-27 23:50:59 +01:00
const serverPropsPages = new Set < string > ( )
2020-01-15 21:57:07 +01:00
const additionalSsgPaths = new Map < string , Array < string > > ( )
2019-05-22 18:36:53 +02:00
const pageInfos = new Map < string , PageInfo > ( )
2019-06-28 22:01:11 +02:00
const pagesManifest = JSON . parse ( await fsReadFile ( manifestPath , 'utf8' ) )
2019-09-19 18:16:51 +02:00
const buildManifest = JSON . parse ( await fsReadFile ( buildManifestPath , 'utf8' ) )
2019-05-22 18:36:53 +02:00
let customAppGetInitialProps : boolean | undefined
process . env . NEXT_PHASE = PHASE_PRODUCTION_BUILD
2019-05-01 22:31:08 +02:00
2019-08-24 20:55:43 +02:00
const staticCheckWorkers = new Worker ( staticCheckWorker , {
numWorkers : config.experimental.cpus ,
2019-10-29 01:01:25 +01:00
enableWorkerThreads : config.experimental.workerThreads ,
2020-01-25 00:41:00 +01:00
} ) as Worker & { isPageStatic : typeof isPageStatic }
2019-10-31 12:38:09 +01:00
staticCheckWorkers . getStdout ( ) . pipe ( process . stdout )
staticCheckWorkers . getStderr ( ) . pipe ( process . stderr )
2019-05-01 22:31:08 +02:00
2019-08-29 18:43:06 +02:00
const analysisBegin = process . hrtime ( )
2019-06-26 04:54:28 +02:00
await Promise . all (
pageKeys . map ( async page = > {
const actualPage = page === '/' ? '/index' : page
2020-01-09 19:49:52 +01:00
const [ selfSize , allSize ] = await getPageSizeInKb (
2019-09-19 18:16:51 +02:00
actualPage ,
distDir ,
buildId ,
buildManifest ,
config . experimental . modern
)
2019-06-26 04:54:28 +02:00
const bundleRelative = path . join (
2019-08-06 00:26:20 +02:00
isLikeServerless ? 'pages' : ` static/ ${ buildId } /pages ` ,
2019-06-26 04:54:28 +02:00
actualPage + '.js'
)
const serverBundle = path . join (
2019-08-23 23:17:40 +02:00
distDir ,
2019-08-06 00:26:20 +02:00
isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY ,
2019-06-26 04:54:28 +02:00
bundleRelative
)
2019-06-10 20:46:30 +02:00
2019-12-12 10:45:45 +01:00
let isSsg = false
2019-12-14 20:23:04 +01:00
let isStatic = false
let isHybridAmp = false
2019-12-12 10:45:45 +01:00
let ssgPageRoutes : string [ ] | null = null
2019-05-22 18:36:53 +02:00
2019-06-28 22:01:11 +02:00
pagesManifest [ page ] = bundleRelative . replace ( /\\/g , '/' )
2019-05-22 18:36:53 +02:00
2019-06-28 22:01:11 +02:00
const runtimeEnvConfig = {
publicRuntimeConfig : config.publicRuntimeConfig ,
serverRuntimeConfig : config.serverRuntimeConfig ,
}
const nonReservedPage = ! page . match ( /^\/(_app|_error|_document|api)/ )
if ( nonReservedPage && customAppGetInitialProps === undefined ) {
customAppGetInitialProps = hasCustomAppGetInitialProps (
2019-08-06 00:26:20 +02:00
isLikeServerless
2019-06-28 22:01:11 +02:00
? serverBundle
: path . join (
2019-08-23 23:17:40 +02:00
distDir ,
2019-06-28 22:01:11 +02:00
SERVER_DIRECTORY ,
` /static/ ${ buildId } /pages/_app.js `
) ,
runtimeEnvConfig
)
if ( customAppGetInitialProps ) {
console . warn (
2019-07-05 17:57:16 +02:00
chalk . bold . yellow ( ` Warning: ` ) +
chalk . yellow (
2019-10-03 00:03:00 +02:00
` You have opted-out of Automatic Static Optimization due to \` getInitialProps \` in \` pages/_app \` . `
2019-07-05 17:57:16 +02:00
)
)
console . warn (
2019-10-03 00:03:00 +02:00
'Read more: https://err.sh/next.js/opt-out-auto-static-optimization\n'
2019-05-29 13:57:26 +02:00
)
2019-06-28 22:01:11 +02:00
}
}
2019-06-26 04:54:28 +02:00
2019-07-01 23:13:52 +02:00
if ( nonReservedPage ) {
2019-06-28 22:01:11 +02:00
try {
2020-01-25 00:41:00 +01:00
let result = await staticCheckWorkers . isPageStatic (
2019-09-24 10:50:04 +02:00
page ,
2019-08-24 20:55:43 +02:00
serverBundle ,
2019-09-24 10:50:04 +02:00
runtimeEnvConfig
)
2019-05-22 18:36:53 +02:00
2019-08-12 03:56:57 +02:00
if ( result . isHybridAmp ) {
2019-12-14 20:23:04 +01:00
isHybridAmp = true
2019-08-12 03:56:57 +02:00
hybridAmpPages . add ( page )
}
2020-01-25 00:41:00 +01:00
if ( result . hasStaticProps ) {
2020-01-15 21:57:07 +01:00
ssgPages . add ( page )
2019-12-12 10:45:45 +01:00
isSsg = true
2019-09-24 10:50:04 +02:00
if ( result . prerenderRoutes ) {
2020-01-15 21:57:07 +01:00
additionalSsgPaths . set ( page , result . prerenderRoutes )
2019-12-12 10:45:45 +01:00
ssgPageRoutes = result . prerenderRoutes
2019-09-24 10:50:04 +02:00
}
2020-01-27 23:50:59 +01:00
} else if ( result . hasServerProps ) {
serverPropsPages . add ( page )
2020-01-25 00:41:00 +01:00
} else if ( result . isStatic && customAppGetInitialProps === false ) {
2019-06-28 22:01:11 +02:00
staticPages . add ( page )
isStatic = true
2019-06-14 02:08:19 +02:00
}
2020-02-01 15:47:42 +01:00
if ( hasPages404 && page === '/404' ) {
if ( ! result . isStatic ) {
throw new Error ( PAGES_404_GET_INITIAL_PROPS_ERROR )
}
// we need to ensure the 404 lambda is present since we use
// it when _app has getInitialProps
if ( customAppGetInitialProps ) {
staticPages . delete ( page )
}
}
2019-06-28 22:01:11 +02:00
} catch ( err ) {
if ( err . message !== 'INVALID_DEFAULT_EXPORT' ) throw err
invalidPages . add ( page )
2019-05-31 02:34:05 +02:00
}
2019-05-22 18:36:53 +02:00
}
2019-12-12 10:45:45 +01:00
pageInfos . set ( page , {
2020-01-09 19:49:52 +01:00
size : selfSize ,
totalSize : allSize ,
2019-12-12 10:45:45 +01:00
serverBundle ,
static : isStatic ,
isSsg ,
2019-12-14 20:23:04 +01:00
isHybridAmp ,
2019-12-12 10:45:45 +01:00
ssgPageRoutes ,
} )
2019-06-26 04:54:28 +02:00
} )
)
2019-08-24 20:55:43 +02:00
staticCheckWorkers . end ( )
2019-04-24 10:48:43 +02:00
2020-01-27 23:50:59 +01:00
if ( serverPropsPages . size > 0 ) {
// We update the routes manifest after the build with the
// serverProps routes since we can't determine this until after build
routesManifest . serverPropsRoutes = { }
for ( const page of serverPropsPages ) {
const dataRoute = path . posix . join (
'/_next/data' ,
buildId ,
` ${ page === '/' ? '/index' : page } .json `
)
routesManifest . serverPropsRoutes [ page ] = {
page ,
dataRouteRegex : isDynamicRoute ( page )
? getRouteRegex ( dataRoute . replace ( /\.json$/ , '' ) ) . re . source . replace (
/\(\?:\\\/\)\?\$$/ ,
'\\.json$'
)
: new RegExp (
` ^ ${ path . posix . join (
'/_next/data' ,
escapeStringRegexp ( buildId ) ,
` ${ page === '/' ? '/index' : page } .json `
) } $ `
) . source ,
}
}
await fsWriteFile (
routesManifestPath ,
JSON . stringify ( routesManifest ) ,
'utf8'
)
}
2020-01-20 15:10:24 +01:00
// Since custom _app.js can wrap the 404 page we have to opt-out of static optimization if it has getInitialProps
// Only export the static 404 when there is no /_error present
const useStatic404 =
! customAppGetInitialProps &&
2020-02-01 15:47:42 +01:00
( ( ! hasCustomErrorPage && config . experimental . static404 ) || hasPages404 )
2020-01-20 15:10:24 +01:00
2019-06-14 02:08:19 +02:00
if ( invalidPages . size > 0 ) {
throw new Error (
2019-10-11 16:39:38 +02:00
` Build optimization failed: found page ${
2019-06-14 02:08:19 +02:00
invalidPages . size === 1 ? '' : 's'
2019-07-15 17:16:35 +02:00
} without a React Component as default export in \ n $ { [ . . . invalidPages ]
2019-06-14 02:08:19 +02:00
. map ( pg = > ` pages ${ pg } ` )
. join (
'\n'
) } \ n \ nSee https : //err.sh/zeit/next.js/page-without-valid-component for more info.\n`
)
}
2019-04-24 10:48:43 +02:00
if ( Array . isArray ( configs [ 0 ] . plugins ) ) {
configs [ 0 ] . plugins . some ( ( plugin : any ) = > {
2019-05-14 17:11:22 +02:00
if ( ! plugin . ampPages ) {
return false
2019-05-01 22:31:08 +02:00
}
2019-05-14 17:11:22 +02:00
plugin . ampPages . forEach ( ( pg : any ) = > {
pageInfos . get ( pg ) ! . isAmp = true
} )
return true
2019-04-24 10:48:43 +02:00
} )
}
2019-08-20 17:07:57 +02:00
await writeBuildId ( distDir , buildId )
2019-11-09 01:51:19 +01:00
2020-01-15 21:57:07 +01:00
const finalPrerenderRoutes : { [ route : string ] : SsgRoute } = { }
2019-09-27 22:34:37 +02:00
const tbdPrerenderRoutes : string [ ] = [ ]
2019-05-22 18:36:53 +02:00
2020-01-20 15:10:24 +01:00
if ( staticPages . size > 0 || ssgPages . size > 0 || useStatic404 ) {
2020-01-15 21:57:07 +01:00
const combinedPages = [ . . . staticPages , . . . ssgPages ]
2019-05-22 18:36:53 +02:00
const exportApp = require ( '../export' ) . default
const exportOptions = {
silent : true ,
buildExport : true ,
2019-10-29 01:01:25 +01:00
threads : config.experimental.cpus ,
2019-08-06 22:26:01 +02:00
pages : combinedPages ,
2019-05-22 18:36:53 +02:00
outdir : path.join ( distDir , 'export' ) ,
}
2019-09-24 10:50:04 +02:00
const exportConfig : any = {
2019-05-22 18:36:53 +02:00
. . . config ,
2019-09-24 10:50:04 +02:00
initialPageRevalidationMap : { } ,
// Default map will be the collection of automatic statically exported
// pages and SPR pages.
// n.b. we cannot handle this above in combinedPages because the dynamic
// page must be in the `pages` array, but not in the mapping.
exportPathMap : ( defaultMap : any ) = > {
// Remove dynamically routed pages from the default path map. These
// pages cannot be prerendered because we don't have enough information
// to do so.
//
// Note: prerendering disables automatic static optimization.
2020-01-15 21:57:07 +01:00
ssgPages . forEach ( page = > {
2019-09-24 10:50:04 +02:00
if ( isDynamicRoute ( page ) ) {
2019-09-27 22:34:37 +02:00
tbdPrerenderRoutes . push ( page )
2019-09-24 10:50:04 +02:00
delete defaultMap [ page ]
}
} )
// Append the "well-known" routes we should prerender for, e.g. blog
// post slugs.
2020-01-15 21:57:07 +01:00
additionalSsgPaths . forEach ( ( routes , page ) = > {
2019-09-24 10:50:04 +02:00
routes . forEach ( route = > {
defaultMap [ route ] = { page }
} )
} )
2020-01-20 15:10:24 +01:00
if ( useStatic404 ) {
2020-02-01 15:47:42 +01:00
defaultMap [ '/_errors/404' ] = {
page : hasPages404 ? '/404' : '/_error' ,
}
2020-01-20 15:10:24 +01:00
}
2019-09-24 10:50:04 +02:00
return defaultMap
} ,
2019-07-03 19:25:44 +02:00
exportTrailingSlash : false ,
2019-05-22 18:36:53 +02:00
}
await exportApp ( dir , exportOptions , exportConfig )
// remove server bundles that were exported
for ( const page of staticPages ) {
const { serverBundle } = pageInfos . get ( page ) !
await fsUnlink ( serverBundle )
}
2019-09-24 10:50:04 +02:00
const moveExportedPage = async (
page : string ,
file : string ,
2020-01-15 21:57:07 +01:00
isSsg : boolean ,
2019-09-24 10:50:04 +02:00
ext : 'html' | 'json'
) = > {
file = ` ${ file } . ${ ext } `
2019-05-22 18:36:53 +02:00
const orig = path . join ( exportOptions . outdir , file )
2019-08-06 00:26:20 +02:00
const relativeDest = ( isLikeServerless
2019-05-22 18:36:53 +02:00
? path . join ( 'pages' , file )
: path . join ( 'static' , buildId , 'pages' , file )
) . replace ( /\\/g , '/' )
2019-08-06 22:26:01 +02:00
const dest = path . join (
distDir ,
isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY ,
relativeDest
)
2020-01-15 21:57:07 +01:00
if ( ! isSsg ) {
2019-08-06 22:26:01 +02:00
pagesManifest [ page ] = relativeDest
if ( page === '/' ) pagesManifest [ '/index' ] = relativeDest
if ( page === '/.amp' ) pagesManifest [ '/index.amp' ] = relativeDest
}
2019-05-22 18:36:53 +02:00
await mkdirp ( path . dirname ( dest ) )
await fsMove ( orig , dest )
}
2019-08-06 22:26:01 +02:00
2020-01-20 15:10:24 +01:00
if ( useStatic404 ) {
await moveExportedPage ( '/_errors/404' , '/_errors/404' , false , 'html' )
}
2019-08-06 22:26:01 +02:00
for ( const page of combinedPages ) {
2020-01-15 21:57:07 +01:00
const isSsg = ssgPages . has ( page )
2019-09-24 10:50:04 +02:00
const isDynamic = isDynamicRoute ( page )
2019-08-06 22:26:01 +02:00
let file = page === '/' ? '/index' : page
2020-01-15 21:57:07 +01:00
// The dynamic version of SSG pages are not prerendered. Below, we handle
2019-09-24 10:50:04 +02:00
// the specific prerenders of these.
2020-01-15 21:57:07 +01:00
if ( ! ( isSsg && isDynamic ) ) {
await moveExportedPage ( page , file , isSsg , 'html' )
2019-09-24 10:50:04 +02:00
}
2019-08-12 03:56:57 +02:00
const hasAmp = hybridAmpPages . has ( page )
2019-09-24 10:50:04 +02:00
if ( hasAmp ) {
2020-01-15 21:57:07 +01:00
await moveExportedPage ( ` ${ page } .amp ` , ` ${ file } .amp ` , isSsg , 'html' )
2019-09-24 10:50:04 +02:00
}
2020-01-15 21:57:07 +01:00
if ( isSsg ) {
// For a non-dynamic SSG page, we must copy its data file from export.
2019-09-24 10:50:04 +02:00
if ( ! isDynamic ) {
2019-09-25 17:29:22 +02:00
await moveExportedPage ( page , file , true , 'json' )
2019-09-24 10:50:04 +02:00
finalPrerenderRoutes [ page ] = {
initialRevalidateSeconds :
exportConfig . initialPageRevalidationMap [ page ] ,
2019-10-02 15:28:38 +02:00
srcRoute : null ,
2019-10-01 04:08:01 +02:00
dataRoute : path.posix.join (
'/_next/data' ,
2019-10-10 19:07:51 +02:00
buildId ,
2019-10-01 04:08:01 +02:00
` ${ page === '/' ? '/index' : page } .json `
) ,
2019-09-24 10:50:04 +02:00
}
} else {
2020-01-15 21:57:07 +01:00
// For a dynamic SSG page, we did not copy its html nor data exports.
2019-09-24 10:50:04 +02:00
// Instead, we must copy specific versions of this page as defined by
2020-01-15 21:57:07 +01:00
// `unstable_getStaticPaths` (additionalSsgPaths).
const extraRoutes = additionalSsgPaths . get ( page ) || [ ]
2019-09-24 10:50:04 +02:00
for ( const route of extraRoutes ) {
await moveExportedPage ( route , route , true , 'html' )
await moveExportedPage ( route , route , true , 'json' )
finalPrerenderRoutes [ route ] = {
initialRevalidateSeconds :
exportConfig . initialPageRevalidationMap [ route ] ,
2019-10-02 15:28:38 +02:00
srcRoute : page ,
2019-10-01 04:08:01 +02:00
dataRoute : path.posix.join (
'/_next/data' ,
2019-10-10 19:07:51 +02:00
buildId ,
2019-10-01 04:08:01 +02:00
` ${ route === '/' ? '/index' : route } .json `
) ,
2019-09-24 10:50:04 +02:00
}
}
}
}
2019-08-06 22:26:01 +02:00
}
2019-05-22 18:36:53 +02:00
// remove temporary export folder
await recursiveDelete ( exportOptions . outdir )
await fsRmdir ( exportOptions . outdir )
await fsWriteFile ( manifestPath , JSON . stringify ( pagesManifest ) , 'utf8' )
}
2019-11-09 23:34:53 +01:00
2019-09-16 17:37:00 +02:00
if ( postBuildSpinner ) postBuildSpinner . stopAndPersist ( )
console . log ( )
2019-08-06 22:26:01 +02:00
2019-08-29 18:43:06 +02:00
const analysisEnd = process . hrtime ( analysisBegin )
2019-12-03 17:18:58 +01:00
telemetry . record (
eventBuildOptimize ( {
durationInSeconds : analysisEnd [ 0 ] ,
totalPageCount : pagePaths.length ,
staticPageCount : staticPages.size ,
ssrPageCount : pagePaths.length - staticPages . size ,
} )
2019-08-29 18:43:06 +02:00
)
2020-01-15 21:57:07 +01:00
if ( ssgPages . size > 0 ) {
2019-10-01 04:08:01 +02:00
const finalDynamicRoutes : PrerenderManifest [ 'dynamicRoutes' ] = { }
tbdPrerenderRoutes . forEach ( tbdRoute = > {
const dataRoute = path . posix . join (
'/_next/data' ,
2019-10-10 19:07:51 +02:00
buildId ,
2019-10-01 04:08:01 +02:00
` ${ tbdRoute === '/' ? '/index' : tbdRoute } .json `
)
finalDynamicRoutes [ tbdRoute ] = {
routeRegex : getRouteRegex ( tbdRoute ) . re . source ,
dataRoute ,
dataRouteRegex : getRouteRegex (
dataRoute . replace ( /\.json$/ , '' )
) . re . source . replace ( /\(\?:\\\/\)\?\$$/ , '\\.json$' ) ,
}
} )
2019-09-24 10:50:04 +02:00
const prerenderManifest : PrerenderManifest = {
version : 1 ,
routes : finalPrerenderRoutes ,
2019-10-01 04:08:01 +02:00
dynamicRoutes : finalDynamicRoutes ,
2019-09-24 10:50:04 +02:00
}
2019-08-06 22:26:01 +02:00
await fsWriteFile (
path . join ( distDir , PRERENDER_MANIFEST ) ,
2019-09-24 10:50:04 +02:00
JSON . stringify ( prerenderManifest ) ,
2019-08-06 22:26:01 +02:00
'utf8'
)
}
2019-12-13 20:30:22 +01:00
await fsWriteFile (
path . join ( distDir , EXPORT_MARKER ) ,
JSON . stringify ( {
version : 1 ,
hasExportPathMap : typeof config . exportPathMap === 'function' ,
exportTrailingSlash : config.exportTrailingSlash === true ,
} ) ,
'utf8'
)
await fsUnlink ( path . join ( distDir , EXPORT_DETAIL ) ) . catch ( err = > {
if ( err . code === 'ENOENT' ) {
return Promise . resolve ( )
}
return Promise . reject ( err )
} )
2019-05-22 18:36:53 +02:00
staticPages . forEach ( pg = > allStaticPages . add ( pg ) )
pageInfos . forEach ( ( info : PageInfo , key : string ) = > {
allPageInfos . set ( key , info )
} )
2019-01-25 18:36:29 +01:00
2019-12-12 17:20:24 +01:00
await printTreeView (
Object . keys ( mappedPages ) ,
allPageInfos ,
isLikeServerless ,
{
2019-12-12 20:44:34 +01:00
distPath : distDir ,
2020-01-09 19:49:52 +01:00
buildId : buildId ,
2019-12-12 17:20:24 +01:00
pagesDir ,
pageExtensions : config.pageExtensions ,
2019-12-12 20:44:34 +01:00
buildManifest ,
isModern : config.experimental.modern ,
2019-12-12 17:20:24 +01:00
}
)
2019-11-26 10:33:47 +01:00
printCustomRoutes ( { redirects , rewrites } )
2019-08-18 21:45:39 +02:00
if ( tracer ) {
const parsedResults = await tracer . profiler . stopProfiling ( )
await new Promise ( resolve = > {
if ( parsedResults === undefined ) {
tracer . profiler . destroy ( )
tracer . trace . flush ( )
tracer . end ( resolve )
return
}
const cpuStartTime = parsedResults . profile . startTime
const cpuEndTime = parsedResults . profile . endTime
tracer . trace . completeEvent ( {
name : 'TaskQueueManager::ProcessTaskFromWorkQueue' ,
id : ++ tracer . counter ,
cat : [ 'toplevel' ] ,
ts : cpuStartTime ,
args : {
src_file : '../../ipc/ipc_moji_bootstrap.cc' ,
src_func : 'Accept' ,
} ,
} )
tracer . trace . completeEvent ( {
name : 'EvaluateScript' ,
id : ++ tracer . counter ,
cat : [ 'devtools.timeline' ] ,
ts : cpuStartTime ,
dur : cpuEndTime - cpuStartTime ,
args : {
data : {
url : 'webpack' ,
lineNumber : 1 ,
columnNumber : 1 ,
frame : '0xFFF' ,
} ,
} ,
} )
tracer . trace . instantEvent ( {
name : 'CpuProfile' ,
id : ++ tracer . counter ,
cat : [ 'disabled-by-default-devtools.timeline' ] ,
ts : cpuEndTime ,
args : {
data : {
cpuProfile : parsedResults.profile ,
} ,
} ,
} )
tracer . profiler . destroy ( )
tracer . trace . flush ( )
tracer . end ( resolve )
} )
}
2019-08-29 18:43:06 +02:00
2019-12-03 17:18:58 +01:00
await telemetry . flush ( )
2018-12-03 14:18:52 +01:00
}