rsnext/packages/next/export/index.js

236 lines
6.4 KiB
JavaScript
Raw Normal View History

import { cpus } from 'os'
import chalk from 'chalk'
import Worker from 'jest-worker'
import { promisify } from 'util'
import mkdirpModule from 'mkdirp'
import { resolve, join } from 'path'
import { API_ROUTE } from '../lib/constants'
import { existsSync, readFileSync } from 'fs'
import createProgress from 'tty-aware-progress'
import { recursiveCopy } from '../lib/recursive-copy'
import { recursiveDelete } from '../lib/recursive-delete'
import { formatAmpMessages } from '../build/output/index'
import loadConfig, {
isTargetLikeServerless
} from '../next-server/server/config'
import {
PHASE_EXPORT,
SERVER_DIRECTORY,
PAGES_MANIFEST,
CONFIG_FILE,
BUILD_ID_FILE,
CLIENT_PUBLIC_FILES_PATH,
CLIENT_STATIC_FILES_PATH
} from '../next-server/lib/constants'
const mkdirp = promisify(mkdirpModule)
2017-05-08 00:47:40 +02:00
export default async function (dir, options, configuration) {
function log (message) {
if (options.silent) return
console.log(message)
}
2017-05-08 08:10:26 +02:00
dir = resolve(dir)
const nextConfig = configuration || loadConfig(PHASE_EXPORT, dir)
const threads = options.threads || Math.max(cpus().length - 1, 1)
const distDir = join(dir, nextConfig.distDir)
const subFolders = nextConfig.exportTrailingSlash
2017-05-08 00:47:40 +02:00
if (!options.buildExport && nextConfig.target !== 'server') {
throw new Error(
'Cannot export when target is not server. https://err.sh/zeit/next.js/next-export-serverless'
)
}
log(`> using build directory: ${distDir}`)
if (!existsSync(distDir)) {
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
}
const buildId = readFileSync(join(distDir, BUILD_ID_FILE), 'utf8')
const pagesManifest =
!options.pages && require(join(distDir, SERVER_DIRECTORY, PAGES_MANIFEST))
const pages = options.pages || Object.keys(pagesManifest)
const defaultPathMap = {}
for (const page of pages) {
// _document and _app are not real pages
// _error is exported as 404.html later on
// API Routes are Node.js functions
2019-08-06 00:46:02 +02:00
if (
page === '/_document' ||
page === '/_app' ||
page === '/_error' ||
page.match(API_ROUTE)
2019-08-06 00:46:02 +02:00
) {
continue
}
defaultPathMap[page] = { page }
}
2017-05-08 00:47:40 +02:00
// Initialize the output directory
const outDir = options.outdir
await recursiveDelete(join(outDir))
2017-05-08 00:47:40 +02:00
await mkdirp(join(outDir, '_next', buildId))
// Copy static directory
if (existsSync(join(dir, 'static'))) {
log(' copying "static" directory')
await recursiveCopy(join(dir, 'static'), join(outDir, 'static'))
}
// Copy .next/static directory
if (existsSync(join(distDir, CLIENT_STATIC_FILES_PATH))) {
log(' copying "static build" directory')
await recursiveCopy(
join(distDir, CLIENT_STATIC_FILES_PATH),
join(outDir, '_next', CLIENT_STATIC_FILES_PATH)
)
}
// Get the exportPathMap from the config file
if (typeof nextConfig.exportPathMap !== 'function') {
console.log(
`> No "exportPathMap" found in "${CONFIG_FILE}". Generating map from "./pages"`
)
nextConfig.exportPathMap = async defaultMap => {
return defaultMap
}
}
2017-05-08 08:10:26 +02:00
// Start the rendering process
const renderOpts = {
dir,
buildId,
nextExport: true,
assetPrefix: nextConfig.assetPrefix.replace(/\/$/, ''),
distDir,
2017-05-08 08:10:26 +02:00
dev: false,
staticMarkup: false,
hotReloader: null,
canonicalBase: (nextConfig.amp && nextConfig.amp.canonicalBase) || '',
isModern: nextConfig.experimental.modern
}
const { serverRuntimeConfig, publicRuntimeConfig } = nextConfig
2018-02-27 17:50:14 +01:00
if (Object.keys(publicRuntimeConfig).length > 0) {
2018-02-27 17:50:14 +01:00
renderOpts.runtimeConfig = publicRuntimeConfig
2017-05-08 08:10:26 +02:00
}
// We need this for server rendering the Link component.
global.__NEXT_DATA__ = {
nextExport: true
2017-05-08 08:10:26 +02:00
}
log(` launching ${threads} workers`)
const exportPathMap = await nextConfig.exportPathMap(defaultPathMap, {
dev: false,
dir,
outDir,
distDir,
buildId
})
if (!exportPathMap['/404']) {
exportPathMap['/404.html'] = exportPathMap['/404.html'] || {
page: '/_error'
}
}
const exportPaths = Object.keys(exportPathMap)
const filteredPaths = exportPaths.filter(
// Remove API routes
route => !exportPathMap[route].page.match(API_ROUTE)
)
const hasApiRoutes = exportPaths.length !== filteredPaths.length
// Warn if the user defines a path for an API page
if (hasApiRoutes) {
log(
chalk.yellow(
' API pages are not supported by next export. https://err.sh/zeit/next.js/api-routes-static-export'
)
)
}
const progress = !options.silent && createProgress(filteredPaths.length)
2017-05-09 03:53:08 +02:00
const ampValidations = {}
let hadValidationError = false
2019-05-03 18:57:47 +02:00
const publicDir = join(dir, CLIENT_PUBLIC_FILES_PATH)
// Copy public directory
if (
nextConfig.experimental &&
nextConfig.experimental.publicDirectory &&
existsSync(publicDir)
) {
2019-05-03 18:57:47 +02:00
log(' copying "public" directory')
await recursiveCopy(publicDir, outDir, {
filter (path) {
// Exclude paths used by pages
return !exportPathMap[path]
2019-05-03 18:57:47 +02:00
}
})
2019-05-03 18:57:47 +02:00
}
const worker = new Worker(require.resolve('./worker'), {
maxRetries: 0,
numWorkers: threads,
enableWorkerThreads: true,
exposedMethods: ['default']
})
worker.getStdout().pipe(process.stdout)
worker.getStderr().pipe(process.stderr)
let renderError = false
2019-05-03 18:57:47 +02:00
await Promise.all(
filteredPaths.map(async path => {
const result = await worker.default({
path,
pathMap: exportPathMap[path],
distDir,
buildId,
outDir,
renderOpts,
serverRuntimeConfig,
subFolders,
serverless: isTargetLikeServerless(nextConfig.target)
})
for (const validation of result.ampValidations || []) {
const { page, result } = validation
ampValidations[page] = result
hadValidationError |=
Array.isArray(result && result.errors) && result.errors.length > 0
}
renderError |= result.error
if (progress) progress()
})
)
worker.end()
if (Object.keys(ampValidations).length) {
console.log(formatAmpMessages(ampValidations))
}
if (hadValidationError) {
throw new Error(
`AMP Validation caused the export to fail. https://err.sh/zeit/next.js/amp-export-validation`
)
}
if (renderError) {
throw new Error(`Export encountered errors`)
}
// Add an empty line to the console for the better readability.
log('')
2017-05-08 00:47:40 +02:00
}