qalita 2.5.4__py3-none-any.whl → 2.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qalita/_frontend/.next/static/_0oyfpC7N2IGDMa04TT9R/_clientMiddlewareManifest.json +6 -0
- qalita/_frontend/.next/static/chunks/236f7e5abd6f09ff.js +4 -0
- qalita/_frontend/.next/static/chunks/30ea11065999f7ac.js +1 -0
- qalita/_frontend/.next/static/chunks/4dd28bc3f722184a.js +2 -0
- qalita/_frontend/.next/static/chunks/711d597b816a80c1.js +1 -0
- qalita/_frontend/.next/static/chunks/7340adf74ff47ec0.js +1 -0
- qalita/_frontend/.next/static/chunks/a6dad97d9634a72d.js.map +1 -0
- qalita/_frontend/.next/static/chunks/{e393fec0d8ba175d.js → ecf559101be0ae12.js} +3 -3
- qalita/_frontend/.next/static/chunks/turbopack-25186fc8e1264445.js +4 -0
- qalita/_frontend/node_modules/@img/sharp-libvips-linux-x64/versions.json +30 -0
- qalita/_frontend/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +30 -0
- qalita/_frontend/node_modules/@next/env/package.json +4 -4
- qalita/_frontend/node_modules/next/dist/build/analyze/index.js +242 -0
- qalita/_frontend/node_modules/next/dist/build/define-env.js +22 -13
- qalita/_frontend/node_modules/next/dist/build/entries.js +5 -5
- qalita/_frontend/node_modules/next/dist/build/generate-routes-manifest.js +91 -0
- qalita/_frontend/node_modules/next/dist/build/index.js +172 -223
- qalita/_frontend/node_modules/next/dist/build/output/log.js +4 -7
- qalita/_frontend/node_modules/next/dist/build/segment-config/app/app-segments.js +23 -99
- qalita/_frontend/node_modules/next/dist/build/segment-config/app/collect-root-param-keys.js +3 -3
- qalita/_frontend/node_modules/next/dist/build/spinner.js +3 -3
- qalita/_frontend/node_modules/next/dist/build/static-paths/app/extract-pathname-route-param-segments-from-loader-tree.js +137 -0
- qalita/_frontend/node_modules/next/dist/build/static-paths/app.js +69 -210
- qalita/_frontend/node_modules/next/dist/build/static-paths/utils.js +83 -11
- qalita/_frontend/node_modules/next/dist/build/swc/index.js +10 -6
- qalita/_frontend/node_modules/next/dist/build/templates/app-page.js +21 -14
- qalita/_frontend/node_modules/next/dist/build/templates/app-route.js +8 -10
- qalita/_frontend/node_modules/next/dist/build/templates/edge-app-route.js +7 -10
- qalita/_frontend/node_modules/next/dist/build/templates/edge-ssr-app.js +16 -20
- qalita/_frontend/node_modules/next/dist/build/templates/edge-ssr.js +20 -14
- qalita/_frontend/node_modules/next/dist/build/templates/edge-wrapper.js +24 -0
- qalita/_frontend/node_modules/next/dist/build/templates/middleware.js +7 -6
- qalita/_frontend/node_modules/next/dist/build/templates/pages-edge-api.js +3 -2
- qalita/_frontend/node_modules/next/dist/build/turbopack-analyze/index.js +116 -0
- qalita/_frontend/node_modules/next/dist/build/turbopack-build/impl.js +2 -1
- qalita/_frontend/node_modules/next/dist/build/type-check.js +7 -8
- qalita/_frontend/node_modules/next/dist/build/utils.js +45 -4
- qalita/_frontend/node_modules/next/dist/build/validate-app-paths.js +242 -0
- qalita/_frontend/node_modules/next/dist/build/webpack/loaders/next-app-loader/index.js +20 -2
- qalita/_frontend/node_modules/next/dist/build/webpack/loaders/next-edge-app-route-loader/index.js +2 -5
- qalita/_frontend/node_modules/next/dist/build/webpack/loaders/next-edge-ssr-loader/index.js +4 -11
- qalita/_frontend/node_modules/next/dist/build/webpack/loaders/next-root-params-loader.js +1 -1
- qalita/_frontend/node_modules/next/dist/build/webpack/plugins/flight-manifest-plugin.js +1 -1
- qalita/_frontend/node_modules/next/dist/build/webpack/plugins/middleware-plugin.js +3 -0
- qalita/_frontend/node_modules/next/dist/build/webpack-build/impl.js +1 -0
- qalita/_frontend/node_modules/next/dist/build/webpack-config.js +35 -3
- qalita/_frontend/node_modules/next/dist/cli/next-test.js +3 -5
- qalita/_frontend/node_modules/next/dist/client/components/app-router-headers.js +5 -0
- qalita/_frontend/node_modules/next/dist/client/components/app-router-instance.js +3 -11
- qalita/_frontend/node_modules/next/dist/client/components/app-router.js +8 -28
- qalita/_frontend/node_modules/next/dist/client/components/navigation-devtools.js +5 -7
- qalita/_frontend/node_modules/next/dist/client/components/navigation.js +4 -3
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/create-initial-router-state.js +3 -22
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/fetch-server-response.js +6 -18
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/ppr-navigations.js +844 -590
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/reducers/hmr-refresh-reducer.js +4 -76
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/reducers/navigate-reducer.js +21 -18
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/reducers/refresh-reducer.js +48 -85
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/reducers/restore-reducer.js +25 -10
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/reducers/server-action-reducer.js +126 -113
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/reducers/server-patch-reducer.js +30 -39
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/router-reducer-types.js +0 -1
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/router-reducer.js +2 -2
- qalita/_frontend/node_modules/next/dist/client/components/segment-cache/cache-map.js +13 -18
- qalita/_frontend/node_modules/next/dist/client/components/segment-cache/cache.js +27 -14
- qalita/_frontend/node_modules/next/dist/client/components/segment-cache/lru.js +3 -1
- qalita/_frontend/node_modules/next/dist/client/components/segment-cache/navigation.js +176 -133
- qalita/_frontend/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +12 -12
- qalita/_frontend/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +12 -12
- qalita/_frontend/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +3 -3
- qalita/_frontend/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +10 -10
- qalita/_frontend/node_modules/next/dist/compiled/react-is/package.json +1 -1
- qalita/_frontend/node_modules/next/dist/lib/build-custom-route.js +4 -4
- qalita/_frontend/node_modules/next/dist/lib/constants.js +0 -5
- qalita/_frontend/node_modules/next/dist/lib/default-transpiled-packages.json +1 -0
- qalita/_frontend/node_modules/next/dist/lib/generate-interception-routes-rewrites.js +0 -1
- qalita/_frontend/node_modules/next/dist/lib/helpers/get-npx-command.js +3 -3
- qalita/_frontend/node_modules/next/dist/lib/inline-static-env.js +1 -1
- qalita/_frontend/node_modules/next/dist/lib/known-edge-safe-packages.json +1 -0
- qalita/_frontend/node_modules/next/dist/lib/metadata/metadata.js +17 -11
- qalita/_frontend/node_modules/next/dist/lib/needs-experimental-react.js +2 -2
- qalita/_frontend/node_modules/next/dist/lib/resolve-build-paths.js +44 -76
- qalita/_frontend/node_modules/next/dist/lib/server-external-packages.jsonc +103 -0
- qalita/_frontend/node_modules/next/dist/lib/static-env.js +6 -6
- qalita/_frontend/node_modules/next/dist/lib/turbopack-warning.js +3 -5
- qalita/_frontend/node_modules/next/dist/lib/typescript/runTypeCheck.js +61 -5
- qalita/_frontend/node_modules/next/dist/lib/typescript/type-paths.js +56 -0
- qalita/_frontend/node_modules/next/dist/lib/typescript/writeConfigurationDefaults.js +6 -17
- qalita/_frontend/node_modules/next/dist/lib/verify-typescript-setup.js +10 -2
- qalita/_frontend/node_modules/next/dist/lib/worker.js +17 -9
- qalita/_frontend/node_modules/next/dist/server/app-render/action-handler.js +113 -77
- qalita/_frontend/node_modules/next/dist/server/app-render/app-render-prerender-utils.js +36 -15
- qalita/_frontend/node_modules/next/dist/server/app-render/app-render-render-utils.js +36 -15
- qalita/_frontend/node_modules/next/dist/server/app-render/app-render-scheduling.js +188 -0
- qalita/_frontend/node_modules/next/dist/server/app-render/app-render.js +804 -748
- qalita/_frontend/node_modules/next/dist/server/app-render/collect-segment-data.js +8 -2
- qalita/_frontend/node_modules/next/dist/server/app-render/create-component-styles-and-scripts.js +1 -1
- qalita/_frontend/node_modules/next/dist/server/app-render/create-error-handler.js +42 -75
- qalita/_frontend/node_modules/next/dist/server/app-render/dynamic-rendering.js +127 -14
- qalita/_frontend/node_modules/next/dist/server/app-render/encryption-utils.js +3 -108
- qalita/_frontend/node_modules/next/dist/server/app-render/encryption.js +4 -3
- qalita/_frontend/node_modules/next/dist/server/app-render/flight-render-result.js +3 -2
- qalita/_frontend/node_modules/next/dist/server/app-render/get-css-inlined-link-tags.js +9 -8
- qalita/_frontend/node_modules/next/dist/server/app-render/get-layer-assets.js +1 -1
- qalita/_frontend/node_modules/next/dist/server/app-render/manifests-singleton.js +257 -0
- qalita/_frontend/node_modules/next/dist/server/app-render/prospective-render-utils.js +25 -8
- qalita/_frontend/node_modules/next/dist/server/app-render/staged-rendering.js +161 -15
- qalita/_frontend/node_modules/next/dist/server/app-render/use-flight-response.js +67 -20
- qalita/_frontend/node_modules/next/dist/server/app-render/walk-tree-with-flight-router-state.js +2 -2
- qalita/_frontend/node_modules/next/dist/server/async-storage/work-store.js +2 -1
- qalita/_frontend/node_modules/next/dist/server/base-server.js +32 -23
- qalita/_frontend/node_modules/next/dist/server/capsize-font-metrics.json +181516 -0
- qalita/_frontend/node_modules/next/dist/server/config-schema.js +8 -1
- qalita/_frontend/node_modules/next/dist/server/config-shared.js +83 -2
- qalita/_frontend/node_modules/next/dist/server/config.js +24 -17
- qalita/_frontend/node_modules/next/dist/server/dev/hot-reloader-rspack.js +171 -0
- qalita/_frontend/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +37 -25
- qalita/_frontend/node_modules/next/dist/server/dev/hot-reloader-types.js +1 -0
- qalita/_frontend/node_modules/next/dist/server/dev/hot-reloader-webpack.js +62 -57
- qalita/_frontend/node_modules/next/dist/server/dev/log-requests.js +11 -3
- qalita/_frontend/node_modules/next/dist/server/dev/messages.js +10 -0
- qalita/_frontend/node_modules/next/dist/server/dev/serialized-errors.js +67 -0
- qalita/_frontend/node_modules/next/dist/server/dev/static-paths-worker.js +10 -0
- qalita/_frontend/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- qalita/_frontend/node_modules/next/dist/server/lib/chrome-devtools-workspace.js +2 -2
- qalita/_frontend/node_modules/next/dist/server/lib/dev-bundler-service.js +6 -10
- qalita/_frontend/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js +4 -4
- qalita/_frontend/node_modules/next/dist/server/lib/lazy-result.js +1 -1
- qalita/_frontend/node_modules/next/dist/server/lib/patch-fetch.js +9 -9
- qalita/_frontend/node_modules/next/dist/server/lib/router-server.js +43 -29
- qalita/_frontend/node_modules/next/dist/server/lib/router-utils/build-prefetch-segment-data-route.js +0 -21
- qalita/_frontend/node_modules/next/dist/server/lib/router-utils/filesystem.js +2 -7
- qalita/_frontend/node_modules/next/dist/server/lib/router-utils/resolve-routes.js +8 -4
- qalita/_frontend/node_modules/next/dist/server/lib/start-server.js +3 -3
- qalita/_frontend/node_modules/next/dist/server/lib/trace/tracer.js +4 -0
- qalita/_frontend/node_modules/next/dist/server/load-components.js +3 -9
- qalita/_frontend/node_modules/next/dist/server/next-server.js +19 -18
- qalita/_frontend/node_modules/next/dist/server/next.js +1 -1
- qalita/_frontend/node_modules/next/dist/server/node-environment-extensions/fast-set-immediate.external.js +570 -0
- qalita/_frontend/node_modules/next/dist/server/node-environment-extensions/utils.js +60 -3
- qalita/_frontend/node_modules/next/dist/server/node-environment.js +1 -0
- qalita/_frontend/node_modules/next/dist/server/patch-error-inspect.js +7 -4
- qalita/_frontend/node_modules/next/dist/server/request/fallback-params.js +23 -66
- qalita/_frontend/node_modules/next/dist/server/route-modules/app-route/module.js +2 -2
- qalita/_frontend/node_modules/next/dist/server/route-modules/pages/pages-handler.js +6 -3
- qalita/_frontend/node_modules/next/dist/server/route-modules/route-module.js +79 -18
- qalita/_frontend/node_modules/next/dist/server/stream-utils/node-web-streams-helper.js +28 -4
- qalita/_frontend/node_modules/next/dist/server/typescript/rules/config.js +50 -6
- qalita/_frontend/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +68 -23
- qalita/_frontend/node_modules/next/dist/server/web/edge-route-module-wrapper.js +7 -5
- qalita/_frontend/node_modules/next/dist/server/web/sandbox/context.js +8 -9
- qalita/_frontend/node_modules/next/dist/server/web/spec-extension/adapters/request-cookies.js +2 -1
- qalita/_frontend/node_modules/next/dist/server/web/spec-extension/revalidate.js +7 -3
- qalita/_frontend/node_modules/next/dist/shared/lib/action-revalidation-kind.js +31 -0
- qalita/_frontend/node_modules/next/dist/shared/lib/constants.js +6 -1
- qalita/_frontend/node_modules/next/dist/shared/lib/deployment-id.js +36 -0
- qalita/_frontend/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
- qalita/_frontend/node_modules/next/dist/shared/lib/hooks-client-context.shared-runtime.js +5 -0
- qalita/_frontend/node_modules/next/dist/shared/lib/router/routes/app.js +122 -0
- qalita/_frontend/node_modules/next/dist/shared/lib/router/utils/get-dynamic-param.js +20 -49
- qalita/_frontend/node_modules/next/dist/shared/lib/router/utils/get-segment-param.js +6 -6
- qalita/_frontend/node_modules/next/dist/shared/lib/router/utils/interception-prefix-from-param-type.js +33 -0
- qalita/_frontend/node_modules/next/dist/shared/lib/router/utils/resolve-param-value.js +116 -0
- qalita/_frontend/node_modules/next/dist/shared/lib/segment-cache/output-export-prefetch-encoding.js +3 -24
- qalita/_frontend/node_modules/next/dist/shared/lib/segment.js +5 -0
- qalita/_frontend/node_modules/next/dist/shared/lib/turbopack/utils.js +6 -5
- qalita/_frontend/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- qalita/_frontend/node_modules/next/dist/telemetry/events/build.js +11 -0
- qalita/_frontend/node_modules/next/dist/telemetry/events/version.js +2 -2
- qalita/_frontend/node_modules/next/package.json +20 -18
- qalita/_frontend/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +2122 -0
- qalita/_frontend/node_modules/typescript/lib/de/diagnosticMessages.generated.json +2122 -0
- qalita/_frontend/node_modules/typescript/lib/es/diagnosticMessages.generated.json +2122 -0
- qalita/_frontend/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +2122 -0
- qalita/_frontend/node_modules/typescript/lib/it/diagnosticMessages.generated.json +2122 -0
- qalita/_frontend/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +2122 -0
- qalita/_frontend/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +2122 -0
- qalita/_frontend/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +2122 -0
- qalita/_frontend/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +2122 -0
- qalita/_frontend/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +2122 -0
- qalita/_frontend/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +2122 -0
- qalita/_frontend/node_modules/typescript/lib/typesMap.json +497 -0
- qalita/_frontend/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +2122 -0
- qalita/_frontend/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +2122 -0
- qalita/_frontend/package.json +3 -3
- qalita/_frontend/server.js +1 -1
- qalita/commands/pack.py +3 -3
- qalita/commands/source.py +1 -1
- qalita/internal/utils.py +11 -7
- qalita/web/app.py +20 -4
- {qalita-2.5.4.dist-info → qalita-2.6.1.dist-info}/METADATA +1 -1
- {qalita-2.5.4.dist-info → qalita-2.6.1.dist-info}/RECORD +197 -168
- qalita/_frontend/.next/static/chunks/023d923a37d494fc.js +0 -1
- qalita/_frontend/.next/static/chunks/247eb132b7f7b574.js +0 -1
- qalita/_frontend/.next/static/chunks/bba035711c7e37a2.js +0 -4
- qalita/_frontend/.next/static/chunks/c903f9580a4b6572.js +0 -2
- qalita/_frontend/.next/static/chunks/cbd55ab9639e1e66.js +0 -1
- qalita/_frontend/.next/static/chunks/turbopack-1ad58da399056f41.js +0 -3
- qalita/_frontend/node_modules/next/dist/build/deployment-id.js +0 -18
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/apply-flight-data.js +0 -53
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/apply-router-state-patch-to-tree.js +0 -105
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/fill-cache-with-new-subtree-data.js +0 -110
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/fill-lazy-items-till-leaf-with-head.js +0 -131
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/handle-segment-mismatch.js +0 -25
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/invalidate-cache-by-router-state.js +0 -32
- qalita/_frontend/node_modules/next/dist/client/components/router-reducer/refetch-inactive-parallel-segments.js +0 -104
- qalita/_frontend/node_modules/next/dist/server/app-render/action-utils.js +0 -90
- qalita/_frontend/node_modules/next/dist/server/normalizers/request/prefetch-rsc.js +0 -31
- /qalita/_frontend/.next/static/{X4_AlYMbCyee-ZVLjCYMg → _0oyfpC7N2IGDMa04TT9R}/_buildManifest.js +0 -0
- /qalita/_frontend/.next/static/{X4_AlYMbCyee-ZVLjCYMg → _0oyfpC7N2IGDMa04TT9R}/_ssgManifest.js +0 -0
- {qalita-2.5.4.dist-info → qalita-2.6.1.dist-info}/WHEEL +0 -0
- {qalita-2.5.4.dist-info → qalita-2.6.1.dist-info}/entry_points.txt +0 -0
- {qalita-2.5.4.dist-info → qalita-2.6.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
5
|
0 && (module.exports = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
FreshnessPolicy: null,
|
|
7
|
+
createInitialCacheNodeForHydration: null,
|
|
8
|
+
isDeferredRsc: null,
|
|
9
|
+
spawnDynamicRequests: null,
|
|
10
|
+
startPPRNavigation: null
|
|
10
11
|
});
|
|
11
12
|
function _export(target, all) {
|
|
12
13
|
for(var name in all)Object.defineProperty(target, name, {
|
|
@@ -15,51 +16,139 @@ function _export(target, all) {
|
|
|
15
16
|
});
|
|
16
17
|
}
|
|
17
18
|
_export(exports, {
|
|
18
|
-
|
|
19
|
-
return
|
|
19
|
+
FreshnessPolicy: function() {
|
|
20
|
+
return FreshnessPolicy;
|
|
20
21
|
},
|
|
21
|
-
|
|
22
|
-
return
|
|
22
|
+
createInitialCacheNodeForHydration: function() {
|
|
23
|
+
return createInitialCacheNodeForHydration;
|
|
24
|
+
},
|
|
25
|
+
isDeferredRsc: function() {
|
|
26
|
+
return isDeferredRsc;
|
|
27
|
+
},
|
|
28
|
+
spawnDynamicRequests: function() {
|
|
29
|
+
return spawnDynamicRequests;
|
|
23
30
|
},
|
|
24
31
|
startPPRNavigation: function() {
|
|
25
32
|
return startPPRNavigation;
|
|
26
|
-
},
|
|
27
|
-
updateCacheNodeOnPopstateRestoration: function() {
|
|
28
|
-
return updateCacheNodeOnPopstateRestoration;
|
|
29
33
|
}
|
|
30
34
|
});
|
|
31
35
|
const _segment = require("../../../shared/lib/segment");
|
|
32
36
|
const _matchsegments = require("../match-segments");
|
|
33
37
|
const _createhreffromurl = require("./create-href-from-url");
|
|
34
38
|
const _createroutercachekey = require("./create-router-cache-key");
|
|
39
|
+
const _fetchserverresponse = require("./fetch-server-response");
|
|
40
|
+
const _useactionqueue = require("../use-action-queue");
|
|
41
|
+
const _routerreducertypes = require("./router-reducer-types");
|
|
35
42
|
const _isnavigatingtonewrootlayout = require("./is-navigating-to-new-root-layout");
|
|
36
43
|
const _navigatereducer = require("./reducers/navigate-reducer");
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
const _navigation = require("../segment-cache/navigation");
|
|
45
|
+
var FreshnessPolicy = /*#__PURE__*/ function(FreshnessPolicy) {
|
|
46
|
+
FreshnessPolicy[FreshnessPolicy["Default"] = 0] = "Default";
|
|
47
|
+
FreshnessPolicy[FreshnessPolicy["Hydration"] = 1] = "Hydration";
|
|
48
|
+
FreshnessPolicy[FreshnessPolicy["HistoryTraversal"] = 2] = "HistoryTraversal";
|
|
49
|
+
FreshnessPolicy[FreshnessPolicy["RefreshAll"] = 3] = "RefreshAll";
|
|
50
|
+
FreshnessPolicy[FreshnessPolicy["HMRRefresh"] = 4] = "HMRRefresh";
|
|
51
|
+
return FreshnessPolicy;
|
|
52
|
+
}({});
|
|
53
|
+
const noop = ()=>{};
|
|
54
|
+
function createInitialCacheNodeForHydration(navigatedAt, initialTree, seedData, seedHead) {
|
|
55
|
+
// Create the initial cache node tree, using the data embedded into the
|
|
56
|
+
// HTML document.
|
|
57
|
+
const accumulation = {
|
|
58
|
+
scrollableSegments: null,
|
|
59
|
+
separateRefreshUrls: null
|
|
60
|
+
};
|
|
61
|
+
const task = createCacheNodeOnNavigation(navigatedAt, initialTree, undefined, 1, seedData, seedHead, null, null, false, null, null, false, accumulation);
|
|
62
|
+
// NOTE: We intentionally don't check if any data needs to be fetched from the
|
|
63
|
+
// server. We assume the initial hydration payload is sufficient to render
|
|
64
|
+
// the page.
|
|
65
|
+
//
|
|
66
|
+
// The completeness of the initial data is an important property that we rely
|
|
67
|
+
// on as a last-ditch mechanism for recovering the app; we must always be able
|
|
68
|
+
// to reload a fresh HTML document to get to a consistent state.
|
|
69
|
+
//
|
|
70
|
+
// In the future, there may be cases where the server intentionally sends
|
|
71
|
+
// partial data and expects the client to fill in the rest, in which case this
|
|
72
|
+
// logic may change. (There already is a similar case where the server sends
|
|
73
|
+
// _no_ hydration data in the HTML document at all, and the client fetches it
|
|
74
|
+
// separately, but that's different because we still end up hydrating with a
|
|
75
|
+
// complete tree.)
|
|
76
|
+
return task.node;
|
|
46
77
|
}
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
78
|
+
function startPPRNavigation(navigatedAt, oldUrl, oldCacheNode, oldRouterState, newRouterState, freshness, seedData, seedHead, prefetchData, prefetchHead, isPrefetchHeadPartial, isSamePageNavigation, accumulation) {
|
|
79
|
+
const didFindRootLayout = false;
|
|
80
|
+
const parentNeedsDynamicRequest = false;
|
|
81
|
+
const parentRefreshUrl = null;
|
|
82
|
+
return updateCacheNodeOnNavigation(navigatedAt, oldUrl, oldCacheNode !== null ? oldCacheNode : undefined, oldRouterState, newRouterState, freshness, didFindRootLayout, seedData, seedHead, prefetchData, prefetchHead, isPrefetchHeadPartial, isSamePageNavigation, null, null, parentNeedsDynamicRequest, parentRefreshUrl, accumulation);
|
|
83
|
+
}
|
|
84
|
+
function updateCacheNodeOnNavigation(navigatedAt, oldUrl, oldCacheNode, oldRouterState, newRouterState, freshness, didFindRootLayout, seedData, seedHead, prefetchData, prefetchHead, isPrefetchHeadPartial, isSamePageNavigation, parentSegmentPath, parentParallelRouteKey, parentNeedsDynamicRequest, parentRefreshUrl, accumulation) {
|
|
85
|
+
// Check if this segment matches the one in the previous route.
|
|
86
|
+
const oldSegment = oldRouterState[0];
|
|
87
|
+
const newSegment = newRouterState[0];
|
|
88
|
+
if (!(0, _matchsegments.matchSegment)(newSegment, oldSegment)) {
|
|
89
|
+
// This segment does not match the previous route. We're now entering the
|
|
90
|
+
// new part of the target route. Switch to the "create" path.
|
|
91
|
+
if (// Check if the route tree changed before we reached a layout. (The
|
|
92
|
+
// highest-level layout in a route tree is referred to as the "root"
|
|
93
|
+
// layout.) This could mean that we're navigating between two different
|
|
94
|
+
// root layouts. When this happens, we perform a full-page (MPA-style)
|
|
95
|
+
// navigation.
|
|
96
|
+
//
|
|
97
|
+
// However, the algorithm for deciding where to start rendering a route
|
|
98
|
+
// (i.e. the one performed in order to reach this function) is stricter
|
|
99
|
+
// than the one used to detect a change in the root layout. So just
|
|
100
|
+
// because we're re-rendering a segment outside of the root layout does
|
|
101
|
+
// not mean we should trigger a full-page navigation.
|
|
102
|
+
//
|
|
103
|
+
// Specifically, we handle dynamic parameters differently: two segments
|
|
104
|
+
// are considered the same even if their parameter values are different.
|
|
105
|
+
//
|
|
106
|
+
// Refer to isNavigatingToNewRootLayout for details.
|
|
107
|
+
//
|
|
108
|
+
// Note that we only have to perform this extra traversal if we didn't
|
|
109
|
+
// already discover a root layout in the part of the tree that is
|
|
110
|
+
// unchanged. We also only need to compare the subtree that is not
|
|
111
|
+
// shared. In the common case, this branch is skipped completely.
|
|
112
|
+
!didFindRootLayout && (0, _isnavigatingtonewrootlayout.isNavigatingToNewRootLayout)(oldRouterState, newRouterState) || // The global Not Found route (app/global-not-found.tsx) is a special
|
|
113
|
+
// case, because it acts like a root layout, but in the router tree, it
|
|
114
|
+
// is rendered in the same position as app/layout.tsx.
|
|
115
|
+
//
|
|
116
|
+
// Any navigation to the global Not Found route should trigger a
|
|
117
|
+
// full-page navigation.
|
|
118
|
+
//
|
|
119
|
+
// TODO: We should probably model this by changing the key of the root
|
|
120
|
+
// segment when this happens. Then the root layout check would work
|
|
121
|
+
// as expected, without a special case.
|
|
122
|
+
newSegment === _segment.NOT_FOUND_SEGMENT_KEY) {
|
|
123
|
+
return null;
|
|
60
124
|
}
|
|
125
|
+
if (parentSegmentPath === null || parentParallelRouteKey === null) {
|
|
126
|
+
// The root should never mismatch. If it does, it suggests an internal
|
|
127
|
+
// Next.js error, or a malformed server response. Trigger a full-
|
|
128
|
+
// page navigation.
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
return createCacheNodeOnNavigation(navigatedAt, newRouterState, oldCacheNode, freshness, seedData, seedHead, prefetchData, prefetchHead, isPrefetchHeadPartial, parentSegmentPath, parentParallelRouteKey, parentNeedsDynamicRequest, accumulation);
|
|
61
132
|
}
|
|
62
|
-
|
|
133
|
+
// TODO: The segment paths are tracked so that LayoutRouter knows which
|
|
134
|
+
// segments to scroll to after a navigation. But we should just mark this
|
|
135
|
+
// information on the CacheNode directly. It used to be necessary to do this
|
|
136
|
+
// separately because CacheNodes were created lazily during render, not when
|
|
137
|
+
// rather than when creating the route tree.
|
|
138
|
+
const segmentPath = parentParallelRouteKey !== null && parentSegmentPath !== null ? parentSegmentPath.concat([
|
|
139
|
+
parentParallelRouteKey,
|
|
140
|
+
newSegment
|
|
141
|
+
]) : [];
|
|
142
|
+
const newRouterStateChildren = newRouterState[1];
|
|
143
|
+
const oldRouterStateChildren = oldRouterState[1];
|
|
144
|
+
const seedDataChildren = seedData !== null ? seedData[1] : null;
|
|
145
|
+
const prefetchDataChildren = prefetchData !== null ? prefetchData[1] : null;
|
|
146
|
+
// We're currently traversing the part of the tree that was also part of
|
|
147
|
+
// the previous route. If we discover a root layout, then we don't need to
|
|
148
|
+
// trigger an MPA navigation.
|
|
149
|
+
const isRootLayout = newRouterState[4] === true;
|
|
150
|
+
const childDidFindRootLayout = didFindRootLayout || isRootLayout;
|
|
151
|
+
const oldParallelRoutes = oldCacheNode !== undefined ? oldCacheNode.parallelRoutes : undefined;
|
|
63
152
|
// Clone the current set of segment children, even if they aren't active in
|
|
64
153
|
// the new tree.
|
|
65
154
|
// TODO: We currently retain all the inactive segments indefinitely, until
|
|
@@ -71,7 +160,84 @@ function updateCacheNodeOnNavigation(navigatedAt, oldUrl, oldCacheNode, oldRoute
|
|
|
71
160
|
// leak. We should figure out a better model for the lifetime of inactive
|
|
72
161
|
// segments, so we can maintain instant back/forward navigations without
|
|
73
162
|
// leaking memory indefinitely.
|
|
74
|
-
|
|
163
|
+
let shouldDropSiblingCaches = false;
|
|
164
|
+
let shouldRefreshDynamicData = false;
|
|
165
|
+
switch(freshness){
|
|
166
|
+
case 0:
|
|
167
|
+
case 2:
|
|
168
|
+
case 1:
|
|
169
|
+
// We should never drop dynamic data in shared layouts, except during
|
|
170
|
+
// a refresh.
|
|
171
|
+
shouldDropSiblingCaches = false;
|
|
172
|
+
shouldRefreshDynamicData = false;
|
|
173
|
+
break;
|
|
174
|
+
case 3:
|
|
175
|
+
case 4:
|
|
176
|
+
shouldDropSiblingCaches = true;
|
|
177
|
+
shouldRefreshDynamicData = true;
|
|
178
|
+
break;
|
|
179
|
+
default:
|
|
180
|
+
freshness;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
const newParallelRoutes = new Map(shouldDropSiblingCaches ? undefined : oldParallelRoutes);
|
|
184
|
+
// TODO: We're not consistent about how we do this check. Some places
|
|
185
|
+
// check if the segment starts with PAGE_SEGMENT_KEY, but most seem to
|
|
186
|
+
// check if there any any children, which is why I'm doing it here. We
|
|
187
|
+
// should probably encode an empty children set as `null` though. Either
|
|
188
|
+
// way, we should update all the checks to be consistent.
|
|
189
|
+
const isLeafSegment = Object.keys(newRouterStateChildren).length === 0;
|
|
190
|
+
// Get the data for this segment. Since it was part of the previous route,
|
|
191
|
+
// usually we just clone the data from the old CacheNode. However, during a
|
|
192
|
+
// refresh or a revalidation, there won't be any existing CacheNode. So we
|
|
193
|
+
// may need to consult the prefetch cache, like we would for a new segment.
|
|
194
|
+
let newCacheNode;
|
|
195
|
+
let needsDynamicRequest;
|
|
196
|
+
if (oldCacheNode !== undefined && !shouldRefreshDynamicData && // During a same-page navigation, we always refetch the page segments
|
|
197
|
+
!(isLeafSegment && isSamePageNavigation)) {
|
|
198
|
+
// Reuse the existing CacheNode
|
|
199
|
+
const dropPrefetchRsc = false;
|
|
200
|
+
newCacheNode = reuseDynamicCacheNode(dropPrefetchRsc, oldCacheNode, newParallelRoutes);
|
|
201
|
+
needsDynamicRequest = false;
|
|
202
|
+
} else if (seedData !== null && seedData[0] !== null) {
|
|
203
|
+
// If this navigation was the result of an action, then check if the
|
|
204
|
+
// server sent back data in the action response. We should favor using
|
|
205
|
+
// that, rather than performing a separate request. This is both better
|
|
206
|
+
// for performance and it's more likely to be consistent with any
|
|
207
|
+
// writes that were just performed by the action, compared to a
|
|
208
|
+
// separate request.
|
|
209
|
+
const seedRsc = seedData[0];
|
|
210
|
+
const seedLoading = seedData[2];
|
|
211
|
+
const isSeedRscPartial = false;
|
|
212
|
+
const isSeedHeadPartial = seedHead === null;
|
|
213
|
+
newCacheNode = readCacheNodeFromSeedData(seedRsc, seedLoading, isSeedRscPartial, seedHead, isSeedHeadPartial, isLeafSegment, newParallelRoutes, navigatedAt);
|
|
214
|
+
needsDynamicRequest = isLeafSegment && isSeedHeadPartial;
|
|
215
|
+
} else if (prefetchData !== null) {
|
|
216
|
+
// Consult the prefetch cache.
|
|
217
|
+
const prefetchRsc = prefetchData[0];
|
|
218
|
+
const prefetchLoading = prefetchData[2];
|
|
219
|
+
const isPrefetchRSCPartial = prefetchData[3];
|
|
220
|
+
newCacheNode = readCacheNodeFromSeedData(prefetchRsc, prefetchLoading, isPrefetchRSCPartial, prefetchHead, isPrefetchHeadPartial, isLeafSegment, newParallelRoutes, navigatedAt);
|
|
221
|
+
needsDynamicRequest = isPrefetchRSCPartial || isLeafSegment && isPrefetchHeadPartial;
|
|
222
|
+
} else {
|
|
223
|
+
// Spawn a request to fetch new data from the server.
|
|
224
|
+
newCacheNode = spawnNewCacheNode(newParallelRoutes, isLeafSegment, navigatedAt, freshness);
|
|
225
|
+
needsDynamicRequest = true;
|
|
226
|
+
}
|
|
227
|
+
// During a refresh navigation, there's a special case that happens when
|
|
228
|
+
// entering a "default" slot. The default slot may not be part of the
|
|
229
|
+
// current route; it may have been reused from an older route. If so,
|
|
230
|
+
// we need to fetch its data from the old route's URL rather than current
|
|
231
|
+
// route's URL. Keep track of this as we traverse the tree.
|
|
232
|
+
const href = newRouterState[2];
|
|
233
|
+
const refreshUrl = typeof href === 'string' && newRouterState[3] === 'refresh' ? // refresh URL as we continue traversing the tree.
|
|
234
|
+
href : parentRefreshUrl;
|
|
235
|
+
// If this segment itself needs to fetch new data from the server, then by
|
|
236
|
+
// definition it is being refreshed. Track its refresh URL so we know which
|
|
237
|
+
// URL to request the data from.
|
|
238
|
+
if (needsDynamicRequest && refreshUrl !== null) {
|
|
239
|
+
accumulateRefreshUrl(accumulation, refreshUrl);
|
|
240
|
+
}
|
|
75
241
|
// As we diff the trees, we may sometimes modify (copy-on-write, not mutate)
|
|
76
242
|
// the Route Tree that was returned by the server — for example, in the case
|
|
77
243
|
// of default parallel routes, we preserve the currently active segment. To
|
|
@@ -88,302 +254,266 @@ function updateCacheNodeOnNavigation(navigatedAt, oldUrl, oldCacheNode, oldRoute
|
|
|
88
254
|
//
|
|
89
255
|
// This starts off as `false`, and is set to `true` if any of the child
|
|
90
256
|
// routes requires a dynamic request.
|
|
91
|
-
let
|
|
257
|
+
let childNeedsDynamicRequest = false;
|
|
92
258
|
// As we traverse the children, we'll construct a FlightRouterState that can
|
|
93
259
|
// be sent to the server to request the dynamic data. If it turns out that
|
|
94
|
-
// nothing in the subtree is dynamic (i.e.
|
|
95
|
-
// end), then this will be discarded.
|
|
260
|
+
// nothing in the subtree is dynamic (i.e. childNeedsDynamicRequest is false
|
|
261
|
+
// at the end), then this will be discarded.
|
|
96
262
|
// TODO: We can probably optimize the format of this data structure to only
|
|
97
263
|
// include paths that are dynamic. Instead of reusing the
|
|
98
264
|
// FlightRouterState type.
|
|
99
265
|
let dynamicRequestTreeChildren = {};
|
|
100
266
|
for(let parallelRouteKey in newRouterStateChildren){
|
|
101
|
-
|
|
267
|
+
let newRouterStateChild = newRouterStateChildren[parallelRouteKey];
|
|
102
268
|
const oldRouterStateChild = oldRouterStateChildren[parallelRouteKey];
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
]
|
|
269
|
+
if (oldRouterStateChild === undefined) {
|
|
270
|
+
// This should never happen, but if it does, it suggests a malformed
|
|
271
|
+
// server response. Trigger a full-page navigation.
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const oldSegmentMapChild = oldParallelRoutes !== undefined ? oldParallelRoutes.get(parallelRouteKey) : undefined;
|
|
275
|
+
let seedDataChild = seedDataChildren !== null ? seedDataChildren[parallelRouteKey] : null;
|
|
276
|
+
let prefetchDataChild = prefetchDataChildren !== null ? prefetchDataChildren[parallelRouteKey] : null;
|
|
277
|
+
let newSegmentChild = newRouterStateChild[0];
|
|
278
|
+
let seedHeadChild = seedHead;
|
|
279
|
+
let prefetchHeadChild = prefetchHead;
|
|
280
|
+
let isPrefetchHeadPartialChild = isPrefetchHeadPartial;
|
|
281
|
+
if (// Skip this branch during a history traversal. We restore the tree that
|
|
282
|
+
// was stashed in the history entry as-is.
|
|
283
|
+
freshness !== 2 && newSegmentChild === _segment.DEFAULT_SEGMENT_KEY) {
|
|
284
|
+
// This is a "default" segment. These are never sent by the server during
|
|
285
|
+
// a soft navigation; instead, the client reuses whatever segment was
|
|
286
|
+
// already active in that slot on the previous route.
|
|
287
|
+
newRouterStateChild = reuseActiveSegmentInDefaultSlot(oldUrl, oldRouterStateChild);
|
|
288
|
+
newSegmentChild = newRouterStateChild[0];
|
|
289
|
+
// Since we're switching to a different route tree, these are no
|
|
290
|
+
// longer valid, because they correspond to the outer tree.
|
|
291
|
+
seedDataChild = null;
|
|
292
|
+
seedHeadChild = null;
|
|
293
|
+
prefetchDataChild = null;
|
|
294
|
+
prefetchHeadChild = null;
|
|
295
|
+
isPrefetchHeadPartialChild = false;
|
|
296
|
+
}
|
|
110
297
|
const newSegmentKeyChild = (0, _createroutercachekey.createRouterCacheKey)(newSegmentChild);
|
|
111
|
-
const oldSegmentChild = oldRouterStateChild !== undefined ? oldRouterStateChild[0] : undefined;
|
|
112
298
|
const oldCacheNodeChild = oldSegmentMapChild !== undefined ? oldSegmentMapChild.get(newSegmentKeyChild) : undefined;
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
// during a client navigation — but not for initial render. The server
|
|
120
|
-
// leaves it to the client to account for this. So we need to handle
|
|
121
|
-
// it here.
|
|
122
|
-
if (oldRouterStateChild !== undefined) {
|
|
123
|
-
// Reuse the existing Router State for this segment. We spawn a "task"
|
|
124
|
-
// just to keep track of the updated router state; unlike most, it's
|
|
125
|
-
// already fulfilled and won't be affected by the dynamic response.
|
|
126
|
-
taskChild = reuseActiveSegmentInDefaultSlot(oldUrl, oldRouterStateChild);
|
|
127
|
-
} else {
|
|
128
|
-
// There's no currently active segment. Switch to the "create" path.
|
|
129
|
-
taskChild = beginRenderingNewRouteTree(navigatedAt, oldRouterStateChild, newRouterStateChild, oldCacheNodeChild, didFindRootLayout, prefetchDataChild !== undefined ? prefetchDataChild : null, prefetchHead, isPrefetchHeadPartial, newSegmentPathChild, scrollableSegmentsResult);
|
|
130
|
-
}
|
|
131
|
-
} else if (isSamePageNavigation && // Check if this is a page segment.
|
|
132
|
-
// TODO: We're not consistent about how we do this check. Some places
|
|
133
|
-
// check if the segment starts with PAGE_SEGMENT_KEY, but most seem to
|
|
134
|
-
// check if there any any children, which is why I'm doing it here. We
|
|
135
|
-
// should probably encode an empty children set as `null` though. Either
|
|
136
|
-
// way, we should update all the checks to be consistent.
|
|
137
|
-
Object.keys(newRouterStateChild[1]).length === 0) {
|
|
138
|
-
// We special case navigations to the exact same URL as the current
|
|
139
|
-
// location. It's a common UI pattern for apps to refresh when you click a
|
|
140
|
-
// link to the current page. So when this happens, we refresh the dynamic
|
|
141
|
-
// data in the page segments.
|
|
142
|
-
//
|
|
143
|
-
// Note that this does not apply if the any part of the hash or search
|
|
144
|
-
// query has changed. This might feel a bit weird but it makes more sense
|
|
145
|
-
// when you consider that the way to trigger this behavior is to click
|
|
146
|
-
// the same link multiple times.
|
|
147
|
-
//
|
|
148
|
-
// TODO: We should probably refresh the *entire* route when this case
|
|
149
|
-
// occurs, not just the page segments. Essentially treating it the same as
|
|
150
|
-
// a refresh() triggered by an action, which is the more explicit way of
|
|
151
|
-
// modeling the UI pattern described above.
|
|
152
|
-
//
|
|
153
|
-
// Also note that this only refreshes the dynamic data, not static/
|
|
154
|
-
// cached data. If the page segment is fully static and prefetched, the
|
|
155
|
-
// request is skipped. (This is also how refresh() works.)
|
|
156
|
-
taskChild = beginRenderingNewRouteTree(navigatedAt, oldRouterStateChild, newRouterStateChild, oldCacheNodeChild, didFindRootLayout, prefetchDataChild !== undefined ? prefetchDataChild : null, prefetchHead, isPrefetchHeadPartial, newSegmentPathChild, scrollableSegmentsResult);
|
|
157
|
-
} else if (oldRouterStateChild !== undefined && oldSegmentChild !== undefined && (0, _matchsegments.matchSegment)(newSegmentChild, oldSegmentChild)) {
|
|
158
|
-
if (oldCacheNodeChild !== undefined && oldRouterStateChild !== undefined) {
|
|
159
|
-
// This segment exists in both the old and new trees. Recursively update
|
|
160
|
-
// the children.
|
|
161
|
-
taskChild = updateCacheNodeOnNavigation(navigatedAt, oldUrl, oldCacheNodeChild, oldRouterStateChild, newRouterStateChild, didFindRootLayout, prefetchDataChild, prefetchHead, isPrefetchHeadPartial, isSamePageNavigation, newSegmentPathChild, scrollableSegmentsResult);
|
|
162
|
-
} else {
|
|
163
|
-
// There's no existing Cache Node for this segment. Switch to the
|
|
164
|
-
// "create" path.
|
|
165
|
-
taskChild = beginRenderingNewRouteTree(navigatedAt, oldRouterStateChild, newRouterStateChild, oldCacheNodeChild, didFindRootLayout, prefetchDataChild !== undefined ? prefetchDataChild : null, prefetchHead, isPrefetchHeadPartial, newSegmentPathChild, scrollableSegmentsResult);
|
|
166
|
-
}
|
|
167
|
-
} else {
|
|
168
|
-
// This is a new tree. Switch to the "create" path.
|
|
169
|
-
taskChild = beginRenderingNewRouteTree(navigatedAt, oldRouterStateChild, newRouterStateChild, oldCacheNodeChild, didFindRootLayout, prefetchDataChild !== undefined ? prefetchDataChild : null, prefetchHead, isPrefetchHeadPartial, newSegmentPathChild, scrollableSegmentsResult);
|
|
299
|
+
const taskChild = updateCacheNodeOnNavigation(navigatedAt, oldUrl, oldCacheNodeChild, oldRouterStateChild, newRouterStateChild, freshness, childDidFindRootLayout, seedDataChild ?? null, seedHeadChild, prefetchDataChild ?? null, prefetchHeadChild, isPrefetchHeadPartialChild, isSamePageNavigation, segmentPath, parallelRouteKey, parentNeedsDynamicRequest || needsDynamicRequest, refreshUrl, accumulation);
|
|
300
|
+
if (taskChild === null) {
|
|
301
|
+
// One of the child tasks discovered a change to the root layout.
|
|
302
|
+
// Immediately unwind from this recursive traversal. This will trigger a
|
|
303
|
+
// full-page navigation.
|
|
304
|
+
return null;
|
|
170
305
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
patchedRouterStateChildren[parallelRouteKey] = taskChildRoute;
|
|
193
|
-
const dynamicRequestTreeChild = taskChild.dynamicRequestTree;
|
|
194
|
-
if (dynamicRequestTreeChild !== null) {
|
|
195
|
-
// Something in the child tree is dynamic.
|
|
196
|
-
needsDynamicRequest = true;
|
|
197
|
-
dynamicRequestTreeChildren[parallelRouteKey] = dynamicRequestTreeChild;
|
|
198
|
-
} else {
|
|
199
|
-
dynamicRequestTreeChildren[parallelRouteKey] = taskChildRoute;
|
|
200
|
-
}
|
|
306
|
+
// Recursively propagate up the child tasks.
|
|
307
|
+
if (taskChildren === null) {
|
|
308
|
+
taskChildren = new Map();
|
|
309
|
+
}
|
|
310
|
+
taskChildren.set(parallelRouteKey, taskChild);
|
|
311
|
+
const newCacheNodeChild = taskChild.node;
|
|
312
|
+
if (newCacheNodeChild !== null) {
|
|
313
|
+
const newSegmentMapChild = new Map(shouldDropSiblingCaches ? undefined : oldSegmentMapChild);
|
|
314
|
+
newSegmentMapChild.set(newSegmentKeyChild, newCacheNodeChild);
|
|
315
|
+
newParallelRoutes.set(parallelRouteKey, newSegmentMapChild);
|
|
316
|
+
}
|
|
317
|
+
// The child tree's route state may be different from the prefetched
|
|
318
|
+
// route sent by the server. We need to clone it as we traverse back up
|
|
319
|
+
// the tree.
|
|
320
|
+
const taskChildRoute = taskChild.route;
|
|
321
|
+
patchedRouterStateChildren[parallelRouteKey] = taskChildRoute;
|
|
322
|
+
const dynamicRequestTreeChild = taskChild.dynamicRequestTree;
|
|
323
|
+
if (dynamicRequestTreeChild !== null) {
|
|
324
|
+
// Something in the child tree is dynamic.
|
|
325
|
+
childNeedsDynamicRequest = true;
|
|
326
|
+
dynamicRequestTreeChildren[parallelRouteKey] = dynamicRequestTreeChild;
|
|
201
327
|
} else {
|
|
202
|
-
|
|
203
|
-
patchedRouterStateChildren[parallelRouteKey] = newRouterStateChild;
|
|
204
|
-
dynamicRequestTreeChildren[parallelRouteKey] = newRouterStateChild;
|
|
328
|
+
dynamicRequestTreeChildren[parallelRouteKey] = taskChildRoute;
|
|
205
329
|
}
|
|
206
330
|
}
|
|
207
|
-
if (taskChildren === null) {
|
|
208
|
-
// No new tasks were spawned.
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
const newCacheNode = {
|
|
212
|
-
lazyData: null,
|
|
213
|
-
rsc: oldCacheNode.rsc,
|
|
214
|
-
// We intentionally aren't updating the prefetchRsc field, since this node
|
|
215
|
-
// is already part of the current tree, because it would be weird for
|
|
216
|
-
// prefetch data to be newer than the final data. It probably won't ever be
|
|
217
|
-
// observable anyway, but it could happen if the segment is unmounted then
|
|
218
|
-
// mounted again, because LayoutRouter will momentarily switch to rendering
|
|
219
|
-
// prefetchRsc, via useDeferredValue.
|
|
220
|
-
prefetchRsc: oldCacheNode.prefetchRsc,
|
|
221
|
-
head: oldCacheNode.head,
|
|
222
|
-
prefetchHead: oldCacheNode.prefetchHead,
|
|
223
|
-
loading: oldCacheNode.loading,
|
|
224
|
-
// Everything is cloned except for the children, which we computed above.
|
|
225
|
-
parallelRoutes: prefetchParallelRoutes,
|
|
226
|
-
navigatedAt
|
|
227
|
-
};
|
|
228
331
|
return {
|
|
229
|
-
|
|
332
|
+
status: needsDynamicRequest ? 0 : 1,
|
|
230
333
|
route: patchRouterStateWithNewChildren(newRouterState, patchedRouterStateChildren),
|
|
231
334
|
node: newCacheNode,
|
|
232
|
-
dynamicRequestTree:
|
|
335
|
+
dynamicRequestTree: createDynamicRequestTree(newRouterState, dynamicRequestTreeChildren, needsDynamicRequest, childNeedsDynamicRequest, parentNeedsDynamicRequest),
|
|
336
|
+
refreshUrl,
|
|
233
337
|
children: taskChildren
|
|
234
338
|
};
|
|
235
339
|
}
|
|
236
|
-
function
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// Refer to isNavigatingToNewRootLayout for details.
|
|
253
|
-
//
|
|
254
|
-
// Note that we only have to perform this extra traversal if we didn't
|
|
255
|
-
// already discover a root layout in the part of the tree that is unchanged.
|
|
256
|
-
// In the common case, this branch is skipped completely.
|
|
257
|
-
if (oldRouterState === undefined || (0, _isnavigatingtonewrootlayout.isNavigatingToNewRootLayout)(oldRouterState, newRouterState)) {
|
|
258
|
-
// The root layout changed. Perform a full-page navigation.
|
|
259
|
-
return MPA_NAVIGATION_TASK;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
return createCacheNodeOnNavigation(navigatedAt, newRouterState, existingCacheNode, prefetchData, possiblyPartialPrefetchHead, isPrefetchHeadPartial, segmentPath, scrollableSegmentsResult);
|
|
263
|
-
}
|
|
264
|
-
function createCacheNodeOnNavigation(navigatedAt, routerState, existingCacheNode, prefetchData, possiblyPartialPrefetchHead, isPrefetchHeadPartial, segmentPath, scrollableSegmentsResult) {
|
|
265
|
-
// Same traversal as updateCacheNodeNavigation, but we switch to this path
|
|
266
|
-
// once we reach the part of the tree that was not in the previous route. We
|
|
267
|
-
// don't need to diff against the old tree, we just need to create a new one.
|
|
268
|
-
// The head is assigned to every leaf segment delivered by the server. Based
|
|
269
|
-
// on corresponding logic in fill-lazy-items-till-leaf-with-head.ts
|
|
270
|
-
const routerStateChildren = routerState[1];
|
|
271
|
-
const isLeafSegment = Object.keys(routerStateChildren).length === 0;
|
|
272
|
-
// Even we're rendering inside the "new" part of the target tree, we may have
|
|
273
|
-
// a locally cached segment that we can reuse. This may come from either 1)
|
|
274
|
-
// the CacheNode tree, which lives in React state and is populated by previous
|
|
275
|
-
// navigations; or 2) the prefetch cache, which is a separate cache that is
|
|
276
|
-
// populated by prefetches.
|
|
277
|
-
let rsc;
|
|
278
|
-
let loading;
|
|
279
|
-
let head;
|
|
280
|
-
let cacheNodeNavigatedAt;
|
|
281
|
-
if (existingCacheNode !== undefined && // DYNAMIC_STALETIME_MS defaults to 0, but it can be increased using
|
|
282
|
-
// the experimental.staleTimes.dynamic config. When set, we'll avoid
|
|
283
|
-
// refetching dynamic data if it was fetched within the given threshold.
|
|
284
|
-
existingCacheNode.navigatedAt + _navigatereducer.DYNAMIC_STALETIME_MS > navigatedAt) {
|
|
285
|
-
// We have an existing CacheNode for this segment, and it's not stale. We
|
|
286
|
-
// should reuse it rather than request a new one.
|
|
287
|
-
rsc = existingCacheNode.rsc;
|
|
288
|
-
loading = existingCacheNode.loading;
|
|
289
|
-
head = existingCacheNode.head;
|
|
290
|
-
// Don't update the navigatedAt timestamp, since we're reusing stale data.
|
|
291
|
-
cacheNodeNavigatedAt = existingCacheNode.navigatedAt;
|
|
292
|
-
} else if (prefetchData !== null) {
|
|
293
|
-
// There's no existing CacheNode for this segment, but we do have prefetch
|
|
294
|
-
// data. If the prefetch data is fully static (i.e. does not contain any
|
|
295
|
-
// dynamic holes), we don't need to request it from the server.
|
|
296
|
-
rsc = prefetchData[0];
|
|
297
|
-
loading = prefetchData[2];
|
|
298
|
-
head = isLeafSegment ? possiblyPartialPrefetchHead : null;
|
|
299
|
-
// Even though we're accessing the data from the prefetch cache, this is
|
|
300
|
-
// conceptually a new segment, not a reused one. So we should update the
|
|
301
|
-
// navigatedAt timestamp.
|
|
302
|
-
cacheNodeNavigatedAt = navigatedAt;
|
|
303
|
-
const isPrefetchRscPartial = prefetchData[3];
|
|
304
|
-
if (// Check if the segment data is partial
|
|
305
|
-
isPrefetchRscPartial || // Check if the head is partial (only relevant if this is a leaf segment)
|
|
306
|
-
isPrefetchHeadPartial && isLeafSegment) {
|
|
307
|
-
// We only have partial data from this segment. Like missing segments, we
|
|
308
|
-
// must request the full data from the server.
|
|
309
|
-
return spawnPendingTask(navigatedAt, routerState, prefetchData, possiblyPartialPrefetchHead, isPrefetchHeadPartial, segmentPath, scrollableSegmentsResult);
|
|
310
|
-
} else {
|
|
311
|
-
// The prefetch data is fully static, so we can omit it from the
|
|
312
|
-
// navigation request.
|
|
313
|
-
}
|
|
314
|
-
} else {
|
|
315
|
-
// There's no prefetch for this segment. Everything from this point will be
|
|
316
|
-
// requested from the server, even if there are static children below it.
|
|
317
|
-
// Create a terminal task node that will later be fulfilled by
|
|
318
|
-
// server response.
|
|
319
|
-
return spawnPendingTask(navigatedAt, routerState, null, possiblyPartialPrefetchHead, isPrefetchHeadPartial, segmentPath, scrollableSegmentsResult);
|
|
320
|
-
}
|
|
321
|
-
// We already have a full segment we can render, so we don't need to request a
|
|
322
|
-
// new one from the server. Keep traversing down the tree until we reach
|
|
323
|
-
// something that requires a dynamic request.
|
|
340
|
+
function createCacheNodeOnNavigation(navigatedAt, newRouterState, oldCacheNode, freshness, seedData, seedHead, prefetchData, prefetchHead, isPrefetchHeadPartial, parentSegmentPath, parentParallelRouteKey, parentNeedsDynamicRequest, accumulation) {
|
|
341
|
+
// Same traversal as updateCacheNodeNavigation, but simpler. We switch to this
|
|
342
|
+
// path once we reach the part of the tree that was not in the previous route.
|
|
343
|
+
// We don't need to diff against the old tree, we just need to create a new
|
|
344
|
+
// one. We also don't need to worry about any refresh-related logic.
|
|
345
|
+
//
|
|
346
|
+
// For the most part, this is a subset of updateCacheNodeOnNavigation, so any
|
|
347
|
+
// change that happens in this function likely needs to be applied to that
|
|
348
|
+
// one, too. However there are some places where the behavior intentionally
|
|
349
|
+
// diverges, which is why we keep them separate.
|
|
350
|
+
const newSegment = newRouterState[0];
|
|
351
|
+
const segmentPath = parentParallelRouteKey !== null && parentSegmentPath !== null ? parentSegmentPath.concat([
|
|
352
|
+
parentParallelRouteKey,
|
|
353
|
+
newSegment
|
|
354
|
+
]) : [];
|
|
355
|
+
const newRouterStateChildren = newRouterState[1];
|
|
324
356
|
const prefetchDataChildren = prefetchData !== null ? prefetchData[1] : null;
|
|
325
|
-
const
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
let
|
|
329
|
-
let
|
|
357
|
+
const seedDataChildren = seedData !== null ? seedData[1] : null;
|
|
358
|
+
const oldParallelRoutes = oldCacheNode !== undefined ? oldCacheNode.parallelRoutes : undefined;
|
|
359
|
+
let shouldDropSiblingCaches = false;
|
|
360
|
+
let shouldRefreshDynamicData = false;
|
|
361
|
+
let dropPrefetchRsc = false;
|
|
362
|
+
switch(freshness){
|
|
363
|
+
case 0:
|
|
364
|
+
// We should never drop dynamic data in sibling caches except during
|
|
365
|
+
// a refresh.
|
|
366
|
+
shouldDropSiblingCaches = false;
|
|
367
|
+
// Only reuse the dynamic data if experimental.staleTimes.dynamic config
|
|
368
|
+
// is set, and the data is not stale. (This is not a recommended API with
|
|
369
|
+
// Cache Components, but it's supported for backwards compatibility. Use
|
|
370
|
+
// cacheLife instead.)
|
|
371
|
+
//
|
|
372
|
+
// DYNAMIC_STALETIME_MS defaults to 0, but it can be increased.
|
|
373
|
+
shouldRefreshDynamicData = oldCacheNode === undefined || navigatedAt - oldCacheNode.navigatedAt >= _navigatereducer.DYNAMIC_STALETIME_MS;
|
|
374
|
+
dropPrefetchRsc = false;
|
|
375
|
+
break;
|
|
376
|
+
case 1:
|
|
377
|
+
// During hydration, we assume the data sent by the server is both
|
|
378
|
+
// consistent and complete.
|
|
379
|
+
shouldRefreshDynamicData = false;
|
|
380
|
+
shouldDropSiblingCaches = false;
|
|
381
|
+
dropPrefetchRsc = false;
|
|
382
|
+
break;
|
|
383
|
+
case 2:
|
|
384
|
+
// During back/forward navigations, we reuse the dynamic data regardless
|
|
385
|
+
// of how stale it may be.
|
|
386
|
+
shouldRefreshDynamicData = false;
|
|
387
|
+
shouldRefreshDynamicData = false;
|
|
388
|
+
// Only show prefetched data if the dynamic data is still pending. This
|
|
389
|
+
// avoids a flash back to the prefetch state in a case where it's highly
|
|
390
|
+
// likely to have already streamed in.
|
|
391
|
+
//
|
|
392
|
+
// Tehnically, what we're actually checking is whether the dynamic network
|
|
393
|
+
// response was received. But since it's a streaming response, this does
|
|
394
|
+
// not mean that all the dynamic data has fully streamed in. It just means
|
|
395
|
+
// that _some_ of the dynamic data was received. But as a heuristic, we
|
|
396
|
+
// assume that the rest dynamic data will stream in quickly, so it's still
|
|
397
|
+
// better to skip the prefetch state.
|
|
398
|
+
if (oldCacheNode !== undefined) {
|
|
399
|
+
const oldRsc = oldCacheNode.rsc;
|
|
400
|
+
const oldRscDidResolve = !isDeferredRsc(oldRsc) || oldRsc.status !== 'pending';
|
|
401
|
+
dropPrefetchRsc = oldRscDidResolve;
|
|
402
|
+
} else {
|
|
403
|
+
dropPrefetchRsc = false;
|
|
404
|
+
}
|
|
405
|
+
break;
|
|
406
|
+
case 3:
|
|
407
|
+
case 4:
|
|
408
|
+
// Drop all dynamic data.
|
|
409
|
+
shouldRefreshDynamicData = true;
|
|
410
|
+
shouldDropSiblingCaches = true;
|
|
411
|
+
dropPrefetchRsc = false;
|
|
412
|
+
break;
|
|
413
|
+
default:
|
|
414
|
+
freshness;
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
const newParallelRoutes = new Map(shouldDropSiblingCaches ? undefined : oldParallelRoutes);
|
|
418
|
+
const isLeafSegment = Object.keys(newRouterStateChildren).length === 0;
|
|
330
419
|
if (isLeafSegment) {
|
|
331
420
|
// The segment path of every leaf segment (i.e. page) is collected into
|
|
332
421
|
// a result array. This is used by the LayoutRouter to scroll to ensure that
|
|
333
422
|
// new pages are visible after a navigation.
|
|
423
|
+
//
|
|
424
|
+
// This only happens for new pages, not for refreshed pages.
|
|
425
|
+
//
|
|
334
426
|
// TODO: We should use a string to represent the segment path instead of
|
|
335
427
|
// an array. We already use a string representation for the path when
|
|
336
428
|
// accessing the Segment Cache, so we can use the same one.
|
|
337
|
-
|
|
429
|
+
if (accumulation.scrollableSegments === null) {
|
|
430
|
+
accumulation.scrollableSegments = [];
|
|
431
|
+
}
|
|
432
|
+
accumulation.scrollableSegments.push(segmentPath);
|
|
433
|
+
}
|
|
434
|
+
let newCacheNode;
|
|
435
|
+
let needsDynamicRequest;
|
|
436
|
+
if (!shouldRefreshDynamicData && oldCacheNode !== undefined) {
|
|
437
|
+
// Reuse the existing CacheNode
|
|
438
|
+
newCacheNode = reuseDynamicCacheNode(dropPrefetchRsc, oldCacheNode, newParallelRoutes);
|
|
439
|
+
needsDynamicRequest = false;
|
|
440
|
+
} else if (seedData !== null && seedData[0] !== null) {
|
|
441
|
+
// If this navigation was the result of an action, then check if the
|
|
442
|
+
// server sent back data in the action response. We should favor using
|
|
443
|
+
// that, rather than performing a separate request. This is both better
|
|
444
|
+
// for performance and it's more likely to be consistent with any
|
|
445
|
+
// writes that were just performed by the action, compared to a
|
|
446
|
+
// separate request.
|
|
447
|
+
const seedRsc = seedData[0];
|
|
448
|
+
const seedLoading = seedData[2];
|
|
449
|
+
const isSeedRscPartial = false;
|
|
450
|
+
const isSeedHeadPartial = seedHead === null && freshness !== 1;
|
|
451
|
+
newCacheNode = readCacheNodeFromSeedData(seedRsc, seedLoading, isSeedRscPartial, seedHead, isSeedHeadPartial, isLeafSegment, newParallelRoutes, navigatedAt);
|
|
452
|
+
needsDynamicRequest = isLeafSegment && isSeedHeadPartial;
|
|
453
|
+
} else if (freshness === 1 && isLeafSegment && seedHead !== null) {
|
|
454
|
+
// This is another weird case related to "not found" pages and hydration.
|
|
455
|
+
// There will be a head sent by the server, but no page seed data.
|
|
456
|
+
// TODO: We really should get rid of all these "not found" specific quirks
|
|
457
|
+
// and make sure the tree is always consistent.
|
|
458
|
+
const seedRsc = null;
|
|
459
|
+
const seedLoading = null;
|
|
460
|
+
const isSeedRscPartial = false;
|
|
461
|
+
const isSeedHeadPartial = false;
|
|
462
|
+
newCacheNode = readCacheNodeFromSeedData(seedRsc, seedLoading, isSeedRscPartial, seedHead, isSeedHeadPartial, isLeafSegment, newParallelRoutes, navigatedAt);
|
|
463
|
+
needsDynamicRequest = false;
|
|
464
|
+
} else if (freshness !== 1 && prefetchData !== null) {
|
|
465
|
+
// Consult the prefetch cache.
|
|
466
|
+
const prefetchRsc = prefetchData[0];
|
|
467
|
+
const prefetchLoading = prefetchData[2];
|
|
468
|
+
const isPrefetchRSCPartial = prefetchData[3];
|
|
469
|
+
newCacheNode = readCacheNodeFromSeedData(prefetchRsc, prefetchLoading, isPrefetchRSCPartial, prefetchHead, isPrefetchHeadPartial, isLeafSegment, newParallelRoutes, navigatedAt);
|
|
470
|
+
needsDynamicRequest = isPrefetchRSCPartial || isLeafSegment && isPrefetchHeadPartial;
|
|
338
471
|
} else {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
472
|
+
// Spawn a request to fetch new data from the server.
|
|
473
|
+
newCacheNode = spawnNewCacheNode(newParallelRoutes, isLeafSegment, navigatedAt, freshness);
|
|
474
|
+
needsDynamicRequest = true;
|
|
475
|
+
}
|
|
476
|
+
let patchedRouterStateChildren = {};
|
|
477
|
+
let taskChildren = null;
|
|
478
|
+
let childNeedsDynamicRequest = false;
|
|
479
|
+
let dynamicRequestTreeChildren = {};
|
|
480
|
+
for(let parallelRouteKey in newRouterStateChildren){
|
|
481
|
+
const newRouterStateChild = newRouterStateChildren[parallelRouteKey];
|
|
482
|
+
const oldSegmentMapChild = oldParallelRoutes !== undefined ? oldParallelRoutes.get(parallelRouteKey) : undefined;
|
|
483
|
+
const seedDataChild = seedDataChildren !== null ? seedDataChildren[parallelRouteKey] : null;
|
|
484
|
+
const prefetchDataChild = prefetchDataChildren !== null ? prefetchDataChildren[parallelRouteKey] : null;
|
|
485
|
+
const newSegmentChild = newRouterStateChild[0];
|
|
486
|
+
const newSegmentKeyChild = (0, _createroutercachekey.createRouterCacheKey)(newSegmentChild);
|
|
487
|
+
const oldCacheNodeChild = oldSegmentMapChild !== undefined ? oldSegmentMapChild.get(newSegmentKeyChild) : undefined;
|
|
488
|
+
const taskChild = createCacheNodeOnNavigation(navigatedAt, newRouterStateChild, oldCacheNodeChild, freshness, seedDataChild ?? null, seedHead, prefetchDataChild ?? null, prefetchHead, isPrefetchHeadPartial, segmentPath, parallelRouteKey, parentNeedsDynamicRequest || needsDynamicRequest, accumulation);
|
|
489
|
+
if (taskChildren === null) {
|
|
490
|
+
taskChildren = new Map();
|
|
491
|
+
}
|
|
492
|
+
taskChildren.set(parallelRouteKey, taskChild);
|
|
493
|
+
const newCacheNodeChild = taskChild.node;
|
|
494
|
+
if (newCacheNodeChild !== null) {
|
|
495
|
+
const newSegmentMapChild = new Map(shouldDropSiblingCaches ? undefined : oldSegmentMapChild);
|
|
496
|
+
newSegmentMapChild.set(newSegmentKeyChild, newCacheNodeChild);
|
|
497
|
+
newParallelRoutes.set(parallelRouteKey, newSegmentMapChild);
|
|
498
|
+
}
|
|
499
|
+
const taskChildRoute = taskChild.route;
|
|
500
|
+
patchedRouterStateChildren[parallelRouteKey] = taskChildRoute;
|
|
501
|
+
const dynamicRequestTreeChild = taskChild.dynamicRequestTree;
|
|
502
|
+
if (dynamicRequestTreeChild !== null) {
|
|
503
|
+
childNeedsDynamicRequest = true;
|
|
504
|
+
dynamicRequestTreeChildren[parallelRouteKey] = dynamicRequestTreeChild;
|
|
505
|
+
} else {
|
|
506
|
+
dynamicRequestTreeChildren[parallelRouteKey] = taskChildRoute;
|
|
366
507
|
}
|
|
367
508
|
}
|
|
368
509
|
return {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
route
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
// Since this segment is already full, we don't need to use the
|
|
377
|
-
// `prefetchRsc` field.
|
|
378
|
-
rsc,
|
|
379
|
-
prefetchRsc: null,
|
|
380
|
-
head,
|
|
381
|
-
prefetchHead: null,
|
|
382
|
-
loading,
|
|
383
|
-
parallelRoutes: cacheNodeChildren,
|
|
384
|
-
navigatedAt: cacheNodeNavigatedAt
|
|
385
|
-
},
|
|
386
|
-
dynamicRequestTree: needsDynamicRequest ? patchRouterStateWithNewChildren(routerState, dynamicRequestTreeChildren) : null,
|
|
510
|
+
status: needsDynamicRequest ? 0 : 1,
|
|
511
|
+
route: patchRouterStateWithNewChildren(newRouterState, patchedRouterStateChildren),
|
|
512
|
+
node: newCacheNode,
|
|
513
|
+
dynamicRequestTree: createDynamicRequestTree(newRouterState, dynamicRequestTreeChildren, needsDynamicRequest, childNeedsDynamicRequest, parentNeedsDynamicRequest),
|
|
514
|
+
// This route is not part of the current tree, so there's no reason to
|
|
515
|
+
// track the refresh URL.
|
|
516
|
+
refreshUrl: null,
|
|
387
517
|
children: taskChildren
|
|
388
518
|
};
|
|
389
519
|
}
|
|
@@ -406,22 +536,48 @@ function patchRouterStateWithNewChildren(baseRouterState, newChildren) {
|
|
|
406
536
|
}
|
|
407
537
|
return clone;
|
|
408
538
|
}
|
|
409
|
-
function
|
|
410
|
-
// Create a
|
|
411
|
-
//
|
|
412
|
-
//
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
//
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
|
|
539
|
+
function createDynamicRequestTree(newRouterState, dynamicRequestTreeChildren, needsDynamicRequest, childNeedsDynamicRequest, parentNeedsDynamicRequest) {
|
|
540
|
+
// Create a FlightRouterState that instructs the server how to render the
|
|
541
|
+
// requested segment.
|
|
542
|
+
//
|
|
543
|
+
// Or, if neither this segment nor any of the children require a new data,
|
|
544
|
+
// then we return `null` to skip the request.
|
|
545
|
+
let dynamicRequestTree = null;
|
|
546
|
+
if (needsDynamicRequest) {
|
|
547
|
+
dynamicRequestTree = patchRouterStateWithNewChildren(newRouterState, dynamicRequestTreeChildren);
|
|
548
|
+
// The "refetch" marker is set on the top-most segment that requires new
|
|
549
|
+
// data. We can omit it if a parent was already marked.
|
|
550
|
+
if (!parentNeedsDynamicRequest) {
|
|
551
|
+
dynamicRequestTree[3] = 'refetch';
|
|
552
|
+
}
|
|
553
|
+
} else if (childNeedsDynamicRequest) {
|
|
554
|
+
// This segment does not request new data, but at least one of its
|
|
555
|
+
// children does.
|
|
556
|
+
dynamicRequestTree = patchRouterStateWithNewChildren(newRouterState, dynamicRequestTreeChildren);
|
|
557
|
+
} else {
|
|
558
|
+
dynamicRequestTree = null;
|
|
559
|
+
}
|
|
560
|
+
return dynamicRequestTree;
|
|
561
|
+
}
|
|
562
|
+
function accumulateRefreshUrl(accumulation, refreshUrl) {
|
|
563
|
+
// This is a refresh navigation, and we're inside a "default" slot that's
|
|
564
|
+
// not part of the current route; it was reused from an older route. In
|
|
565
|
+
// order to get fresh data for this reused route, we need to issue a
|
|
566
|
+
// separate request using the old route's URL.
|
|
567
|
+
//
|
|
568
|
+
// Track these extra URLs in the accumulated result. Later, we'll construct
|
|
569
|
+
// an appropriate request for each unique URL in the final set. The reason
|
|
570
|
+
// we don't do it immediately here is so we can deduplicate multiple
|
|
571
|
+
// instances of the same URL into a single request. See
|
|
572
|
+
// listenForDynamicRequest for more details.
|
|
573
|
+
const separateRefreshUrls = accumulation.separateRefreshUrls;
|
|
574
|
+
if (separateRefreshUrls === null) {
|
|
575
|
+
accumulation.separateRefreshUrls = new Set([
|
|
576
|
+
refreshUrl
|
|
577
|
+
]);
|
|
578
|
+
} else {
|
|
579
|
+
separateRefreshUrls.add(refreshUrl);
|
|
580
|
+
}
|
|
425
581
|
}
|
|
426
582
|
function reuseActiveSegmentInDefaultSlot(oldUrl, oldRouterState) {
|
|
427
583
|
// This is a "default" segment. These are never sent by the server during a
|
|
@@ -446,166 +602,349 @@ function reuseActiveSegmentInDefaultSlot(oldUrl, oldRouterState) {
|
|
|
446
602
|
reusedRouterState[2] = (0, _createhreffromurl.createHrefFromUrl)(oldUrl);
|
|
447
603
|
reusedRouterState[3] = 'refresh';
|
|
448
604
|
}
|
|
449
|
-
return
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
605
|
+
return reusedRouterState;
|
|
606
|
+
}
|
|
607
|
+
function reuseDynamicCacheNode(dropPrefetchRsc, existingCacheNode, parallelRoutes) {
|
|
608
|
+
// Clone an existing CacheNode's data, with (possibly) new children.
|
|
609
|
+
const cacheNode = {
|
|
610
|
+
rsc: existingCacheNode.rsc,
|
|
611
|
+
prefetchRsc: dropPrefetchRsc ? null : existingCacheNode.prefetchRsc,
|
|
612
|
+
head: existingCacheNode.head,
|
|
613
|
+
prefetchHead: dropPrefetchRsc ? null : existingCacheNode.prefetchHead,
|
|
614
|
+
loading: existingCacheNode.loading,
|
|
615
|
+
parallelRoutes,
|
|
616
|
+
// Don't update the navigatedAt timestamp, since we're reusing
|
|
617
|
+
// existing data.
|
|
618
|
+
navigatedAt: existingCacheNode.navigatedAt
|
|
454
619
|
};
|
|
620
|
+
return cacheNode;
|
|
455
621
|
}
|
|
456
|
-
function
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
622
|
+
function readCacheNodeFromSeedData(seedRsc, seedLoading, isSeedRscPartial, seedHead, isSeedHeadPartial, isPageSegment, parallelRoutes, navigatedAt) {
|
|
623
|
+
// TODO: Currently this is threaded through the navigation logic using the
|
|
624
|
+
// CacheNodeSeedData type, but in the future this will read directly from
|
|
625
|
+
// the Segment Cache. See readRenderSnapshotFromCache.
|
|
626
|
+
let rsc;
|
|
627
|
+
let prefetchRsc;
|
|
628
|
+
if (isSeedRscPartial) {
|
|
629
|
+
// The prefetched data contains dynamic holes. Create a pending promise that
|
|
630
|
+
// will be fulfilled when the dynamic data is received from the server.
|
|
631
|
+
prefetchRsc = seedRsc;
|
|
632
|
+
rsc = createDeferredRsc();
|
|
633
|
+
} else {
|
|
634
|
+
// The prefetched data is complete. Use it directly.
|
|
635
|
+
prefetchRsc = null;
|
|
636
|
+
rsc = seedRsc;
|
|
637
|
+
}
|
|
638
|
+
// If this is a page segment, also read the head.
|
|
639
|
+
let prefetchHead;
|
|
640
|
+
let head;
|
|
641
|
+
if (isPageSegment) {
|
|
642
|
+
if (isSeedHeadPartial) {
|
|
643
|
+
prefetchHead = seedHead;
|
|
644
|
+
head = createDeferredRsc();
|
|
645
|
+
} else {
|
|
646
|
+
prefetchHead = null;
|
|
647
|
+
head = seedHead;
|
|
463
648
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
649
|
+
} else {
|
|
650
|
+
prefetchHead = null;
|
|
651
|
+
head = null;
|
|
652
|
+
}
|
|
653
|
+
const cacheNode = {
|
|
654
|
+
rsc,
|
|
655
|
+
prefetchRsc,
|
|
656
|
+
head,
|
|
657
|
+
prefetchHead,
|
|
658
|
+
// TODO: Technically, a loading boundary could contain dynamic data. We
|
|
659
|
+
// should have separate `loading` and `prefetchLoading` fields to handle
|
|
660
|
+
// this, like we do for the segment data and head.
|
|
661
|
+
loading: seedLoading,
|
|
662
|
+
parallelRoutes,
|
|
663
|
+
navigatedAt
|
|
664
|
+
};
|
|
665
|
+
return cacheNode;
|
|
666
|
+
}
|
|
667
|
+
function spawnNewCacheNode(parallelRoutes, isLeafSegment, navigatedAt, freshness) {
|
|
668
|
+
// We should never spawn network requests during hydration. We must treat the
|
|
669
|
+
// initial payload as authoritative, because the initial page load is used
|
|
670
|
+
// as a last-ditch mechanism for recovering the app.
|
|
671
|
+
//
|
|
672
|
+
// This is also an important safety check because if this leaks into the
|
|
673
|
+
// server rendering path (which theoretically it never should because
|
|
674
|
+
// the server payload should be consistent), the server would hang because
|
|
675
|
+
// these promises would never resolve.
|
|
676
|
+
//
|
|
677
|
+
// TODO: There is an existing case where the global "not found" boundary
|
|
678
|
+
// triggers this path. But it does render correctly despite that. That's an
|
|
679
|
+
// unusual render path so it's not surprising, but we should look into
|
|
680
|
+
// modeling it in a more consistent way. See also the /_notFound special
|
|
681
|
+
// case in updateCacheNodeOnNavigation.
|
|
682
|
+
const isHydration = freshness === 1;
|
|
683
|
+
const cacheNode = {
|
|
684
|
+
rsc: !isHydration ? createDeferredRsc() : null,
|
|
685
|
+
prefetchRsc: null,
|
|
686
|
+
head: !isHydration && isLeafSegment ? createDeferredRsc() : null,
|
|
687
|
+
prefetchHead: null,
|
|
688
|
+
loading: !isHydration ? createDeferredRsc() : null,
|
|
689
|
+
parallelRoutes,
|
|
690
|
+
navigatedAt
|
|
691
|
+
};
|
|
692
|
+
return cacheNode;
|
|
693
|
+
}
|
|
694
|
+
// Represents whether the previuos navigation resulted in a route tree mismatch.
|
|
695
|
+
// A mismatch results in a refresh of the page. If there are two successive
|
|
696
|
+
// mismatches, we will fall back to an MPA navigation, to prevent a retry loop.
|
|
697
|
+
let previousNavigationDidMismatch = false;
|
|
698
|
+
function spawnDynamicRequests(task, primaryUrl, nextUrl, freshnessPolicy, accumulation) {
|
|
699
|
+
const dynamicRequestTree = task.dynamicRequestTree;
|
|
700
|
+
if (dynamicRequestTree === null) {
|
|
701
|
+
// This navigation was fully cached. There are no dynamic requests to spawn.
|
|
702
|
+
previousNavigationDidMismatch = false;
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
// This is intentionally not an async function to discourage the caller from
|
|
706
|
+
// awaiting the result. Any subsequent async operations spawned by this
|
|
707
|
+
// function should result in a separate navigation task, rather than
|
|
708
|
+
// block the original one.
|
|
709
|
+
//
|
|
710
|
+
// In this function we spawn (but do not await) all the network requests that
|
|
711
|
+
// block the navigation, and collect the promises. The next function,
|
|
712
|
+
// `finishNavigationTask`, can await the promises in any order without
|
|
713
|
+
// accidentally introducing a network waterfall.
|
|
714
|
+
const primaryRequestPromise = fetchMissingDynamicData(task, dynamicRequestTree, primaryUrl, nextUrl, freshnessPolicy);
|
|
715
|
+
const separateRefreshUrls = accumulation.separateRefreshUrls;
|
|
716
|
+
let refreshRequestPromises = null;
|
|
717
|
+
if (separateRefreshUrls !== null) {
|
|
718
|
+
// There are multiple URLs that we need to request the data from. This
|
|
719
|
+
// happens when a "default" parallel route slot is present in the tree, and
|
|
720
|
+
// its data cannot be fetched from the current route. We need to split the
|
|
721
|
+
// combined dynamic request tree into separate requests per URL.
|
|
722
|
+
// TODO: Create a scoped dynamic request tree that omits anything that
|
|
723
|
+
// is not relevant to the given URL. Without doing this, the server may
|
|
724
|
+
// sometimes render more data than necessary; this is not a regression
|
|
725
|
+
// compared to the pre-Segment Cache implementation, though, just an
|
|
726
|
+
// optimization we can make in the future.
|
|
727
|
+
// Construct a request tree for each additional refresh URL. This will
|
|
728
|
+
// prune away everything except the parts of the tree that match the
|
|
729
|
+
// given refresh URL.
|
|
730
|
+
refreshRequestPromises = [];
|
|
731
|
+
const canonicalUrl = (0, _createhreffromurl.createHrefFromUrl)(primaryUrl);
|
|
732
|
+
for (const refreshUrl of separateRefreshUrls){
|
|
733
|
+
if (refreshUrl === canonicalUrl) {
|
|
468
734
|
continue;
|
|
469
735
|
}
|
|
470
|
-
|
|
736
|
+
// TODO: Create a scoped dynamic request tree that omits anything that
|
|
737
|
+
// is not relevant to the given URL. Without doing this, the server may
|
|
738
|
+
// sometimes render more data than necessary; this is not a regression
|
|
739
|
+
// compared to the pre-Segment Cache implementation, though, just an
|
|
740
|
+
// optimization we can make in the future.
|
|
741
|
+
// const scopedDynamicRequestTree = splitTaskByURL(task, refreshUrl)
|
|
742
|
+
const scopedDynamicRequestTree = dynamicRequestTree;
|
|
743
|
+
if (scopedDynamicRequestTree !== null) {
|
|
744
|
+
refreshRequestPromises.push(fetchMissingDynamicData(task, scopedDynamicRequestTree, new URL(refreshUrl, location.origin), // TODO: Just noticed that this should actually the Next-Url at the
|
|
745
|
+
// time the refresh URL was set, not the current Next-Url. Need to
|
|
746
|
+
// start tracking this alongside the refresh URL. In the meantime,
|
|
747
|
+
// if a refresh fails due to a mismatch, it will trigger a
|
|
748
|
+
// hard refresh.
|
|
749
|
+
nextUrl, freshnessPolicy));
|
|
750
|
+
}
|
|
471
751
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
});
|
|
752
|
+
}
|
|
753
|
+
// Further async operations are moved into this separate function to
|
|
754
|
+
// discourage sequential network requests.
|
|
755
|
+
const voidPromise = finishNavigationTask(task, nextUrl, primaryRequestPromise, refreshRequestPromises);
|
|
756
|
+
// `finishNavigationTask` is responsible for error handling, so we can attach
|
|
757
|
+
// noop callbacks to this promise.
|
|
758
|
+
voidPromise.then(noop, noop);
|
|
480
759
|
}
|
|
481
|
-
function
|
|
482
|
-
//
|
|
483
|
-
|
|
484
|
-
//
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
//
|
|
760
|
+
async function finishNavigationTask(task, nextUrl, primaryRequestPromise, refreshRequestPromises) {
|
|
761
|
+
// Wait for all the requests to finish, or for the first one to fail.
|
|
762
|
+
let exitStatus = await waitForRequestsToFinish(primaryRequestPromise, refreshRequestPromises);
|
|
763
|
+
// Once the all the requests have finished, check the tree for any remaining
|
|
764
|
+
// pending tasks. If anything is still pending, it means the server response
|
|
765
|
+
// does not match the client, and we must refresh to get back to a consistent
|
|
766
|
+
// state. We can skip this step if we already detected a mismatch during the
|
|
767
|
+
// first phase; it doesn't matter in that case because we're going to refresh
|
|
768
|
+
// the whole tree regardless.
|
|
769
|
+
if (exitStatus === 0) {
|
|
770
|
+
exitStatus = abortRemainingPendingTasks(task, null, null);
|
|
771
|
+
}
|
|
772
|
+
switch(exitStatus){
|
|
773
|
+
case 0:
|
|
774
|
+
{
|
|
775
|
+
// The task has completely finished. There's no missing data. Exit.
|
|
776
|
+
previousNavigationDidMismatch = false;
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
case 1:
|
|
780
|
+
{
|
|
781
|
+
// Some data failed to finish loading. Trigger a soft retry.
|
|
782
|
+
// TODO: As an extra precaution against soft retry loops, consider
|
|
783
|
+
// tracking whether a navigation was itself triggered by a retry. If two
|
|
784
|
+
// happen in a row, fall back to a hard retry.
|
|
785
|
+
const isHardRetry = false;
|
|
786
|
+
const primaryRequestResult = await primaryRequestPromise;
|
|
787
|
+
dispatchRetryDueToTreeMismatch(isHardRetry, primaryRequestResult.url, nextUrl, primaryRequestResult.seed, task.route);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
case 2:
|
|
791
|
+
{
|
|
792
|
+
// Some data failed to finish loading in a non-recoverable way, such as a
|
|
793
|
+
// network error. Trigger an MPA navigation.
|
|
794
|
+
//
|
|
795
|
+
// Hard navigating/refreshing is how we prevent an infinite retry loop
|
|
796
|
+
// caused by a network error — when the network fails, we fall back to the
|
|
797
|
+
// browser behavior for offline navigations. In the future, Next.js may
|
|
798
|
+
// introduce its own custom handling of offline navigations, but that
|
|
799
|
+
// doesn't exist yet.
|
|
800
|
+
const isHardRetry = true;
|
|
801
|
+
const primaryRequestResult = await primaryRequestPromise;
|
|
802
|
+
dispatchRetryDueToTreeMismatch(isHardRetry, primaryRequestResult.url, nextUrl, primaryRequestResult.seed, task.route);
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
default:
|
|
806
|
+
{
|
|
807
|
+
return exitStatus;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
function waitForRequestsToFinish(primaryRequestPromise, refreshRequestPromises) {
|
|
812
|
+
// Custom async combinator logic. This could be replaced by Promise.any but
|
|
813
|
+
// we don't assume that's available.
|
|
488
814
|
//
|
|
489
|
-
//
|
|
815
|
+
// Each promise resolves once the server responsds and the data is written
|
|
816
|
+
// into the CacheNode tree. Resolve the combined promise once all the
|
|
817
|
+
// requests finish.
|
|
490
818
|
//
|
|
491
|
-
//
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
const taskSegment = taskChild.route[0];
|
|
501
|
-
if ((0, _matchsegments.matchSegment)(segment, taskSegment)) {
|
|
502
|
-
// Found a match for this task. Keep traversing down the task tree.
|
|
503
|
-
task = taskChild;
|
|
504
|
-
continue;
|
|
819
|
+
// Or, resolve as soon as one of the requests fails, without waiting for the
|
|
820
|
+
// others to finish.
|
|
821
|
+
return new Promise((resolve)=>{
|
|
822
|
+
const onFulfill = (result)=>{
|
|
823
|
+
if (result.exitStatus === 0) {
|
|
824
|
+
remainingCount--;
|
|
825
|
+
if (remainingCount === 0) {
|
|
826
|
+
// All the requests finished successfully.
|
|
827
|
+
resolve(0);
|
|
505
828
|
}
|
|
829
|
+
} else {
|
|
830
|
+
// One of the requests failed. Exit with a failing status.
|
|
831
|
+
// NOTE: It's possible for one of the requests to fail with SoftRetry
|
|
832
|
+
// and a later one to fail with HardRetry. In this case, we choose to
|
|
833
|
+
// retry immediately, rather than delay the retry until all the requests
|
|
834
|
+
// finish. If it fails again, we will hard retry on the next
|
|
835
|
+
// attempt, anyway.
|
|
836
|
+
resolve(result.exitStatus);
|
|
506
837
|
}
|
|
838
|
+
};
|
|
839
|
+
// onReject shouldn't ever be called because fetchMissingDynamicData's
|
|
840
|
+
// entire body is wrapped in a try/catch. This is just defensive.
|
|
841
|
+
const onReject = ()=>resolve(2);
|
|
842
|
+
// Attach the listeners to the promises.
|
|
843
|
+
let remainingCount = 1;
|
|
844
|
+
primaryRequestPromise.then(onFulfill, onReject);
|
|
845
|
+
if (refreshRequestPromises !== null) {
|
|
846
|
+
remainingCount += refreshRequestPromises.length;
|
|
847
|
+
refreshRequestPromises.forEach((refreshRequestPromise)=>refreshRequestPromise.then(onFulfill, onReject));
|
|
507
848
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
function dispatchRetryDueToTreeMismatch(isHardRetry, retryUrl, retryNextUrl, seed, baseTree) {
|
|
852
|
+
// If this is the second time in a row that a navigation resulted in a
|
|
853
|
+
// mismatch, fall back to a hard (MPA) refresh.
|
|
854
|
+
isHardRetry = isHardRetry || previousNavigationDidMismatch;
|
|
855
|
+
previousNavigationDidMismatch = true;
|
|
856
|
+
const retryAction = {
|
|
857
|
+
type: _routerreducertypes.ACTION_SERVER_PATCH,
|
|
858
|
+
previousTree: baseTree,
|
|
859
|
+
url: retryUrl,
|
|
860
|
+
nextUrl: retryNextUrl,
|
|
861
|
+
seed,
|
|
862
|
+
mpa: isHardRetry
|
|
863
|
+
};
|
|
864
|
+
(0, _useactionqueue.dispatchAppRouterAction)(retryAction);
|
|
865
|
+
}
|
|
866
|
+
async function fetchMissingDynamicData(task, dynamicRequestTree, url, nextUrl, freshnessPolicy) {
|
|
867
|
+
try {
|
|
868
|
+
const result = await (0, _fetchserverresponse.fetchServerResponse)(url, {
|
|
869
|
+
flightRouterState: dynamicRequestTree,
|
|
870
|
+
nextUrl,
|
|
871
|
+
isHmrRefresh: freshnessPolicy === 4
|
|
872
|
+
});
|
|
873
|
+
if (typeof result === 'string') {
|
|
874
|
+
// fetchServerResponse will return an href to indicate that the SPA
|
|
875
|
+
// navigation failed. For example, if the server triggered a hard
|
|
876
|
+
// redirect, or the fetch request errored. Initiate an MPA navigation
|
|
877
|
+
// to the given href.
|
|
878
|
+
return {
|
|
879
|
+
exitStatus: 2,
|
|
880
|
+
url: new URL(result, location.origin),
|
|
881
|
+
seed: null
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
const seed = (0, _navigation.convertServerPatchToFullTree)(task.route, result.flightData, result.renderedSearch);
|
|
885
|
+
const didReceiveUnknownParallelRoute = writeDynamicDataIntoNavigationTask(task, seed.tree, seed.data, seed.head, result.debugInfo);
|
|
886
|
+
return {
|
|
887
|
+
exitStatus: didReceiveUnknownParallelRoute ? 1 : 0,
|
|
888
|
+
url: new URL(result.canonicalUrl, location.origin),
|
|
889
|
+
seed
|
|
890
|
+
};
|
|
891
|
+
} catch {
|
|
892
|
+
// This shouldn't happen because fetchServerResponse's entire body is
|
|
893
|
+
// wrapped in a try/catch. If it does, though, it implies the server failed
|
|
894
|
+
// to respond with any tree at all. So we must fall back to a hard retry.
|
|
895
|
+
return {
|
|
896
|
+
exitStatus: 2,
|
|
897
|
+
url: url,
|
|
898
|
+
seed: null
|
|
899
|
+
};
|
|
513
900
|
}
|
|
514
|
-
finishTaskUsingDynamicDataPayload(task, serverRouterState, dynamicData, dynamicHead, debugInfo);
|
|
515
901
|
}
|
|
516
|
-
function
|
|
517
|
-
if (task.
|
|
518
|
-
|
|
519
|
-
|
|
902
|
+
function writeDynamicDataIntoNavigationTask(task, serverRouterState, dynamicData, dynamicHead, debugInfo) {
|
|
903
|
+
if (task.status === 0 && dynamicData !== null) {
|
|
904
|
+
task.status = 1;
|
|
905
|
+
finishPendingCacheNode(task.node, dynamicData, dynamicHead, debugInfo);
|
|
520
906
|
}
|
|
521
|
-
// dynamicData may represent a larger subtree than the task. Before we can
|
|
522
|
-
// finish the task, we need to line them up.
|
|
523
907
|
const taskChildren = task.children;
|
|
524
|
-
const taskNode = task.node;
|
|
525
|
-
if (taskChildren === null) {
|
|
526
|
-
// We've reached the leaf node of the pending task. The server data tree
|
|
527
|
-
// lines up the pending Cache Node tree. We can now switch to the
|
|
528
|
-
// normal algorithm.
|
|
529
|
-
if (taskNode !== null) {
|
|
530
|
-
finishPendingCacheNode(taskNode, task.route, serverRouterState, dynamicData, dynamicHead, debugInfo);
|
|
531
|
-
// Set this to null to indicate that this task is now complete.
|
|
532
|
-
task.dynamicRequestTree = null;
|
|
533
|
-
}
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
// The server returned more data than we need to finish the task. Skip over
|
|
537
|
-
// the extra segments until we reach the leaf task node.
|
|
538
908
|
const serverChildren = serverRouterState[1];
|
|
539
|
-
const dynamicDataChildren = dynamicData[1];
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
909
|
+
const dynamicDataChildren = dynamicData !== null ? dynamicData[1] : null;
|
|
910
|
+
// Detect whether the server sends a parallel route slot that the client
|
|
911
|
+
// doesn't know about.
|
|
912
|
+
let didReceiveUnknownParallelRoute = false;
|
|
913
|
+
if (taskChildren !== null) {
|
|
914
|
+
for(const parallelRouteKey in serverChildren){
|
|
915
|
+
const serverRouterStateChild = serverChildren[parallelRouteKey];
|
|
916
|
+
const dynamicDataChild = dynamicDataChildren !== null ? dynamicDataChildren[parallelRouteKey] : null;
|
|
917
|
+
const taskChild = taskChildren.get(parallelRouteKey);
|
|
918
|
+
if (taskChild === undefined) {
|
|
919
|
+
// The server sent a child segment that the client doesn't know about.
|
|
920
|
+
//
|
|
921
|
+
// When we receive an unknown parallel route, we must consider it a
|
|
922
|
+
// mismatch. This is unlike the case where the segment itself
|
|
923
|
+
// mismatches, because multiple routes can be active simultaneously.
|
|
924
|
+
// But a given layout should never have a mismatching set of
|
|
925
|
+
// child slots.
|
|
926
|
+
//
|
|
927
|
+
// Theoretically, this should only happen in development during an HMR
|
|
928
|
+
// refresh, because the set of parallel routes for a layout does not
|
|
929
|
+
// change over the lifetime of a build/deployment. In production, we
|
|
930
|
+
// should have already mismatched on either the build id or the segment
|
|
931
|
+
// path. But as an extra precaution, we validate in prod, too.
|
|
932
|
+
didReceiveUnknownParallelRoute = true;
|
|
933
|
+
} else {
|
|
934
|
+
const taskSegment = taskChild.route[0];
|
|
935
|
+
if ((0, _matchsegments.matchSegment)(serverRouterStateChild[0], taskSegment) && dynamicDataChild !== null && dynamicDataChild !== undefined) {
|
|
936
|
+
// Found a match for this task. Keep traversing down the task tree.
|
|
937
|
+
const childDidReceiveUnknownParallelRoute = writeDynamicDataIntoNavigationTask(taskChild, serverRouterStateChild, dynamicDataChild, dynamicHead, debugInfo);
|
|
938
|
+
if (childDidReceiveUnknownParallelRoute) {
|
|
939
|
+
didReceiveUnknownParallelRoute = true;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
549
942
|
}
|
|
550
943
|
}
|
|
551
|
-
// We didn't find a child task that matches the server data. We won't abort
|
|
552
|
-
// the task, though, because a different FlightDataPath may be able to
|
|
553
|
-
// fulfill it (see loop in listenForDynamicRequest). We only abort tasks
|
|
554
|
-
// once we've run out of data.
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
function createPendingCacheNode(navigatedAt, routerState, prefetchData, prefetchHead, isPrefetchHeadPartial, segmentPath, scrollableSegmentsResult) {
|
|
558
|
-
const routerStateChildren = routerState[1];
|
|
559
|
-
const prefetchDataChildren = prefetchData !== null ? prefetchData[1] : null;
|
|
560
|
-
const parallelRoutes = new Map();
|
|
561
|
-
for(let parallelRouteKey in routerStateChildren){
|
|
562
|
-
const routerStateChild = routerStateChildren[parallelRouteKey];
|
|
563
|
-
const prefetchDataChild = prefetchDataChildren !== null ? prefetchDataChildren[parallelRouteKey] : null;
|
|
564
|
-
const segmentChild = routerStateChild[0];
|
|
565
|
-
const segmentPathChild = segmentPath.concat([
|
|
566
|
-
parallelRouteKey,
|
|
567
|
-
segmentChild
|
|
568
|
-
]);
|
|
569
|
-
const segmentKeyChild = (0, _createroutercachekey.createRouterCacheKey)(segmentChild);
|
|
570
|
-
const newCacheNodeChild = createPendingCacheNode(navigatedAt, routerStateChild, prefetchDataChild === undefined ? null : prefetchDataChild, prefetchHead, isPrefetchHeadPartial, segmentPathChild, scrollableSegmentsResult);
|
|
571
|
-
const newSegmentMapChild = new Map();
|
|
572
|
-
newSegmentMapChild.set(segmentKeyChild, newCacheNodeChild);
|
|
573
|
-
parallelRoutes.set(parallelRouteKey, newSegmentMapChild);
|
|
574
|
-
}
|
|
575
|
-
// The head is assigned to every leaf segment delivered by the server. Based
|
|
576
|
-
// on corresponding logic in fill-lazy-items-till-leaf-with-head.ts
|
|
577
|
-
const isLeafSegment = parallelRoutes.size === 0;
|
|
578
|
-
if (isLeafSegment) {
|
|
579
|
-
// The segment path of every leaf segment (i.e. page) is collected into
|
|
580
|
-
// a result array. This is used by the LayoutRouter to scroll to ensure that
|
|
581
|
-
// new pages are visible after a navigation.
|
|
582
|
-
// TODO: We should use a string to represent the segment path instead of
|
|
583
|
-
// an array. We already use a string representation for the path when
|
|
584
|
-
// accessing the Segment Cache, so we can use the same one.
|
|
585
|
-
scrollableSegmentsResult.push(segmentPath);
|
|
586
944
|
}
|
|
587
|
-
|
|
588
|
-
return {
|
|
589
|
-
lazyData: null,
|
|
590
|
-
parallelRoutes: parallelRoutes,
|
|
591
|
-
prefetchRsc: maybePrefetchRsc !== undefined ? maybePrefetchRsc : null,
|
|
592
|
-
prefetchHead: isLeafSegment ? prefetchHead : [
|
|
593
|
-
null,
|
|
594
|
-
null
|
|
595
|
-
],
|
|
596
|
-
// Create a deferred promise. This will be fulfilled once the dynamic
|
|
597
|
-
// response is received from the server.
|
|
598
|
-
rsc: createDeferredRsc(),
|
|
599
|
-
head: isLeafSegment ? createDeferredRsc() : null,
|
|
600
|
-
// TODO: Technically, a loading boundary could contain dynamic data. We must
|
|
601
|
-
// have separate `loading` and `prefetchLoading` fields to handle this, like
|
|
602
|
-
// we do for the segment data and head.
|
|
603
|
-
loading: prefetchData !== null ? prefetchData[2] ?? null : // We'll fulfill it based on the dynamic response, just like `rsc` and `head`.
|
|
604
|
-
createDeferredRsc(),
|
|
605
|
-
navigatedAt
|
|
606
|
-
};
|
|
945
|
+
return didReceiveUnknownParallelRoute;
|
|
607
946
|
}
|
|
608
|
-
function finishPendingCacheNode(cacheNode,
|
|
947
|
+
function finishPendingCacheNode(cacheNode, dynamicData, dynamicHead, debugInfo) {
|
|
609
948
|
// Writes a dynamic response into an existing Cache Node tree. This does _not_
|
|
610
949
|
// create a new tree, it updates the existing tree in-place. So it must follow
|
|
611
950
|
// the Suspense rules of cache safety — it can resolve pending promises, but
|
|
@@ -616,49 +955,16 @@ function finishPendingCacheNode(cacheNode, taskState, serverState, dynamicData,
|
|
|
616
955
|
// We must resolve every promise in the tree, or else it will suspend
|
|
617
956
|
// indefinitely. If we did not receive data for a segment, we will resolve its
|
|
618
957
|
// data promise to `null` to trigger a lazy fetch during render.
|
|
619
|
-
const taskStateChildren = taskState[1];
|
|
620
|
-
const serverStateChildren = serverState[1];
|
|
621
|
-
const dataChildren = dynamicData[1];
|
|
622
|
-
// The router state that we traverse the tree with (taskState) is the same one
|
|
623
|
-
// that we used to construct the pending Cache Node tree. That way we're sure
|
|
624
|
-
// to resolve all the pending promises.
|
|
625
|
-
const parallelRoutes = cacheNode.parallelRoutes;
|
|
626
|
-
for(let parallelRouteKey in taskStateChildren){
|
|
627
|
-
const taskStateChild = taskStateChildren[parallelRouteKey];
|
|
628
|
-
const serverStateChild = serverStateChildren[parallelRouteKey];
|
|
629
|
-
const dataChild = dataChildren[parallelRouteKey];
|
|
630
|
-
const segmentMapChild = parallelRoutes.get(parallelRouteKey);
|
|
631
|
-
const taskSegmentChild = taskStateChild[0];
|
|
632
|
-
const taskSegmentKeyChild = (0, _createroutercachekey.createRouterCacheKey)(taskSegmentChild);
|
|
633
|
-
const cacheNodeChild = segmentMapChild !== undefined ? segmentMapChild.get(taskSegmentKeyChild) : undefined;
|
|
634
|
-
if (cacheNodeChild !== undefined) {
|
|
635
|
-
if (serverStateChild !== undefined && (0, _matchsegments.matchSegment)(taskSegmentChild, serverStateChild[0])) {
|
|
636
|
-
if (dataChild !== undefined && dataChild !== null) {
|
|
637
|
-
// This is the happy path. Recursively update all the children.
|
|
638
|
-
finishPendingCacheNode(cacheNodeChild, taskStateChild, serverStateChild, dataChild, dynamicHead, debugInfo);
|
|
639
|
-
} else {
|
|
640
|
-
// The server never returned data for this segment. Trigger a lazy
|
|
641
|
-
// fetch during render. This shouldn't happen because the Route Tree
|
|
642
|
-
// and the Seed Data tree sent by the server should always be the same
|
|
643
|
-
// shape when part of the same server response.
|
|
644
|
-
abortPendingCacheNode(taskStateChild, cacheNodeChild, null, debugInfo);
|
|
645
|
-
}
|
|
646
|
-
} else {
|
|
647
|
-
// The server never returned data for this segment. Trigger a lazy
|
|
648
|
-
// fetch during render.
|
|
649
|
-
abortPendingCacheNode(taskStateChild, cacheNodeChild, null, debugInfo);
|
|
650
|
-
}
|
|
651
|
-
} else {
|
|
652
|
-
// The server response matches what was expected to receive, but there's
|
|
653
|
-
// no matching Cache Node in the task tree. This is a bug in the
|
|
654
|
-
// implementation because we should have created a node for every
|
|
655
|
-
// segment in the tree that's associated with this task.
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
958
|
// Use the dynamic data from the server to fulfill the deferred RSC promise
|
|
659
959
|
// on the Cache Node.
|
|
660
960
|
const rsc = cacheNode.rsc;
|
|
661
961
|
const dynamicSegmentData = dynamicData[0];
|
|
962
|
+
if (dynamicSegmentData === null) {
|
|
963
|
+
// This is an empty CacheNode; this particular server request did not
|
|
964
|
+
// render this segment. There may be a separate pending request that will,
|
|
965
|
+
// though, so we won't abort the task until all pending requests finish.
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
662
968
|
if (rsc === null) {
|
|
663
969
|
// This is a lazy cache node. We can overwrite it. This is only safe
|
|
664
970
|
// because we know that the LayoutRouter suspends if `rsc` is `null`.
|
|
@@ -687,51 +993,56 @@ function finishPendingCacheNode(cacheNode, taskState, serverState, dynamicData,
|
|
|
687
993
|
head.resolve(dynamicHead, debugInfo);
|
|
688
994
|
}
|
|
689
995
|
}
|
|
690
|
-
function
|
|
691
|
-
|
|
692
|
-
if (
|
|
693
|
-
//
|
|
694
|
-
|
|
996
|
+
function abortRemainingPendingTasks(task, error, debugInfo) {
|
|
997
|
+
let exitStatus;
|
|
998
|
+
if (task.status === 0) {
|
|
999
|
+
// The data for this segment is still missing.
|
|
1000
|
+
task.status = 2;
|
|
1001
|
+
abortPendingCacheNode(task.node, error, debugInfo);
|
|
1002
|
+
// If the server failed to fulfill the data for this segment, it implies
|
|
1003
|
+
// that the route tree received from the server mismatched the tree that
|
|
1004
|
+
// was previously prefetched.
|
|
1005
|
+
//
|
|
1006
|
+
// In an app with fully static routes and no proxy-driven redirects or
|
|
1007
|
+
// rewrites, this should never happen, because the route for a URL would
|
|
1008
|
+
// always be the same across multiple requests. So, this implies that some
|
|
1009
|
+
// runtime routing condition changed, likely in a proxy, without being
|
|
1010
|
+
// pushed to the client.
|
|
1011
|
+
//
|
|
1012
|
+
// When this happens, we treat this the same as a refresh(). The entire
|
|
1013
|
+
// tree will be re-rendered from the root.
|
|
1014
|
+
if (task.refreshUrl === null) {
|
|
1015
|
+
// Trigger a "soft" refresh. Essentially the same as calling `refresh()`
|
|
1016
|
+
// in a Server Action.
|
|
1017
|
+
exitStatus = 1;
|
|
1018
|
+
} else {
|
|
1019
|
+
// The mismatch was discovered inside an inactive parallel route. This
|
|
1020
|
+
// implies the inactive parallel route is no longer reachable at the URL
|
|
1021
|
+
// that originally rendered it. Fall back to an MPA refresh.
|
|
1022
|
+
// TODO: An alternative could be to trigger a soft refresh but to _not_
|
|
1023
|
+
// re-use the inactive parallel routes this time. Similar to what would
|
|
1024
|
+
// happen if were to do a hard refrehs, but without the HTML page.
|
|
1025
|
+
exitStatus = 2;
|
|
1026
|
+
}
|
|
1027
|
+
} else {
|
|
1028
|
+
// This segment finished. (An error here is treated as Done because they are
|
|
1029
|
+
// surfaced to the application during render.)
|
|
1030
|
+
exitStatus = 0;
|
|
695
1031
|
}
|
|
696
1032
|
const taskChildren = task.children;
|
|
697
|
-
if (taskChildren
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
for (const taskChild of taskChildren.values()){
|
|
706
|
-
abortTask(taskChild, error, debugInfo);
|
|
1033
|
+
if (taskChildren !== null) {
|
|
1034
|
+
for (const [, taskChild] of taskChildren){
|
|
1035
|
+
const childExitStatus = abortRemainingPendingTasks(taskChild, error, debugInfo);
|
|
1036
|
+
// Propagate the exit status up the tree. The statuses are ordered by
|
|
1037
|
+
// their precedence.
|
|
1038
|
+
if (childExitStatus > exitStatus) {
|
|
1039
|
+
exitStatus = childExitStatus;
|
|
1040
|
+
}
|
|
707
1041
|
}
|
|
708
1042
|
}
|
|
709
|
-
|
|
710
|
-
task.dynamicRequestTree = null;
|
|
1043
|
+
return exitStatus;
|
|
711
1044
|
}
|
|
712
|
-
function abortPendingCacheNode(
|
|
713
|
-
// For every pending segment in the tree, resolve its `rsc` promise to `null`
|
|
714
|
-
// to trigger a lazy fetch during render.
|
|
715
|
-
//
|
|
716
|
-
// Or, if an error object is provided, it will error instead.
|
|
717
|
-
const routerStateChildren = routerState[1];
|
|
718
|
-
const parallelRoutes = cacheNode.parallelRoutes;
|
|
719
|
-
for(let parallelRouteKey in routerStateChildren){
|
|
720
|
-
const routerStateChild = routerStateChildren[parallelRouteKey];
|
|
721
|
-
const segmentMapChild = parallelRoutes.get(parallelRouteKey);
|
|
722
|
-
if (segmentMapChild === undefined) {
|
|
723
|
-
continue;
|
|
724
|
-
}
|
|
725
|
-
const segmentChild = routerStateChild[0];
|
|
726
|
-
const segmentKeyChild = (0, _createroutercachekey.createRouterCacheKey)(segmentChild);
|
|
727
|
-
const cacheNodeChild = segmentMapChild.get(segmentKeyChild);
|
|
728
|
-
if (cacheNodeChild !== undefined) {
|
|
729
|
-
abortPendingCacheNode(routerStateChild, cacheNodeChild, error, debugInfo);
|
|
730
|
-
} else {
|
|
731
|
-
// This shouldn't happen because we're traversing the same tree that was
|
|
732
|
-
// used to construct the cache nodes in the first place.
|
|
733
|
-
}
|
|
734
|
-
}
|
|
1045
|
+
function abortPendingCacheNode(cacheNode, error, debugInfo) {
|
|
735
1046
|
const rsc = cacheNode.rsc;
|
|
736
1047
|
if (isDeferredRsc(rsc)) {
|
|
737
1048
|
if (error === null) {
|
|
@@ -755,64 +1066,7 @@ function abortPendingCacheNode(routerState, cacheNode, error, debugInfo) {
|
|
|
755
1066
|
head.resolve(null, debugInfo);
|
|
756
1067
|
}
|
|
757
1068
|
}
|
|
758
|
-
function updateCacheNodeOnPopstateRestoration(oldCacheNode, routerState) {
|
|
759
|
-
// A popstate navigation reads data from the local cache. It does not issue
|
|
760
|
-
// new network requests (unless the cache entries have been evicted). So, we
|
|
761
|
-
// update the cache to drop the prefetch data for any segment whose dynamic
|
|
762
|
-
// data was already received. This prevents an unnecessary flash back to PPR
|
|
763
|
-
// state during a back/forward navigation.
|
|
764
|
-
//
|
|
765
|
-
// This function clones the entire cache node tree and sets the `prefetchRsc`
|
|
766
|
-
// field to `null` to prevent it from being rendered. We can't mutate the node
|
|
767
|
-
// in place because this is a concurrent data structure.
|
|
768
|
-
const routerStateChildren = routerState[1];
|
|
769
|
-
const oldParallelRoutes = oldCacheNode.parallelRoutes;
|
|
770
|
-
const newParallelRoutes = new Map(oldParallelRoutes);
|
|
771
|
-
for(let parallelRouteKey in routerStateChildren){
|
|
772
|
-
const routerStateChild = routerStateChildren[parallelRouteKey];
|
|
773
|
-
const segmentChild = routerStateChild[0];
|
|
774
|
-
const segmentKeyChild = (0, _createroutercachekey.createRouterCacheKey)(segmentChild);
|
|
775
|
-
const oldSegmentMapChild = oldParallelRoutes.get(parallelRouteKey);
|
|
776
|
-
if (oldSegmentMapChild !== undefined) {
|
|
777
|
-
const oldCacheNodeChild = oldSegmentMapChild.get(segmentKeyChild);
|
|
778
|
-
if (oldCacheNodeChild !== undefined) {
|
|
779
|
-
const newCacheNodeChild = updateCacheNodeOnPopstateRestoration(oldCacheNodeChild, routerStateChild);
|
|
780
|
-
const newSegmentMapChild = new Map(oldSegmentMapChild);
|
|
781
|
-
newSegmentMapChild.set(segmentKeyChild, newCacheNodeChild);
|
|
782
|
-
newParallelRoutes.set(parallelRouteKey, newSegmentMapChild);
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
// Only show prefetched data if the dynamic data is still pending.
|
|
787
|
-
//
|
|
788
|
-
// Tehnically, what we're actually checking is whether the dynamic network
|
|
789
|
-
// response was received. But since it's a streaming response, this does not
|
|
790
|
-
// mean that all the dynamic data has fully streamed in. It just means that
|
|
791
|
-
// _some_ of the dynamic data was received. But as a heuristic, we assume that
|
|
792
|
-
// the rest dynamic data will stream in quickly, so it's still better to skip
|
|
793
|
-
// the prefetch state.
|
|
794
|
-
const rsc = oldCacheNode.rsc;
|
|
795
|
-
const shouldUsePrefetch = isDeferredRsc(rsc) && rsc.status === 'pending';
|
|
796
|
-
return {
|
|
797
|
-
lazyData: null,
|
|
798
|
-
rsc,
|
|
799
|
-
head: oldCacheNode.head,
|
|
800
|
-
prefetchHead: shouldUsePrefetch ? oldCacheNode.prefetchHead : [
|
|
801
|
-
null,
|
|
802
|
-
null
|
|
803
|
-
],
|
|
804
|
-
prefetchRsc: shouldUsePrefetch ? oldCacheNode.prefetchRsc : null,
|
|
805
|
-
loading: oldCacheNode.loading,
|
|
806
|
-
// These are the cloned children we computed above
|
|
807
|
-
parallelRoutes: newParallelRoutes,
|
|
808
|
-
navigatedAt: oldCacheNode.navigatedAt
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
1069
|
const DEFERRED = Symbol();
|
|
812
|
-
// This type exists to distinguish a DeferredRsc from a Flight promise. It's a
|
|
813
|
-
// compromise to avoid adding an extra field on every Cache Node, which would be
|
|
814
|
-
// awkward because the pre-PPR parts of codebase would need to account for it,
|
|
815
|
-
// too. We can remove it once type Cache Node type is more settled.
|
|
816
1070
|
function isDeferredRsc(value) {
|
|
817
1071
|
return value && typeof value === 'object' && value.tag === DEFERRED;
|
|
818
1072
|
}
|