1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00
next.js/server/export.js

195 lines
5.8 KiB
JavaScript
Raw Normal View History

2017-05-07 22:47:40 +00:00
import del from 'del'
import cp from 'recursive-copy'
import mkdirp from 'mkdirp-then'
import walk from 'walk'
import { extname, resolve, join, dirname, sep } from 'path'
2017-05-08 06:10:26 +00:00
import { existsSync, readFileSync, writeFileSync } from 'fs'
import getConfig from './config'
import {PHASE_EXPORT} from '../lib/constants'
2017-05-08 06:10:26 +00:00
import { renderToHTML } from './render'
import { getAvailableChunks } from './utils'
2017-05-08 06:10:26 +00:00
import { printAndExit } from '../lib/utils'
import { setAssetPrefix } from '../lib/asset'
import * as envConfig from '../lib/runtime-config'
2017-05-07 22:47:40 +00:00
export default async function (dir, options, configuration) {
2017-05-08 06:10:26 +00:00
dir = resolve(dir)
const nextConfig = configuration || getConfig(PHASE_EXPORT, dir)
const nextDir = join(dir, nextConfig.distDir)
2017-05-07 22:47:40 +00:00
log(` using build directory: ${nextDir}`)
2017-05-07 22:47:40 +00:00
if (!existsSync(nextDir)) {
console.error(
`Build directory ${nextDir} does not exist. Make sure you run "next build" before running "next start" or "next export".`
)
2017-05-07 22:47:40 +00:00
process.exit(1)
}
const buildId = readFileSync(join(nextDir, 'BUILD_ID'), 'utf8')
const buildStats = require(join(nextDir, 'build-stats.json'))
// Initialize the output directory
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', buildStats['app.js'].hash))
await mkdirp(join(outDir, '_next', buildId))
// Copy files
await cp(
join(nextDir, 'app.js'),
join(outDir, '_next', buildStats['app.js'].hash, 'app.js')
)
// Copy static directory
if (existsSync(join(dir, 'static'))) {
log(' copying "static" directory')
await cp(
join(dir, 'static'),
join(outDir, 'static'),
{ expand: true }
)
}
// Copy .next/static directory
if (existsSync(join(nextDir, 'static'))) {
log(' copying "static build" directory')
await cp(
join(nextDir, 'static'),
join(outDir, '_next', 'static')
)
}
// Copy dynamic import chunks
if (existsSync(join(nextDir, 'chunks'))) {
log(' copying dynamic import chunks')
await mkdirp(join(outDir, '_next', 'webpack'))
await cp(
join(nextDir, 'chunks'),
join(outDir, '_next', 'webpack', 'chunks')
)
}
await copyPages(nextDir, outDir, buildId)
2017-05-08 06:10:26 +00:00
// Get the exportPathMap from the `next.config.js`
if (typeof nextConfig.exportPathMap !== 'function') {
printAndExit(
'> Could not find "exportPathMap" function inside "next.config.js"\n' +
'> "next export" uses that function to build html pages.'
)
}
const exportPathMap = await nextConfig.exportPathMap()
const exportPaths = Object.keys(exportPathMap)
2017-05-08 06:10:26 +00:00
// Start the rendering process
const renderOpts = {
dir,
dist: nextConfig.distDir,
2017-05-08 06:10:26 +00:00
buildStats,
buildId,
nextExport: true,
assetPrefix: nextConfig.assetPrefix.replace(/\/$/, ''),
2017-05-08 06:10:26 +00:00
dev: false,
staticMarkup: false,
hotReloader: null,
availableChunks: getAvailableChunks(dir, nextConfig.distDir)
}
// Allow configuration from next.config.js to be passed to the server / client
if (nextConfig.runtimeConfig) {
// Initialize next/config with the environment configuration
envConfig.setConfig(nextConfig.runtimeConfig)
// Only the `public` key is exposed to the client side
// It'll be rendered as part of __NEXT_DATA__ on the client side
if (nextConfig.runtimeConfig.public) {
renderOpts.runtimeConfig = {
public: nextConfig.runtimeConfig.public
}
}
2017-05-08 06:10:26 +00:00
}
// set the assetPrefix to use for 'next/asset'
setAssetPrefix(renderOpts.assetPrefix)
// We need this for server rendering the Link component.
global.__NEXT_DATA__ = {
nextExport: true
2017-05-08 06:10:26 +00:00
}
for (const path of exportPaths) {
2017-05-16 16:41:35 +00:00
log(` exporting path: ${path}`)
if (!path.startsWith('/')) {
throw new Error(`path "${path}" doesn't start with a backslash`)
}
2017-05-09 01:53:08 +00:00
const { page, query = {} } = exportPathMap[path]
2017-05-08 06:10:26 +00:00
const req = { url: path }
const res = {}
let htmlFilename = `${path}${sep}index.html`
if (extname(path) !== '') {
// If the path has an extension, use that as the filename instead
htmlFilename = path
} else if (path === '/') {
// If the path is the root, just use index.html
htmlFilename = 'index.html'
}
2017-05-08 06:10:26 +00:00
const baseDir = join(outDir, dirname(htmlFilename))
const htmlFilepath = join(outDir, htmlFilename)
await mkdirp(baseDir)
const html = await renderToHTML(req, res, page, query, renderOpts)
writeFileSync(htmlFilepath, html, 'utf8')
}
// Add an empty line to the console for the better readability.
log('')
function log (message) {
if (options.silent) return
console.log(message)
}
2017-05-07 22:47:40 +00:00
}
function copyPages (nextDir, outDir, buildId) {
// TODO: do some proper error handling
return new Promise((resolve, reject) => {
const nextBundlesDir = join(nextDir, 'bundles', 'pages')
const walker = walk.walk(nextBundlesDir, { followLinks: false })
walker.on('file', (root, stat, next) => {
const filename = stat.name
const fullFilePath = `${root}${sep}${filename}`
const relativeFilePath = fullFilePath.replace(nextBundlesDir, '')
// We should not expose this page to the client side since
// it has no use in the client side.
if (relativeFilePath === `${sep}_document.js`) {
next()
return
}
let destFilePath = null
if (relativeFilePath === `${sep}index.js`) {
destFilePath = join(outDir, '_next', buildId, 'page', relativeFilePath)
} else if (/index\.js$/.test(filename)) {
const newRelativeFilePath = relativeFilePath.replace(`${sep}index.js`, '.js')
destFilePath = join(outDir, '_next', buildId, 'page', newRelativeFilePath)
} else {
destFilePath = join(outDir, '_next', buildId, 'page', relativeFilePath)
}
cp(fullFilePath, destFilePath)
.then(next)
.catch(reject)
})
walker.on('end', resolve)
})
}