worker.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = exportPage;
  6. require("../server/node-polyfill-fetch");
  7. var _requireHook = _interopRequireDefault(require("../build/webpack/require-hook"));
  8. var _url = _interopRequireDefault(require("url"));
  9. var _path = require("path");
  10. var _render = require("../server/render");
  11. var _fs = require("fs");
  12. var _amphtmlValidator = _interopRequireDefault(require("next/dist/compiled/amphtml-validator"));
  13. var _loadComponents = require("../server/load-components");
  14. var _isDynamic = require("../shared/lib/router/utils/is-dynamic");
  15. var _routeMatcher = require("../shared/lib/router/utils/route-matcher");
  16. var _routeRegex = require("../shared/lib/router/utils/route-regex");
  17. var _normalizePagePath = require("../shared/lib/page-path/normalize-page-path");
  18. var _constants = require("../lib/constants");
  19. var _require = require("../server/require");
  20. var _normalizeLocalePath = require("../shared/lib/i18n/normalize-locale-path");
  21. var _trace = require("../trace");
  22. var _ampMode = require("../shared/lib/amp-mode");
  23. var _config = require("../server/config");
  24. var _renderResult = _interopRequireDefault(require("../server/render-result"));
  25. var _isError = _interopRequireDefault(require("../lib/is-error"));
  26. var _requestMeta = require("../server/request-meta");
  27. var _appPaths = require("../shared/lib/router/utils/app-paths");
  28. async function exportPage({ parentSpanId , path , pathMap , distDir , outDir , pagesDataDir , renderOpts , buildExport , serverRuntimeConfig , subFolders , serverless , optimizeFonts , optimizeCss , disableOptimizedLoading , httpAgentOptions , serverComponents }) {
  29. (0, _config).setHttpAgentOptions(httpAgentOptions);
  30. const exportPageSpan = (0, _trace).trace("export-page-worker", parentSpanId);
  31. return exportPageSpan.traceAsyncFn(async ()=>{
  32. const start = Date.now();
  33. let results = {
  34. ampValidations: []
  35. };
  36. try {
  37. var ref3;
  38. const { query: originalQuery = {} } = pathMap;
  39. const { page } = pathMap;
  40. const isAppDir = pathMap._isAppDir;
  41. const filePath = (0, _normalizePagePath).normalizePagePath(path);
  42. const isDynamic = (0, _isDynamic).isDynamicRoute(page);
  43. const ampPath = `${filePath}.amp`;
  44. let renderAmpPath = ampPath;
  45. let query = {
  46. ...originalQuery
  47. };
  48. let params;
  49. if (isAppDir) {
  50. outDir = (0, _path).join(distDir, "server/app");
  51. }
  52. let updatedPath = query.__nextSsgPath || path;
  53. let locale = query.__nextLocale || renderOpts.locale;
  54. delete query.__nextLocale;
  55. delete query.__nextSsgPath;
  56. if (renderOpts.locale) {
  57. const localePathResult = (0, _normalizeLocalePath).normalizeLocalePath(path, renderOpts.locales);
  58. if (localePathResult.detectedLocale) {
  59. updatedPath = localePathResult.pathname;
  60. locale = localePathResult.detectedLocale;
  61. if (locale === renderOpts.defaultLocale) {
  62. renderAmpPath = `${(0, _normalizePagePath).normalizePagePath(updatedPath)}.amp`;
  63. }
  64. }
  65. }
  66. // We need to show a warning if they try to provide query values
  67. // for an auto-exported page since they won't be available
  68. const hasOrigQueryValues = Object.keys(originalQuery).length > 0;
  69. const queryWithAutoExportWarn = ()=>{
  70. if (hasOrigQueryValues) {
  71. 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`);
  72. }
  73. };
  74. // Check if the page is a specified dynamic route
  75. const nonLocalizedPath = (0, _normalizeLocalePath).normalizeLocalePath(path, renderOpts.locales).pathname;
  76. if (isDynamic && page !== nonLocalizedPath) {
  77. const normalizedPage = isAppDir ? (0, _appPaths).normalizeAppPath(page) : page;
  78. params = (0, _routeMatcher).getRouteMatcher((0, _routeRegex).getRouteRegex(normalizedPage))(updatedPath) || undefined;
  79. if (params) {
  80. // we have to pass these separately for serverless
  81. if (!serverless) {
  82. query = {
  83. ...query,
  84. ...params
  85. };
  86. }
  87. } else {
  88. throw new Error(`The provided export path '${updatedPath}' doesn't match the '${page}' page.\nRead more: https://nextjs.org/docs/messages/export-path-mismatch`);
  89. }
  90. }
  91. const headerMocks = {
  92. headers: {},
  93. getHeader: ()=>({}),
  94. setHeader: ()=>{},
  95. hasHeader: ()=>false,
  96. removeHeader: ()=>{},
  97. getHeaderNames: ()=>[]
  98. };
  99. const req = {
  100. url: updatedPath,
  101. ...headerMocks
  102. };
  103. const res = {
  104. ...headerMocks
  105. };
  106. if (updatedPath === "/500" && page === "/_error") {
  107. res.statusCode = 500;
  108. }
  109. if (renderOpts.trailingSlash && !((ref3 = req.url) == null ? void 0 : ref3.endsWith("/"))) {
  110. req.url += "/";
  111. }
  112. if (locale && buildExport && renderOpts.domainLocales && renderOpts.domainLocales.some((dl)=>{
  113. var ref;
  114. return dl.defaultLocale === locale || ((ref = dl.locales) == null ? void 0 : ref.includes(locale || ""));
  115. })) {
  116. (0, _requestMeta).addRequestMeta(req, "__nextIsLocaleDomain", true);
  117. }
  118. envConfig.setConfig({
  119. serverRuntimeConfig,
  120. publicRuntimeConfig: renderOpts.runtimeConfig
  121. });
  122. const getHtmlFilename = (_path1)=>subFolders ? `${_path1}${_path.sep}index.html` : `${_path1}.html`;
  123. let htmlFilename = getHtmlFilename(filePath);
  124. // dynamic routes can provide invalid extensions e.g. /blog/[...slug] returns an
  125. // extension of `.slug]`
  126. const pageExt = isDynamic ? "" : (0, _path).extname(page);
  127. const pathExt = isDynamic ? "" : (0, _path).extname(path);
  128. // force output 404.html for backwards compat
  129. if (path === "/404.html") {
  130. htmlFilename = path;
  131. } else if (pageExt !== pathExt && pathExt !== "") {
  132. const isBuiltinPaths = [
  133. "/500",
  134. "/404"
  135. ].some((p)=>p === path || p === path + ".html");
  136. // If the ssg path has .html extension, and it's not builtin paths, use it directly
  137. // Otherwise, use that as the filename instead
  138. const isHtmlExtPath = !serverless && !isBuiltinPaths && path.endsWith(".html");
  139. htmlFilename = isHtmlExtPath ? getHtmlFilename(path) : path;
  140. } else if (path === "/") {
  141. // If the path is the root, just use index.html
  142. htmlFilename = "index.html";
  143. }
  144. const baseDir = (0, _path).join(outDir, (0, _path).dirname(htmlFilename));
  145. let htmlFilepath = (0, _path).join(outDir, htmlFilename);
  146. await _fs.promises.mkdir(baseDir, {
  147. recursive: true
  148. });
  149. let renderResult;
  150. let curRenderOpts = {};
  151. let renderMethod = _render.renderToHTML;
  152. let inAmpMode = false, hybridAmp = false;
  153. const renderedDuringBuild = (getStaticProps)=>{
  154. return !buildExport && getStaticProps && !(0, _isDynamic).isDynamicRoute(path);
  155. };
  156. if (serverless) {
  157. const curUrl = _url.default.parse(req.url, true);
  158. req.url = _url.default.format({
  159. ...curUrl,
  160. query: {
  161. ...curUrl.query,
  162. ...query
  163. }
  164. });
  165. const { Component , ComponentMod , getServerSideProps , getStaticProps , pageConfig , } = await (0, _loadComponents).loadComponents({
  166. distDir,
  167. pathname: page,
  168. serverless,
  169. hasServerComponents: !!serverComponents,
  170. isAppPath: isAppDir
  171. });
  172. const ampState = {
  173. ampFirst: (pageConfig == null ? void 0 : pageConfig.amp) === true,
  174. hasQuery: Boolean(query.amp),
  175. hybrid: (pageConfig == null ? void 0 : pageConfig.amp) === "hybrid"
  176. };
  177. inAmpMode = (0, _ampMode).isInAmpMode(ampState);
  178. hybridAmp = ampState.hybrid;
  179. if (getServerSideProps) {
  180. throw new Error(`Error for page ${page}: ${_constants.SERVER_PROPS_EXPORT_ERROR}`);
  181. }
  182. // if it was auto-exported the HTML is loaded here
  183. if (typeof Component === "string") {
  184. renderResult = _renderResult.default.fromStatic(Component);
  185. queryWithAutoExportWarn();
  186. } else {
  187. // for non-dynamic SSG pages we should have already
  188. // prerendered the file
  189. if (renderedDuringBuild(getStaticProps)) return {
  190. ...results,
  191. duration: Date.now() - start
  192. };
  193. if (getStaticProps && !htmlFilepath.endsWith(".html")) {
  194. // make sure it ends with .html if the name contains a dot
  195. htmlFilename += ".html";
  196. htmlFilepath += ".html";
  197. }
  198. renderMethod = ComponentMod.renderReqToHTML;
  199. const result = await renderMethod(req, res, "export", {
  200. ampPath: renderAmpPath,
  201. /// @ts-ignore
  202. optimizeFonts,
  203. /// @ts-ignore
  204. optimizeCss,
  205. disableOptimizedLoading,
  206. distDir,
  207. fontManifest: optimizeFonts ? (0, _require).requireFontManifest(distDir, serverless) : null,
  208. locale: locale,
  209. locales: renderOpts.locales
  210. }, // @ts-ignore
  211. params);
  212. curRenderOpts = result.renderOpts || {};
  213. renderResult = result.html;
  214. }
  215. if (!renderResult && !curRenderOpts.isNotFound) {
  216. throw new Error(`Failed to render serverless page`);
  217. }
  218. } else {
  219. var ref1, ref2;
  220. const components = await (0, _loadComponents).loadComponents({
  221. distDir,
  222. pathname: page,
  223. serverless,
  224. hasServerComponents: !!serverComponents,
  225. isAppPath: isAppDir
  226. });
  227. curRenderOpts = {
  228. ...components,
  229. ...renderOpts,
  230. ampPath: renderAmpPath,
  231. params,
  232. optimizeFonts,
  233. optimizeCss,
  234. disableOptimizedLoading,
  235. fontManifest: optimizeFonts ? (0, _require).requireFontManifest(distDir, serverless) : null,
  236. locale: locale
  237. };
  238. // during build we attempt rendering app dir paths
  239. // and bail when dynamic dependencies are detected
  240. // only fully static paths are fully generated here
  241. if (isAppDir) {
  242. const { DynamicServerError , } = require("../client/components/hooks-server-context");
  243. const { renderToHTMLOrFlight } = require("../server/app-render");
  244. try {
  245. (_curRenderOpts = curRenderOpts).params || (_curRenderOpts.params = {});
  246. const result = await renderToHTMLOrFlight(req, res, page, query, curRenderOpts, false, true);
  247. const html = result == null ? void 0 : result.toUnchunkedString();
  248. const flightData = curRenderOpts.pageData;
  249. const revalidate = curRenderOpts.revalidate;
  250. results.fromBuildExportRevalidate = revalidate;
  251. if (revalidate !== 0) {
  252. await _fs.promises.writeFile(htmlFilepath, html, "utf8");
  253. await _fs.promises.writeFile(htmlFilepath.replace(/\.html$/, ".rsc"), flightData);
  254. }
  255. } catch (err) {
  256. if (!(err instanceof DynamicServerError)) {
  257. throw err;
  258. }
  259. }
  260. return {
  261. ...results,
  262. duration: Date.now() - start
  263. };
  264. }
  265. const ampState = {
  266. ampFirst: ((ref1 = components.pageConfig) == null ? void 0 : ref1.amp) === true,
  267. hasQuery: Boolean(query.amp),
  268. hybrid: ((ref2 = components.pageConfig) == null ? void 0 : ref2.amp) === "hybrid"
  269. };
  270. inAmpMode = (0, _ampMode).isInAmpMode(ampState);
  271. hybridAmp = ampState.hybrid;
  272. if (components.getServerSideProps) {
  273. throw new Error(`Error for page ${page}: ${_constants.SERVER_PROPS_EXPORT_ERROR}`);
  274. }
  275. // for non-dynamic SSG pages we should have already
  276. // prerendered the file
  277. if (renderedDuringBuild(components.getStaticProps)) {
  278. return {
  279. ...results,
  280. duration: Date.now() - start
  281. };
  282. }
  283. // TODO: de-dupe the logic here between serverless and server mode
  284. if (components.getStaticProps && !htmlFilepath.endsWith(".html")) {
  285. // make sure it ends with .html if the name contains a dot
  286. htmlFilepath += ".html";
  287. htmlFilename += ".html";
  288. }
  289. if (typeof components.Component === "string") {
  290. renderResult = _renderResult.default.fromStatic(components.Component);
  291. queryWithAutoExportWarn();
  292. } else {
  293. /**
  294. * This sets environment variable to be used at the time of static export by head.tsx.
  295. * Using this from process.env allows targeting both serverless and SSR by calling
  296. * `process.env.__NEXT_OPTIMIZE_FONTS`.
  297. * TODO(prateekbh@): Remove this when experimental.optimizeFonts are being cleaned up.
  298. */ if (optimizeFonts) {
  299. process.env.__NEXT_OPTIMIZE_FONTS = JSON.stringify(optimizeFonts);
  300. }
  301. if (optimizeCss) {
  302. process.env.__NEXT_OPTIMIZE_CSS = JSON.stringify(true);
  303. }
  304. renderResult = await renderMethod(req, res, page, query, // @ts-ignore
  305. curRenderOpts);
  306. }
  307. }
  308. results.ssgNotFound = curRenderOpts.isNotFound;
  309. const validateAmp = async (rawAmpHtml, ampPageName, validatorPath)=>{
  310. const validator = await _amphtmlValidator.default.getInstance(validatorPath);
  311. const result = validator.validateString(rawAmpHtml);
  312. const errors = result.errors.filter((e)=>e.severity === "ERROR");
  313. const warnings = result.errors.filter((e)=>e.severity !== "ERROR");
  314. if (warnings.length || errors.length) {
  315. results.ampValidations.push({
  316. page: ampPageName,
  317. result: {
  318. errors,
  319. warnings
  320. }
  321. });
  322. }
  323. };
  324. const html = renderResult ? renderResult.toUnchunkedString() : "";
  325. if (inAmpMode && !curRenderOpts.ampSkipValidation) {
  326. if (!results.ssgNotFound) {
  327. await validateAmp(html, path, curRenderOpts.ampValidatorPath);
  328. }
  329. } else if (hybridAmp) {
  330. // we need to render the AMP version
  331. let ampHtmlFilename = `${ampPath}${_path.sep}index.html`;
  332. if (!subFolders) {
  333. ampHtmlFilename = `${ampPath}.html`;
  334. }
  335. const ampBaseDir = (0, _path).join(outDir, (0, _path).dirname(ampHtmlFilename));
  336. const ampHtmlFilepath = (0, _path).join(outDir, ampHtmlFilename);
  337. try {
  338. await _fs.promises.access(ampHtmlFilepath);
  339. } catch (_) {
  340. // make sure it doesn't exist from manual mapping
  341. let ampRenderResult;
  342. if (serverless) {
  343. req.url += (req.url.includes("?") ? "&" : "?") + "amp=1";
  344. // @ts-ignore
  345. ampRenderResult = (await renderMethod(req, res, "export", curRenderOpts, params)).html;
  346. } else {
  347. ampRenderResult = await renderMethod(req, res, page, // @ts-ignore
  348. {
  349. ...query,
  350. amp: "1"
  351. }, curRenderOpts);
  352. }
  353. const ampHtml = ampRenderResult ? ampRenderResult.toUnchunkedString() : "";
  354. if (!curRenderOpts.ampSkipValidation) {
  355. await validateAmp(ampHtml, page + "?amp=1");
  356. }
  357. await _fs.promises.mkdir(ampBaseDir, {
  358. recursive: true
  359. });
  360. await _fs.promises.writeFile(ampHtmlFilepath, ampHtml, "utf8");
  361. }
  362. }
  363. if (curRenderOpts.pageData) {
  364. const dataFile = (0, _path).join(pagesDataDir, htmlFilename.replace(/\.html$/, ".json"));
  365. await _fs.promises.mkdir((0, _path).dirname(dataFile), {
  366. recursive: true
  367. });
  368. await _fs.promises.writeFile(dataFile, JSON.stringify(curRenderOpts.pageData), "utf8");
  369. if (hybridAmp) {
  370. await _fs.promises.writeFile(dataFile.replace(/\.json$/, ".amp.json"), JSON.stringify(curRenderOpts.pageData), "utf8");
  371. }
  372. }
  373. results.fromBuildExportRevalidate = curRenderOpts.revalidate;
  374. if (!results.ssgNotFound) {
  375. // don't attempt writing to disk if getStaticProps returned not found
  376. await _fs.promises.writeFile(htmlFilepath, html, "utf8");
  377. }
  378. } catch (error) {
  379. 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));
  380. results.error = true;
  381. }
  382. return {
  383. ...results,
  384. duration: Date.now() - start
  385. };
  386. });
  387. }
  388. function _interopRequireDefault(obj) {
  389. return obj && obj.__esModule ? obj : {
  390. default: obj
  391. };
  392. }
  393. var _curRenderOpts;
  394. (0, _requireHook).default();
  395. const envConfig = require("../shared/lib/runtime-config");
  396. global.__NEXT_DATA__ = {
  397. nextExport: true
  398. };
  399. //# sourceMappingURL=worker.js.map