2020-03-29 00:43:52 +01:00
import chalk from 'next/dist/compiled/chalk'
2020-03-29 18:21:53 +02:00
import findUp from 'next/dist/compiled/find-up'
2019-12-13 20:30:22 +01:00
import {
2020-05-02 06:10:19 +02:00
promises ,
2019-12-13 20:30:22 +01:00
existsSync ,
2020-03-25 09:22:34 +01:00
exists as existsOrig ,
2019-12-13 20:30:22 +01:00
readFileSync ,
writeFileSync ,
} from 'fs'
2019-09-05 01:56:11 +02:00
import Worker from 'jest-worker'
2019-10-04 17:26:44 +02:00
import { cpus } from 'os'
2019-10-31 21:26:47 +01:00
import { dirname , join , resolve , sep } from 'path'
2019-10-04 17:26:44 +02:00
import { promisify } from 'util'
import { AmpPageStatus , formatAmpMessages } from '../build/output/index'
import createSpinner from '../build/spinner'
2020-05-19 15:29:34 +02:00
import { API_ROUTE , SSG_FALLBACK_EXPORT_ERROR } from '../lib/constants'
2019-09-05 01:56:11 +02:00
import { recursiveCopy } from '../lib/recursive-copy'
import { recursiveDelete } from '../lib/recursive-delete'
2019-05-29 13:57:26 +02:00
import {
BUILD_ID_FILE ,
2019-10-04 17:26:44 +02:00
CLIENT_PUBLIC_FILES_PATH ,
CLIENT_STATIC_FILES_PATH ,
CONFIG_FILE ,
2019-12-13 20:30:22 +01:00
EXPORT_DETAIL ,
2019-10-04 17:26:44 +02:00
PAGES_MANIFEST ,
PHASE_EXPORT ,
2019-09-24 10:50:04 +02:00
PRERENDER_MANIFEST ,
SERVERLESS_DIRECTORY ,
2019-12-13 20:30:22 +01:00
SERVER_DIRECTORY ,
2019-09-04 16:00:54 +02:00
} from '../next-server/lib/constants'
2019-10-04 17:26:44 +02:00
import loadConfig , {
isTargetLikeServerless ,
} from '../next-server/server/config'
2020-02-14 21:42:44 +01:00
import { eventCliSession } from '../telemetry/events'
2019-10-10 19:18:07 +02:00
import { Telemetry } from '../telemetry/storage'
2020-06-04 19:32:45 +02:00
import {
normalizePagePath ,
denormalizePagePath ,
} from '../next-server/server/normalize-page-path'
2020-03-26 13:32:41 +01:00
import { loadEnvConfig } from '../lib/load-env-config'
2020-05-19 15:29:34 +02:00
import { PrerenderManifest } from '../build'
2020-05-26 10:50:51 +02:00
import type exportPage from './worker'
2020-05-25 23:15:56 +02:00
import { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin'
2020-06-22 23:12:36 +02:00
import { getPagePath } from '../next-server/server/require'
2019-02-22 17:33:28 +01:00
2020-03-25 09:22:34 +01:00
const exists = promisify ( existsOrig )
2017-05-08 00:47:40 +02:00
2019-10-04 17:26:44 +02:00
const createProgress = ( total : number , label = 'Exporting' ) = > {
2019-09-16 17:37:00 +02:00
let curProgress = 0
let progressSpinner = createSpinner ( ` ${ label } ( ${ curProgress } / ${ total } ) ` , {
spinner : {
frames : [
'[ ]' ,
'[= ]' ,
'[== ]' ,
'[=== ]' ,
'[ ===]' ,
'[ ==]' ,
'[ =]' ,
'[ ]' ,
'[ =]' ,
'[ ==]' ,
'[ ===]' ,
'[====]' ,
'[=== ]' ,
'[== ]' ,
2019-10-04 17:26:44 +02:00
'[= ]' ,
2019-09-16 17:37:00 +02:00
] ,
2019-10-04 17:26:44 +02:00
interval : 80 ,
} ,
2019-09-16 17:37:00 +02:00
} )
return ( ) = > {
curProgress ++
const newText = ` ${ label } ( ${ curProgress } / ${ total } ) `
if ( progressSpinner ) {
progressSpinner . text = newText
} else {
console . log ( newText )
}
if ( curProgress === total && progressSpinner ) {
progressSpinner . stop ( )
console . log ( newText )
}
}
}
2019-10-04 17:26:44 +02:00
type ExportPathMap = {
[ page : string ] : { page : string ; query ? : { [ key : string ] : string } }
}
2020-05-25 23:15:56 +02:00
interface ExportOptions {
outdir : string
silent? : boolean
threads? : number
pages? : string [ ]
buildExport? : boolean
}
export default async function exportApp (
2019-10-04 17:26:44 +02:00
dir : string ,
2020-05-25 23:15:56 +02:00
options : ExportOptions ,
2019-10-04 17:26:44 +02:00
configuration? : any
) : Promise < void > {
2020-05-25 00:44:05 +02:00
function log ( message : string ) : void {
2019-10-04 17:26:44 +02:00
if ( options . silent ) {
return
}
2018-09-04 16:01:50 +02:00
console . log ( message )
}
2017-05-08 08:10:26 +02:00
dir = resolve ( dir )
2020-05-21 14:06:57 +02:00
// attempt to load global env values so they are available in next.config.js
loadEnvConfig ( dir )
2018-06-04 11:38:46 +02:00
const nextConfig = configuration || loadConfig ( PHASE_EXPORT , dir )
2018-12-12 13:59:11 +01:00
const threads = options . threads || Math . max ( cpus ( ) . length - 1 , 1 )
2018-06-04 15:45:39 +02:00
const distDir = join ( dir , nextConfig . distDir )
2019-12-03 17:18:58 +01:00
const telemetry = options . buildExport ? null : new Telemetry ( { distDir } )
if ( telemetry ) {
2019-11-08 18:03:50 +01:00
telemetry . record (
2020-02-14 21:42:44 +01:00
eventCliSession ( PHASE_EXPORT , distDir , {
2019-11-08 18:03:50 +01:00
cliCommand : 'export' ,
isSrcDir : null ,
hasNowJson : ! ! ( await findUp ( 'now.json' , { cwd : dir } ) ) ,
isCustomServer : null ,
} )
)
2019-10-03 16:21:15 +02:00
}
2019-07-03 19:25:44 +02:00
const subFolders = nextConfig . exportTrailingSlash
2019-09-24 10:50:04 +02:00
const isLikeServerless = nextConfig . target !== 'server'
2017-05-08 00:47:40 +02:00
2018-06-04 15:45:39 +02:00
log ( ` > using build directory: ${ distDir } ` )
2017-05-11 18:23:08 +02:00
2018-06-04 15:45:39 +02:00
if ( ! existsSync ( distDir ) ) {
2019-05-29 13:57:26 +02:00
throw new Error (
` Build directory ${ distDir } does not exist. Make sure you run "next build" before running "next start" or "next export". `
)
2017-05-08 00:47:40 +02:00
}
2018-06-04 15:45:39 +02:00
const buildId = readFileSync ( join ( distDir , BUILD_ID_FILE ) , 'utf8' )
2019-05-29 13:57:26 +02:00
const pagesManifest =
2019-12-14 07:31:48 +01:00
! options . pages &&
2020-05-25 23:15:56 +02:00
( require ( join (
2019-12-14 07:31:48 +01:00
distDir ,
isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY ,
PAGES_MANIFEST
2020-05-25 23:15:56 +02:00
) ) as PagesManifest )
2018-03-30 15:08:09 +02:00
2020-05-19 15:29:34 +02:00
let prerenderManifest : PrerenderManifest | undefined = undefined
2019-09-24 10:50:04 +02:00
try {
prerenderManifest = require ( join ( distDir , PRERENDER_MANIFEST ) )
} catch ( _ ) { }
2020-05-19 15:29:34 +02:00
const excludedPrerenderRoutes = new Set < string > ( )
2019-05-22 18:36:53 +02:00
const pages = options . pages || Object . keys ( pagesManifest )
2019-10-04 17:26:44 +02:00
const defaultPathMap : ExportPathMap = { }
2020-03-24 18:38:22 +01:00
let hasApiRoutes = false
2018-03-30 15:08:09 +02:00
for ( const page of pages ) {
Export 404 even if undefined in exportPathMap (#6912)
This PR adds the `_error`-page as an `404.html`-export, even when it is not explicitly defined in a custom `exportPathMap`.
It also fixes two false negative tests related to this. Previously the tests were matching the fallback 404-page from the test-server, rather than the `404.html`-page from next, which was actually not being generated. The test server is also not set up to serve `/404.html` as `/404` which the tests now reflect.
**Caveat**
In its current state, this PR removes `/404.html` from the `defaultPathMap` passed to the custom `exportPathMap`-functions, since it instead adds it after that function is run. While it is possible that someone is relying on this to exist, it is to my knowledge undocumented and also unlikely to be used for anything but merging it into the custom pathMap.
Since this would now merge `undefined` which would result in it being added later on anyway, I deemed it safe, but would be happy to undo that part of the PR if necessary as it was only cleanup.
**Examples**
As a way to demonstrate what this PR does, this is how examples changed:
* `basic-export` - Behaviour is unchanged, still has a `404.html`
* `with-static-export` - Now has a `404.html`
2019-04-21 22:24:28 +02:00
// _document and _app are not real pages
// _error is exported as 404.html later on
2019-08-12 00:37:20 +02:00
// API Routes are Node.js functions
2020-03-24 18:38:22 +01:00
if ( page . match ( API_ROUTE ) ) {
hasApiRoutes = true
continue
}
if ( page === '/_document' || page === '/_app' || page === '/_error' ) {
2018-08-27 12:28:54 +02:00
continue
}
2019-11-22 02:20:19 +01:00
// iSSG pages that are dynamic should not export templated version by
// default. In most cases, this would never work. There is no server that
// could run `getStaticProps`. If users make their page work lazily, they
// can manually add it to the `exportPathMap`.
2020-01-08 17:30:53 +01:00
if ( prerenderManifest ? . dynamicRoutes [ page ] ) {
2020-05-19 15:29:34 +02:00
excludedPrerenderRoutes . add ( page )
2019-11-22 02:20:19 +01:00
continue
}
2018-03-30 15:08:09 +02:00
defaultPathMap [ page ] = { page }
}
2017-05-08 00:47:40 +02:00
// Initialize the output directory
2017-06-08 03:39:45 +02:00
const outDir = options . outdir
2019-10-06 13:44:03 +02:00
if ( outDir === join ( dir , 'public' ) ) {
throw new Error (
2020-05-27 23:51:11 +02:00
` The 'public' directory is reserved in Next.js and can not be used as the export out directory. https://err.sh/vercel/next.js/can-not-output-to-public `
2019-10-06 13:44:03 +02:00
)
}
2019-03-05 14:01:42 +01:00
await recursiveDelete ( join ( outDir ) )
2020-05-02 06:10:19 +02:00
await promises . mkdir ( join ( outDir , '_next' , buildId ) , { recursive : true } )
2017-05-08 00:47:40 +02:00
2019-12-13 20:30:22 +01:00
writeFileSync (
join ( distDir , EXPORT_DETAIL ) ,
JSON . stringify ( {
version : 1 ,
outDirectory : outDir ,
success : false ,
} ) ,
'utf8'
)
2017-05-14 02:11:13 +02:00
// Copy static directory
2019-10-02 15:19:23 +02:00
if ( ! options . buildExport && existsSync ( join ( dir , 'static' ) ) ) {
2017-05-14 02:11:13 +02:00
log ( ' copying "static" directory' )
2019-06-06 12:33:11 +02:00
await recursiveCopy ( join ( dir , 'static' ) , join ( outDir , 'static' ) )
2017-05-14 02:11:13 +02:00
}
2018-02-07 11:54:07 +01:00
// Copy .next/static directory
2018-07-25 13:45:42 +02:00
if ( existsSync ( join ( distDir , CLIENT_STATIC_FILES_PATH ) ) ) {
2018-02-07 11:54:07 +01:00
log ( ' copying "static build" directory' )
2019-06-06 12:33:11 +02:00
await recursiveCopy (
2018-07-25 13:45:42 +02:00
join ( distDir , CLIENT_STATIC_FILES_PATH ) ,
join ( outDir , '_next' , CLIENT_STATIC_FILES_PATH )
2018-02-07 11:54:07 +01:00
)
}
2018-06-04 11:38:46 +02:00
// Get the exportPathMap from the config file
2018-02-26 12:03:27 +01:00
if ( typeof nextConfig . exportPathMap !== 'function' ) {
2019-05-29 13:57:26 +02:00
console . log (
` > No "exportPathMap" found in " ${ CONFIG_FILE } ". Generating map from "./pages" `
)
2019-10-04 17:26:44 +02:00
nextConfig . exportPathMap = async ( defaultMap : ExportPathMap ) = > {
2018-03-30 15:08:09 +02:00
return defaultMap
}
2017-05-09 03:20:50 +02:00
}
2017-05-08 08:10:26 +02:00
// Start the rendering process
const renderOpts = {
dir ,
buildId ,
2017-05-09 03:20:50 +02:00
nextExport : true ,
2018-02-26 12:03:27 +01:00
assetPrefix : nextConfig.assetPrefix.replace ( /\/$/ , '' ) ,
2018-06-04 15:45:39 +02:00
distDir ,
2017-05-08 08:10:26 +02:00
dev : false ,
2019-05-29 02:32:18 +02:00
hotReloader : null ,
2020-06-18 12:10:20 +02:00
basePath : nextConfig.basePath ,
2020-01-08 17:30:53 +01:00
canonicalBase : nextConfig.amp?.canonicalBase || '' ,
2019-10-04 17:26:44 +02:00
isModern : nextConfig.experimental.modern ,
2020-02-02 19:02:56 +01:00
ampValidatorPath : nextConfig.experimental.amp?.validator || undefined ,
2020-03-24 09:31:04 +01:00
ampSkipValidation : nextConfig.experimental.amp?.skipValidation || false ,
ampOptimizerConfig : nextConfig.experimental.amp?.optimizer || undefined ,
2018-02-26 12:03:27 +01:00
}
2019-02-19 22:45:07 +01:00
const { serverRuntimeConfig , publicRuntimeConfig } = nextConfig
2018-02-27 17:50:14 +01:00
2019-07-10 16:43:04 +02:00
if ( Object . keys ( publicRuntimeConfig ) . length > 0 ) {
2019-10-04 17:26:44 +02:00
; ( renderOpts as any ) . runtimeConfig = publicRuntimeConfig
2017-05-08 08:10:26 +02:00
}
2017-05-09 03:20:50 +02:00
// We need this for server rendering the Link component.
2019-10-04 17:26:44 +02:00
; ( global as any ) . __NEXT_DATA__ = {
nextExport : true ,
2017-05-08 08:10:26 +02:00
}
2019-09-05 01:56:11 +02:00
log ( ` launching ${ threads } workers ` )
2019-05-29 13:57:26 +02:00
const exportPathMap = await nextConfig . exportPathMap ( defaultPathMap , {
dev : false ,
dir ,
outDir ,
distDir ,
2019-10-04 17:26:44 +02:00
buildId ,
2019-05-29 13:57:26 +02:00
} )
2020-05-27 07:48:50 +02:00
2020-01-15 21:18:31 +01:00
if ( ! exportPathMap [ '/404' ] && ! exportPathMap [ '/404.html' ] ) {
exportPathMap [ '/404' ] = exportPathMap [ '/404.html' ] = {
2019-10-04 17:26:44 +02:00
page : '/_error' ,
2019-09-04 16:00:54 +02:00
}
2019-08-19 17:16:00 +02:00
}
2020-05-27 07:48:50 +02:00
// make sure to prevent duplicates
const exportPaths = [
. . . new Set (
2020-06-04 19:32:45 +02:00
Object . keys ( exportPathMap ) . map ( ( path ) = >
denormalizePagePath ( normalizePagePath ( path ) )
2020-05-27 07:48:50 +02:00
)
) ,
]
2019-08-12 00:37:20 +02:00
const filteredPaths = exportPaths . filter (
// Remove API routes
2020-05-18 21:24:37 +02:00
( route ) = > ! exportPathMap [ route ] . page . match ( API_ROUTE )
2019-08-12 00:37:20 +02:00
)
2020-03-24 18:38:22 +01:00
if ( filteredPaths . length !== exportPaths . length ) {
hasApiRoutes = true
}
2019-08-12 00:37:20 +02:00
2020-05-19 15:29:34 +02:00
if ( prerenderManifest && ! options . buildExport ) {
const fallbackTruePages = new Set ( )
for ( const key of Object . keys ( prerenderManifest . dynamicRoutes ) ) {
// only error if page is included in path map
if ( ! exportPathMap [ key ] && ! excludedPrerenderRoutes . has ( key ) ) {
continue
}
if ( prerenderManifest . dynamicRoutes [ key ] . fallback !== false ) {
fallbackTruePages . add ( key )
}
}
if ( fallbackTruePages . size ) {
throw new Error (
` Found pages with \` fallback: true \` : \ n ${ [ . . . fallbackTruePages ] . join (
'\n'
) } \ n $ { SSG_FALLBACK_EXPORT_ERROR } \ n `
)
}
}
2019-08-12 00:37:20 +02:00
// Warn if the user defines a path for an API page
if ( hasApiRoutes ) {
log (
2020-06-09 21:43:15 +02:00
chalk . bold . red ( ` Warning ` ) +
2020-03-24 18:38:22 +01:00
': ' +
chalk . yellow (
` Statically exporting a Next.js application via \` next export \` disables API routes. `
) +
` \ n ` +
chalk . yellow (
` This command is meant for static-only hosts, and is ` +
' ' +
chalk . bold ( ` not necessary to make your application static. ` )
) +
` \ n ` +
chalk . yellow (
` Pages in your application without server-side data dependencies will be automatically statically exported by \` next build \` , including pages powered by \` getStaticProps \` . `
) +
2020-05-27 23:51:11 +02:00
` \ nLearn more: https://err.sh/vercel/next.js/api-routes-static-export `
2019-08-12 00:37:20 +02:00
)
}
2018-05-11 14:52:39 +02:00
2019-08-12 00:37:20 +02:00
const progress = ! options . silent && createProgress ( filteredPaths . length )
2020-01-15 02:22:15 +01:00
const pagesDataDir = options . buildExport
2019-10-10 19:07:51 +02:00
? outDir
: join ( outDir , '_next/data' , buildId )
2017-05-09 03:53:08 +02:00
2019-10-04 17:26:44 +02:00
const ampValidations : AmpPageStatus = { }
2019-03-26 22:21:27 +01:00
let hadValidationError = false
2019-05-03 18:57:47 +02:00
const publicDir = join ( dir , CLIENT_PUBLIC_FILES_PATH )
// Copy public directory
2019-10-06 13:44:03 +02:00
if ( ! options . buildExport && existsSync ( publicDir ) ) {
2019-05-03 18:57:47 +02:00
log ( ' copying "public" directory' )
2019-06-06 12:33:11 +02:00
await recursiveCopy ( publicDir , outDir , {
2019-10-04 17:26:44 +02:00
filter ( path ) {
2019-05-29 13:57:26 +02:00
// Exclude paths used by pages
2019-06-06 12:33:11 +02:00
return ! exportPathMap [ path ]
2019-10-04 17:26:44 +02:00
} ,
2019-05-29 13:57:26 +02:00
} )
2019-05-03 18:57:47 +02:00
}
2019-09-05 01:56:11 +02:00
2020-05-25 23:15:56 +02:00
const worker = new Worker ( require . resolve ( './worker' ) , {
maxRetries : 0 ,
numWorkers : threads ,
enableWorkerThreads : nextConfig.experimental.workerThreads ,
exposedMethods : [ 'default' ] ,
2020-05-26 10:50:51 +02:00
} ) as Worker & { default : typeof exportPage }
2019-09-05 01:56:11 +02:00
worker . getStdout ( ) . pipe ( process . stdout )
worker . getStderr ( ) . pipe ( process . stderr )
let renderError = false
Add failing paths to export error summary (#10026)
Closes #9990 by collecting all paths with errors during `next export` and reporting them sorted in the error summary at the end.
It will produce an output similar to:
```
Error: Export encountered errors on following paths:
/nested/page
/page
/page-1
/page-10
/page-11
/page-12
/page-13
/page-2
/page-3
/page-4
/page-5
/page-6
/page-7
/page-8
/page-9
at _default (/app/next.js/packages/next/dist/export/index.js:19:788)
at process._tickCallback (internal/process/next_tick.js:68:7)
```
I tested the output with the `handle-export-errors` integration test suite, but I'm not sure how to gracefully test this added output.
I thought of collecting all page source files with [recursiveReaddirSync](https://github.com/zeit/next.js/blob/2ba352da39ee00b6595aecdc9ffb2f103e803a85/packages/next/next-server/server/lib/recursive-readdir-sync.ts) but it seems I can't import it in js test files:
```
SyntaxError: /app/next.js/packages/next/next-server/server/lib/recursive-readdir-sync.ts: Unexpected token, expected "," (11:5)
9 | */
10 | export function recursiveReadDirSync(
> 11 | dir: string,
| ^
12 | arr: string[] = [],
13 | rootDir = dir
14 | ): string[] {
```
The test itself could look like:
```js
it('Reports failing paths', async () => {
const { stderr } = await nextBuild(appDir, [], {
stdout: true,
stderr: true,
})
const pages = []
// collect pages to be ['/page', '/page-1', ... etc.]
pages.forEach(page => {
expect(stderr).toContain(page)
})
})
```
2020-05-26 21:50:25 +02:00
const errorPaths : string [ ] = [ ]
2019-05-03 18:57:47 +02:00
2018-12-12 13:59:11 +01:00
await Promise . all (
2020-05-18 21:24:37 +02:00
filteredPaths . map ( async ( path ) = > {
2019-09-05 01:56:11 +02:00
const result = await worker . default ( {
path ,
pathMap : exportPathMap [ path ] ,
distDir ,
outDir ,
2020-01-15 02:22:15 +01:00
pagesDataDir ,
2019-09-05 01:56:11 +02:00
renderOpts ,
serverRuntimeConfig ,
subFolders ,
2019-09-24 10:50:04 +02:00
buildExport : options.buildExport ,
2019-10-04 17:26:44 +02:00
serverless : isTargetLikeServerless ( nextConfig . target ) ,
2019-09-05 01:56:11 +02:00
} )
for ( const validation of result . ampValidations || [ ] ) {
2020-06-01 23:00:22 +02:00
const { page , result : ampValidationResult } = validation
ampValidations [ page ] = ampValidationResult
2019-10-04 17:26:44 +02:00
hadValidationError =
hadValidationError ||
2020-06-01 23:00:22 +02:00
( Array . isArray ( ampValidationResult ? . errors ) &&
ampValidationResult . errors . length > 0 )
2019-09-05 01:56:11 +02:00
}
2019-10-04 17:26:44 +02:00
renderError = renderError || ! ! result . error
Add failing paths to export error summary (#10026)
Closes #9990 by collecting all paths with errors during `next export` and reporting them sorted in the error summary at the end.
It will produce an output similar to:
```
Error: Export encountered errors on following paths:
/nested/page
/page
/page-1
/page-10
/page-11
/page-12
/page-13
/page-2
/page-3
/page-4
/page-5
/page-6
/page-7
/page-8
/page-9
at _default (/app/next.js/packages/next/dist/export/index.js:19:788)
at process._tickCallback (internal/process/next_tick.js:68:7)
```
I tested the output with the `handle-export-errors` integration test suite, but I'm not sure how to gracefully test this added output.
I thought of collecting all page source files with [recursiveReaddirSync](https://github.com/zeit/next.js/blob/2ba352da39ee00b6595aecdc9ffb2f103e803a85/packages/next/next-server/server/lib/recursive-readdir-sync.ts) but it seems I can't import it in js test files:
```
SyntaxError: /app/next.js/packages/next/next-server/server/lib/recursive-readdir-sync.ts: Unexpected token, expected "," (11:5)
9 | */
10 | export function recursiveReadDirSync(
> 11 | dir: string,
| ^
12 | arr: string[] = [],
13 | rootDir = dir
14 | ): string[] {
```
The test itself could look like:
```js
it('Reports failing paths', async () => {
const { stderr } = await nextBuild(appDir, [], {
stdout: true,
stderr: true,
})
const pages = []
// collect pages to be ['/page', '/page-1', ... etc.]
pages.forEach(page => {
expect(stderr).toContain(page)
})
})
```
2020-05-26 21:50:25 +02:00
if ( ! ! result . error ) errorPaths . push ( path )
2019-09-24 10:50:04 +02:00
if (
options . buildExport &&
typeof result . fromBuildExportRevalidate !== 'undefined'
) {
configuration . initialPageRevalidationMap [ path ] =
result . fromBuildExportRevalidate
}
2019-09-05 01:56:11 +02:00
if ( progress ) progress ( )
} )
2018-12-12 13:59:11 +01:00
)
2017-05-11 18:23:08 +02:00
2019-09-05 01:56:11 +02:00
worker . end ( )
2019-05-22 18:36:53 +02:00
2019-09-24 10:50:04 +02:00
// copy prerendered routes to outDir
if ( ! options . buildExport && prerenderManifest ) {
await Promise . all (
2020-05-18 21:24:37 +02:00
Object . keys ( prerenderManifest . routes ) . map ( async ( route ) = > {
2020-06-22 23:12:36 +02:00
const { srcRoute } = prerenderManifest ! . routes [ route ]
const pageName = srcRoute || route
const pagePath = getPagePath ( pageName , distDir , isLikeServerless )
const distPagesDir = join (
pagePath ,
// strip leading / and then recurse number of nested dirs
// to place from base folder
pageName
. substr ( 1 )
. split ( '/' )
. map ( ( ) = > '..' )
. join ( '/' )
)
2020-02-05 22:10:39 +01:00
route = normalizePagePath ( route )
2020-06-22 23:12:36 +02:00
2019-09-24 10:50:04 +02:00
const orig = join ( distPagesDir , route )
2019-10-31 21:26:47 +01:00
const htmlDest = join (
outDir ,
` ${ route } ${
subFolders && route !== '/index' ? ` ${ sep } index ` : ''
} . html `
)
2020-03-25 09:22:34 +01:00
const ampHtmlDest = join (
outDir ,
` ${ route } .amp ${ subFolders ? ` ${ sep } index ` : '' } .html `
)
2020-01-15 02:22:15 +01:00
const jsonDest = join ( pagesDataDir , ` ${ route } .json ` )
2019-09-24 10:50:04 +02:00
2020-05-02 06:10:19 +02:00
await promises . mkdir ( dirname ( htmlDest ) , { recursive : true } )
await promises . mkdir ( dirname ( jsonDest ) , { recursive : true } )
await promises . copyFile ( ` ${ orig } .html ` , htmlDest )
await promises . copyFile ( ` ${ orig } .json ` , jsonDest )
2020-03-25 09:22:34 +01:00
if ( await exists ( ` ${ orig } .amp.html ` ) ) {
2020-05-02 06:10:19 +02:00
await promises . mkdir ( dirname ( ampHtmlDest ) , { recursive : true } )
await promises . copyFile ( ` ${ orig } .amp.html ` , ampHtmlDest )
2020-03-25 09:22:34 +01:00
}
2019-09-24 10:50:04 +02:00
} )
)
}
2019-03-26 22:21:27 +01:00
if ( Object . keys ( ampValidations ) . length ) {
console . log ( formatAmpMessages ( ampValidations ) )
}
if ( hadValidationError ) {
2019-05-29 13:57:26 +02:00
throw new Error (
2020-05-27 23:51:11 +02:00
` AMP Validation caused the export to fail. https://err.sh/vercel/next.js/amp-export-validation `
2019-05-29 13:57:26 +02:00
)
2019-03-26 22:21:27 +01:00
}
2019-09-05 01:56:11 +02:00
if ( renderError ) {
Add failing paths to export error summary (#10026)
Closes #9990 by collecting all paths with errors during `next export` and reporting them sorted in the error summary at the end.
It will produce an output similar to:
```
Error: Export encountered errors on following paths:
/nested/page
/page
/page-1
/page-10
/page-11
/page-12
/page-13
/page-2
/page-3
/page-4
/page-5
/page-6
/page-7
/page-8
/page-9
at _default (/app/next.js/packages/next/dist/export/index.js:19:788)
at process._tickCallback (internal/process/next_tick.js:68:7)
```
I tested the output with the `handle-export-errors` integration test suite, but I'm not sure how to gracefully test this added output.
I thought of collecting all page source files with [recursiveReaddirSync](https://github.com/zeit/next.js/blob/2ba352da39ee00b6595aecdc9ffb2f103e803a85/packages/next/next-server/server/lib/recursive-readdir-sync.ts) but it seems I can't import it in js test files:
```
SyntaxError: /app/next.js/packages/next/next-server/server/lib/recursive-readdir-sync.ts: Unexpected token, expected "," (11:5)
9 | */
10 | export function recursiveReadDirSync(
> 11 | dir: string,
| ^
12 | arr: string[] = [],
13 | rootDir = dir
14 | ): string[] {
```
The test itself could look like:
```js
it('Reports failing paths', async () => {
const { stderr } = await nextBuild(appDir, [], {
stdout: true,
stderr: true,
})
const pages = []
// collect pages to be ['/page', '/page-1', ... etc.]
pages.forEach(page => {
expect(stderr).toContain(page)
})
})
```
2020-05-26 21:50:25 +02:00
throw new Error (
` Export encountered errors on following paths: \ n \ t ${ errorPaths
. sort ( )
. join ( '\n\t' ) } `
)
2019-09-05 01:56:11 +02:00
}
2017-05-11 18:23:08 +02:00
// Add an empty line to the console for the better readability.
log ( '' )
2019-12-03 17:18:58 +01:00
2019-12-13 20:30:22 +01:00
writeFileSync (
join ( distDir , EXPORT_DETAIL ) ,
JSON . stringify ( {
version : 1 ,
outDirectory : outDir ,
success : true ,
} ) ,
'utf8'
)
2019-12-03 17:18:58 +01:00
if ( telemetry ) {
await telemetry . flush ( )
}
2017-05-08 00:47:40 +02:00
}