1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00
next.js/export/index.js
Tim Neutkens a8a97b07c7
Provide a way to copy files in exportPathMap (#5089)
Related #4659 

Adds the possibility for users to copy files inside of `exportPathMap`. This allows for adding `robots.txt` `sitemap.xml` etc. another use case is for https://github.com/hanford/next-offline, currently it's manually reading the buildId in `exportPathMap`.

To allow users to do this we'll introduce a new parameter holding an object with the following keys:

- `dev` - `true` when `exportPathMap` is being called in development. `false` when running `next export`. In development `exportPathMap` is used to define routes and behavior like copying files is not required.
- `dir` - Absolute path to the project directory
- `outDir` - Absolute path to the `out` directory (configurable with `-o` or `--outdir`). When `dev` is `true` the value of `outDir` will be `null`.
- `distDir` - Absolute path to the `.next` directory (configurable using the `distDir` config key)
- `buildId` - The buildId the export is running for

Example usage:

```js
// next.config.js
const fs = require('fs')
const {join} = require('path')
const {promisify} = require('util')
const copyFile = promisify(fs.copyFile)

module.exports = {
  exportPathMap: async function (defaultPathMap, {dev, dir, outDir, distDir, buildId}) {
    if(dev) {
      return defaultPathMap
    }
    // This will copy robots.txt from your project root into the out directory
    await copyFile(join(dir, 'robots.txt'), join(outDir, 'robots.txt'))
    return defaultPathMap
  }
}
```
2018-09-04 16:01:50 +02:00

144 lines
4.1 KiB
JavaScript

import del from 'del'
import cp from 'recursive-copy'
import mkdirp from 'mkdirp-then'
import { extname, resolve, join, dirname, sep } from 'path'
import { existsSync, readFileSync, writeFileSync } from 'fs'
import loadConfig from '../server/config'
import {PHASE_EXPORT, SERVER_DIRECTORY, PAGES_MANIFEST, CONFIG_FILE, BUILD_ID_FILE, CLIENT_STATIC_FILES_PATH} from '../lib/constants'
import { renderToHTML } from '../server/render'
import { setAssetPrefix } from '../lib/asset'
import * as envConfig from '../lib/runtime-config'
export default async function (dir, options, configuration) {
function log (message) {
if (options.silent) return
console.log(message)
}
dir = resolve(dir)
const nextConfig = configuration || loadConfig(PHASE_EXPORT, dir)
const distDir = join(dir, nextConfig.distDir)
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".`)
}
const buildId = readFileSync(join(distDir, BUILD_ID_FILE), 'utf8')
const pagesManifest = require(join(distDir, SERVER_DIRECTORY, PAGES_MANIFEST))
const pages = Object.keys(pagesManifest)
const defaultPathMap = {}
for (const page of pages) {
// _document and _app are not real pages.
if (page === '/_document' || page === '/_app') {
continue
}
if (page === '/_error') {
defaultPathMap['/404'] = { page }
continue
}
defaultPathMap[page] = { page }
}
// Initialize the output directory
const outDir = options.outdir
await del(join(outDir, '*'))
await mkdirp(join(outDir, '_next', buildId))
// 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(distDir, CLIENT_STATIC_FILES_PATH))) {
log(' copying "static build" directory')
await cp(
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
}
}
// Start the rendering process
const renderOpts = {
dir,
buildId,
nextExport: true,
assetPrefix: nextConfig.assetPrefix.replace(/\/$/, ''),
distDir,
dev: false,
staticMarkup: false,
hotReloader: null
}
const {serverRuntimeConfig, publicRuntimeConfig} = nextConfig
if (publicRuntimeConfig) {
renderOpts.runtimeConfig = publicRuntimeConfig
}
envConfig.setConfig({
serverRuntimeConfig,
publicRuntimeConfig
})
// 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
}
const exportPathMap = await nextConfig.exportPathMap(defaultPathMap, {dev: false, dir, outDir, distDir, buildId})
const exportPaths = Object.keys(exportPathMap)
for (const path of exportPaths) {
log(`> exporting path: ${path}`)
if (!path.startsWith('/')) {
throw new Error(`path "${path}" doesn't start with a backslash`)
}
const { page, query = {} } = exportPathMap[path]
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'
}
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('')
}