123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744 |
- "use strict";
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
- exports.renderToHTMLOrFlight = renderToHTMLOrFlight;
- var _react = _interopRequireWildcard(require("react"));
- var _querystring = require("querystring");
- var _reactServerDomWebpack = require("next/dist/compiled/react-server-dom-webpack");
- var _writerBrowserServer = require("next/dist/compiled/react-server-dom-webpack/writer.browser.server");
- var _renderResult = _interopRequireDefault(require("./render-result"));
- var _nodeWebStreamsHelper = require("./node-web-streams-helper");
- var _htmlescape = require("./htmlescape");
- var _utils = require("./utils");
- var _matchSegments = require("../client/components/match-segments");
- var _flushEffects = require("../shared/lib/flush-effects");
- var _internalUtils = require("./internal-utils");
- function _interopRequireDefault(obj) {
- return obj && obj.__esModule ? obj : {
- default: obj
- };
- }
- function _getRequireWildcardCache() {
- if (typeof WeakMap !== "function") return null;
- var cache = new WeakMap();
- _getRequireWildcardCache = function() {
- return cache;
- };
- return cache;
- }
- function _interopRequireWildcard(obj) {
- if (obj && obj.__esModule) {
- return obj;
- }
- if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
- return {
- default: obj
- };
- }
- var cache = _getRequireWildcardCache();
- if (cache && cache.has(obj)) {
- return cache.get(obj);
- }
- var newObj = {};
- var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
- for(var key in obj){
- if (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;
- }
- // this needs to be required lazily so that `next-server` can set
- // the env before we require
- const ReactDOMServer = _utils.shouldUseReactRoot ? require("react-dom/server.browser") : require("react-dom/server");
- /**
- * Flight Response is always set to application/octet-stream to ensure it does not
- */ class FlightRenderResult extends _renderResult.default {
- constructor(response){
- super(response, {
- contentType: "application/octet-stream"
- });
- }
- }
- /**
- * Interop between "export default" and "module.exports".
- */ function interopDefault(mod) {
- return mod.default || mod;
- }
- // tolerate dynamic server errors during prerendering so console
- // isn't spammed with unactionable errors
- function onError(err) {
- const { DynamicServerError } = require("../client/components/hooks-server-context");
- if (!(err instanceof DynamicServerError)) {
- console.error(err);
- }
- }
- let isFetchPatched = false;
- // we patch fetch to collect cache information used for
- // determining if a page is static or not
- function patchFetch() {
- if (isFetchPatched) return;
- isFetchPatched = true;
- const { DynamicServerError } = require("../client/components/hooks-server-context");
- const { useTrackStaticGeneration } = require("../client/components/hooks-server");
- const origFetch = global.fetch;
- global.fetch = async (init, opts)=>{
- let staticGenerationContext = {};
- try {
- // eslint-disable-next-line react-hooks/rules-of-hooks
- staticGenerationContext = useTrackStaticGeneration() || {};
- } catch (_) {}
- const { isStaticGeneration , fetchRevalidate , pathname } = staticGenerationContext;
- if (isStaticGeneration) {
- if (opts && typeof opts === "object") {
- if (opts.cache === "no-store") {
- staticGenerationContext.revalidate = 0;
- // TODO: ensure this error isn't logged to the user
- // seems it's slipping through currently
- throw new DynamicServerError(`no-store fetch ${init}${pathname ? ` ${pathname}` : ""}`);
- }
- if (typeof opts.revalidate === "number" && (typeof fetchRevalidate === "undefined" || opts.revalidate < fetchRevalidate)) {
- staticGenerationContext.fetchRevalidate = opts.revalidate;
- }
- }
- }
- return origFetch(init, opts);
- };
- }
- /**
- * Render Flight stream.
- * This is only used for renderToHTML, the Flight response does not need additional wrappers.
- */ function useFlightResponse(writable, req, serverComponentManifest, rscChunks, flightResponseRef, nonce) {
- if (flightResponseRef.current !== null) {
- return flightResponseRef.current;
- }
- const [renderStream, forwardStream] = (0, _nodeWebStreamsHelper).readableStreamTee(req);
- const res = (0, _reactServerDomWebpack).createFromReadableStream(renderStream, {
- moduleMap: serverComponentManifest.__ssr_module_mapping__
- });
- flightResponseRef.current = res;
- let bootstrapped = false;
- // We only attach CSS chunks to the inlined data.
- const forwardReader = forwardStream.getReader();
- const writer = writable.getWriter();
- const startScriptTag = nonce ? `<script nonce=${JSON.stringify(nonce)}>` : "<script>";
- function process() {
- forwardReader.read().then(({ done , value })=>{
- if (value) {
- rscChunks.push(value);
- }
- if (!bootstrapped) {
- bootstrapped = true;
- writer.write((0, _nodeWebStreamsHelper).encodeText(`${startScriptTag}(self.__next_s=self.__next_s||[]).push(${(0, _htmlescape).htmlEscapeJsonString(JSON.stringify([
- 0
- ]))})</script>`));
- }
- if (done) {
- flightResponseRef.current = null;
- writer.close();
- } else {
- const responsePartial = (0, _nodeWebStreamsHelper).decodeText(value);
- const scripts = `${startScriptTag}(self.__next_s=self.__next_s||[]).push(${(0, _htmlescape).htmlEscapeJsonString(JSON.stringify([
- 1,
- responsePartial
- ]))})</script>`;
- writer.write((0, _nodeWebStreamsHelper).encodeText(scripts));
- process();
- }
- });
- }
- process();
- return res;
- }
- /**
- * Create a component that renders the Flight stream.
- * This is only used for renderToHTML, the Flight response does not need additional wrappers.
- */ function createServerComponentRenderer(ComponentToRender, ComponentMod, { transformStream , serverComponentManifest , serverContexts , rscChunks }, nonce) {
- // We need to expose the `__webpack_require__` API globally for
- // react-server-dom-webpack. This is a hack until we find a better way.
- if (ComponentMod.__next_app_webpack_require__ || ComponentMod.__next_rsc__) {
- var ref;
- // @ts-ignore
- globalThis.__next_require__ = ComponentMod.__next_app_webpack_require__ || ((ref = ComponentMod.__next_rsc__) == null ? void 0 : ref.__webpack_require__);
- // @ts-ignore
- globalThis.__next_chunk_load__ = ()=>Promise.resolve();
- }
- let RSCStream;
- const createRSCStream = ()=>{
- if (!RSCStream) {
- RSCStream = (0, _writerBrowserServer).renderToReadableStream(/*#__PURE__*/ _react.default.createElement(ComponentToRender, null), serverComponentManifest, {
- context: serverContexts,
- onError
- });
- }
- return RSCStream;
- };
- const flightResponseRef = {
- current: null
- };
- const writable = transformStream.writable;
- return function ServerComponentWrapper() {
- const reqStream = createRSCStream();
- const response = useFlightResponse(writable, reqStream, serverComponentManifest, rscChunks, flightResponseRef, nonce);
- return (0, _react).experimental_use(response);
- };
- }
- /**
- * Shorten the dynamic param in order to make it smaller when transmitted to the browser.
- */ function getShortDynamicParamType(type) {
- switch(type){
- case "catchall":
- return "c";
- case "optional-catchall":
- return "oc";
- case "dynamic":
- return "d";
- default:
- throw new Error("Unknown dynamic param type");
- }
- }
- /**
- * Parse dynamic route segment to type of parameter
- */ function getSegmentParam(segment) {
- if (segment.startsWith("[[...") && segment.endsWith("]]")) {
- return {
- type: "optional-catchall",
- param: segment.slice(5, -2)
- };
- }
- if (segment.startsWith("[...") && segment.endsWith("]")) {
- return {
- type: "catchall",
- param: segment.slice(4, -1)
- };
- }
- if (segment.startsWith("[") && segment.endsWith("]")) {
- return {
- type: "dynamic",
- param: segment.slice(1, -1)
- };
- }
- return null;
- }
- /**
- * Get inline <link> tags based on server CSS manifest. Only used when rendering to HTML.
- */ function getCssInlinedLinkTags(serverComponentManifest, serverCSSManifest, filePath) {
- var ref;
- const layoutOrPageCss = serverCSSManifest[filePath] || ((ref = serverComponentManifest.__client_css_manifest__) == null ? void 0 : ref[filePath]);
- if (!layoutOrPageCss) {
- return [];
- }
- const chunks = new Set();
- for (const css of layoutOrPageCss){
- const mod = serverComponentManifest[css];
- if (mod) {
- for (const chunk of mod.default.chunks){
- chunks.add(chunk);
- }
- }
- }
- return [
- ...chunks
- ];
- }
- function getScriptNonceFromHeader(cspHeaderValue) {
- var ref;
- const directives = cspHeaderValue// Directives are split by ';'.
- .split(";").map((directive)=>directive.trim());
- // First try to find the directive for the 'script-src', otherwise try to
- // fallback to the 'default-src'.
- const directive1 = directives.find((dir)=>dir.startsWith("script-src")) || directives.find((dir)=>dir.startsWith("default-src"));
- // If no directive could be found, then we're done.
- if (!directive1) {
- return;
- }
- // Extract the nonce from the directive
- const nonce = (ref = directive1.split(" ")// Remove the 'strict-src'/'default-src' string, this can't be the nonce.
- .slice(1).map((source)=>source.trim())// Find the first source with the 'nonce-' prefix.
- .find((source)=>source.startsWith("'nonce-") && source.length > 8 && source.endsWith("'"))) == null ? void 0 : ref.slice(7, -1);
- // If we could't find the nonce, then we're done.
- if (!nonce) {
- return;
- }
- // Don't accept the nonce value if it contains HTML escape characters.
- // Technically, the spec requires a base64'd value, but this is just an
- // extra layer.
- if (_htmlescape.ESCAPE_REGEX.test(nonce)) {
- throw new Error("Nonce value from Content-Security-Policy contained HTML escape characters.\nLearn more: https://nextjs.org/docs/messages/nonce-contained-invalid-characters");
- }
- return nonce;
- }
- async function renderToHTMLOrFlight(req, res, pathname, query, renderOpts, isPagesDir, isStaticGeneration = false) {
- patchFetch();
- const { CONTEXT_NAMES } = require("../client/components/hooks-server-context");
- // @ts-expect-error createServerContext exists in react@experimental + react-dom@experimental
- if (typeof _react.default.createServerContext === "undefined") {
- 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.');
- }
- // don't modify original query object
- query = Object.assign({}, query);
- const { buildManifest , subresourceIntegrityManifest , serverComponentManifest , serverCSSManifest ={} , supportsDynamicHTML , ComponentMod , } = renderOpts;
- const isFlight = query.__flight__ !== undefined;
- const isPrefetch = query.__flight_prefetch__ !== undefined;
- // Handle client-side navigation to pages directory
- if (isFlight && isPagesDir) {
- (0, _internalUtils).stripInternalQueries(query);
- const search = (0, _querystring).stringify(query);
- // Empty so that the client-side router will do a full page navigation.
- const flightData = pathname + (search ? `?${search}` : "");
- return new FlightRenderResult((0, _writerBrowserServer).renderToReadableStream(flightData, serverComponentManifest, {
- onError
- }).pipeThrough((0, _nodeWebStreamsHelper).createBufferedTransformStream()));
- }
- // TODO-APP: verify the tree is valid
- // TODO-APP: verify query param is single value (not an array)
- // TODO-APP: verify tree can't grow out of control
- /**
- * Router state provided from the client-side router. Used to handle rendering from the common layout down.
- */ const providedFlightRouterState = isFlight ? query.__flight_router_state_tree__ ? JSON.parse(query.__flight_router_state_tree__) : {} : undefined;
- (0, _internalUtils).stripInternalQueries(query);
- const LayoutRouter = ComponentMod.LayoutRouter;
- const RenderFromTemplateContext = ComponentMod.RenderFromTemplateContext;
- const HotReloader = ComponentMod.HotReloader;
- const headers = req.headers;
- // TODO-APP: fix type of req
- // @ts-expect-error
- const cookies = req.cookies;
- /**
- * The tree created in next-app-loader that holds component segments and modules
- */ const loaderTree = ComponentMod.tree;
- const tryGetPreviewData = process.env.NEXT_RUNTIME === "edge" ? ()=>false : require("./api-utils/node").tryGetPreviewData;
- // Reads of this are cached on the `req` object, so this should resolve
- // instantly. There's no need to pass this data down from a previous
- // invoke, where we'd have to consider server & serverless.
- const previewData = tryGetPreviewData(req, res, renderOpts.previewProps);
- /**
- * Server Context is specifically only available in Server Components.
- * It has to hold values that can't change while rendering from the common layout down.
- * 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.
- */ const staticGenerationContext = {
- isStaticGeneration,
- pathname
- };
- const serverContexts = [
- [
- "WORKAROUND",
- null
- ],
- [
- CONTEXT_NAMES.HeadersContext,
- headers
- ],
- [
- CONTEXT_NAMES.CookiesContext,
- cookies
- ],
- [
- CONTEXT_NAMES.PreviewDataContext,
- previewData
- ],
- [
- CONTEXT_NAMES.StaticGenerationContext,
- staticGenerationContext
- ],
- ];
- /**
- * Dynamic parameters. E.g. when you visit `/dashboard/vercel` which is rendered by `/dashboard/[slug]` the value will be {"slug": "vercel"}.
- */ const pathParams = renderOpts.params;
- /**
- * Parse the dynamic segment and return the associated value.
- */ const getDynamicParamFromSegment = (// [slug] / [[slug]] / [...slug]
- segment)=>{
- const segmentParam = getSegmentParam(segment);
- if (!segmentParam) {
- return null;
- }
- const key = segmentParam.param;
- const value = pathParams[key];
- if (!value) {
- // Handle case where optional catchall does not have a value, e.g. `/dashboard/[...slug]` when requesting `/dashboard`
- if (segmentParam.type === "optional-catchall") {
- const type = getShortDynamicParamType(segmentParam.type);
- return {
- param: key,
- value: null,
- type: type,
- // This value always has to be a string.
- treeSegment: [
- key,
- "",
- type
- ]
- };
- }
- return null;
- }
- const type = getShortDynamicParamType(segmentParam.type);
- return {
- param: key,
- // The value that is passed to user code.
- value: value,
- // The value that is rendered in the router tree.
- treeSegment: [
- key,
- Array.isArray(value) ? value.join("/") : value,
- type
- ],
- type: type
- };
- };
- const createFlightRouterStateFromLoaderTree = ([segment, parallelRoutes])=>{
- const dynamicParam = getDynamicParamFromSegment(segment);
- const segmentTree = [
- dynamicParam ? dynamicParam.treeSegment : segment,
- {},
- ];
- if (parallelRoutes) {
- segmentTree[1] = Object.keys(parallelRoutes).reduce((existingValue, currentValue)=>{
- existingValue[currentValue] = createFlightRouterStateFromLoaderTree(parallelRoutes[currentValue]);
- return existingValue;
- }, {});
- }
- return segmentTree;
- };
- let defaultRevalidate = false;
- /**
- * Use the provided loader tree to create the React Component tree.
- */ const createComponentTree = async ({ createSegmentPath , loaderTree: [segment, parallelRoutes, { layoutOrPagePath , layout , template , error , loading , page }, ] , parentParams , firstItem , rootLayoutIncluded })=>{
- // TODO-APP: enable stylesheet per layout/page
- const stylesheets = layoutOrPagePath ? getCssInlinedLinkTags(serverComponentManifest, serverCSSManifest, layoutOrPagePath) : [];
- const Template = template ? await interopDefault(template()) : _react.default.Fragment;
- const ErrorComponent = error ? await interopDefault(error()) : undefined;
- const Loading = loading ? await interopDefault(loading()) : undefined;
- const isLayout = typeof layout !== "undefined";
- const isPage = typeof page !== "undefined";
- const layoutOrPageMod = isLayout ? await layout() : isPage ? await page() : undefined;
- if (layoutOrPageMod == null ? void 0 : layoutOrPageMod.config) {
- defaultRevalidate = layoutOrPageMod.config.revalidate;
- }
- /**
- * Checks if the current segment is a root layout.
- */ const rootLayoutAtThisLevel = isLayout && !rootLayoutIncluded;
- /**
- * Checks if the current segment or any level above it has a root layout.
- */ const rootLayoutIncludedAtThisLevelOrAbove = rootLayoutIncluded || rootLayoutAtThisLevel;
- // TODO-APP: move these errors to the loader instead?
- // we will also need a migration doc here to link to
- if (typeof (layoutOrPageMod == null ? void 0 : layoutOrPageMod.getServerSideProps) === "function") {
- throw new Error(`getServerSideProps is not supported in app/, detected in ${segment}`);
- }
- if (typeof (layoutOrPageMod == null ? void 0 : layoutOrPageMod.getStaticProps) === "function") {
- throw new Error(`getStaticProps is not supported in app/, detected in ${segment}`);
- }
- /**
- * The React Component to render.
- */ const Component = layoutOrPageMod ? interopDefault(layoutOrPageMod) : undefined;
- // Handle dynamic segment params.
- const segmentParam = getDynamicParamFromSegment(segment);
- /**
- * Create object holding the parent params and current params
- */ const currentParams = // Handle null case where dynamic param is optional
- segmentParam && segmentParam.value !== null ? {
- ...parentParams,
- [segmentParam.param]: segmentParam.value
- } : parentParams;
- // Resolve the segment param
- const actualSegment = segmentParam ? segmentParam.treeSegment : segment;
- // This happens outside of rendering in order to eagerly kick off data fetching for layouts / the page further down
- const parallelRouteMap = await Promise.all(Object.keys(parallelRoutes).map(async (parallelRouteKey)=>{
- const currentSegmentPath = firstItem ? [
- parallelRouteKey
- ] : [
- actualSegment,
- parallelRouteKey
- ];
- const childSegment = parallelRoutes[parallelRouteKey][0];
- const childSegmentParam = getDynamicParamFromSegment(childSegment);
- if (isPrefetch && Loading) {
- const childProp = {
- // Null indicates the tree is not fully rendered
- current: null,
- segment: childSegmentParam ? childSegmentParam.treeSegment : childSegment
- };
- // This is turned back into an object below.
- return [
- parallelRouteKey,
- /*#__PURE__*/ _react.default.createElement(LayoutRouter, {
- parallelRouterKey: parallelRouteKey,
- segmentPath: createSegmentPath(currentSegmentPath),
- loading: Loading ? /*#__PURE__*/ _react.default.createElement(Loading, null) : undefined,
- error: ErrorComponent,
- template: /*#__PURE__*/ _react.default.createElement(Template, null, /*#__PURE__*/ _react.default.createElement(RenderFromTemplateContext, null)),
- childProp: childProp,
- rootLayoutIncluded: rootLayoutIncludedAtThisLevelOrAbove
- }),
- ];
- }
- // Create the child component
- const { Component: ChildComponent } = await createComponentTree({
- createSegmentPath: (child)=>{
- return createSegmentPath([
- ...currentSegmentPath,
- ...child
- ]);
- },
- loaderTree: parallelRoutes[parallelRouteKey],
- parentParams: currentParams,
- rootLayoutIncluded: rootLayoutIncludedAtThisLevelOrAbove
- });
- const childProp = {
- current: /*#__PURE__*/ _react.default.createElement(ChildComponent, null),
- segment: childSegmentParam ? childSegmentParam.treeSegment : childSegment
- };
- const segmentPath = createSegmentPath(currentSegmentPath);
- // This is turned back into an object below.
- return [
- parallelRouteKey,
- /*#__PURE__*/ _react.default.createElement(LayoutRouter, {
- parallelRouterKey: parallelRouteKey,
- segmentPath: segmentPath,
- error: ErrorComponent,
- loading: Loading ? /*#__PURE__*/ _react.default.createElement(Loading, null) : undefined,
- template: /*#__PURE__*/ _react.default.createElement(Template, null, /*#__PURE__*/ _react.default.createElement(RenderFromTemplateContext, null)),
- childProp: childProp,
- rootLayoutIncluded: rootLayoutIncludedAtThisLevelOrAbove
- }),
- ];
- }));
- // Convert the parallel route map into an object after all promises have been resolved.
- const parallelRouteComponents = parallelRouteMap.reduce((list, [parallelRouteKey, Comp])=>{
- list[parallelRouteKey] = Comp;
- return list;
- }, {});
- // 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
- if (!Component) {
- return {
- Component: ()=>/*#__PURE__*/ _react.default.createElement(_react.default.Fragment, null, parallelRouteComponents.children)
- };
- }
- return {
- Component: ()=>{
- let props = {};
- return /*#__PURE__*/ _react.default.createElement(_react.default.Fragment, null, stylesheets ? stylesheets.map((href)=>/*#__PURE__*/ _react.default.createElement("link", {
- rel: "stylesheet",
- href: `/_next/${href}?ts=${Date.now()}`,
- // `Precedence` is an opt-in signal for React to handle
- // resource loading and deduplication, etc:
- // https://github.com/facebook/react/pull/25060
- // @ts-ignore
- precedence: "high",
- key: href
- })) : null, /*#__PURE__*/ _react.default.createElement(Component, Object.assign({}, props, parallelRouteComponents, {
- // TODO-APP: params and query have to be blocked parallel route names. Might have to add a reserved name list.
- // Params are always the current params that apply to the layout
- // If you have a `/dashboard/[team]/layout.js` it will provide `team` as a param but not anything further down.
- params: currentParams
- }, isPage ? {
- searchParams: query
- } : {})));
- }
- };
- };
- /**
- * Rules of Static & Dynamic HTML:
- *
- * 1.) We must generate static HTML unless the caller explicitly opts
- * in to dynamic HTML support.
- *
- * 2.) If dynamic HTML support is requested, we must honor that request
- * or throw an error. It is the sole responsibility of the caller to
- * ensure they aren't e.g. requesting dynamic HTML for an AMP page.
- *
- * These rules help ensure that other existing features like request caching,
- * coalescing, and ISR continue working as intended.
- */ const generateStaticHTML = supportsDynamicHTML !== true;
- // Handle Flight render request. This is only used when client-side navigating. E.g. when you `router.push('/dashboard')` or `router.reload()`.
- if (isFlight) {
- // TODO-APP: throw on invalid flightRouterState
- /**
- * Use router state to decide at what common layout to render the page.
- * 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.
- */ const walkTreeWithFlightRouterState = async (loaderTreeToFilter, parentParams, flightRouterState, parentRendered)=>{
- const [segment, parallelRoutes] = loaderTreeToFilter;
- const parallelRoutesKeys = Object.keys(parallelRoutes);
- // 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
- const segmentParam = getDynamicParamFromSegment(segment);
- const currentParams = // Handle null case where dynamic param is optional
- segmentParam && segmentParam.value !== null ? {
- ...parentParams,
- [segmentParam.param]: segmentParam.value
- } : parentParams;
- const actualSegment = segmentParam ? segmentParam.treeSegment : segment;
- /**
- * Decide if the current segment is where rendering has to start.
- */ const renderComponentsOnThisLevel = // No further router state available
- !flightRouterState || // Segment in router state does not match current segment
- !(0, _matchSegments).matchSegment(actualSegment, flightRouterState[0]) || // Last item in the tree
- parallelRoutesKeys.length === 0 || // Explicit refresh
- flightRouterState[3] === "refetch";
- if (!parentRendered && renderComponentsOnThisLevel) {
- return [
- actualSegment,
- // Create router state using the slice of the loaderTree
- createFlightRouterStateFromLoaderTree(loaderTreeToFilter),
- // 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.
- isPrefetch && !Boolean(loaderTreeToFilter[2].loading) ? null : /*#__PURE__*/ _react.default.createElement((await createComponentTree(// This ensures flightRouterPath is valid and filters down the tree
- {
- createSegmentPath: (child)=>child,
- loaderTree: loaderTreeToFilter,
- parentParams: currentParams,
- firstItem: true
- })).Component),
- ];
- }
- // Walk through all parallel routes.
- for (const parallelRouteKey of parallelRoutesKeys){
- const parallelRoute = parallelRoutes[parallelRouteKey];
- const path = await walkTreeWithFlightRouterState(parallelRoute, currentParams, flightRouterState && flightRouterState[1][parallelRouteKey], parentRendered || renderComponentsOnThisLevel);
- if (typeof path[path.length - 1] !== "string") {
- return [
- actualSegment,
- parallelRouteKey,
- ...path
- ];
- }
- }
- return [
- actualSegment
- ];
- };
- // Flight data that is going to be passed to the browser.
- // Currently a single item array but in the future multiple patches might be combined in a single request.
- const flightData = [
- // TODO-APP: change walk to output without ''
- (await walkTreeWithFlightRouterState(loaderTree, {}, providedFlightRouterState)).slice(1),
- ];
- const readable = (0, _writerBrowserServer).renderToReadableStream(flightData, serverComponentManifest, {
- context: serverContexts,
- onError
- }).pipeThrough((0, _nodeWebStreamsHelper).createBufferedTransformStream());
- if (generateStaticHTML) {
- let staticHtml = Buffer.from((await readable.getReader().read()).value || "").toString();
- return new FlightRenderResult(staticHtml);
- }
- return new FlightRenderResult(readable);
- }
- // Below this line is handling for rendering to HTML.
- // Create full component tree from root to leaf.
- const { Component: ComponentTree } = await createComponentTree({
- createSegmentPath: (child)=>child,
- loaderTree: loaderTree,
- parentParams: {},
- firstItem: true
- });
- // AppRouter is provided by next-app-loader
- const AppRouter = ComponentMod.AppRouter;
- let serverComponentsInlinedTransformStream = new TransformStream();
- // TODO-APP: validate req.url as it gets passed to render.
- const initialCanonicalUrl = req.url;
- // Get the nonce from the incomming request if it has one.
- const csp = req.headers["content-security-policy"];
- let nonce;
- if (csp && typeof csp === "string") {
- nonce = getScriptNonceFromHeader(csp);
- }
- const serverComponentsRenderOpts = {
- transformStream: serverComponentsInlinedTransformStream,
- serverComponentManifest,
- serverContexts,
- rscChunks: []
- };
- /**
- * A new React Component that renders the provided React Component
- * using Flight which can then be rendered to HTML.
- */ const ServerComponentsRenderer = createServerComponentRenderer(()=>{
- const initialTree = createFlightRouterStateFromLoaderTree(loaderTree);
- return /*#__PURE__*/ _react.default.createElement(AppRouter, {
- hotReloader: HotReloader && /*#__PURE__*/ _react.default.createElement(HotReloader, {
- assetPrefix: renderOpts.assetPrefix || ""
- }),
- initialCanonicalUrl: initialCanonicalUrl,
- initialTree: initialTree
- }, /*#__PURE__*/ _react.default.createElement(ComponentTree, null));
- }, ComponentMod, serverComponentsRenderOpts, nonce);
- const flushEffectsCallbacks = new Set();
- function FlushEffects({ children }) {
- // Reset flushEffectsHandler on each render
- flushEffectsCallbacks.clear();
- const addFlushEffects = _react.default.useCallback((handler)=>{
- flushEffectsCallbacks.add(handler);
- }, []);
- return /*#__PURE__*/ _react.default.createElement(_flushEffects.FlushEffectsContext.Provider, {
- value: addFlushEffects
- }, children);
- }
- const bodyResult = async ()=>{
- const content = /*#__PURE__*/ _react.default.createElement(FlushEffects, null, /*#__PURE__*/ _react.default.createElement(ServerComponentsRenderer, null));
- const flushEffectHandler = ()=>{
- const flushed = ReactDOMServer.renderToString(/*#__PURE__*/ _react.default.createElement(_react.default.Fragment, null, Array.from(flushEffectsCallbacks).map((callback)=>callback())));
- return flushed;
- };
- try {
- const renderStream = await (0, _nodeWebStreamsHelper).renderToInitialStream({
- ReactDOMServer,
- element: content,
- streamOptions: {
- nonce,
- // Include hydration scripts in the HTML
- bootstrapScripts: subresourceIntegrityManifest ? buildManifest.rootMainFiles.map((src)=>({
- src: `${renderOpts.assetPrefix || ""}/_next/` + src,
- integrity: subresourceIntegrityManifest[src]
- })) : buildManifest.rootMainFiles.map((src)=>`${renderOpts.assetPrefix || ""}/_next/` + src)
- }
- });
- return await (0, _nodeWebStreamsHelper).continueFromInitialStream(renderStream, {
- dataStream: serverComponentsInlinedTransformStream == null ? void 0 : serverComponentsInlinedTransformStream.readable,
- generateStaticHTML: generateStaticHTML,
- flushEffectHandler,
- flushEffectsToHead: true
- });
- } catch (err) {
- // TODO-APP: show error overlay in development. `element` should probably be wrapped in AppRouter for this case.
- const renderStream = await (0, _nodeWebStreamsHelper).renderToInitialStream({
- ReactDOMServer,
- element: /*#__PURE__*/ _react.default.createElement("html", {
- id: "__next_error__"
- }, /*#__PURE__*/ _react.default.createElement("head", null), /*#__PURE__*/ _react.default.createElement("body", null)),
- streamOptions: {
- nonce,
- // Include hydration scripts in the HTML
- bootstrapScripts: subresourceIntegrityManifest ? buildManifest.rootMainFiles.map((src)=>({
- src: `${renderOpts.assetPrefix || ""}/_next/` + src,
- integrity: subresourceIntegrityManifest[src]
- })) : buildManifest.rootMainFiles.map((src)=>`${renderOpts.assetPrefix || ""}/_next/` + src)
- }
- });
- return await (0, _nodeWebStreamsHelper).continueFromInitialStream(renderStream, {
- dataStream: serverComponentsInlinedTransformStream == null ? void 0 : serverComponentsInlinedTransformStream.readable,
- generateStaticHTML: generateStaticHTML,
- flushEffectHandler,
- flushEffectsToHead: true
- });
- }
- };
- const readable = await bodyResult();
- if (generateStaticHTML) {
- let staticHtml = Buffer.from((await readable.getReader().read()).value || "").toString();
- renderOpts.pageData = Buffer.concat(serverComponentsRenderOpts.rscChunks).toString();
- renderOpts.revalidate = typeof staticGenerationContext.revalidate === "undefined" ? defaultRevalidate : staticGenerationContext.revalidate;
- return new _renderResult.default(staticHtml);
- }
- return new _renderResult.default(readable);
- }
- //# sourceMappingURL=app-render.js.map
|