vinext 0.1.0 → 0.1.2
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.
- package/README.md +2 -5
- package/dist/build/assets-ignore.d.ts +32 -0
- package/dist/build/assets-ignore.js +48 -0
- package/dist/build/client-build-config.d.ts +33 -1
- package/dist/build/client-build-config.js +66 -1
- package/dist/check.js +4 -3
- package/dist/cli.js +2 -0
- package/dist/client/navigation-runtime.d.ts +11 -2
- package/dist/client/navigation-runtime.js +1 -1
- package/dist/client/vinext-next-data.d.ts +2 -1
- package/dist/client/window-next.d.ts +6 -4
- package/dist/config/config-matchers.d.ts +31 -5
- package/dist/config/config-matchers.js +50 -3
- package/dist/config/next-config.d.ts +29 -3
- package/dist/config/next-config.js +32 -2
- package/dist/deploy.js +47 -304
- package/dist/entries/app-rsc-entry.d.ts +8 -2
- package/dist/entries/app-rsc-entry.js +61 -5
- package/dist/entries/app-rsc-manifest.js +20 -2
- package/dist/entries/pages-client-entry.js +1 -1
- package/dist/entries/pages-server-entry.js +16 -7
- package/dist/index.d.ts +0 -2
- package/dist/index.js +233 -280
- package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
- package/dist/plugins/dynamic-preload-metadata.js +415 -0
- package/dist/plugins/og-assets.js +2 -2
- package/dist/plugins/optimize-imports.d.ts +8 -4
- package/dist/plugins/optimize-imports.js +16 -12
- package/dist/plugins/postcss.js +18 -14
- package/dist/plugins/require-context.d.ts +6 -0
- package/dist/plugins/require-context.js +184 -0
- package/dist/plugins/sass.d.ts +53 -24
- package/dist/plugins/sass.js +249 -1
- package/dist/plugins/wasm-module-import.d.ts +15 -0
- package/dist/plugins/wasm-module-import.js +50 -0
- package/dist/routing/app-route-graph.d.ts +35 -2
- package/dist/routing/app-route-graph.js +179 -8
- package/dist/routing/file-matcher.js +1 -1
- package/dist/routing/route-pattern.d.ts +2 -1
- package/dist/routing/route-pattern.js +16 -1
- package/dist/server/api-handler.js +4 -0
- package/dist/server/app-browser-entry.js +155 -215
- package/dist/server/app-browser-error.d.ts +4 -1
- package/dist/server/app-browser-error.js +7 -1
- package/dist/server/app-browser-history-controller.d.ts +104 -0
- package/dist/server/app-browser-history-controller.js +210 -0
- package/dist/server/app-browser-interception-context.d.ts +2 -1
- package/dist/server/app-browser-interception-context.js +15 -2
- package/dist/server/app-browser-navigation-controller.d.ts +13 -2
- package/dist/server/app-browser-navigation-controller.js +83 -4
- package/dist/server/app-browser-popstate.d.ts +12 -3
- package/dist/server/app-browser-popstate.js +19 -4
- package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
- package/dist/server/app-browser-rsc-redirect.js +30 -8
- package/dist/server/app-browser-state.d.ts +3 -0
- package/dist/server/app-browser-state.js +10 -10
- package/dist/server/app-browser-visible-commit.js +10 -8
- package/dist/server/app-fallback-renderer.d.ts +2 -1
- package/dist/server/app-fallback-renderer.js +3 -1
- package/dist/server/app-history-state.d.ts +45 -1
- package/dist/server/app-history-state.js +109 -1
- package/dist/server/app-middleware.js +1 -0
- package/dist/server/app-optimistic-routing.js +22 -1
- package/dist/server/app-page-boundary-render.d.ts +2 -1
- package/dist/server/app-page-boundary-render.js +45 -21
- package/dist/server/app-page-cache.js +9 -7
- package/dist/server/app-page-dispatch.d.ts +14 -0
- package/dist/server/app-page-dispatch.js +21 -6
- package/dist/server/app-page-element-builder.d.ts +23 -2
- package/dist/server/app-page-element-builder.js +58 -17
- package/dist/server/app-page-execution.d.ts +1 -1
- package/dist/server/app-page-execution.js +32 -17
- package/dist/server/app-page-render.d.ts +7 -1
- package/dist/server/app-page-render.js +11 -16
- package/dist/server/app-page-request.d.ts +9 -6
- package/dist/server/app-page-request.js +14 -10
- package/dist/server/app-page-response.d.ts +2 -2
- package/dist/server/app-page-response.js +2 -2
- package/dist/server/app-page-route-wiring.d.ts +3 -1
- package/dist/server/app-page-route-wiring.js +10 -8
- package/dist/server/app-page-stream.d.ts +37 -7
- package/dist/server/app-page-stream.js +36 -6
- package/dist/server/app-pages-bridge.d.ts +16 -0
- package/dist/server/app-pages-bridge.js +23 -3
- package/dist/server/app-route-handler-cache.d.ts +1 -0
- package/dist/server/app-route-handler-cache.js +1 -0
- package/dist/server/app-route-handler-dispatch.d.ts +1 -0
- package/dist/server/app-route-handler-dispatch.js +2 -0
- package/dist/server/app-route-handler-execution.d.ts +1 -0
- package/dist/server/app-route-handler-execution.js +1 -0
- package/dist/server/app-route-handler-response.js +11 -10
- package/dist/server/app-route-handler-runtime.d.ts +1 -0
- package/dist/server/app-route-handler-runtime.js +15 -3
- package/dist/server/app-rsc-handler.d.ts +1 -0
- package/dist/server/app-rsc-handler.js +5 -4
- package/dist/server/app-rsc-response-finalizer.js +1 -1
- package/dist/server/app-rsc-route-matching.d.ts +20 -1
- package/dist/server/app-rsc-route-matching.js +29 -4
- package/dist/server/app-server-action-execution.d.ts +22 -1
- package/dist/server/app-server-action-execution.js +73 -12
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +19 -3
- package/dist/server/app-ssr-stream.js +9 -1
- package/dist/server/dev-lockfile.js +2 -1
- package/dist/server/dev-server.d.ts +1 -1
- package/dist/server/dev-server.js +97 -43
- package/dist/server/headers.d.ts +8 -1
- package/dist/server/headers.js +8 -1
- package/dist/server/instrumentation-runtime.d.ts +6 -0
- package/dist/server/instrumentation-runtime.js +8 -0
- package/dist/server/isr-cache.d.ts +37 -1
- package/dist/server/isr-cache.js +85 -1
- package/dist/server/isr-decision.d.ts +79 -0
- package/dist/server/isr-decision.js +70 -0
- package/dist/server/metadata-route-response.js +5 -3
- package/dist/server/middleware-runtime.d.ts +13 -0
- package/dist/server/middleware-runtime.js +11 -7
- package/dist/server/middleware.js +1 -0
- package/dist/server/navigation-planner.d.ts +62 -1
- package/dist/server/navigation-planner.js +193 -3
- package/dist/server/navigation-trace.d.ts +12 -2
- package/dist/server/navigation-trace.js +11 -1
- package/dist/server/normalize-path.d.ts +0 -8
- package/dist/server/normalize-path.js +3 -1
- package/dist/server/otel-tracer-extension.d.ts +45 -0
- package/dist/server/otel-tracer-extension.js +89 -0
- package/dist/server/pages-api-route.d.ts +14 -3
- package/dist/server/pages-api-route.js +6 -1
- package/dist/server/pages-asset-tags.d.ts +15 -4
- package/dist/server/pages-asset-tags.js +18 -12
- package/dist/server/pages-data-route.js +5 -1
- package/dist/server/pages-node-compat.d.ts +5 -11
- package/dist/server/pages-node-compat.js +175 -118
- package/dist/server/pages-page-data.d.ts +38 -7
- package/dist/server/pages-page-data.js +64 -18
- package/dist/server/pages-page-handler.d.ts +10 -2
- package/dist/server/pages-page-handler.js +49 -20
- package/dist/server/pages-page-response.d.ts +55 -2
- package/dist/server/pages-page-response.js +74 -6
- package/dist/server/pages-readiness.d.ts +36 -0
- package/dist/server/pages-readiness.js +21 -0
- package/dist/server/pages-request-pipeline.d.ts +113 -0
- package/dist/server/pages-request-pipeline.js +230 -0
- package/dist/server/pages-revalidate.d.ts +15 -0
- package/dist/server/pages-revalidate.js +19 -0
- package/dist/server/prod-server.d.ts +45 -3
- package/dist/server/prod-server.js +182 -234
- package/dist/server/socket-error-backstop.d.ts +19 -1
- package/dist/server/socket-error-backstop.js +77 -4
- package/dist/shims/app-router-scroll.js +22 -4
- package/dist/shims/cache-runtime.js +39 -2
- package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
- package/dist/shims/dynamic-preload-chunks.js +77 -0
- package/dist/shims/dynamic.d.ts +4 -0
- package/dist/shims/dynamic.js +4 -2
- package/dist/shims/error-boundary.d.ts +17 -7
- package/dist/shims/error-boundary.js +8 -1
- package/dist/shims/error.js +37 -11
- package/dist/shims/fetch-cache.d.ts +22 -1
- package/dist/shims/fetch-cache.js +28 -1
- package/dist/shims/hash-scroll.d.ts +1 -0
- package/dist/shims/hash-scroll.js +3 -1
- package/dist/shims/head.js +6 -1
- package/dist/shims/headers.d.ts +16 -2
- package/dist/shims/headers.js +37 -1
- package/dist/shims/image-config.js +7 -1
- package/dist/shims/internal/app-route-detection.d.ts +6 -3
- package/dist/shims/internal/app-route-detection.js +10 -6
- package/dist/shims/internal/app-router-context.d.ts +5 -0
- package/dist/shims/internal/link-status-registry.d.ts +43 -0
- package/dist/shims/internal/link-status-registry.js +42 -0
- package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
- package/dist/shims/internal/route-pattern-for-warning.js +40 -0
- package/dist/shims/internal/utils.d.ts +1 -0
- package/dist/shims/link.js +20 -6
- package/dist/shims/metadata.d.ts +6 -2
- package/dist/shims/metadata.js +32 -14
- package/dist/shims/navigation.d.ts +9 -18
- package/dist/shims/navigation.js +96 -23
- package/dist/shims/router-state.d.ts +1 -0
- package/dist/shims/router-state.js +2 -0
- package/dist/shims/router.d.ts +6 -3
- package/dist/shims/router.js +156 -22
- package/dist/shims/script-nonce-context.d.ts +1 -1
- package/dist/shims/script-nonce-context.js +11 -3
- package/dist/shims/server.d.ts +17 -1
- package/dist/shims/server.js +31 -6
- package/dist/shims/slot.js +1 -1
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/typegen.js +1 -0
- package/dist/utils/client-build-manifest.d.ts +8 -1
- package/dist/utils/client-build-manifest.js +41 -6
- package/dist/utils/client-entry-manifest.d.ts +11 -0
- package/dist/utils/client-entry-manifest.js +29 -0
- package/dist/utils/client-runtime-metadata.d.ts +45 -0
- package/dist/utils/client-runtime-metadata.js +63 -0
- package/dist/utils/hash.d.ts +17 -1
- package/dist/utils/hash.js +36 -1
- package/dist/utils/lazy-chunks.d.ts +27 -1
- package/dist/utils/lazy-chunks.js +65 -1
- package/dist/utils/manifest-paths.d.ts +20 -2
- package/dist/utils/manifest-paths.js +38 -3
- package/dist/utils/path.d.ts +2 -1
- package/dist/utils/path.js +5 -1
- package/package.json +6 -2
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
* `uncaughtException`, where this listener filters it.
|
|
16
16
|
*
|
|
17
17
|
* Filters strictly on peer-disconnect codes (ECONNRESET / EPIPE /
|
|
18
|
-
* ECONNABORTED)
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* ECONNABORTED) plus benign static-asset `import()` rejections (see
|
|
19
|
+
* `isBenignAssetImportError`), and synchronously re-throws everything
|
|
20
|
+
* else, preserving Node's default crash semantics for genuine bugs.
|
|
21
|
+
* This is more conservative than Next.js's equivalent
|
|
21
22
|
* (`router-server.ts`'s log-only handler), which silently swallows
|
|
22
23
|
* every uncaught — vinext keeps real bugs surfacing.
|
|
23
24
|
*
|
|
@@ -89,6 +90,68 @@ function peerDisconnectCode(err) {
|
|
|
89
90
|
const code = err?.code;
|
|
90
91
|
return code === "ECONNRESET" || code === "EPIPE" || code === "ECONNABORTED" ? code : void 0;
|
|
91
92
|
}
|
|
93
|
+
const STATIC_ASSET_IMPORT_EXTENSIONS = new Set([
|
|
94
|
+
".css",
|
|
95
|
+
".scss",
|
|
96
|
+
".sass",
|
|
97
|
+
".less",
|
|
98
|
+
".styl",
|
|
99
|
+
".png",
|
|
100
|
+
".jpg",
|
|
101
|
+
".jpeg",
|
|
102
|
+
".gif",
|
|
103
|
+
".svg",
|
|
104
|
+
".webp",
|
|
105
|
+
".avif",
|
|
106
|
+
".ico",
|
|
107
|
+
".bmp",
|
|
108
|
+
".woff",
|
|
109
|
+
".woff2",
|
|
110
|
+
".ttf",
|
|
111
|
+
".otf",
|
|
112
|
+
".eot",
|
|
113
|
+
".txt",
|
|
114
|
+
".md",
|
|
115
|
+
".csv",
|
|
116
|
+
".xml",
|
|
117
|
+
".pdf",
|
|
118
|
+
".wasm"
|
|
119
|
+
]);
|
|
120
|
+
/**
|
|
121
|
+
* Pure predicate: returns `true` when `err` is a benign failure from a
|
|
122
|
+
* dynamic `import()` of a static asset URL — the "URL dependency" pattern
|
|
123
|
+
* that Next.js tolerates at build time. Two shapes are recognised:
|
|
124
|
+
*
|
|
125
|
+
* - `ERR_UNKNOWN_FILE_EXTENSION`: the asset resolved on disk but is not an
|
|
126
|
+
* ES module (e.g. a co-located `./style.css`). The error message ends in
|
|
127
|
+
* the offending extension in quotes: `... extension ".css" for /path`.
|
|
128
|
+
* - `ERR_MODULE_NOT_FOUND`: the asset URL did not resolve (the chunk lives
|
|
129
|
+
* in `dist/server/` but the source asset does not). Node attaches the
|
|
130
|
+
* unresolved specifier on `err.url`; we match only when it points at a
|
|
131
|
+
* static-asset extension.
|
|
132
|
+
*
|
|
133
|
+
* Anything outside this allow-list (including missing `.js`/`.mjs`/`.ts`
|
|
134
|
+
* modules with no extension) returns `false` so real bugs still crash.
|
|
135
|
+
* Exported for unit testing in isolation.
|
|
136
|
+
*/
|
|
137
|
+
function isBenignAssetImportError(err) {
|
|
138
|
+
if (err === null || typeof err !== "object") return false;
|
|
139
|
+
const code = err.code;
|
|
140
|
+
if (code === "ERR_UNKNOWN_FILE_EXTENSION") {
|
|
141
|
+
const message = err.message ?? "";
|
|
142
|
+
const match = /extension\s+"([^"]+)"/.exec(message);
|
|
143
|
+
return match != null && STATIC_ASSET_IMPORT_EXTENSIONS.has(match[1].toLowerCase());
|
|
144
|
+
}
|
|
145
|
+
if (code === "ERR_MODULE_NOT_FOUND") {
|
|
146
|
+
const url = err.url;
|
|
147
|
+
if (typeof url !== "string") return false;
|
|
148
|
+
const pathname = url.split("?", 1)[0].split("#", 1)[0];
|
|
149
|
+
const dot = pathname.lastIndexOf(".");
|
|
150
|
+
if (dot === -1) return false;
|
|
151
|
+
return STATIC_ASSET_IMPORT_EXTENSIONS.has(pathname.slice(dot).toLowerCase());
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
92
155
|
/**
|
|
93
156
|
* Test-only: returns whether the backstop has been installed in this
|
|
94
157
|
* process. Used by the unit test to assert idempotent install via the
|
|
@@ -104,7 +167,16 @@ function installSocketErrorBackstop() {
|
|
|
104
167
|
proc[SOCKET_BACKSTOP_FLAG] = true;
|
|
105
168
|
const debug = process.env.VINEXT_DEBUG_SOCKET_ERRORS === "1";
|
|
106
169
|
if (debug) console.warn("[vinext] socket-error backstop installed");
|
|
170
|
+
const absorbBenignAssetImport = (reason, kind) => {
|
|
171
|
+
if (!isBenignAssetImportError(reason)) return false;
|
|
172
|
+
if (debug) {
|
|
173
|
+
const code = reason?.code;
|
|
174
|
+
console.warn(`[vinext] absorbed ${kind} ${code} (asset URL import)`);
|
|
175
|
+
}
|
|
176
|
+
return true;
|
|
177
|
+
};
|
|
107
178
|
process.on("uncaughtException", (err) => {
|
|
179
|
+
if (absorbBenignAssetImport(err, "uncaughtException")) return;
|
|
108
180
|
if (process.env.VINEXT_PRERENDER === "1") throw err;
|
|
109
181
|
const code = peerDisconnectCode(err);
|
|
110
182
|
if (code) {
|
|
@@ -114,6 +186,7 @@ function installSocketErrorBackstop() {
|
|
|
114
186
|
throw err;
|
|
115
187
|
});
|
|
116
188
|
process.on("unhandledRejection", (reason) => {
|
|
189
|
+
if (absorbBenignAssetImport(reason, "unhandledRejection")) return;
|
|
117
190
|
if (process.env.VINEXT_PRERENDER === "1") throw reason;
|
|
118
191
|
const code = peerDisconnectCode(reason);
|
|
119
192
|
if (code) {
|
|
@@ -124,4 +197,4 @@ function installSocketErrorBackstop() {
|
|
|
124
197
|
});
|
|
125
198
|
}
|
|
126
199
|
//#endregion
|
|
127
|
-
export { installSocketErrorBackstop, isSocketErrorBackstopInstalled, peerDisconnectCode };
|
|
200
|
+
export { installSocketErrorBackstop, isBenignAssetImportError, isSocketErrorBackstopInstalled, peerDisconnectCode };
|
|
@@ -49,14 +49,22 @@ function getHashFragmentDomNode(hash) {
|
|
|
49
49
|
const element = document.getElementById(fragment) ?? document.getElementsByName(fragment)[0];
|
|
50
50
|
return element instanceof HTMLElement ? element : null;
|
|
51
51
|
}
|
|
52
|
+
function isInDocumentHead(node) {
|
|
53
|
+
const head = node.ownerDocument?.head;
|
|
54
|
+
return head != null && head.contains(node);
|
|
55
|
+
}
|
|
52
56
|
function findNextScrollTarget(node) {
|
|
53
57
|
if (!(node instanceof Element)) return null;
|
|
58
|
+
if (isInDocumentHead(node)) return { kind: "document-top" };
|
|
54
59
|
let target = node;
|
|
55
60
|
while (!(target instanceof HTMLElement) || shouldSkipElement(target)) {
|
|
56
61
|
if (target.nextElementSibling === null) return null;
|
|
57
62
|
target = target.nextElementSibling;
|
|
58
63
|
}
|
|
59
|
-
return
|
|
64
|
+
return {
|
|
65
|
+
kind: "element",
|
|
66
|
+
element: target
|
|
67
|
+
};
|
|
60
68
|
}
|
|
61
69
|
function scrollToElement(target, hash) {
|
|
62
70
|
if (hash !== null) {
|
|
@@ -79,9 +87,19 @@ var AppRouterScrollTargetInner = class extends React$1.Component {
|
|
|
79
87
|
if (intent === null) return;
|
|
80
88
|
if (this.props.commitId === null || intent.commitId !== this.props.commitId) return;
|
|
81
89
|
let target;
|
|
82
|
-
if (intent.hash !== null)
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
if (intent.hash !== null) {
|
|
91
|
+
target = getHashFragmentDomNode(intent.hash);
|
|
92
|
+
if (target === null) return;
|
|
93
|
+
} else {
|
|
94
|
+
const next = findNextScrollTarget(findDOMNode(this));
|
|
95
|
+
if (next === null) return;
|
|
96
|
+
if (next.kind === "document-top") {
|
|
97
|
+
if (consumeAppRouterScrollIntent(intent, this.props.commitId) === null) return;
|
|
98
|
+
document.documentElement.scrollTop = 0;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
target = next.element;
|
|
102
|
+
}
|
|
85
103
|
const consumed = consumeAppRouterScrollIntent(intent, this.props.commitId);
|
|
86
104
|
if (consumed === null) return;
|
|
87
105
|
scrollToElement(target, consumed.hash);
|
|
@@ -3,6 +3,7 @@ import { getRequestContext, isInsideUnifiedScope, runWithUnifiedStateMutation }
|
|
|
3
3
|
import { VINEXT_RSC_MARKER_HEADER } from "../server/headers.js";
|
|
4
4
|
import { markDynamicUsage } from "./headers.js";
|
|
5
5
|
import { _registerCacheContextAccessor, _setRequestScopedCacheLife, cacheLifeProfiles, getDataCacheHandler } from "./cache.js";
|
|
6
|
+
import { addCollectedRequestTags, getCurrentFetchSoftTags } from "./fetch-cache.js";
|
|
6
7
|
//#region src/shims/cache-runtime.ts
|
|
7
8
|
/**
|
|
8
9
|
* "use cache" runtime
|
|
@@ -268,8 +269,13 @@ function registerCachedFunction(fn, id, variant, options = {}) {
|
|
|
268
269
|
}
|
|
269
270
|
if (isDev) return executeWithContext(fn, args, cacheVariant);
|
|
270
271
|
const handler = getDataCacheHandler();
|
|
271
|
-
const
|
|
272
|
+
const softTags = getCurrentFetchSoftTags();
|
|
273
|
+
const existing = await handler.get(cacheKey, {
|
|
274
|
+
kind: "FETCH",
|
|
275
|
+
softTags
|
|
276
|
+
});
|
|
272
277
|
if (existing?.value && existing.value.kind === "FETCH" && existing.cacheState !== "stale") try {
|
|
278
|
+
propagateCacheTagsToRequest(existing.value.tags);
|
|
273
279
|
if (rsc && existing.value.data.headers["x-vinext-rsc"] === "1") {
|
|
274
280
|
const stream = uint8ToStream(base64ToUint8(existing.value.data.body));
|
|
275
281
|
const result = await rsc.createFromReadableStream(stream);
|
|
@@ -282,6 +288,7 @@ function registerCachedFunction(fn, id, variant, options = {}) {
|
|
|
282
288
|
} catch {}
|
|
283
289
|
const { result, ctx, effectiveLife } = await runCachedFunctionWithContext(fn, args, cacheVariant);
|
|
284
290
|
recordRequestScopedCacheLife(effectiveLife);
|
|
291
|
+
propagateCacheTagsToRequest(ctx.tags);
|
|
285
292
|
const revalidateSeconds = effectiveLife.revalidate ?? cacheLifeProfiles.default.revalidate ?? 900;
|
|
286
293
|
try {
|
|
287
294
|
let body;
|
|
@@ -318,8 +325,11 @@ function registerCachedFunction(fn, id, variant, options = {}) {
|
|
|
318
325
|
value: fn.length,
|
|
319
326
|
configurable: true
|
|
320
327
|
});
|
|
328
|
+
cachedFn[USE_CACHE_FUNCTION_SYMBOL] = true;
|
|
321
329
|
return cachedFn;
|
|
322
330
|
}
|
|
331
|
+
/** @internal Symbol used to identify "use cache" wrapper functions. */
|
|
332
|
+
const USE_CACHE_FUNCTION_SYMBOL = Symbol.for("vinext.useCacheFunction");
|
|
323
333
|
function throwPrivateUseCacheInsidePublicUseCacheError() {
|
|
324
334
|
const error = /* @__PURE__ */ new Error("\"use cache: private\" must not be used within \"use cache\". It can only be nested inside of another \"use cache: private\".");
|
|
325
335
|
const ctx = getRequestContext();
|
|
@@ -336,6 +346,30 @@ function recordRequestScopedCacheControl(cacheControl) {
|
|
|
336
346
|
function recordRequestScopedCacheLife(cacheLife) {
|
|
337
347
|
_setRequestScopedCacheLife(cacheLife);
|
|
338
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* Bubble a `"use cache"` scope's tags toward where they can drive invalidation.
|
|
351
|
+
*
|
|
352
|
+
* When this cache is nested inside another (`parentCtx` present), the tags flow
|
|
353
|
+
* into the parent scope so they end up on the outer cache entry — mirroring
|
|
354
|
+
* Next.js's `propagateCacheLifeAndTagsToRevalidateStore`. The outermost scope
|
|
355
|
+
* (no parent) instead records onto the surrounding request's collected tags, so
|
|
356
|
+
* the enclosing page / route-handler ISR entry carries them and `revalidateTag`
|
|
357
|
+
* can evict the rendered output (issue #1453).
|
|
358
|
+
*
|
|
359
|
+
* Used by both the data cache HIT and MISS paths. On MISS the parent-bubble for
|
|
360
|
+
* the *executed* scope also happens in `runCachedFunctionWithContext`; this keeps
|
|
361
|
+
* the HIT path (where that function never runs) correct without dropping a nested
|
|
362
|
+
* inner entry's stored tags. Deduped to keep tag lists tidy.
|
|
363
|
+
*/
|
|
364
|
+
function propagateCacheTagsToRequest(tags) {
|
|
365
|
+
if (!tags || tags.length === 0) return;
|
|
366
|
+
const parentCtx = cacheContextStorage.getStore();
|
|
367
|
+
if (parentCtx) {
|
|
368
|
+
for (const tag of tags) if (!parentCtx.tags.includes(tag)) parentCtx.tags.push(tag);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
addCollectedRequestTags(tags);
|
|
372
|
+
}
|
|
339
373
|
async function executeWithContext(fn, args, variant) {
|
|
340
374
|
const { result, ctx: _ctx, effectiveLife } = await runCachedFunctionWithContext(fn, args, variant);
|
|
341
375
|
recordRequestScopedCacheLife(effectiveLife);
|
|
@@ -360,7 +394,10 @@ async function runCachedFunctionWithContext(fn, args, variant) {
|
|
|
360
394
|
const result = await cacheContextStorage.run(ctx, () => fn(...args));
|
|
361
395
|
if (ctx.invalidDynamicUsageError) throw ctx.invalidDynamicUsageError;
|
|
362
396
|
const effectiveLife = resolveCacheLife(ctx.lifeConfigs);
|
|
363
|
-
if (parentCtx)
|
|
397
|
+
if (parentCtx) {
|
|
398
|
+
parentCtx.lifeConfigs.push(effectiveLife);
|
|
399
|
+
for (const tag of ctx.tags) if (!parentCtx.tags.includes(tag)) parentCtx.tags.push(tag);
|
|
400
|
+
}
|
|
364
401
|
if (parentCtx && eagerError && (effectiveLife.revalidate === 0 || effectiveLife.expire !== void 0 && effectiveLife.expire < DYNAMIC_EXPIRE)) parentCtx.dynamicNestedCacheError ??= eagerError;
|
|
365
402
|
if (typeof process !== "undefined" && (process.env.VINEXT_PRERENDER === "1" || process.env.NODE_ENV === "development") && ctx.dynamicNestedCacheError) {
|
|
366
403
|
if (effectiveLife.revalidate === 0 && !ctx.hasExplicitRevalidate) throw new Error(getNestedCacheZeroRevalidateErrorMessage(), { cause: ctx.dynamicNestedCacheError });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/shims/dynamic-preload-chunks.d.ts
|
|
4
|
+
declare function DynamicPreloadChunks(props: {
|
|
5
|
+
moduleIds?: readonly string[];
|
|
6
|
+
}): React.FunctionComponentElement<React.FragmentProps> | null;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { DynamicPreloadChunks };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useScriptNonce } from "./script-nonce-context.js";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import * as ReactDOM from "react-dom";
|
|
5
|
+
//#region src/shims/dynamic-preload-chunks.tsx
|
|
6
|
+
/**
|
|
7
|
+
* Preload links for rendered next/dynamic() boundaries.
|
|
8
|
+
*
|
|
9
|
+
* This MUST be a "use client" component. next/dynamic() can be called from
|
|
10
|
+
* either a Server Component or a Client Component. If this rendered in the
|
|
11
|
+
* environment of the call site, a Server-Component call site would render it in
|
|
12
|
+
* the RSC environment, where the script-nonce React context is unavailable
|
|
13
|
+
* (createContext is not callable in react-server), so emitted preload links
|
|
14
|
+
* would drop the request CSP nonce — a CSP violation under
|
|
15
|
+
* `script-src 'nonce-…' 'strict-dynamic'`.
|
|
16
|
+
*
|
|
17
|
+
* Marking it "use client" forces it into the SSR pass (where vinext installs
|
|
18
|
+
* the ScriptNonceProvider via withScriptNonce()), so the nonce is available
|
|
19
|
+
* regardless of whether the dynamic() call site is a Server or Client
|
|
20
|
+
* Component. This mirrors Next.js's <PreloadChunks> ('use client') and vinext's
|
|
21
|
+
* own next/script shim.
|
|
22
|
+
*
|
|
23
|
+
* Deliberate divergence from Next.js: for CSS we render
|
|
24
|
+
* `<link rel="stylesheet">` WITHOUT `as="style"`. Next.js emits `as="style"`,
|
|
25
|
+
* but per the HTML spec `as` is only meaningful on `rel="preload"`/`modulepreload`
|
|
26
|
+
* — on `rel="stylesheet"` it is ignored by browsers and is semantically wrong.
|
|
27
|
+
* React keys stylesheet resources on href + precedence, not `as`, so omitting it
|
|
28
|
+
* is safe. This is an intentional, documented difference, not a parity bug.
|
|
29
|
+
*/
|
|
30
|
+
function dynamicPreloadHref(file) {
|
|
31
|
+
if (file.startsWith("/") || file.startsWith("http://") || file.startsWith("https://") || file.startsWith("//")) return file;
|
|
32
|
+
return `/${file}`;
|
|
33
|
+
}
|
|
34
|
+
function resolveDynamicPreloadFiles(moduleIds) {
|
|
35
|
+
if (!moduleIds || moduleIds.length === 0) return [];
|
|
36
|
+
const preloadMap = globalThis.__VINEXT_DYNAMIC_PRELOADS__;
|
|
37
|
+
if (!preloadMap) return [];
|
|
38
|
+
const files = [];
|
|
39
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40
|
+
for (const moduleId of moduleIds) for (const file of preloadMap[moduleId] ?? []) {
|
|
41
|
+
if (seen.has(file)) continue;
|
|
42
|
+
seen.add(file);
|
|
43
|
+
files.push(file);
|
|
44
|
+
}
|
|
45
|
+
return files;
|
|
46
|
+
}
|
|
47
|
+
function DynamicPreloadChunks(props) {
|
|
48
|
+
const nonce = useScriptNonce();
|
|
49
|
+
if (typeof window !== "undefined") return null;
|
|
50
|
+
const files = resolveDynamicPreloadFiles(props.moduleIds);
|
|
51
|
+
if (files.length === 0) return null;
|
|
52
|
+
const stylesheets = [];
|
|
53
|
+
for (const file of files) {
|
|
54
|
+
const href = dynamicPreloadHref(file);
|
|
55
|
+
if (href.endsWith(".css")) {
|
|
56
|
+
stylesheets.push(React.createElement("link", {
|
|
57
|
+
key: href,
|
|
58
|
+
rel: "stylesheet",
|
|
59
|
+
href,
|
|
60
|
+
nonce,
|
|
61
|
+
precedence: "dynamic"
|
|
62
|
+
}));
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (href.endsWith(".js") && typeof ReactDOM.preload === "function") {
|
|
66
|
+
const preloadOptions = {
|
|
67
|
+
as: "script",
|
|
68
|
+
fetchPriority: "low",
|
|
69
|
+
nonce
|
|
70
|
+
};
|
|
71
|
+
ReactDOM.preload(href, preloadOptions);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return stylesheets.length > 0 ? React.createElement(React.Fragment, null, ...stylesheets) : null;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { DynamicPreloadChunks };
|
package/dist/shims/dynamic.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ type LoaderFn<P> = () => LoaderComponent<P>;
|
|
|
16
16
|
type DynamicOptions<P> = {
|
|
17
17
|
loading?: ComponentType<DynamicLoadingProps>;
|
|
18
18
|
loader?: Loader<P>;
|
|
19
|
+
loadableGenerated?: {
|
|
20
|
+
modules?: readonly string[];
|
|
21
|
+
};
|
|
22
|
+
modules?: readonly string[];
|
|
19
23
|
ssr?: boolean;
|
|
20
24
|
};
|
|
21
25
|
type Loader<P> = LoaderFn<P> | LoaderComponent<P>;
|
package/dist/shims/dynamic.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DynamicPreloadChunks } from "./dynamic-preload-chunks.js";
|
|
1
2
|
import React from "react";
|
|
2
3
|
//#region src/shims/dynamic.ts
|
|
3
4
|
/**
|
|
@@ -121,8 +122,9 @@ function flushPreloads() {
|
|
|
121
122
|
return Promise.all(pending);
|
|
122
123
|
}
|
|
123
124
|
function dynamic(dynamicInput, options) {
|
|
124
|
-
const { loader: dynamicLoader, loading: LoadingComponent, ssr = true } = normalizeDynamicOptions(dynamicInput, options);
|
|
125
|
+
const { loader: dynamicLoader, loadableGenerated, loading: LoadingComponent, modules, ssr = true } = normalizeDynamicOptions(dynamicInput, options);
|
|
125
126
|
const loader = dynamicLoader ? normalizeLoader(dynamicLoader) : () => Promise.resolve(() => null);
|
|
127
|
+
const preloadModuleIds = loadableGenerated?.modules ?? modules;
|
|
126
128
|
if (!ssr) {
|
|
127
129
|
if (isServer) {
|
|
128
130
|
const SSRFalse = (_props) => LoadingComponent ? React.createElement(LoadingComponent, createDynamicLoadingProps({ pastDelay: false })) : null;
|
|
@@ -174,7 +176,7 @@ function dynamic(dynamicInput, options) {
|
|
|
174
176
|
resetKey: 0
|
|
175
177
|
}, lazyElement);
|
|
176
178
|
}
|
|
177
|
-
return React.createElement(React.Suspense, { fallback }, content);
|
|
179
|
+
return React.createElement(React.Fragment, null, React.createElement(DynamicPreloadChunks, { moduleIds: preloadModuleIds }), React.createElement(React.Suspense, { fallback }, content));
|
|
178
180
|
};
|
|
179
181
|
ServerDynamic.displayName = "DynamicServer";
|
|
180
182
|
return ServerDynamic;
|
|
@@ -17,11 +17,11 @@ type RedirectBoundaryState = {
|
|
|
17
17
|
redirectType: "push" | "replace" | null;
|
|
18
18
|
};
|
|
19
19
|
type ErrorBoundaryInnerProps = {
|
|
20
|
-
pathname: string;
|
|
20
|
+
pathname: string | null;
|
|
21
21
|
} & ErrorBoundaryProps;
|
|
22
22
|
type ErrorBoundaryState = {
|
|
23
23
|
error: CapturedError | null;
|
|
24
|
-
previousPathname: string;
|
|
24
|
+
previousPathname: string | null;
|
|
25
25
|
previousResetKey: string | null;
|
|
26
26
|
};
|
|
27
27
|
declare class RedirectErrorBoundary extends React.Component<{
|
|
@@ -58,6 +58,16 @@ declare function ErrorBoundary({
|
|
|
58
58
|
children,
|
|
59
59
|
resetKey
|
|
60
60
|
}: ErrorBoundaryProps): React.JSX.Element;
|
|
61
|
+
declare function GlobalErrorBoundary({
|
|
62
|
+
fallback,
|
|
63
|
+
children
|
|
64
|
+
}: {
|
|
65
|
+
fallback: React.ComponentType<{
|
|
66
|
+
error: unknown;
|
|
67
|
+
reset: () => void;
|
|
68
|
+
}>;
|
|
69
|
+
children: React.ReactNode;
|
|
70
|
+
}): React.JSX.Element;
|
|
61
71
|
type NotFoundBoundaryProps = {
|
|
62
72
|
fallback: React.ReactNode;
|
|
63
73
|
children: React.ReactNode;
|
|
@@ -78,11 +88,11 @@ type ForbiddenBoundaryProps = {
|
|
|
78
88
|
resetKey?: string | null;
|
|
79
89
|
};
|
|
80
90
|
type ForbiddenBoundaryInnerProps = {
|
|
81
|
-
pathname: string;
|
|
91
|
+
pathname: string | null;
|
|
82
92
|
} & ForbiddenBoundaryProps;
|
|
83
93
|
type ForbiddenBoundaryState = {
|
|
84
94
|
forbidden: boolean;
|
|
85
|
-
previousPathname: string;
|
|
95
|
+
previousPathname: string | null;
|
|
86
96
|
previousResetKey: string | null;
|
|
87
97
|
};
|
|
88
98
|
declare class ForbiddenBoundaryInner extends React.Component<ForbiddenBoundaryInnerProps, ForbiddenBoundaryState> {
|
|
@@ -102,11 +112,11 @@ type UnauthorizedBoundaryProps = {
|
|
|
102
112
|
resetKey?: string | null;
|
|
103
113
|
};
|
|
104
114
|
type UnauthorizedBoundaryInnerProps = {
|
|
105
|
-
pathname: string;
|
|
115
|
+
pathname: string | null;
|
|
106
116
|
} & UnauthorizedBoundaryProps;
|
|
107
117
|
type UnauthorizedBoundaryState = {
|
|
108
118
|
unauthorized: boolean;
|
|
109
|
-
previousPathname: string;
|
|
119
|
+
previousPathname: string | null;
|
|
110
120
|
previousResetKey: string | null;
|
|
111
121
|
};
|
|
112
122
|
declare class UnauthorizedBoundaryInner extends React.Component<UnauthorizedBoundaryInnerProps, UnauthorizedBoundaryState> {
|
|
@@ -140,4 +150,4 @@ declare class DevRecoveryBoundary extends React.Component<DevRecoveryBoundaryPro
|
|
|
140
150
|
render(): React.ReactNode;
|
|
141
151
|
}
|
|
142
152
|
//#endregion
|
|
143
|
-
export { DevRecoveryBoundary, DevRecoveryBoundaryProps, ErrorBoundary, ErrorBoundaryInner, ErrorBoundaryProps, ErrorBoundaryState, ForbiddenBoundary, ForbiddenBoundaryInner, NotFoundBoundary, RedirectBoundary, RedirectErrorBoundary, UnauthorizedBoundary, UnauthorizedBoundaryInner };
|
|
153
|
+
export { DevRecoveryBoundary, DevRecoveryBoundaryProps, ErrorBoundary, ErrorBoundaryInner, ErrorBoundaryProps, ErrorBoundaryState, ForbiddenBoundary, ForbiddenBoundaryInner, GlobalErrorBoundary, NotFoundBoundary, RedirectBoundary, RedirectErrorBoundary, UnauthorizedBoundary, UnauthorizedBoundaryInner };
|
|
@@ -146,6 +146,13 @@ function ErrorBoundary({ fallback, children, resetKey }) {
|
|
|
146
146
|
children
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
|
+
function GlobalErrorBoundary({ fallback, children }) {
|
|
150
|
+
return /* @__PURE__ */ jsx(ErrorBoundaryInner, {
|
|
151
|
+
pathname: usePathname(),
|
|
152
|
+
fallback,
|
|
153
|
+
children
|
|
154
|
+
});
|
|
155
|
+
}
|
|
149
156
|
/**
|
|
150
157
|
* Inner class component that catches notFound() errors and renders the
|
|
151
158
|
* not-found.tsx fallback. Resets on the caller's segment reset key when one is
|
|
@@ -323,4 +330,4 @@ var DevRecoveryBoundary = class extends React.Component {
|
|
|
323
330
|
}
|
|
324
331
|
};
|
|
325
332
|
//#endregion
|
|
326
|
-
export { DevRecoveryBoundary, ErrorBoundary, ErrorBoundaryInner, ForbiddenBoundary, ForbiddenBoundaryInner, NotFoundBoundary, RedirectBoundary, RedirectErrorBoundary, UnauthorizedBoundary, UnauthorizedBoundaryInner };
|
|
333
|
+
export { DevRecoveryBoundary, ErrorBoundary, ErrorBoundaryInner, ForbiddenBoundary, ForbiddenBoundaryInner, GlobalErrorBoundary, NotFoundBoundary, RedirectBoundary, RedirectErrorBoundary, UnauthorizedBoundary, UnauthorizedBoundaryInner };
|
package/dist/shims/error.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { AppRouterContext } from "./internal/app-router-context.js";
|
|
1
2
|
import { RouterContext } from "./internal/router-context.js";
|
|
2
|
-
import {
|
|
3
|
+
import { isNextRouterError, usePathname } from "./navigation.js";
|
|
3
4
|
import React from "react";
|
|
4
5
|
//#region src/shims/error.tsx
|
|
5
6
|
/**
|
|
@@ -39,33 +40,53 @@ function ErrorComponent({ statusCode, title }) {
|
|
|
39
40
|
margin: 0
|
|
40
41
|
} }, displayTitle + "."))));
|
|
41
42
|
}
|
|
43
|
+
const _CatchErrorAppRouterContext = AppRouterContext ?? React.createContext(null);
|
|
42
44
|
var _CatchError = class extends React.Component {
|
|
43
|
-
static contextType =
|
|
45
|
+
static contextType = _CatchErrorAppRouterContext;
|
|
44
46
|
static displayName = "unstable_catchError(Next.CatchError)";
|
|
45
|
-
|
|
47
|
+
constructor(props) {
|
|
48
|
+
super(props);
|
|
49
|
+
this.state = {
|
|
50
|
+
error: null,
|
|
51
|
+
previousPathname: props.pathname
|
|
52
|
+
};
|
|
53
|
+
}
|
|
46
54
|
static getDerivedStateFromError(thrownValue) {
|
|
47
55
|
if (isNextRouterError(thrownValue)) throw thrownValue;
|
|
48
56
|
return { error: { thrownValue } };
|
|
49
57
|
}
|
|
58
|
+
static getDerivedStateFromProps(props, state) {
|
|
59
|
+
if (props.pathname !== state.previousPathname && state.error) return {
|
|
60
|
+
error: null,
|
|
61
|
+
previousPathname: props.pathname
|
|
62
|
+
};
|
|
63
|
+
return {
|
|
64
|
+
error: state.error,
|
|
65
|
+
previousPathname: props.pathname
|
|
66
|
+
};
|
|
67
|
+
}
|
|
50
68
|
reset = () => {
|
|
51
69
|
this.setState({ error: null });
|
|
52
70
|
};
|
|
53
71
|
unstable_retry = () => {
|
|
54
|
-
if (this.
|
|
55
|
-
if (typeof window === "undefined") throw new Error("`unstable_retry()` can only be used on the client. Call it from a user interaction handler inside the error fallback.");
|
|
72
|
+
if (this.props.isPagesRouter) throw new Error("`unstable_retry()` can only be used in the App Router. Use `reset()` in the Pages Router.");
|
|
56
73
|
React.startTransition(() => {
|
|
57
|
-
|
|
74
|
+
this.context?.refresh();
|
|
58
75
|
this.reset();
|
|
59
76
|
});
|
|
60
77
|
};
|
|
61
78
|
render() {
|
|
62
79
|
if (this.state.error) {
|
|
80
|
+
const Fallback = this.props.fallback;
|
|
63
81
|
const errorInfo = {
|
|
64
82
|
error: this.state.error.thrownValue,
|
|
65
83
|
reset: this.reset,
|
|
66
84
|
unstable_retry: this.unstable_retry
|
|
67
85
|
};
|
|
68
|
-
return
|
|
86
|
+
return React.createElement(Fallback, {
|
|
87
|
+
props: this.props.props,
|
|
88
|
+
errorInfo
|
|
89
|
+
});
|
|
69
90
|
}
|
|
70
91
|
return this.props.children;
|
|
71
92
|
}
|
|
@@ -79,13 +100,18 @@ var _CatchError = class extends React.Component {
|
|
|
79
100
|
* https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/catch-error.tsx
|
|
80
101
|
*/
|
|
81
102
|
function unstable_catchError(fallback) {
|
|
82
|
-
const
|
|
103
|
+
const Fallback = ({ props, errorInfo }) => fallback(props, errorInfo);
|
|
104
|
+
Fallback.displayName = fallback.name || "CatchErrorFallback";
|
|
83
105
|
function CatchErrorBoundary(allProps) {
|
|
84
106
|
const { children, ...rest } = allProps;
|
|
107
|
+
const pathname = usePathname();
|
|
108
|
+
const isPagesRouter = React.useContext(RouterContext) !== null;
|
|
85
109
|
const forwardedProps = rest;
|
|
86
|
-
return React.createElement(
|
|
87
|
-
fallback,
|
|
88
|
-
|
|
110
|
+
return React.createElement(_CatchError, {
|
|
111
|
+
fallback: Fallback,
|
|
112
|
+
isPagesRouter,
|
|
113
|
+
pathname,
|
|
114
|
+
props: forwardedProps
|
|
89
115
|
}, children);
|
|
90
116
|
}
|
|
91
117
|
CatchErrorBoundary.displayName = `unstable_catchError(${fallback.name || "CatchErrorFallback"})`;
|
|
@@ -54,6 +54,19 @@ declare function consumeDynamicFetchObservations(): string[];
|
|
|
54
54
|
* fetch tags used during rendering.
|
|
55
55
|
*/
|
|
56
56
|
declare function getCollectedFetchTags(): string[];
|
|
57
|
+
/**
|
|
58
|
+
* Append cache tags to the current request's collected tags.
|
|
59
|
+
*
|
|
60
|
+
* Mirrors Next.js's `propagateCacheLifeAndTagsToRevalidateStore`: tags declared
|
|
61
|
+
* inside a `"use cache"` function (via `cacheTag()`, persisted on the data cache
|
|
62
|
+
* entry) must also bubble up to the surrounding page / route-handler ISR entry
|
|
63
|
+
* so `revalidateTag()` / `revalidatePath()` can evict the rendered output, not
|
|
64
|
+
* just the inner data cache entry. Without this, a cached `"use cache"` result
|
|
65
|
+
* keeps being served from a stale page/route entry after its tag is revalidated
|
|
66
|
+
* (issue #1453). Tags are already encoded by the caller; deduped to match the
|
|
67
|
+
* tagged-fetch path. A no-op for empty input.
|
|
68
|
+
*/
|
|
69
|
+
declare function addCollectedRequestTags(tags: readonly string[]): void;
|
|
57
70
|
/**
|
|
58
71
|
* Set path-derived implicit tags for fetch cache reads in the current render.
|
|
59
72
|
*
|
|
@@ -62,6 +75,14 @@ declare function getCollectedFetchTags(): string[];
|
|
|
62
75
|
* affected route, without permanently coupling a shared fetch entry to one path.
|
|
63
76
|
*/
|
|
64
77
|
declare function setCurrentFetchSoftTags(tags: string[]): void;
|
|
78
|
+
/**
|
|
79
|
+
* Read the path-derived soft tags for the current render.
|
|
80
|
+
*
|
|
81
|
+
* Used by the "use cache" runtime to pass soft tags to the cache handler
|
|
82
|
+
* so that `revalidatePath()` invalidates "use cache" entries during the
|
|
83
|
+
* affected route's next request, even when the entry carries no hard tags.
|
|
84
|
+
*/
|
|
85
|
+
declare function getCurrentFetchSoftTags(): string[];
|
|
65
86
|
declare function setCurrentFetchCacheMode(mode: FetchCacheMode | null): void;
|
|
66
87
|
/**
|
|
67
88
|
* Install the patched fetch and reset per-request tag state.
|
|
@@ -110,4 +131,4 @@ declare function ensureFetchPatch(): void;
|
|
|
110
131
|
*/
|
|
111
132
|
declare function getOriginalFetch(): typeof globalThis.fetch;
|
|
112
133
|
//#endregion
|
|
113
|
-
export { FetchCacheMode, FetchCacheState, _resetPendingRefetches, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, getOriginalFetch, peekCacheableFetchObservations, peekDynamicFetchObservations, runWithFetchCache, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, withFetchCache };
|
|
134
|
+
export { FetchCacheMode, FetchCacheState, _resetPendingRefetches, addCollectedRequestTags, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, getCurrentFetchSoftTags, getOriginalFetch, peekCacheableFetchObservations, peekDynamicFetchObservations, runWithFetchCache, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, withFetchCache };
|
|
@@ -343,6 +343,23 @@ function getCollectedFetchTags() {
|
|
|
343
343
|
return [..._getState().currentRequestTags];
|
|
344
344
|
}
|
|
345
345
|
/**
|
|
346
|
+
* Append cache tags to the current request's collected tags.
|
|
347
|
+
*
|
|
348
|
+
* Mirrors Next.js's `propagateCacheLifeAndTagsToRevalidateStore`: tags declared
|
|
349
|
+
* inside a `"use cache"` function (via `cacheTag()`, persisted on the data cache
|
|
350
|
+
* entry) must also bubble up to the surrounding page / route-handler ISR entry
|
|
351
|
+
* so `revalidateTag()` / `revalidatePath()` can evict the rendered output, not
|
|
352
|
+
* just the inner data cache entry. Without this, a cached `"use cache"` result
|
|
353
|
+
* keeps being served from a stale page/route entry after its tag is revalidated
|
|
354
|
+
* (issue #1453). Tags are already encoded by the caller; deduped to match the
|
|
355
|
+
* tagged-fetch path. A no-op for empty input.
|
|
356
|
+
*/
|
|
357
|
+
function addCollectedRequestTags(tags) {
|
|
358
|
+
if (tags.length === 0) return;
|
|
359
|
+
const reqTags = _getState().currentRequestTags;
|
|
360
|
+
for (const tag of tags) if (!reqTags.includes(tag)) reqTags.push(tag);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
346
363
|
* Set path-derived implicit tags for fetch cache reads in the current render.
|
|
347
364
|
*
|
|
348
365
|
* These are intentionally not persisted on fetch entries. They mirror Next.js
|
|
@@ -352,6 +369,16 @@ function getCollectedFetchTags() {
|
|
|
352
369
|
function setCurrentFetchSoftTags(tags) {
|
|
353
370
|
_getState().currentFetchSoftTags = [...tags];
|
|
354
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Read the path-derived soft tags for the current render.
|
|
374
|
+
*
|
|
375
|
+
* Used by the "use cache" runtime to pass soft tags to the cache handler
|
|
376
|
+
* so that `revalidatePath()` invalidates "use cache" entries during the
|
|
377
|
+
* affected route's next request, even when the entry carries no hard tags.
|
|
378
|
+
*/
|
|
379
|
+
function getCurrentFetchSoftTags() {
|
|
380
|
+
return _getState().currentFetchSoftTags;
|
|
381
|
+
}
|
|
355
382
|
function setCurrentFetchCacheMode(mode) {
|
|
356
383
|
_getState().currentFetchCacheMode = mode;
|
|
357
384
|
}
|
|
@@ -711,4 +738,4 @@ function getOriginalFetch() {
|
|
|
711
738
|
return originalFetch;
|
|
712
739
|
}
|
|
713
740
|
//#endregion
|
|
714
|
-
export { _resetPendingRefetches, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, getOriginalFetch, peekCacheableFetchObservations, peekDynamicFetchObservations, runWithFetchCache, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, withFetchCache };
|
|
741
|
+
export { _resetPendingRefetches, addCollectedRequestTags, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, getCurrentFetchSoftTags, getOriginalFetch, peekCacheableFetchObservations, peekDynamicFetchObservations, runWithFetchCache, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, withFetchCache };
|
|
@@ -3,6 +3,7 @@ declare function decodeHashFragment(fragment: string): string;
|
|
|
3
3
|
declare function scrollToHashTarget(hash: string): void;
|
|
4
4
|
declare function scrollToHashTargetOnNextFrame(hash: string): void;
|
|
5
5
|
declare function retryScrollTo(x: number, y: number, opts?: {
|
|
6
|
+
minFrames?: number;
|
|
6
7
|
shouldContinue?: () => boolean;
|
|
7
8
|
}): void;
|
|
8
9
|
//#endregion
|
|
@@ -25,12 +25,14 @@ function scrollToHashTargetOnNextFrame(hash) {
|
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
function retryScrollTo(x, y, opts) {
|
|
28
|
+
const minFrames = opts?.minFrames ?? 0;
|
|
28
29
|
const shouldContinue = opts?.shouldContinue ?? (() => true);
|
|
29
30
|
let attempts = 0;
|
|
30
31
|
const restore = () => {
|
|
31
32
|
if (!shouldContinue()) return;
|
|
32
33
|
window.scrollTo(x, y);
|
|
33
|
-
|
|
34
|
+
const reachedTarget = Math.abs(window.scrollY - y) <= 1;
|
|
35
|
+
if (!shouldContinue() || reachedTarget && attempts >= minFrames || attempts >= 60) return;
|
|
34
36
|
attempts += 1;
|
|
35
37
|
requestAnimationFrame(restore);
|
|
36
38
|
};
|