249 lines
12 KiB
JavaScript
249 lines
12 KiB
JavaScript
'use client';
|
|
"use strict";
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
0 && (module.exports = {
|
|
createFetch: null,
|
|
createFromNextReadableStream: null,
|
|
fetchServerResponse: null,
|
|
urlToUrlWithoutFlightMarker: null
|
|
});
|
|
function _export(target, all) {
|
|
for(var name in all)Object.defineProperty(target, name, {
|
|
enumerable: true,
|
|
get: all[name]
|
|
});
|
|
}
|
|
_export(exports, {
|
|
createFetch: function() {
|
|
return createFetch;
|
|
},
|
|
createFromNextReadableStream: function() {
|
|
return createFromNextReadableStream;
|
|
},
|
|
fetchServerResponse: function() {
|
|
return fetchServerResponse;
|
|
},
|
|
urlToUrlWithoutFlightMarker: function() {
|
|
return urlToUrlWithoutFlightMarker;
|
|
}
|
|
});
|
|
const _approuterheaders = require("../app-router-headers");
|
|
const _appcallserver = require("../../app-call-server");
|
|
const _appfindsourcemapurl = require("../../app-find-source-map-url");
|
|
const _routerreducertypes = require("./router-reducer-types");
|
|
const _flightdatahelpers = require("../../flight-data-helpers");
|
|
const _appbuildid = require("../../app-build-id");
|
|
const _setcachebustingsearchparam = require("./set-cache-busting-search-param");
|
|
// @ts-ignore
|
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
// import { createFromReadableStream } from 'react-server-dom-webpack/client'
|
|
const { createFromReadableStream } = !!process.env.NEXT_RUNTIME ? require('react-server-dom-webpack/client.edge') : require('react-server-dom-webpack/client');
|
|
function urlToUrlWithoutFlightMarker(url) {
|
|
const urlWithoutFlightParameters = new URL(url, location.origin);
|
|
urlWithoutFlightParameters.searchParams.delete(_approuterheaders.NEXT_RSC_UNION_QUERY);
|
|
if (process.env.NODE_ENV === 'production') {
|
|
if (process.env.__NEXT_CONFIG_OUTPUT === 'export' && urlWithoutFlightParameters.pathname.endsWith('.txt')) {
|
|
const { pathname } = urlWithoutFlightParameters;
|
|
const length = pathname.endsWith('/index.txt') ? 10 : 4;
|
|
// Slice off `/index.txt` or `.txt` from the end of the pathname
|
|
urlWithoutFlightParameters.pathname = pathname.slice(0, -length);
|
|
}
|
|
}
|
|
return urlWithoutFlightParameters;
|
|
}
|
|
function doMpaNavigation(url) {
|
|
return {
|
|
flightData: urlToUrlWithoutFlightMarker(url).toString(),
|
|
canonicalUrl: undefined,
|
|
couldBeIntercepted: false,
|
|
prerendered: false,
|
|
postponed: false,
|
|
staleTime: -1
|
|
};
|
|
}
|
|
let abortController = new AbortController();
|
|
if (typeof window !== 'undefined') {
|
|
// Abort any in-flight requests when the page is unloaded, e.g. due to
|
|
// reloading the page or performing hard navigations. This allows us to ignore
|
|
// what would otherwise be a thrown TypeError when the browser cancels the
|
|
// requests.
|
|
window.addEventListener('pagehide', ()=>{
|
|
abortController.abort();
|
|
});
|
|
// Use a fresh AbortController instance on pageshow, e.g. when navigating back
|
|
// and the JavaScript execution context is restored by the browser.
|
|
window.addEventListener('pageshow', ()=>{
|
|
abortController = new AbortController();
|
|
});
|
|
}
|
|
async function fetchServerResponse(url, options) {
|
|
const { flightRouterState, nextUrl, prefetchKind } = options;
|
|
const headers = {
|
|
// Enable flight response
|
|
[_approuterheaders.RSC_HEADER]: '1',
|
|
// Provide the current router state
|
|
[_approuterheaders.NEXT_ROUTER_STATE_TREE_HEADER]: encodeURIComponent(JSON.stringify(flightRouterState))
|
|
};
|
|
/**
|
|
* Three cases:
|
|
* - `prefetchKind` is `undefined`, it means it's a normal navigation, so we want to prefetch the page data fully
|
|
* - `prefetchKind` is `full` - we want to prefetch the whole page so same as above
|
|
* - `prefetchKind` is `auto` - if the page is dynamic, prefetch the page data partially, if static prefetch the page data fully
|
|
*/ if (prefetchKind === _routerreducertypes.PrefetchKind.AUTO) {
|
|
headers[_approuterheaders.NEXT_ROUTER_PREFETCH_HEADER] = '1';
|
|
}
|
|
if (process.env.NODE_ENV === 'development' && options.isHmrRefresh) {
|
|
headers[_approuterheaders.NEXT_HMR_REFRESH_HEADER] = '1';
|
|
}
|
|
if (nextUrl) {
|
|
headers[_approuterheaders.NEXT_URL] = nextUrl;
|
|
}
|
|
try {
|
|
var _res_headers_get;
|
|
// When creating a "temporary" prefetch (the "on-demand" prefetch that gets created on navigation, if one doesn't exist)
|
|
// we send the request with a "high" priority as it's in response to a user interaction that could be blocking a transition.
|
|
// Otherwise, all other prefetches are sent with a "low" priority.
|
|
// We use "auto" for in all other cases to match the existing default, as this function is shared outside of prefetching.
|
|
const fetchPriority = prefetchKind ? prefetchKind === _routerreducertypes.PrefetchKind.TEMPORARY ? 'high' : 'low' : 'auto';
|
|
if (process.env.NODE_ENV === 'production') {
|
|
if (process.env.__NEXT_CONFIG_OUTPUT === 'export') {
|
|
// In "output: export" mode, we can't rely on headers to distinguish
|
|
// between HTML and RSC requests. Instead, we append an extra prefix
|
|
// to the request.
|
|
url = new URL(url);
|
|
if (url.pathname.endsWith('/')) {
|
|
url.pathname += 'index.txt';
|
|
} else {
|
|
url.pathname += '.txt';
|
|
}
|
|
}
|
|
}
|
|
const res = await createFetch(url, headers, fetchPriority, abortController.signal);
|
|
const responseUrl = urlToUrlWithoutFlightMarker(res.url);
|
|
const canonicalUrl = res.redirected ? responseUrl : undefined;
|
|
const contentType = res.headers.get('content-type') || '';
|
|
const interception = !!((_res_headers_get = res.headers.get('vary')) == null ? void 0 : _res_headers_get.includes(_approuterheaders.NEXT_URL));
|
|
const postponed = !!res.headers.get(_approuterheaders.NEXT_DID_POSTPONE_HEADER);
|
|
const staleTimeHeader = res.headers.get(_approuterheaders.NEXT_ROUTER_STALE_TIME_HEADER);
|
|
const staleTime = staleTimeHeader !== null ? parseInt(staleTimeHeader, 10) : -1;
|
|
let isFlightResponse = contentType.startsWith(_approuterheaders.RSC_CONTENT_TYPE_HEADER);
|
|
if (process.env.NODE_ENV === 'production') {
|
|
if (process.env.__NEXT_CONFIG_OUTPUT === 'export') {
|
|
if (!isFlightResponse) {
|
|
isFlightResponse = contentType.startsWith('text/plain');
|
|
}
|
|
}
|
|
}
|
|
// If fetch returns something different than flight response handle it like a mpa navigation
|
|
// If the fetch was not 200, we also handle it like a mpa navigation
|
|
if (!isFlightResponse || !res.ok || !res.body) {
|
|
// in case the original URL came with a hash, preserve it before redirecting to the new URL
|
|
if (url.hash) {
|
|
responseUrl.hash = url.hash;
|
|
}
|
|
return doMpaNavigation(responseUrl.toString());
|
|
}
|
|
// We may navigate to a page that requires a different Webpack runtime.
|
|
// In prod, every page will have the same Webpack runtime.
|
|
// In dev, the Webpack runtime is minimal for each page.
|
|
// We need to ensure the Webpack runtime is updated before executing client-side JS of the new page.
|
|
if (process.env.NODE_ENV !== 'production' && !process.env.TURBOPACK) {
|
|
await require('../react-dev-overlay/app/hot-reloader-client').waitForWebpackRuntimeHotUpdate();
|
|
}
|
|
// Handle the `fetch` readable stream that can be unwrapped by `React.use`.
|
|
const flightStream = postponed ? createUnclosingPrefetchStream(res.body) : res.body;
|
|
const response = await createFromNextReadableStream(flightStream);
|
|
if ((0, _appbuildid.getAppBuildId)() !== response.b) {
|
|
return doMpaNavigation(res.url);
|
|
}
|
|
return {
|
|
flightData: (0, _flightdatahelpers.normalizeFlightData)(response.f),
|
|
canonicalUrl: canonicalUrl,
|
|
couldBeIntercepted: interception,
|
|
prerendered: response.S,
|
|
postponed,
|
|
staleTime
|
|
};
|
|
} catch (err) {
|
|
if (!abortController.signal.aborted) {
|
|
console.error("Failed to fetch RSC payload for " + url + ". Falling back to browser navigation.", err);
|
|
}
|
|
// If fetch fails handle it like a mpa navigation
|
|
// TODO-APP: Add a test for the case where a CORS request fails, e.g. external url redirect coming from the response.
|
|
// See https://github.com/vercel/next.js/issues/43605#issuecomment-1451617521 for a reproduction.
|
|
return {
|
|
flightData: url.toString(),
|
|
canonicalUrl: undefined,
|
|
couldBeIntercepted: false,
|
|
prerendered: false,
|
|
postponed: false,
|
|
staleTime: -1
|
|
};
|
|
}
|
|
}
|
|
function createFetch(url, headers, fetchPriority, signal) {
|
|
const fetchUrl = new URL(url);
|
|
// TODO: In output: "export" mode, the headers do nothing. Omit them (and the
|
|
// cache busting search param) from the request so they're
|
|
// maximally cacheable.
|
|
(0, _setcachebustingsearchparam.setCacheBustingSearchParam)(fetchUrl, headers);
|
|
if (process.env.__NEXT_TEST_MODE && fetchPriority !== null) {
|
|
headers['Next-Test-Fetch-Priority'] = fetchPriority;
|
|
}
|
|
if (process.env.NEXT_DEPLOYMENT_ID) {
|
|
headers['x-deployment-id'] = process.env.NEXT_DEPLOYMENT_ID;
|
|
}
|
|
return fetch(fetchUrl, {
|
|
// Backwards compat for older browsers. `same-origin` is the default in modern browsers.
|
|
credentials: 'same-origin',
|
|
headers,
|
|
priority: fetchPriority || undefined,
|
|
signal
|
|
});
|
|
}
|
|
function createFromNextReadableStream(flightStream) {
|
|
return createFromReadableStream(flightStream, {
|
|
callServer: _appcallserver.callServer,
|
|
findSourceMapURL: _appfindsourcemapurl.findSourceMapURL
|
|
});
|
|
}
|
|
function createUnclosingPrefetchStream(originalFlightStream) {
|
|
// When PPR is enabled, prefetch streams may contain references that never
|
|
// resolve, because that's how we encode dynamic data access. In the decoded
|
|
// object returned by the Flight client, these are reified into hanging
|
|
// promises that suspend during render, which is effectively what we want.
|
|
// The UI resolves when it switches to the dynamic data stream
|
|
// (via useDeferredValue(dynamic, static)).
|
|
//
|
|
// However, the Flight implementation currently errors if the server closes
|
|
// the response before all the references are resolved. As a cheat to work
|
|
// around this, we wrap the original stream in a new stream that never closes,
|
|
// and therefore doesn't error.
|
|
const reader = originalFlightStream.getReader();
|
|
return new ReadableStream({
|
|
async pull (controller) {
|
|
while(true){
|
|
const { done, value } = await reader.read();
|
|
if (!done) {
|
|
// Pass to the target stream and keep consuming the Flight response
|
|
// from the server.
|
|
controller.enqueue(value);
|
|
continue;
|
|
}
|
|
// The server stream has closed. Exit, but intentionally do not close
|
|
// the target stream.
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
|
|
Object.defineProperty(exports.default, '__esModule', { value: true });
|
|
Object.assign(exports.default, exports);
|
|
module.exports = exports.default;
|
|
}
|
|
|
|
//# sourceMappingURL=fetch-server-response.js.map
|