2019-02-22 17:33:28 +01:00
|
|
|
import mkdirpModule from 'mkdirp'
|
|
|
|
import { promisify } from 'util'
|
|
|
|
import { extname, join, dirname, sep } from 'path'
|
|
|
|
import { renderToHTML } from 'next-server/dist/server/render'
|
2019-04-11 20:59:26 +02:00
|
|
|
import { writeFile, access } from 'fs'
|
2019-02-22 17:33:28 +01:00
|
|
|
import Sema from 'async-sema'
|
2019-03-26 22:21:27 +01:00
|
|
|
import AmpHtmlValidator from 'amphtml-validator'
|
2019-02-22 17:33:28 +01:00
|
|
|
import { loadComponents } from 'next-server/dist/server/load-components'
|
|
|
|
|
|
|
|
const envConfig = require('next-server/config')
|
|
|
|
const mkdirp = promisify(mkdirpModule)
|
2019-04-11 20:59:26 +02:00
|
|
|
const writeFileP = promisify(writeFile)
|
|
|
|
const accessP = promisify(access)
|
2019-02-22 17:33:28 +01:00
|
|
|
|
2018-12-12 13:59:11 +01:00
|
|
|
global.__NEXT_DATA__ = {
|
|
|
|
nextExport: true
|
|
|
|
}
|
|
|
|
|
|
|
|
process.on(
|
|
|
|
'message',
|
|
|
|
async ({
|
2018-12-18 17:12:49 +01:00
|
|
|
distDir,
|
|
|
|
buildId,
|
2018-12-12 13:59:11 +01:00
|
|
|
exportPaths,
|
|
|
|
exportPathMap,
|
|
|
|
outDir,
|
|
|
|
renderOpts,
|
2019-02-12 02:28:47 +01:00
|
|
|
serverRuntimeConfig,
|
2018-12-12 13:59:11 +01:00
|
|
|
concurrency
|
|
|
|
}) => {
|
|
|
|
const sema = new Sema(concurrency, { capacity: exportPaths.length })
|
|
|
|
try {
|
|
|
|
const work = async path => {
|
|
|
|
await sema.acquire()
|
2019-04-11 20:59:26 +02:00
|
|
|
const ampPath = `${path === '/' ? '/index' : path}.amp`
|
2018-12-12 13:59:11 +01:00
|
|
|
const { page, query = {} } = exportPathMap[path]
|
2019-04-02 20:01:34 +02:00
|
|
|
delete query.ampOnly
|
2019-03-20 04:53:47 +01:00
|
|
|
delete query.hasAmp
|
|
|
|
delete query.ampPath
|
2019-04-02 20:01:34 +02:00
|
|
|
delete query.amphtml
|
2019-03-20 04:53:47 +01:00
|
|
|
|
2018-12-12 13:59:11 +01:00
|
|
|
const req = { url: path }
|
|
|
|
const res = {}
|
2019-02-12 02:28:47 +01:00
|
|
|
envConfig.setConfig({
|
|
|
|
serverRuntimeConfig,
|
|
|
|
publicRuntimeConfig: renderOpts.runtimeConfig
|
|
|
|
})
|
2018-12-12 13:59:11 +01:00
|
|
|
|
|
|
|
let htmlFilename = `${path}${sep}index.html`
|
2019-03-13 00:30:03 +01:00
|
|
|
const pageExt = extname(page)
|
|
|
|
const pathExt = extname(path)
|
|
|
|
// Make sure page isn't a folder with a dot in the name e.g. `v1.2`
|
|
|
|
if (pageExt !== pathExt && pathExt !== '') {
|
2018-12-12 13:59:11 +01:00
|
|
|
// 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)
|
2018-12-18 17:12:49 +01:00
|
|
|
const components = await loadComponents(distDir, buildId, page)
|
2019-04-11 20:59:26 +02:00
|
|
|
const curRenderOpts = { ...components, ...renderOpts, ampPath }
|
|
|
|
const html = await renderToHTML(req, res, page, query, curRenderOpts)
|
2019-03-26 22:21:27 +01:00
|
|
|
|
2019-04-11 20:59:26 +02:00
|
|
|
const validateAmp = async (html, page) => {
|
2019-03-26 22:21:27 +01:00
|
|
|
const validator = await AmpHtmlValidator.getInstance()
|
|
|
|
const result = validator.validateString(html)
|
|
|
|
const errors = result.errors.filter(e => e.severity === 'ERROR')
|
|
|
|
const warnings = result.errors.filter(e => e.severity !== 'ERROR')
|
|
|
|
|
|
|
|
if (warnings.length || errors.length) {
|
|
|
|
process.send({
|
|
|
|
type: 'amp-validation',
|
|
|
|
payload: {
|
|
|
|
page,
|
|
|
|
result: {
|
|
|
|
errors,
|
|
|
|
warnings
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-11 20:59:26 +02:00
|
|
|
if (curRenderOpts.amphtml && query.amp) {
|
|
|
|
await validateAmp(html, path)
|
|
|
|
}
|
|
|
|
if (
|
2019-04-15 11:49:10 +02:00
|
|
|
(curRenderOpts.amphtml && !query.amp) || curRenderOpts.hasAmp
|
2019-04-11 20:59:26 +02:00
|
|
|
) {
|
|
|
|
// we need to render a clean AMP version
|
|
|
|
const ampHtmlFilename = `${ampPath}${sep}index.html`
|
|
|
|
const ampBaseDir = join(outDir, dirname(ampHtmlFilename))
|
|
|
|
const ampHtmlFilepath = join(outDir, ampHtmlFilename)
|
|
|
|
|
|
|
|
try {
|
|
|
|
await accessP(ampHtmlFilepath)
|
|
|
|
} catch (_) {
|
|
|
|
// make sure it doesn't exist from manual mapping
|
|
|
|
const ampHtml = await renderToHTML(req, res, page, { ...query, amp: 1 }, curRenderOpts)
|
|
|
|
|
|
|
|
await validateAmp(ampHtml, page + '?amp=1')
|
|
|
|
await mkdirp(ampBaseDir)
|
|
|
|
await writeFileP(
|
|
|
|
ampHtmlFilepath,
|
|
|
|
ampHtml,
|
|
|
|
'utf8'
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
await writeFileP(
|
|
|
|
htmlFilepath,
|
|
|
|
html,
|
|
|
|
'utf8'
|
2018-12-12 13:59:11 +01:00
|
|
|
)
|
|
|
|
process.send({ type: 'progress' })
|
|
|
|
sema.release()
|
|
|
|
}
|
|
|
|
await Promise.all(exportPaths.map(work))
|
|
|
|
process.send({ type: 'done' })
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err)
|
|
|
|
process.send({ type: 'error', payload: err })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|