2017-05-07 22:47:40 +00:00
|
|
|
import del from 'del'
|
2018-12-12 12:59:11 +00:00
|
|
|
import { cpus } from 'os'
|
|
|
|
import { fork } from 'child_process'
|
2017-05-07 22:47:40 +00:00
|
|
|
import cp from 'recursive-copy'
|
|
|
|
import mkdirp from 'mkdirp-then'
|
2018-12-12 12:59:11 +00:00
|
|
|
import { resolve, join } from 'path'
|
|
|
|
import { existsSync, readFileSync } from 'fs'
|
2018-10-01 22:55:31 +00:00
|
|
|
import loadConfig from 'next-server/next-config'
|
2019-01-26 01:01:16 +00:00
|
|
|
import { PHASE_EXPORT, SERVER_DIRECTORY, PAGES_MANIFEST, CONFIG_FILE, BUILD_ID_FILE, CLIENT_STATIC_FILES_PATH } from 'next-server/constants'
|
2018-12-12 12:59:11 +00:00
|
|
|
import createProgress from 'tty-aware-progress'
|
2017-05-07 22:47:40 +00:00
|
|
|
|
2017-09-27 18:48:46 +00:00
|
|
|
export default async function (dir, options, configuration) {
|
2018-09-04 14:01:50 +00:00
|
|
|
function log (message) {
|
|
|
|
if (options.silent) return
|
|
|
|
console.log(message)
|
|
|
|
}
|
|
|
|
|
2017-05-08 06:10:26 +00:00
|
|
|
dir = resolve(dir)
|
2018-06-04 09:38:46 +00:00
|
|
|
const nextConfig = configuration || loadConfig(PHASE_EXPORT, dir)
|
2018-12-12 12:59:11 +00:00
|
|
|
const concurrency = options.concurrency || 10
|
|
|
|
const threads = options.threads || Math.max(cpus().length - 1, 1)
|
2018-06-04 13:45:39 +00:00
|
|
|
const distDir = join(dir, nextConfig.distDir)
|
2017-05-07 22:47:40 +00:00
|
|
|
|
2018-06-04 13:45:39 +00:00
|
|
|
log(`> using build directory: ${distDir}`)
|
2017-05-11 16:23:08 +00:00
|
|
|
|
2018-06-04 13:45:39 +00:00
|
|
|
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-07 22:47:40 +00:00
|
|
|
}
|
|
|
|
|
2018-06-04 13:45:39 +00:00
|
|
|
const buildId = readFileSync(join(distDir, BUILD_ID_FILE), 'utf8')
|
|
|
|
const pagesManifest = require(join(distDir, SERVER_DIRECTORY, PAGES_MANIFEST))
|
2018-03-30 13:08:09 +00:00
|
|
|
|
|
|
|
const pages = Object.keys(pagesManifest)
|
|
|
|
const defaultPathMap = {}
|
|
|
|
|
|
|
|
for (const page of pages) {
|
2018-04-23 20:27:53 +00:00
|
|
|
// _document and _app are not real pages.
|
|
|
|
if (page === '/_document' || page === '/_app') {
|
2018-03-30 13:08:09 +00:00
|
|
|
continue
|
|
|
|
}
|
2018-08-27 10:28:54 +00:00
|
|
|
|
|
|
|
if (page === '/_error') {
|
2018-11-03 00:19:41 +00:00
|
|
|
defaultPathMap['/404.html'] = { page }
|
2018-08-27 10:28:54 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-03-30 13:08:09 +00:00
|
|
|
defaultPathMap[page] = { page }
|
|
|
|
}
|
2017-05-07 22:47:40 +00:00
|
|
|
|
|
|
|
// Initialize the output directory
|
2017-06-08 01:39:45 +00:00
|
|
|
const outDir = options.outdir
|
2017-05-18 04:28:22 +00:00
|
|
|
await del(join(outDir, '*'))
|
2017-05-07 22:47:40 +00:00
|
|
|
await mkdirp(join(outDir, '_next', buildId))
|
|
|
|
|
2017-05-14 00:11:13 +00:00
|
|
|
// Copy static directory
|
|
|
|
if (existsSync(join(dir, 'static'))) {
|
|
|
|
log(' copying "static" directory')
|
|
|
|
await cp(
|
|
|
|
join(dir, 'static'),
|
2018-01-31 07:38:43 +00:00
|
|
|
join(outDir, 'static'),
|
|
|
|
{ expand: true }
|
2017-05-14 00:11:13 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2018-02-07 10:54:07 +00:00
|
|
|
// Copy .next/static directory
|
2018-07-25 11:45:42 +00:00
|
|
|
if (existsSync(join(distDir, CLIENT_STATIC_FILES_PATH))) {
|
2018-02-07 10:54:07 +00:00
|
|
|
log(' copying "static build" directory')
|
|
|
|
await cp(
|
2018-07-25 11:45:42 +00:00
|
|
|
join(distDir, CLIENT_STATIC_FILES_PATH),
|
|
|
|
join(outDir, '_next', CLIENT_STATIC_FILES_PATH)
|
2018-02-07 10:54:07 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2018-06-04 09:38:46 +00:00
|
|
|
// Get the exportPathMap from the config file
|
2018-02-26 11:03:27 +00:00
|
|
|
if (typeof nextConfig.exportPathMap !== 'function') {
|
2018-06-04 09:38:46 +00:00
|
|
|
console.log(`> No "exportPathMap" found in "${CONFIG_FILE}". Generating map from "./pages"`)
|
2018-03-30 13:08:09 +00:00
|
|
|
nextConfig.exportPathMap = async (defaultMap) => {
|
|
|
|
return defaultMap
|
|
|
|
}
|
2017-05-09 01:20:50 +00:00
|
|
|
}
|
|
|
|
|
2017-05-08 06:10:26 +00:00
|
|
|
// Start the rendering process
|
|
|
|
const renderOpts = {
|
|
|
|
dir,
|
|
|
|
buildId,
|
2017-05-09 01:20:50 +00:00
|
|
|
nextExport: true,
|
2018-02-26 11:03:27 +00:00
|
|
|
assetPrefix: nextConfig.assetPrefix.replace(/\/$/, ''),
|
2018-06-04 13:45:39 +00:00
|
|
|
distDir,
|
2017-05-08 06:10:26 +00:00
|
|
|
dev: false,
|
|
|
|
staticMarkup: false,
|
2018-07-24 09:24:40 +00:00
|
|
|
hotReloader: null
|
2018-02-26 11:03:27 +00:00
|
|
|
}
|
|
|
|
|
2018-02-27 16:50:14 +00:00
|
|
|
const {serverRuntimeConfig, publicRuntimeConfig} = nextConfig
|
|
|
|
|
|
|
|
if (publicRuntimeConfig) {
|
|
|
|
renderOpts.runtimeConfig = publicRuntimeConfig
|
2017-05-08 06:10:26 +00:00
|
|
|
}
|
|
|
|
|
2017-05-09 01:20:50 +00:00
|
|
|
// We need this for server rendering the Link component.
|
|
|
|
global.__NEXT_DATA__ = {
|
|
|
|
nextExport: true
|
2017-05-08 06:10:26 +00:00
|
|
|
}
|
|
|
|
|
2018-12-12 12:59:11 +00:00
|
|
|
log(` launching ${threads} threads with concurrency of ${concurrency} per thread`)
|
2018-09-04 14:01:50 +00:00
|
|
|
const exportPathMap = await nextConfig.exportPathMap(defaultPathMap, {dev: false, dir, outDir, distDir, buildId})
|
2018-05-11 12:52:39 +00:00
|
|
|
const exportPaths = Object.keys(exportPathMap)
|
|
|
|
|
2018-12-12 12:59:11 +00:00
|
|
|
const progress = !options.silent && createProgress(exportPaths.length)
|
2017-05-09 01:53:08 +00:00
|
|
|
|
2018-12-12 12:59:11 +00:00
|
|
|
const chunks = exportPaths.reduce((result, route, i) => {
|
|
|
|
const worker = i % threads
|
|
|
|
if (!result[worker]) {
|
|
|
|
result[worker] = { paths: [], pathMap: {} }
|
2017-10-05 18:33:10 +00:00
|
|
|
}
|
2018-12-12 12:59:11 +00:00
|
|
|
result[worker].pathMap[route] = exportPathMap[route]
|
|
|
|
result[worker].paths.push(route)
|
|
|
|
return result
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
await Promise.all(
|
|
|
|
chunks.map(
|
|
|
|
chunk =>
|
|
|
|
new Promise((resolve, reject) => {
|
|
|
|
const worker = fork(require.resolve('./worker'), [], {
|
|
|
|
env: process.env
|
|
|
|
})
|
|
|
|
worker.send({
|
2018-12-18 16:12:49 +00:00
|
|
|
distDir,
|
|
|
|
buildId,
|
2018-12-12 12:59:11 +00:00
|
|
|
exportPaths: chunk.paths,
|
|
|
|
exportPathMap: chunk.pathMap,
|
|
|
|
outDir,
|
|
|
|
renderOpts,
|
2019-02-12 01:28:47 +00:00
|
|
|
serverRuntimeConfig,
|
2018-12-12 12:59:11 +00:00
|
|
|
concurrency
|
|
|
|
})
|
|
|
|
worker.on('message', ({ type, payload }) => {
|
|
|
|
if (type === 'progress' && progress) {
|
|
|
|
progress()
|
|
|
|
} else if (type === 'error') {
|
|
|
|
reject(payload)
|
|
|
|
} else if (type === 'done') {
|
|
|
|
resolve()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
)
|
|
|
|
)
|
2017-05-11 16:23:08 +00:00
|
|
|
|
|
|
|
// Add an empty line to the console for the better readability.
|
|
|
|
log('')
|
2017-05-07 22:47:40 +00:00
|
|
|
}
|