gatsby
Version:
Blazing fast modern site generator for React
309 lines (260 loc) • 11.9 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.renderHTMLProd = exports.renderHTMLDev = void 0;
exports.renderPartialHydrationProd = renderPartialHydrationProd;
var _truncate2 = _interopRequireDefault(require("lodash/truncate"));
var _fsExtra = _interopRequireDefault(require("fs-extra"));
var _bluebird = _interopRequireDefault(require("bluebird"));
var path = _interopRequireWildcard(require("path"));
var _pageHtml = require("gatsby-core-utils/page-html");
var _pageData = require("gatsby-core-utils/page-data");
var _clientAssetsForTemplate = require("../../client-assets-for-template");
var _pageData2 = require("../../page-data");
var _staticQueryUtils = require("../../static-query-utils");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/* eslint-disable @typescript-eslint/no-namespace */
// we want to force posix-style joins, so Windows doesn't produce backslashes for urls
const {
join
} = path.posix;
/**
* Used to track if renderHTMLProd / renderHTMLDev are called within same "session" (from same renderHTMLQueue call).
* As long as sessionId remains the same we can rely on memoized/cached resources for templates, css file content for inlining and static query results.
* If session changes we invalidate our memoization caches.
*/
let lastSessionId = 0;
let htmlComponentRenderer;
let webpackStats;
const resourcesForTemplateCache = new Map();
const inFlightResourcesForTemplate = new Map();
function clearCaches() {
(0, _staticQueryUtils.clearStaticQueryCaches)();
resourcesForTemplateCache.clear();
inFlightResourcesForTemplate.clear();
(0, _clientAssetsForTemplate.clearCache)();
}
async function doGetResourcesForTemplate(pageData) {
const scriptsAndStyles = await (0, _clientAssetsForTemplate.getScriptsAndStylesForTemplate)(pageData.componentChunkName, webpackStats);
const {
staticQueryContext
} = await (0, _staticQueryUtils.getStaticQueryContext)(pageData.staticQueryHashes);
return {
staticQueryContext,
...scriptsAndStyles
};
}
async function getResourcesForTemplate(pageData) {
const memoizedResourcesForTemplate = resourcesForTemplateCache.get(pageData.componentChunkName);
if (memoizedResourcesForTemplate) {
return memoizedResourcesForTemplate;
}
const inFlight = inFlightResourcesForTemplate.get(pageData.componentChunkName);
if (inFlight) {
return inFlight;
}
const doWorkPromise = doGetResourcesForTemplate(pageData);
inFlightResourcesForTemplate.set(pageData.componentChunkName, doWorkPromise);
const resources = await doWorkPromise;
resourcesForTemplateCache.set(pageData.componentChunkName, resources);
inFlightResourcesForTemplate.delete(pageData.componentChunkName);
return resources;
}
const truncateObjStrings = obj => {
// Recursively truncate strings nested in object
// These objs can be quite large, but we want to preserve each field
for (const key in obj) {
if (typeof obj[key] === `object` && obj[key] !== null) {
truncateObjStrings(obj[key]);
} else if (typeof obj[key] === `string`) {
obj[key] = (0, _truncate2.default)(obj[key], {
length: 250
});
}
}
return obj;
};
const renderHTMLProd = async ({
htmlComponentRendererPath,
paths,
envVars,
sessionId,
webpackCompilationHash
}) => {
const publicDir = join(process.cwd(), `public`);
const isPreview = process.env.GATSBY_IS_PREVIEW === `true`;
const unsafeBuiltinsUsageByPagePath = {};
const previewErrors = {}; // Check if we need to do setup and cache clearing. Within same session we can reuse memoized data,
// but it's not safe to reuse them in different sessions. Check description of `lastSessionId` for more details
if (sessionId !== lastSessionId) {
clearCaches(); // This is being executed in child process, so we need to set some vars
// for modules that aren't bundled by webpack.
envVars.forEach(([key, value]) => process.env[key] = value);
htmlComponentRenderer = require(htmlComponentRendererPath);
webpackStats = await (0, _clientAssetsForTemplate.readWebpackStats)(publicDir);
lastSessionId = sessionId;
if (global.unsafeBuiltinUsage && global.unsafeBuiltinUsage.length > 0) {
unsafeBuiltinsUsageByPagePath[`__import_time__`] = global.unsafeBuiltinUsage;
}
}
await _bluebird.default.map(paths, async pagePath => {
try {
const pageData = await (0, _pageData2.readPageData)(publicDir, pagePath);
const resourcesForTemplate = await getResourcesForTemplate(pageData);
const {
html,
unsafeBuiltinsUsage
} = await htmlComponentRenderer.default({
pagePath,
pageData,
webpackCompilationHash,
...resourcesForTemplate
});
if (unsafeBuiltinsUsage.length > 0) {
unsafeBuiltinsUsageByPagePath[pagePath] = unsafeBuiltinsUsage;
}
await _fsExtra.default.outputFile((0, _pageHtml.generateHtmlPath)(publicDir, pagePath), html);
} catch (e) {
if (e.unsafeBuiltinsUsage && e.unsafeBuiltinsUsage.length > 0) {
unsafeBuiltinsUsageByPagePath[pagePath] = e.unsafeBuiltinsUsage;
}
const htmlRenderError = e;
htmlRenderError.context = {
path: pagePath,
unsafeBuiltinsUsageByPagePath
}; // If we're in Preview-mode, write out a simple error html file.
if (isPreview) {
const pageData = await (0, _pageData2.readPageData)(publicDir, pagePath);
const truncatedPageData = truncateObjStrings(pageData);
const html = `<h1>Preview build error</h1>
<p>There was an error when building the preview page for this page ("${pagePath}").</p>
<h3>Error</h3>
<pre><code>${htmlRenderError === null || htmlRenderError === void 0 ? void 0 : htmlRenderError.stack}</code></pre>
<h3>Page component id</h3>
<p><code>${pageData.componentChunkName}</code></p>
<h3>Page data</h3>
<pre><code>${JSON.stringify(truncatedPageData, null, 4)}</code></pre>`;
await _fsExtra.default.outputFile((0, _pageHtml.generateHtmlPath)(publicDir, pagePath), html);
previewErrors[pagePath] = {
e: htmlRenderError,
name: htmlRenderError.name,
message: htmlRenderError.message,
code: htmlRenderError === null || htmlRenderError === void 0 ? void 0 : htmlRenderError.code,
stack: htmlRenderError === null || htmlRenderError === void 0 ? void 0 : htmlRenderError.stack
};
} else {
throw e;
}
}
}, {
concurrency: 2
});
return {
unsafeBuiltinsUsageByPagePath,
previewErrors
};
}; // TODO: remove when DEV_SSR is done
exports.renderHTMLProd = renderHTMLProd;
const renderHTMLDev = async ({
htmlComponentRendererPath,
paths,
envVars,
sessionId
}) => {
const outputDir = join(process.cwd(), `.cache`, `develop-html`); // Check if we need to do setup and cache clearing. Within same session we can reuse memoized data,
// but it's not safe to reuse them in different sessions. Check description of `lastSessionId` for more details
if (sessionId !== lastSessionId) {
clearCaches(); // This is being executed in child process, so we need to set some vars
// for modules that aren't bundled by webpack.
envVars.forEach(([key, value]) => process.env[key] = value);
htmlComponentRenderer = require(htmlComponentRendererPath);
lastSessionId = sessionId;
}
return _bluebird.default.map(paths, async pagePath => {
try {
const htmlString = await htmlComponentRenderer.default({
pagePath
});
return _fsExtra.default.outputFile((0, _pageHtml.generateHtmlPath)(outputDir, pagePath), htmlString);
} catch (e) {
// add some context to error so we can display more helpful message
e.context = {
path: pagePath
};
throw e;
}
}, {
concurrency: 2
});
};
exports.renderHTMLDev = renderHTMLDev;
async function renderPartialHydrationProd({
paths,
envVars,
sessionId
}) {
const publicDir = join(process.cwd(), `public`);
const unsafeBuiltinsUsageByPagePath = {}; // Check if we need to do setup and cache clearing. Within same session we can reuse memoized data,
// but it's not safe to reuse them in different sessions. Check description of `lastSessionId` for more details
if (sessionId !== lastSessionId) {
clearCaches(); // This is being executed in child process, so we need to set some vars
// for modules that aren't bundled by webpack.
envVars.forEach(([key, value]) => process.env[key] = value);
webpackStats = await (0, _clientAssetsForTemplate.readWebpackStats)(publicDir);
lastSessionId = sessionId;
if (global.unsafeBuiltinUsage && global.unsafeBuiltinUsage.length > 0) {
unsafeBuiltinsUsageByPagePath[`__import_time__`] = global.unsafeBuiltinUsage;
}
}
for (const pagePath of paths) {
const pageData = await (0, _pageData2.readPageData)(publicDir, pagePath);
const {
staticQueryContext
} = await (0, _staticQueryUtils.getStaticQueryContext)(pageData.staticQueryHashes);
const pageRenderer = path.join(process.cwd(), `.cache`, `partial-hydration`, `render-page`);
const {
getPageChunk,
StaticQueryServerContext,
renderToPipeableStream,
React
} = require(pageRenderer);
const chunk = await getPageChunk({
componentChunkName: pageData.componentChunkName
});
const outputPath = (0, _pageData.generatePageDataPath)(path.join(process.cwd(), `public`), pagePath).replace(`.json`, `-rsc.json`);
const stream = _fsExtra.default.createWriteStream(outputPath);
const {
pipe
} = renderToPipeableStream(React.createElement(StaticQueryServerContext.Provider, {
value: staticQueryContext
}, [React.createElement(chunk.default, {
data: pageData.result.data,
pageContext: pageData.result.pageContext,
location: {
pathname: pageData.path
}
})]), JSON.parse(_fsExtra.default.readFileSync(path.join(process.cwd(), `.cache`, `partial-hydration`, `manifest.json`), `utf8`)), {
// React spits out the error here and does not emit it, we want to emit it
// so we can reject with the error and handle it upstream
onError: error => {
const partialHydrationError = error;
partialHydrationError.context = {
path: pagePath,
unsafeBuiltinsUsageByPagePath
};
stream.emit(`error`, error);
}
});
await new Promise((resolve, reject) => {
stream.on(`error`, error => {
reject(error);
});
stream.on(`close`, () => {
resolve();
});
pipe(stream);
});
}
}
//# sourceMappingURL=render-html.js.map