app-render.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.renderToHTMLOrFlight = renderToHTMLOrFlight;
  6. var _react = _interopRequireWildcard(require("react"));
  7. var _querystring = require("querystring");
  8. var _reactServerDomWebpack = require("next/dist/compiled/react-server-dom-webpack");
  9. var _writerBrowserServer = require("next/dist/compiled/react-server-dom-webpack/writer.browser.server");
  10. var _renderResult = _interopRequireDefault(require("./render-result"));
  11. var _nodeWebStreamsHelper = require("./node-web-streams-helper");
  12. var _htmlescape = require("./htmlescape");
  13. var _utils = require("./utils");
  14. var _matchSegments = require("../client/components/match-segments");
  15. var _flushEffects = require("../shared/lib/flush-effects");
  16. var _internalUtils = require("./internal-utils");
  17. function _interopRequireDefault(obj) {
  18. return obj && obj.__esModule ? obj : {
  19. default: obj
  20. };
  21. }
  22. function _getRequireWildcardCache() {
  23. if (typeof WeakMap !== "function") return null;
  24. var cache = new WeakMap();
  25. _getRequireWildcardCache = function() {
  26. return cache;
  27. };
  28. return cache;
  29. }
  30. function _interopRequireWildcard(obj) {
  31. if (obj && obj.__esModule) {
  32. return obj;
  33. }
  34. if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
  35. return {
  36. default: obj
  37. };
  38. }
  39. var cache = _getRequireWildcardCache();
  40. if (cache && cache.has(obj)) {
  41. return cache.get(obj);
  42. }
  43. var newObj = {};
  44. var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
  45. for(var key in obj){
  46. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  47. var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
  48. if (desc && (desc.get || desc.set)) {
  49. Object.defineProperty(newObj, key, desc);
  50. } else {
  51. newObj[key] = obj[key];
  52. }
  53. }
  54. }
  55. newObj.default = obj;
  56. if (cache) {
  57. cache.set(obj, newObj);
  58. }
  59. return newObj;
  60. }
  61. // this needs to be required lazily so that `next-server` can set
  62. // the env before we require
  63. const ReactDOMServer = _utils.shouldUseReactRoot ? require("react-dom/server.browser") : require("react-dom/server");
  64. /**
  65. * Flight Response is always set to application/octet-stream to ensure it does not
  66. */ class FlightRenderResult extends _renderResult.default {
  67. constructor(response){
  68. super(response, {
  69. contentType: "application/octet-stream"
  70. });
  71. }
  72. }
  73. /**
  74. * Interop between "export default" and "module.exports".
  75. */ function interopDefault(mod) {
  76. return mod.default || mod;
  77. }
  78. // tolerate dynamic server errors during prerendering so console
  79. // isn't spammed with unactionable errors
  80. function onError(err) {
  81. const { DynamicServerError } = require("../client/components/hooks-server-context");
  82. if (!(err instanceof DynamicServerError)) {
  83. console.error(err);
  84. }
  85. }
  86. let isFetchPatched = false;
  87. // we patch fetch to collect cache information used for
  88. // determining if a page is static or not
  89. function patchFetch() {
  90. if (isFetchPatched) return;
  91. isFetchPatched = true;
  92. const { DynamicServerError } = require("../client/components/hooks-server-context");
  93. const { useTrackStaticGeneration } = require("../client/components/hooks-server");
  94. const origFetch = global.fetch;
  95. global.fetch = async (init, opts)=>{
  96. let staticGenerationContext = {};
  97. try {
  98. // eslint-disable-next-line react-hooks/rules-of-hooks
  99. staticGenerationContext = useTrackStaticGeneration() || {};
  100. } catch (_) {}
  101. const { isStaticGeneration , fetchRevalidate , pathname } = staticGenerationContext;
  102. if (isStaticGeneration) {
  103. if (opts && typeof opts === "object") {
  104. if (opts.cache === "no-store") {
  105. staticGenerationContext.revalidate = 0;
  106. // TODO: ensure this error isn't logged to the user
  107. // seems it's slipping through currently
  108. throw new DynamicServerError(`no-store fetch ${init}${pathname ? ` ${pathname}` : ""}`);
  109. }
  110. if (typeof opts.revalidate === "number" && (typeof fetchRevalidate === "undefined" || opts.revalidate < fetchRevalidate)) {
  111. staticGenerationContext.fetchRevalidate = opts.revalidate;
  112. }
  113. }
  114. }
  115. return origFetch(init, opts);
  116. };
  117. }
  118. /**
  119. * Render Flight stream.
  120. * This is only used for renderToHTML, the Flight response does not need additional wrappers.
  121. */ function useFlightResponse(writable, req, serverComponentManifest, rscChunks, flightResponseRef, nonce) {
  122. if (flightResponseRef.current !== null) {
  123. return flightResponseRef.current;
  124. }
  125. const [renderStream, forwardStream] = (0, _nodeWebStreamsHelper).readableStreamTee(req);
  126. const res = (0, _reactServerDomWebpack).createFromReadableStream(renderStream, {
  127. moduleMap: serverComponentManifest.__ssr_module_mapping__
  128. });
  129. flightResponseRef.current = res;
  130. let bootstrapped = false;
  131. // We only attach CSS chunks to the inlined data.
  132. const forwardReader = forwardStream.getReader();
  133. const writer = writable.getWriter();
  134. const startScriptTag = nonce ? `<script nonce=${JSON.stringify(nonce)}>` : "<script>";
  135. function process() {
  136. forwardReader.read().then(({ done , value })=>{
  137. if (value) {
  138. rscChunks.push(value);
  139. }
  140. if (!bootstrapped) {
  141. bootstrapped = true;
  142. writer.write((0, _nodeWebStreamsHelper).encodeText(`${startScriptTag}(self.__next_s=self.__next_s||[]).push(${(0, _htmlescape).htmlEscapeJsonString(JSON.stringify([
  143. 0
  144. ]))})</script>`));
  145. }
  146. if (done) {
  147. flightResponseRef.current = null;
  148. writer.close();
  149. } else {
  150. const responsePartial = (0, _nodeWebStreamsHelper).decodeText(value);
  151. const scripts = `${startScriptTag}(self.__next_s=self.__next_s||[]).push(${(0, _htmlescape).htmlEscapeJsonString(JSON.stringify([
  152. 1,
  153. responsePartial
  154. ]))})</script>`;
  155. writer.write((0, _nodeWebStreamsHelper).encodeText(scripts));
  156. process();
  157. }
  158. });
  159. }
  160. process();
  161. return res;
  162. }
  163. /**
  164. * Create a component that renders the Flight stream.
  165. * This is only used for renderToHTML, the Flight response does not need additional wrappers.
  166. */ function createServerComponentRenderer(ComponentToRender, ComponentMod, { transformStream , serverComponentManifest , serverContexts , rscChunks }, nonce) {
  167. // We need to expose the `__webpack_require__` API globally for
  168. // react-server-dom-webpack. This is a hack until we find a better way.
  169. if (ComponentMod.__next_app_webpack_require__ || ComponentMod.__next_rsc__) {
  170. var ref;
  171. // @ts-ignore
  172. globalThis.__next_require__ = ComponentMod.__next_app_webpack_require__ || ((ref = ComponentMod.__next_rsc__) == null ? void 0 : ref.__webpack_require__);
  173. // @ts-ignore
  174. globalThis.__next_chunk_load__ = ()=>Promise.resolve();
  175. }
  176. let RSCStream;
  177. const createRSCStream = ()=>{
  178. if (!RSCStream) {
  179. RSCStream = (0, _writerBrowserServer).renderToReadableStream(/*#__PURE__*/ _react.default.createElement(ComponentToRender, null), serverComponentManifest, {
  180. context: serverContexts,
  181. onError
  182. });
  183. }
  184. return RSCStream;
  185. };
  186. const flightResponseRef = {
  187. current: null
  188. };
  189. const writable = transformStream.writable;
  190. return function ServerComponentWrapper() {
  191. const reqStream = createRSCStream();
  192. const response = useFlightResponse(writable, reqStream, serverComponentManifest, rscChunks, flightResponseRef, nonce);
  193. return (0, _react).experimental_use(response);
  194. };
  195. }
  196. /**
  197. * Shorten the dynamic param in order to make it smaller when transmitted to the browser.
  198. */ function getShortDynamicParamType(type) {
  199. switch(type){
  200. case "catchall":
  201. return "c";
  202. case "optional-catchall":
  203. return "oc";
  204. case "dynamic":
  205. return "d";
  206. default:
  207. throw new Error("Unknown dynamic param type");
  208. }
  209. }
  210. /**
  211. * Parse dynamic route segment to type of parameter
  212. */ function getSegmentParam(segment) {
  213. if (segment.startsWith("[[...") && segment.endsWith("]]")) {
  214. return {
  215. type: "optional-catchall",
  216. param: segment.slice(5, -2)
  217. };
  218. }
  219. if (segment.startsWith("[...") && segment.endsWith("]")) {
  220. return {
  221. type: "catchall",
  222. param: segment.slice(4, -1)
  223. };
  224. }
  225. if (segment.startsWith("[") && segment.endsWith("]")) {
  226. return {
  227. type: "dynamic",
  228. param: segment.slice(1, -1)
  229. };
  230. }
  231. return null;
  232. }
  233. /**
  234. * Get inline <link> tags based on server CSS manifest. Only used when rendering to HTML.
  235. */ function getCssInlinedLinkTags(serverComponentManifest, serverCSSManifest, filePath) {
  236. var ref;
  237. const layoutOrPageCss = serverCSSManifest[filePath] || ((ref = serverComponentManifest.__client_css_manifest__) == null ? void 0 : ref[filePath]);
  238. if (!layoutOrPageCss) {
  239. return [];
  240. }
  241. const chunks = new Set();
  242. for (const css of layoutOrPageCss){
  243. const mod = serverComponentManifest[css];
  244. if (mod) {
  245. for (const chunk of mod.default.chunks){
  246. chunks.add(chunk);
  247. }
  248. }
  249. }
  250. return [
  251. ...chunks
  252. ];
  253. }
  254. function getScriptNonceFromHeader(cspHeaderValue) {
  255. var ref;
  256. const directives = cspHeaderValue// Directives are split by ';'.
  257. .split(";").map((directive)=>directive.trim());
  258. // First try to find the directive for the 'script-src', otherwise try to
  259. // fallback to the 'default-src'.
  260. const directive1 = directives.find((dir)=>dir.startsWith("script-src")) || directives.find((dir)=>dir.startsWith("default-src"));
  261. // If no directive could be found, then we're done.
  262. if (!directive1) {
  263. return;
  264. }
  265. // Extract the nonce from the directive
  266. const nonce = (ref = directive1.split(" ")// Remove the 'strict-src'/'default-src' string, this can't be the nonce.
  267. .slice(1).map((source)=>source.trim())// Find the first source with the 'nonce-' prefix.
  268. .find((source)=>source.startsWith("'nonce-") && source.length > 8 && source.endsWith("'"))) == null ? void 0 : ref.slice(7, -1);
  269. // If we could't find the nonce, then we're done.
  270. if (!nonce) {
  271. return;
  272. }
  273. // Don't accept the nonce value if it contains HTML escape characters.
  274. // Technically, the spec requires a base64'd value, but this is just an
  275. // extra layer.
  276. if (_htmlescape.ESCAPE_REGEX.test(nonce)) {
  277. throw new Error("Nonce value from Content-Security-Policy contained HTML escape characters.\nLearn more: https://nextjs.org/docs/messages/nonce-contained-invalid-characters");
  278. }
  279. return nonce;
  280. }
  281. async function renderToHTMLOrFlight(req, res, pathname, query, renderOpts, isPagesDir, isStaticGeneration = false) {
  282. patchFetch();
  283. const { CONTEXT_NAMES } = require("../client/components/hooks-server-context");
  284. // @ts-expect-error createServerContext exists in react@experimental + react-dom@experimental
  285. if (typeof _react.default.createServerContext === "undefined") {
  286. throw new Error('"app" directory requires React.createServerContext which is not available in the version of React you are using. Please update to react@experimental and react-dom@experimental.');
  287. }
  288. // don't modify original query object
  289. query = Object.assign({}, query);
  290. const { buildManifest , subresourceIntegrityManifest , serverComponentManifest , serverCSSManifest ={} , supportsDynamicHTML , ComponentMod , } = renderOpts;
  291. const isFlight = query.__flight__ !== undefined;
  292. const isPrefetch = query.__flight_prefetch__ !== undefined;
  293. // Handle client-side navigation to pages directory
  294. if (isFlight && isPagesDir) {
  295. (0, _internalUtils).stripInternalQueries(query);
  296. const search = (0, _querystring).stringify(query);
  297. // Empty so that the client-side router will do a full page navigation.
  298. const flightData = pathname + (search ? `?${search}` : "");
  299. return new FlightRenderResult((0, _writerBrowserServer).renderToReadableStream(flightData, serverComponentManifest, {
  300. onError
  301. }).pipeThrough((0, _nodeWebStreamsHelper).createBufferedTransformStream()));
  302. }
  303. // TODO-APP: verify the tree is valid
  304. // TODO-APP: verify query param is single value (not an array)
  305. // TODO-APP: verify tree can't grow out of control
  306. /**
  307. * Router state provided from the client-side router. Used to handle rendering from the common layout down.
  308. */ const providedFlightRouterState = isFlight ? query.__flight_router_state_tree__ ? JSON.parse(query.__flight_router_state_tree__) : {} : undefined;
  309. (0, _internalUtils).stripInternalQueries(query);
  310. const LayoutRouter = ComponentMod.LayoutRouter;
  311. const RenderFromTemplateContext = ComponentMod.RenderFromTemplateContext;
  312. const HotReloader = ComponentMod.HotReloader;
  313. const headers = req.headers;
  314. // TODO-APP: fix type of req
  315. // @ts-expect-error
  316. const cookies = req.cookies;
  317. /**
  318. * The tree created in next-app-loader that holds component segments and modules
  319. */ const loaderTree = ComponentMod.tree;
  320. const tryGetPreviewData = process.env.NEXT_RUNTIME === "edge" ? ()=>false : require("./api-utils/node").tryGetPreviewData;
  321. // Reads of this are cached on the `req` object, so this should resolve
  322. // instantly. There's no need to pass this data down from a previous
  323. // invoke, where we'd have to consider server & serverless.
  324. const previewData = tryGetPreviewData(req, res, renderOpts.previewProps);
  325. /**
  326. * Server Context is specifically only available in Server Components.
  327. * It has to hold values that can't change while rendering from the common layout down.
  328. * An example of this would be that `headers` are available but `searchParams` are not because that'd mean we have to render from the root layout down on all requests.
  329. */ const staticGenerationContext = {
  330. isStaticGeneration,
  331. pathname
  332. };
  333. const serverContexts = [
  334. [
  335. "WORKAROUND",
  336. null
  337. ],
  338. [
  339. CONTEXT_NAMES.HeadersContext,
  340. headers
  341. ],
  342. [
  343. CONTEXT_NAMES.CookiesContext,
  344. cookies
  345. ],
  346. [
  347. CONTEXT_NAMES.PreviewDataContext,
  348. previewData
  349. ],
  350. [
  351. CONTEXT_NAMES.StaticGenerationContext,
  352. staticGenerationContext
  353. ],
  354. ];
  355. /**
  356. * Dynamic parameters. E.g. when you visit `/dashboard/vercel` which is rendered by `/dashboard/[slug]` the value will be {"slug": "vercel"}.
  357. */ const pathParams = renderOpts.params;
  358. /**
  359. * Parse the dynamic segment and return the associated value.
  360. */ const getDynamicParamFromSegment = (// [slug] / [[slug]] / [...slug]
  361. segment)=>{
  362. const segmentParam = getSegmentParam(segment);
  363. if (!segmentParam) {
  364. return null;
  365. }
  366. const key = segmentParam.param;
  367. const value = pathParams[key];
  368. if (!value) {
  369. // Handle case where optional catchall does not have a value, e.g. `/dashboard/[...slug]` when requesting `/dashboard`
  370. if (segmentParam.type === "optional-catchall") {
  371. const type = getShortDynamicParamType(segmentParam.type);
  372. return {
  373. param: key,
  374. value: null,
  375. type: type,
  376. // This value always has to be a string.
  377. treeSegment: [
  378. key,
  379. "",
  380. type
  381. ]
  382. };
  383. }
  384. return null;
  385. }
  386. const type = getShortDynamicParamType(segmentParam.type);
  387. return {
  388. param: key,
  389. // The value that is passed to user code.
  390. value: value,
  391. // The value that is rendered in the router tree.
  392. treeSegment: [
  393. key,
  394. Array.isArray(value) ? value.join("/") : value,
  395. type
  396. ],
  397. type: type
  398. };
  399. };
  400. const createFlightRouterStateFromLoaderTree = ([segment, parallelRoutes])=>{
  401. const dynamicParam = getDynamicParamFromSegment(segment);
  402. const segmentTree = [
  403. dynamicParam ? dynamicParam.treeSegment : segment,
  404. {},
  405. ];
  406. if (parallelRoutes) {
  407. segmentTree[1] = Object.keys(parallelRoutes).reduce((existingValue, currentValue)=>{
  408. existingValue[currentValue] = createFlightRouterStateFromLoaderTree(parallelRoutes[currentValue]);
  409. return existingValue;
  410. }, {});
  411. }
  412. return segmentTree;
  413. };
  414. let defaultRevalidate = false;
  415. /**
  416. * Use the provided loader tree to create the React Component tree.
  417. */ const createComponentTree = async ({ createSegmentPath , loaderTree: [segment, parallelRoutes, { layoutOrPagePath , layout , template , error , loading , page }, ] , parentParams , firstItem , rootLayoutIncluded })=>{
  418. // TODO-APP: enable stylesheet per layout/page
  419. const stylesheets = layoutOrPagePath ? getCssInlinedLinkTags(serverComponentManifest, serverCSSManifest, layoutOrPagePath) : [];
  420. const Template = template ? await interopDefault(template()) : _react.default.Fragment;
  421. const ErrorComponent = error ? await interopDefault(error()) : undefined;
  422. const Loading = loading ? await interopDefault(loading()) : undefined;
  423. const isLayout = typeof layout !== "undefined";
  424. const isPage = typeof page !== "undefined";
  425. const layoutOrPageMod = isLayout ? await layout() : isPage ? await page() : undefined;
  426. if (layoutOrPageMod == null ? void 0 : layoutOrPageMod.config) {
  427. defaultRevalidate = layoutOrPageMod.config.revalidate;
  428. }
  429. /**
  430. * Checks if the current segment is a root layout.
  431. */ const rootLayoutAtThisLevel = isLayout && !rootLayoutIncluded;
  432. /**
  433. * Checks if the current segment or any level above it has a root layout.
  434. */ const rootLayoutIncludedAtThisLevelOrAbove = rootLayoutIncluded || rootLayoutAtThisLevel;
  435. // TODO-APP: move these errors to the loader instead?
  436. // we will also need a migration doc here to link to
  437. if (typeof (layoutOrPageMod == null ? void 0 : layoutOrPageMod.getServerSideProps) === "function") {
  438. throw new Error(`getServerSideProps is not supported in app/, detected in ${segment}`);
  439. }
  440. if (typeof (layoutOrPageMod == null ? void 0 : layoutOrPageMod.getStaticProps) === "function") {
  441. throw new Error(`getStaticProps is not supported in app/, detected in ${segment}`);
  442. }
  443. /**
  444. * The React Component to render.
  445. */ const Component = layoutOrPageMod ? interopDefault(layoutOrPageMod) : undefined;
  446. // Handle dynamic segment params.
  447. const segmentParam = getDynamicParamFromSegment(segment);
  448. /**
  449. * Create object holding the parent params and current params
  450. */ const currentParams = // Handle null case where dynamic param is optional
  451. segmentParam && segmentParam.value !== null ? {
  452. ...parentParams,
  453. [segmentParam.param]: segmentParam.value
  454. } : parentParams;
  455. // Resolve the segment param
  456. const actualSegment = segmentParam ? segmentParam.treeSegment : segment;
  457. // This happens outside of rendering in order to eagerly kick off data fetching for layouts / the page further down
  458. const parallelRouteMap = await Promise.all(Object.keys(parallelRoutes).map(async (parallelRouteKey)=>{
  459. const currentSegmentPath = firstItem ? [
  460. parallelRouteKey
  461. ] : [
  462. actualSegment,
  463. parallelRouteKey
  464. ];
  465. const childSegment = parallelRoutes[parallelRouteKey][0];
  466. const childSegmentParam = getDynamicParamFromSegment(childSegment);
  467. if (isPrefetch && Loading) {
  468. const childProp = {
  469. // Null indicates the tree is not fully rendered
  470. current: null,
  471. segment: childSegmentParam ? childSegmentParam.treeSegment : childSegment
  472. };
  473. // This is turned back into an object below.
  474. return [
  475. parallelRouteKey,
  476. /*#__PURE__*/ _react.default.createElement(LayoutRouter, {
  477. parallelRouterKey: parallelRouteKey,
  478. segmentPath: createSegmentPath(currentSegmentPath),
  479. loading: Loading ? /*#__PURE__*/ _react.default.createElement(Loading, null) : undefined,
  480. error: ErrorComponent,
  481. template: /*#__PURE__*/ _react.default.createElement(Template, null, /*#__PURE__*/ _react.default.createElement(RenderFromTemplateContext, null)),
  482. childProp: childProp,
  483. rootLayoutIncluded: rootLayoutIncludedAtThisLevelOrAbove
  484. }),
  485. ];
  486. }
  487. // Create the child component
  488. const { Component: ChildComponent } = await createComponentTree({
  489. createSegmentPath: (child)=>{
  490. return createSegmentPath([
  491. ...currentSegmentPath,
  492. ...child
  493. ]);
  494. },
  495. loaderTree: parallelRoutes[parallelRouteKey],
  496. parentParams: currentParams,
  497. rootLayoutIncluded: rootLayoutIncludedAtThisLevelOrAbove
  498. });
  499. const childProp = {
  500. current: /*#__PURE__*/ _react.default.createElement(ChildComponent, null),
  501. segment: childSegmentParam ? childSegmentParam.treeSegment : childSegment
  502. };
  503. const segmentPath = createSegmentPath(currentSegmentPath);
  504. // This is turned back into an object below.
  505. return [
  506. parallelRouteKey,
  507. /*#__PURE__*/ _react.default.createElement(LayoutRouter, {
  508. parallelRouterKey: parallelRouteKey,
  509. segmentPath: segmentPath,
  510. error: ErrorComponent,
  511. loading: Loading ? /*#__PURE__*/ _react.default.createElement(Loading, null) : undefined,
  512. template: /*#__PURE__*/ _react.default.createElement(Template, null, /*#__PURE__*/ _react.default.createElement(RenderFromTemplateContext, null)),
  513. childProp: childProp,
  514. rootLayoutIncluded: rootLayoutIncludedAtThisLevelOrAbove
  515. }),
  516. ];
  517. }));
  518. // Convert the parallel route map into an object after all promises have been resolved.
  519. const parallelRouteComponents = parallelRouteMap.reduce((list, [parallelRouteKey, Comp])=>{
  520. list[parallelRouteKey] = Comp;
  521. return list;
  522. }, {});
  523. // When the segment does not have a layout or page we still have to add the layout router to ensure the path holds the loading component
  524. if (!Component) {
  525. return {
  526. Component: ()=>/*#__PURE__*/ _react.default.createElement(_react.default.Fragment, null, parallelRouteComponents.children)
  527. };
  528. }
  529. return {
  530. Component: ()=>{
  531. let props = {};
  532. return /*#__PURE__*/ _react.default.createElement(_react.default.Fragment, null, stylesheets ? stylesheets.map((href)=>/*#__PURE__*/ _react.default.createElement("link", {
  533. rel: "stylesheet",
  534. href: `/_next/${href}?ts=${Date.now()}`,
  535. // `Precedence` is an opt-in signal for React to handle
  536. // resource loading and deduplication, etc:
  537. // https://github.com/facebook/react/pull/25060
  538. // @ts-ignore
  539. precedence: "high",
  540. key: href
  541. })) : null, /*#__PURE__*/ _react.default.createElement(Component, Object.assign({}, props, parallelRouteComponents, {
  542. // TODO-APP: params and query have to be blocked parallel route names. Might have to add a reserved name list.
  543. // Params are always the current params that apply to the layout
  544. // If you have a `/dashboard/[team]/layout.js` it will provide `team` as a param but not anything further down.
  545. params: currentParams
  546. }, isPage ? {
  547. searchParams: query
  548. } : {})));
  549. }
  550. };
  551. };
  552. /**
  553. * Rules of Static & Dynamic HTML:
  554. *
  555. * 1.) We must generate static HTML unless the caller explicitly opts
  556. * in to dynamic HTML support.
  557. *
  558. * 2.) If dynamic HTML support is requested, we must honor that request
  559. * or throw an error. It is the sole responsibility of the caller to
  560. * ensure they aren't e.g. requesting dynamic HTML for an AMP page.
  561. *
  562. * These rules help ensure that other existing features like request caching,
  563. * coalescing, and ISR continue working as intended.
  564. */ const generateStaticHTML = supportsDynamicHTML !== true;
  565. // Handle Flight render request. This is only used when client-side navigating. E.g. when you `router.push('/dashboard')` or `router.reload()`.
  566. if (isFlight) {
  567. // TODO-APP: throw on invalid flightRouterState
  568. /**
  569. * Use router state to decide at what common layout to render the page.
  570. * This can either be the common layout between two pages or a specific place to start rendering from using the "refetch" marker in the tree.
  571. */ const walkTreeWithFlightRouterState = async (loaderTreeToFilter, parentParams, flightRouterState, parentRendered)=>{
  572. const [segment, parallelRoutes] = loaderTreeToFilter;
  573. const parallelRoutesKeys = Object.keys(parallelRoutes);
  574. // Because this function walks to a deeper point in the tree to start rendering we have to track the dynamic parameters up to the point where rendering starts
  575. const segmentParam = getDynamicParamFromSegment(segment);
  576. const currentParams = // Handle null case where dynamic param is optional
  577. segmentParam && segmentParam.value !== null ? {
  578. ...parentParams,
  579. [segmentParam.param]: segmentParam.value
  580. } : parentParams;
  581. const actualSegment = segmentParam ? segmentParam.treeSegment : segment;
  582. /**
  583. * Decide if the current segment is where rendering has to start.
  584. */ const renderComponentsOnThisLevel = // No further router state available
  585. !flightRouterState || // Segment in router state does not match current segment
  586. !(0, _matchSegments).matchSegment(actualSegment, flightRouterState[0]) || // Last item in the tree
  587. parallelRoutesKeys.length === 0 || // Explicit refresh
  588. flightRouterState[3] === "refetch";
  589. if (!parentRendered && renderComponentsOnThisLevel) {
  590. return [
  591. actualSegment,
  592. // Create router state using the slice of the loaderTree
  593. createFlightRouterStateFromLoaderTree(loaderTreeToFilter),
  594. // Check if one level down from the common layout has a loading component. If it doesn't only provide the router state as part of the Flight data.
  595. isPrefetch && !Boolean(loaderTreeToFilter[2].loading) ? null : /*#__PURE__*/ _react.default.createElement((await createComponentTree(// This ensures flightRouterPath is valid and filters down the tree
  596. {
  597. createSegmentPath: (child)=>child,
  598. loaderTree: loaderTreeToFilter,
  599. parentParams: currentParams,
  600. firstItem: true
  601. })).Component),
  602. ];
  603. }
  604. // Walk through all parallel routes.
  605. for (const parallelRouteKey of parallelRoutesKeys){
  606. const parallelRoute = parallelRoutes[parallelRouteKey];
  607. const path = await walkTreeWithFlightRouterState(parallelRoute, currentParams, flightRouterState && flightRouterState[1][parallelRouteKey], parentRendered || renderComponentsOnThisLevel);
  608. if (typeof path[path.length - 1] !== "string") {
  609. return [
  610. actualSegment,
  611. parallelRouteKey,
  612. ...path
  613. ];
  614. }
  615. }
  616. return [
  617. actualSegment
  618. ];
  619. };
  620. // Flight data that is going to be passed to the browser.
  621. // Currently a single item array but in the future multiple patches might be combined in a single request.
  622. const flightData = [
  623. // TODO-APP: change walk to output without ''
  624. (await walkTreeWithFlightRouterState(loaderTree, {}, providedFlightRouterState)).slice(1),
  625. ];
  626. const readable = (0, _writerBrowserServer).renderToReadableStream(flightData, serverComponentManifest, {
  627. context: serverContexts,
  628. onError
  629. }).pipeThrough((0, _nodeWebStreamsHelper).createBufferedTransformStream());
  630. if (generateStaticHTML) {
  631. let staticHtml = Buffer.from((await readable.getReader().read()).value || "").toString();
  632. return new FlightRenderResult(staticHtml);
  633. }
  634. return new FlightRenderResult(readable);
  635. }
  636. // Below this line is handling for rendering to HTML.
  637. // Create full component tree from root to leaf.
  638. const { Component: ComponentTree } = await createComponentTree({
  639. createSegmentPath: (child)=>child,
  640. loaderTree: loaderTree,
  641. parentParams: {},
  642. firstItem: true
  643. });
  644. // AppRouter is provided by next-app-loader
  645. const AppRouter = ComponentMod.AppRouter;
  646. let serverComponentsInlinedTransformStream = new TransformStream();
  647. // TODO-APP: validate req.url as it gets passed to render.
  648. const initialCanonicalUrl = req.url;
  649. // Get the nonce from the incomming request if it has one.
  650. const csp = req.headers["content-security-policy"];
  651. let nonce;
  652. if (csp && typeof csp === "string") {
  653. nonce = getScriptNonceFromHeader(csp);
  654. }
  655. const serverComponentsRenderOpts = {
  656. transformStream: serverComponentsInlinedTransformStream,
  657. serverComponentManifest,
  658. serverContexts,
  659. rscChunks: []
  660. };
  661. /**
  662. * A new React Component that renders the provided React Component
  663. * using Flight which can then be rendered to HTML.
  664. */ const ServerComponentsRenderer = createServerComponentRenderer(()=>{
  665. const initialTree = createFlightRouterStateFromLoaderTree(loaderTree);
  666. return /*#__PURE__*/ _react.default.createElement(AppRouter, {
  667. hotReloader: HotReloader && /*#__PURE__*/ _react.default.createElement(HotReloader, {
  668. assetPrefix: renderOpts.assetPrefix || ""
  669. }),
  670. initialCanonicalUrl: initialCanonicalUrl,
  671. initialTree: initialTree
  672. }, /*#__PURE__*/ _react.default.createElement(ComponentTree, null));
  673. }, ComponentMod, serverComponentsRenderOpts, nonce);
  674. const flushEffectsCallbacks = new Set();
  675. function FlushEffects({ children }) {
  676. // Reset flushEffectsHandler on each render
  677. flushEffectsCallbacks.clear();
  678. const addFlushEffects = _react.default.useCallback((handler)=>{
  679. flushEffectsCallbacks.add(handler);
  680. }, []);
  681. return /*#__PURE__*/ _react.default.createElement(_flushEffects.FlushEffectsContext.Provider, {
  682. value: addFlushEffects
  683. }, children);
  684. }
  685. const bodyResult = async ()=>{
  686. const content = /*#__PURE__*/ _react.default.createElement(FlushEffects, null, /*#__PURE__*/ _react.default.createElement(ServerComponentsRenderer, null));
  687. const flushEffectHandler = ()=>{
  688. const flushed = ReactDOMServer.renderToString(/*#__PURE__*/ _react.default.createElement(_react.default.Fragment, null, Array.from(flushEffectsCallbacks).map((callback)=>callback())));
  689. return flushed;
  690. };
  691. try {
  692. const renderStream = await (0, _nodeWebStreamsHelper).renderToInitialStream({
  693. ReactDOMServer,
  694. element: content,
  695. streamOptions: {
  696. nonce,
  697. // Include hydration scripts in the HTML
  698. bootstrapScripts: subresourceIntegrityManifest ? buildManifest.rootMainFiles.map((src)=>({
  699. src: `${renderOpts.assetPrefix || ""}/_next/` + src,
  700. integrity: subresourceIntegrityManifest[src]
  701. })) : buildManifest.rootMainFiles.map((src)=>`${renderOpts.assetPrefix || ""}/_next/` + src)
  702. }
  703. });
  704. return await (0, _nodeWebStreamsHelper).continueFromInitialStream(renderStream, {
  705. dataStream: serverComponentsInlinedTransformStream == null ? void 0 : serverComponentsInlinedTransformStream.readable,
  706. generateStaticHTML: generateStaticHTML,
  707. flushEffectHandler,
  708. flushEffectsToHead: true
  709. });
  710. } catch (err) {
  711. // TODO-APP: show error overlay in development. `element` should probably be wrapped in AppRouter for this case.
  712. const renderStream = await (0, _nodeWebStreamsHelper).renderToInitialStream({
  713. ReactDOMServer,
  714. element: /*#__PURE__*/ _react.default.createElement("html", {
  715. id: "__next_error__"
  716. }, /*#__PURE__*/ _react.default.createElement("head", null), /*#__PURE__*/ _react.default.createElement("body", null)),
  717. streamOptions: {
  718. nonce,
  719. // Include hydration scripts in the HTML
  720. bootstrapScripts: subresourceIntegrityManifest ? buildManifest.rootMainFiles.map((src)=>({
  721. src: `${renderOpts.assetPrefix || ""}/_next/` + src,
  722. integrity: subresourceIntegrityManifest[src]
  723. })) : buildManifest.rootMainFiles.map((src)=>`${renderOpts.assetPrefix || ""}/_next/` + src)
  724. }
  725. });
  726. return await (0, _nodeWebStreamsHelper).continueFromInitialStream(renderStream, {
  727. dataStream: serverComponentsInlinedTransformStream == null ? void 0 : serverComponentsInlinedTransformStream.readable,
  728. generateStaticHTML: generateStaticHTML,
  729. flushEffectHandler,
  730. flushEffectsToHead: true
  731. });
  732. }
  733. };
  734. const readable = await bodyResult();
  735. if (generateStaticHTML) {
  736. let staticHtml = Buffer.from((await readable.getReader().read()).value || "").toString();
  737. renderOpts.pageData = Buffer.concat(serverComponentsRenderOpts.rscChunks).toString();
  738. renderOpts.revalidate = typeof staticGenerationContext.revalidate === "undefined" ? defaultRevalidate : staticGenerationContext.revalidate;
  739. return new _renderResult.default(staticHtml);
  740. }
  741. return new _renderResult.default(readable);
  742. }
  743. //# sourceMappingURL=app-render.js.map