next
Version:
The React Framework
349 lines (348 loc) • 17.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exportPage;
var _url = _interopRequireDefault(require("url"));
var _path = require("path");
var _render = require("../server/render");
var _fs = require("fs");
var _amphtmlValidator = _interopRequireDefault(require("next/dist/compiled/amphtml-validator"));
var _loadComponents = require("../server/load-components");
var _isDynamic = require("../shared/lib/router/utils/is-dynamic");
var _routeMatcher = require("../shared/lib/router/utils/route-matcher");
var _routeRegex = require("../shared/lib/router/utils/route-regex");
var _normalizePagePath = require("../server/normalize-page-path");
var _constants = require("../lib/constants");
require("../server/node-polyfill-fetch");
var _require = require("../server/require");
var _normalizeLocalePath = require("../shared/lib/i18n/normalize-locale-path");
var _trace = require("../trace");
var _amp = require("../shared/lib/amp");
var _config = require("../server/config");
var _renderResult = _interopRequireDefault(require("../server/render-result"));
var _isError = _interopRequireDefault(require("../lib/is-error"));
async function exportPage({ parentSpanId , path , pathMap , distDir , outDir , pagesDataDir , renderOpts , buildExport , serverRuntimeConfig , subFolders , serverless , optimizeFonts , optimizeCss , disableOptimizedLoading , httpAgentOptions , serverComponents }) {
(0, _config).setHttpAgentOptions(httpAgentOptions);
const exportPageSpan = (0, _trace).trace('export-page-worker', parentSpanId);
return exportPageSpan.traceAsyncFn(async ()=>{
const start = Date.now();
let results = {
ampValidations: []
};
try {
var ref;
const { query: originalQuery = {} } = pathMap;
const { page } = pathMap;
const filePath = (0, _normalizePagePath).normalizePagePath(path);
const isDynamic = (0, _isDynamic).isDynamicRoute(page);
const ampPath = `${filePath}.amp`;
let renderAmpPath = ampPath;
let query = {
...originalQuery
};
let params;
let updatedPath = query.__nextSsgPath || path;
let locale = query.__nextLocale || renderOpts.locale;
delete query.__nextLocale;
delete query.__nextSsgPath;
if (renderOpts.locale) {
const localePathResult = (0, _normalizeLocalePath).normalizeLocalePath(path, renderOpts.locales);
if (localePathResult.detectedLocale) {
updatedPath = localePathResult.pathname;
locale = localePathResult.detectedLocale;
if (locale === renderOpts.defaultLocale) {
renderAmpPath = `${(0, _normalizePagePath).normalizePagePath(updatedPath)}.amp`;
}
}
}
// We need to show a warning if they try to provide query values
// for an auto-exported page since they won't be available
const hasOrigQueryValues = Object.keys(originalQuery).length > 0;
const queryWithAutoExportWarn = ()=>{
if (hasOrigQueryValues) {
throw new Error(`\nError: you provided query values for ${path} which is an auto-exported page. These can not be applied since the page can no longer be re-rendered on the server. To disable auto-export for this page add \`getInitialProps\`\n`);
}
};
// Check if the page is a specified dynamic route
const nonLocalizedPath = (0, _normalizeLocalePath).normalizeLocalePath(path, renderOpts.locales).pathname;
if (isDynamic && page !== nonLocalizedPath) {
params = (0, _routeMatcher).getRouteMatcher((0, _routeRegex).getRouteRegex(page))(updatedPath) || undefined;
if (params) {
// we have to pass these separately for serverless
if (!serverless) {
query = {
...query,
...params
};
}
} else {
throw new Error(`The provided export path '${updatedPath}' doesn't match the '${page}' page.\nRead more: https://nextjs.org/docs/messages/export-path-mismatch`);
}
}
const headerMocks = {
headers: {},
getHeader: ()=>({})
,
setHeader: ()=>{},
hasHeader: ()=>false
,
removeHeader: ()=>{},
getHeaderNames: ()=>[]
};
const req = {
url: updatedPath,
...headerMocks
};
const res = {
...headerMocks
};
if (updatedPath === '/500' && page === '/_error') {
res.statusCode = 500;
}
if (renderOpts.trailingSlash && !((ref = req.url) === null || ref === void 0 ? void 0 : ref.endsWith('/'))) {
req.url += '/';
}
envConfig.setConfig({
serverRuntimeConfig,
publicRuntimeConfig: renderOpts.runtimeConfig
});
const getHtmlFilename = (_path1)=>subFolders ? `${_path1}${_path.sep}index.html` : `${_path1}.html`
;
let htmlFilename = getHtmlFilename(filePath);
// dynamic routes can provide invalid extensions e.g. /blog/[...slug] returns an
// extension of `.slug]`
const pageExt = isDynamic ? '' : (0, _path).extname(page);
const pathExt = isDynamic ? '' : (0, _path).extname(path);
// Make sure page isn't a folder with a dot in the name e.g. `v1.2`
if (pageExt !== pathExt && pathExt !== '') {
const isBuiltinPaths = [
'/500',
'/404'
].some((p)=>p === path || p === path + '.html'
);
// If the ssg path has .html extension, and it's not builtin paths, use it directly
// Otherwise, use that as the filename instead
const isHtmlExtPath = !serverless && !isBuiltinPaths && path.endsWith('.html');
htmlFilename = isHtmlExtPath ? getHtmlFilename(path) : path;
} else if (path === '/') {
// If the path is the root, just use index.html
htmlFilename = 'index.html';
}
const baseDir = (0, _path).join(outDir, (0, _path).dirname(htmlFilename));
let htmlFilepath = (0, _path).join(outDir, htmlFilename);
await _fs.promises.mkdir(baseDir, {
recursive: true
});
let renderResult;
let curRenderOpts = {};
let renderMethod = _render.renderToHTML;
let inAmpMode = false, hybridAmp = false;
const renderedDuringBuild = (getStaticProps)=>{
return !buildExport && getStaticProps && !(0, _isDynamic).isDynamicRoute(path);
};
if (serverless) {
const curUrl = _url.default.parse(req.url, true);
req.url = _url.default.format({
...curUrl,
query: {
...curUrl.query,
...query
}
});
const { Component , ComponentMod , getServerSideProps , getStaticProps , pageConfig , } = await (0, _loadComponents).loadComponents(distDir, page, serverless, serverComponents);
const ampState = {
ampFirst: (pageConfig === null || pageConfig === void 0 ? void 0 : pageConfig.amp) === true,
hasQuery: Boolean(query.amp),
hybrid: (pageConfig === null || pageConfig === void 0 ? void 0 : pageConfig.amp) === 'hybrid'
};
inAmpMode = (0, _amp).isInAmpMode(ampState);
hybridAmp = ampState.hybrid;
if (getServerSideProps) {
throw new Error(`Error for page ${page}: ${_constants.SERVER_PROPS_EXPORT_ERROR}`);
}
// if it was auto-exported the HTML is loaded here
if (typeof Component === 'string') {
renderResult = _renderResult.default.fromStatic(Component);
queryWithAutoExportWarn();
} else {
// for non-dynamic SSG pages we should have already
// prerendered the file
if (renderedDuringBuild(getStaticProps)) return {
...results,
duration: Date.now() - start
};
if (getStaticProps && !htmlFilepath.endsWith('.html')) {
// make sure it ends with .html if the name contains a dot
htmlFilename += '.html';
htmlFilepath += '.html';
}
renderMethod = ComponentMod.renderReqToHTML;
const result = await renderMethod(req, res, 'export', {
ampPath: renderAmpPath,
/// @ts-ignore
optimizeFonts,
/// @ts-ignore
optimizeCss,
disableOptimizedLoading,
distDir,
fontManifest: optimizeFonts ? (0, _require).requireFontManifest(distDir, serverless) : null,
locale: locale,
locales: renderOpts.locales
}, // @ts-ignore
params);
curRenderOpts = result.renderOpts || {};
renderResult = result.html;
}
if (!renderResult && !curRenderOpts.isNotFound) {
throw new Error(`Failed to render serverless page`);
}
} else {
var ref1, ref2;
const components = await (0, _loadComponents).loadComponents(distDir, page, serverless, serverComponents);
const ampState = {
ampFirst: ((ref1 = components.pageConfig) === null || ref1 === void 0 ? void 0 : ref1.amp) === true,
hasQuery: Boolean(query.amp),
hybrid: ((ref2 = components.pageConfig) === null || ref2 === void 0 ? void 0 : ref2.amp) === 'hybrid'
};
inAmpMode = (0, _amp).isInAmpMode(ampState);
hybridAmp = ampState.hybrid;
if (components.getServerSideProps) {
throw new Error(`Error for page ${page}: ${_constants.SERVER_PROPS_EXPORT_ERROR}`);
}
// for non-dynamic SSG pages we should have already
// prerendered the file
if (renderedDuringBuild(components.getStaticProps)) {
return {
...results,
duration: Date.now() - start
};
}
// TODO: de-dupe the logic here between serverless and server mode
if (components.getStaticProps && !htmlFilepath.endsWith('.html')) {
// make sure it ends with .html if the name contains a dot
htmlFilepath += '.html';
htmlFilename += '.html';
}
if (typeof components.Component === 'string') {
renderResult = _renderResult.default.fromStatic(components.Component);
queryWithAutoExportWarn();
} else {
/**
* This sets environment variable to be used at the time of static export by head.tsx.
* Using this from process.env allows targeting both serverless and SSR by calling
* `process.env.__NEXT_OPTIMIZE_FONTS`.
* TODO(prateekbh@): Remove this when experimental.optimizeFonts are being cleaned up.
*/ if (optimizeFonts) {
process.env.__NEXT_OPTIMIZE_FONTS = JSON.stringify(true);
}
if (optimizeCss) {
process.env.__NEXT_OPTIMIZE_CSS = JSON.stringify(true);
}
curRenderOpts = {
...components,
...renderOpts,
ampPath: renderAmpPath,
params,
optimizeFonts,
optimizeCss,
disableOptimizedLoading,
fontManifest: optimizeFonts ? (0, _require).requireFontManifest(distDir, serverless) : null,
locale: locale
};
renderResult = await renderMethod(req, res, page, query, // @ts-ignore
curRenderOpts);
}
}
results.ssgNotFound = curRenderOpts.isNotFound;
const validateAmp = async (rawAmpHtml, ampPageName, validatorPath)=>{
const validator = await _amphtmlValidator.default.getInstance(validatorPath);
const result = validator.validateString(rawAmpHtml);
const errors = result.errors.filter((e)=>e.severity === 'ERROR'
);
const warnings = result.errors.filter((e)=>e.severity !== 'ERROR'
);
if (warnings.length || errors.length) {
results.ampValidations.push({
page: ampPageName,
result: {
errors,
warnings
}
});
}
};
const html = renderResult ? renderResult.toUnchunkedString() : '';
if (inAmpMode && !curRenderOpts.ampSkipValidation) {
if (!results.ssgNotFound) {
await validateAmp(html, path, curRenderOpts.ampValidatorPath);
}
} else if (hybridAmp) {
// we need to render the AMP version
let ampHtmlFilename = `${ampPath}${_path.sep}index.html`;
if (!subFolders) {
ampHtmlFilename = `${ampPath}.html`;
}
const ampBaseDir = (0, _path).join(outDir, (0, _path).dirname(ampHtmlFilename));
const ampHtmlFilepath = (0, _path).join(outDir, ampHtmlFilename);
try {
await _fs.promises.access(ampHtmlFilepath);
} catch (_) {
// make sure it doesn't exist from manual mapping
let ampRenderResult;
if (serverless) {
req.url += (req.url.includes('?') ? '&' : '?') + 'amp=1';
// @ts-ignore
ampRenderResult = (await renderMethod(req, res, 'export', curRenderOpts, params)).html;
} else {
ampRenderResult = await renderMethod(req, res, page, // @ts-ignore
{
...query,
amp: '1'
}, curRenderOpts);
}
const ampHtml = ampRenderResult ? ampRenderResult.toUnchunkedString() : '';
if (!curRenderOpts.ampSkipValidation) {
await validateAmp(ampHtml, page + '?amp=1');
}
await _fs.promises.mkdir(ampBaseDir, {
recursive: true
});
await _fs.promises.writeFile(ampHtmlFilepath, ampHtml, 'utf8');
}
}
if (curRenderOpts.pageData) {
const dataFile = (0, _path).join(pagesDataDir, htmlFilename.replace(/\.html$/, '.json'));
await _fs.promises.mkdir((0, _path).dirname(dataFile), {
recursive: true
});
await _fs.promises.writeFile(dataFile, JSON.stringify(curRenderOpts.pageData), 'utf8');
if (hybridAmp) {
await _fs.promises.writeFile(dataFile.replace(/\.json$/, '.amp.json'), JSON.stringify(curRenderOpts.pageData), 'utf8');
}
}
results.fromBuildExportRevalidate = curRenderOpts.revalidate;
if (!results.ssgNotFound) {
// don't attempt writing to disk if getStaticProps returned not found
await _fs.promises.writeFile(htmlFilepath, html, 'utf8');
}
} catch (error) {
console.error(`\nError occurred prerendering page "${path}". Read more: https://nextjs.org/docs/messages/prerender-error\n` + ((0, _isError).default(error) && error.stack ? error.stack : error));
results.error = true;
}
return {
...results,
duration: Date.now() - start
};
});
}
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
const envConfig = require('../shared/lib/runtime-config');
global.__NEXT_DATA__ = {
nextExport: true
};
//# sourceMappingURL=worker.js.map
;