vinext 0.1.0 → 0.1.1
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/dist/build/assets-ignore.d.ts +32 -0
- package/dist/build/assets-ignore.js +48 -0
- package/dist/build/client-build-config.d.ts +27 -1
- package/dist/build/client-build-config.js +58 -1
- package/dist/cli.js +2 -0
- package/dist/client/navigation-runtime.d.ts +8 -0
- package/dist/client/navigation-runtime.js +1 -1
- package/dist/client/vinext-next-data.d.ts +2 -1
- package/dist/config/config-matchers.d.ts +20 -1
- package/dist/config/config-matchers.js +35 -1
- package/dist/config/next-config.d.ts +16 -3
- package/dist/config/next-config.js +30 -2
- package/dist/deploy.js +40 -304
- package/dist/entries/app-rsc-entry.d.ts +8 -2
- package/dist/entries/app-rsc-entry.js +54 -4
- package/dist/entries/app-rsc-manifest.js +20 -2
- package/dist/entries/pages-server-entry.js +9 -1
- package/dist/index.js +162 -217
- 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/routing/app-route-graph.d.ts +12 -1
- package/dist/routing/app-route-graph.js +137 -5
- 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 +84 -39
- 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 +11 -1
- package/dist/server/app-browser-navigation-controller.js +77 -1
- package/dist/server/app-browser-popstate.d.ts +12 -3
- package/dist/server/app-browser-popstate.js +19 -4
- package/dist/server/app-browser-state.d.ts +3 -0
- package/dist/server/app-browser-state.js +6 -3
- package/dist/server/app-browser-visible-commit.js +9 -7
- package/dist/server/app-history-state.d.ts +45 -1
- package/dist/server/app-history-state.js +109 -1
- package/dist/server/app-page-boundary-render.js +41 -19
- package/dist/server/app-page-dispatch.d.ts +6 -0
- package/dist/server/app-page-dispatch.js +3 -1
- package/dist/server/app-page-element-builder.d.ts +1 -0
- package/dist/server/app-page-element-builder.js +22 -10
- package/dist/server/app-page-render.d.ts +6 -0
- package/dist/server/app-page-render.js +5 -3
- package/dist/server/app-page-request.d.ts +8 -6
- package/dist/server/app-page-request.js +12 -9
- package/dist/server/app-page-response.d.ts +2 -2
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.js +2 -1
- package/dist/server/app-page-stream.d.ts +37 -2
- package/dist/server/app-page-stream.js +36 -3
- 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-runtime.d.ts +1 -0
- package/dist/server/app-route-handler-runtime.js +3 -2
- package/dist/server/app-rsc-handler.d.ts +1 -0
- package/dist/server/app-rsc-handler.js +4 -3
- 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 +11 -1
- package/dist/server/app-server-action-execution.js +68 -10
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +17 -1
- package/dist/server/dev-server.d.ts +1 -1
- package/dist/server/dev-server.js +54 -31
- package/dist/server/isr-cache.d.ts +37 -1
- package/dist/server/isr-cache.js +85 -1
- package/dist/server/navigation-planner.js +5 -3
- package/dist/server/navigation-trace.d.ts +1 -1
- package/dist/server/pages-node-compat.d.ts +2 -0
- package/dist/server/pages-node-compat.js +4 -0
- package/dist/server/pages-page-data.d.ts +10 -7
- package/dist/server/pages-page-data.js +4 -2
- package/dist/server/pages-page-handler.d.ts +9 -2
- package/dist/server/pages-page-handler.js +29 -16
- package/dist/server/pages-page-response.d.ts +11 -2
- package/dist/server/pages-page-response.js +8 -1
- 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 +99 -0
- package/dist/server/pages-request-pipeline.js +209 -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 +6 -2
- package/dist/server/prod-server.js +101 -217
- 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 +31 -1
- package/dist/shims/error-boundary.d.ts +21 -11
- package/dist/shims/error-boundary.js +8 -1
- package/dist/shims/fetch-cache.d.ts +14 -1
- package/dist/shims/fetch-cache.js +18 -1
- package/dist/shims/hash-scroll.d.ts +1 -0
- package/dist/shims/hash-scroll.js +3 -1
- 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/navigation.d.ts +2 -2
- package/dist/shims/navigation.js +63 -7
- 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 +128 -21
- package/dist/utils/client-build-manifest.d.ts +8 -1
- package/dist/utils/client-build-manifest.js +30 -5
- package/dist/utils/client-entry-manifest.d.ts +11 -0
- package/dist/utils/client-entry-manifest.js +29 -0
- package/package.json +5 -1
|
@@ -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 } from "./fetch-cache.js";
|
|
6
7
|
//#region src/shims/cache-runtime.ts
|
|
7
8
|
/**
|
|
8
9
|
* "use cache" runtime
|
|
@@ -270,6 +271,7 @@ function registerCachedFunction(fn, id, variant, options = {}) {
|
|
|
270
271
|
const handler = getDataCacheHandler();
|
|
271
272
|
const existing = await handler.get(cacheKey, { kind: "FETCH" });
|
|
272
273
|
if (existing?.value && existing.value.kind === "FETCH" && existing.cacheState !== "stale") try {
|
|
274
|
+
propagateCacheTagsToRequest(existing.value.tags);
|
|
273
275
|
if (rsc && existing.value.data.headers["x-vinext-rsc"] === "1") {
|
|
274
276
|
const stream = uint8ToStream(base64ToUint8(existing.value.data.body));
|
|
275
277
|
const result = await rsc.createFromReadableStream(stream);
|
|
@@ -282,6 +284,7 @@ function registerCachedFunction(fn, id, variant, options = {}) {
|
|
|
282
284
|
} catch {}
|
|
283
285
|
const { result, ctx, effectiveLife } = await runCachedFunctionWithContext(fn, args, cacheVariant);
|
|
284
286
|
recordRequestScopedCacheLife(effectiveLife);
|
|
287
|
+
propagateCacheTagsToRequest(ctx.tags);
|
|
285
288
|
const revalidateSeconds = effectiveLife.revalidate ?? cacheLifeProfiles.default.revalidate ?? 900;
|
|
286
289
|
try {
|
|
287
290
|
let body;
|
|
@@ -336,6 +339,30 @@ function recordRequestScopedCacheControl(cacheControl) {
|
|
|
336
339
|
function recordRequestScopedCacheLife(cacheLife) {
|
|
337
340
|
_setRequestScopedCacheLife(cacheLife);
|
|
338
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* Bubble a `"use cache"` scope's tags toward where they can drive invalidation.
|
|
344
|
+
*
|
|
345
|
+
* When this cache is nested inside another (`parentCtx` present), the tags flow
|
|
346
|
+
* into the parent scope so they end up on the outer cache entry — mirroring
|
|
347
|
+
* Next.js's `propagateCacheLifeAndTagsToRevalidateStore`. The outermost scope
|
|
348
|
+
* (no parent) instead records onto the surrounding request's collected tags, so
|
|
349
|
+
* the enclosing page / route-handler ISR entry carries them and `revalidateTag`
|
|
350
|
+
* can evict the rendered output (issue #1453).
|
|
351
|
+
*
|
|
352
|
+
* Used by both the data cache HIT and MISS paths. On MISS the parent-bubble for
|
|
353
|
+
* the *executed* scope also happens in `runCachedFunctionWithContext`; this keeps
|
|
354
|
+
* the HIT path (where that function never runs) correct without dropping a nested
|
|
355
|
+
* inner entry's stored tags. Deduped to keep tag lists tidy.
|
|
356
|
+
*/
|
|
357
|
+
function propagateCacheTagsToRequest(tags) {
|
|
358
|
+
if (!tags || tags.length === 0) return;
|
|
359
|
+
const parentCtx = cacheContextStorage.getStore();
|
|
360
|
+
if (parentCtx) {
|
|
361
|
+
for (const tag of tags) if (!parentCtx.tags.includes(tag)) parentCtx.tags.push(tag);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
addCollectedRequestTags(tags);
|
|
365
|
+
}
|
|
339
366
|
async function executeWithContext(fn, args, variant) {
|
|
340
367
|
const { result, ctx: _ctx, effectiveLife } = await runCachedFunctionWithContext(fn, args, variant);
|
|
341
368
|
recordRequestScopedCacheLife(effectiveLife);
|
|
@@ -360,7 +387,10 @@ async function runCachedFunctionWithContext(fn, args, variant) {
|
|
|
360
387
|
const result = await cacheContextStorage.run(ctx, () => fn(...args));
|
|
361
388
|
if (ctx.invalidDynamicUsageError) throw ctx.invalidDynamicUsageError;
|
|
362
389
|
const effectiveLife = resolveCacheLife(ctx.lifeConfigs);
|
|
363
|
-
if (parentCtx)
|
|
390
|
+
if (parentCtx) {
|
|
391
|
+
parentCtx.lifeConfigs.push(effectiveLife);
|
|
392
|
+
for (const tag of ctx.tags) if (!parentCtx.tags.includes(tag)) parentCtx.tags.push(tag);
|
|
393
|
+
}
|
|
364
394
|
if (parentCtx && eagerError && (effectiveLife.revalidate === 0 || effectiveLife.expire !== void 0 && effectiveLife.expire < DYNAMIC_EXPIRE)) parentCtx.dynamicNestedCacheError ??= eagerError;
|
|
365
395
|
if (typeof process !== "undefined" && (process.env.VINEXT_PRERENDER === "1" || process.env.NODE_ENV === "development") && ctx.dynamicNestedCacheError) {
|
|
366
396
|
if (effectiveLife.revalidate === 0 && !ctx.hasExplicitRevalidate) throw new Error(getNestedCacheZeroRevalidateErrorMessage(), { cause: ctx.dynamicNestedCacheError });
|
|
@@ -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<{
|
|
@@ -31,7 +31,7 @@ declare class RedirectErrorBoundary extends React.Component<{
|
|
|
31
31
|
children?: React.ReactNode;
|
|
32
32
|
});
|
|
33
33
|
static getDerivedStateFromError(error: unknown): RedirectBoundaryState;
|
|
34
|
-
render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> |
|
|
34
|
+
render(): string | number | bigint | boolean | React.JSX.Element | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | null | undefined;
|
|
35
35
|
}
|
|
36
36
|
declare function RedirectBoundary({
|
|
37
37
|
children
|
|
@@ -51,13 +51,23 @@ declare class ErrorBoundaryInner extends React.Component<ErrorBoundaryInnerProps
|
|
|
51
51
|
componentDidMount(): void;
|
|
52
52
|
componentWillUnmount(): void;
|
|
53
53
|
reset: () => void;
|
|
54
|
-
render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> |
|
|
54
|
+
render(): string | number | bigint | boolean | React.JSX.Element | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | null | undefined;
|
|
55
55
|
}
|
|
56
56
|
declare function ErrorBoundary({
|
|
57
57
|
fallback,
|
|
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,18 +88,18 @@ 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> {
|
|
89
99
|
constructor(props: ForbiddenBoundaryInnerProps);
|
|
90
100
|
static getDerivedStateFromProps(props: ForbiddenBoundaryInnerProps, state: ForbiddenBoundaryState): ForbiddenBoundaryState | null;
|
|
91
101
|
static getDerivedStateFromError(error: unknown): Partial<ForbiddenBoundaryState>;
|
|
92
|
-
render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> |
|
|
102
|
+
render(): string | number | bigint | boolean | React.JSX.Element | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | null | undefined;
|
|
93
103
|
}
|
|
94
104
|
declare function ForbiddenBoundary({
|
|
95
105
|
fallback,
|
|
@@ -102,18 +112,18 @@ 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> {
|
|
113
123
|
constructor(props: UnauthorizedBoundaryInnerProps);
|
|
114
124
|
static getDerivedStateFromProps(props: UnauthorizedBoundaryInnerProps, state: UnauthorizedBoundaryState): UnauthorizedBoundaryState | null;
|
|
115
125
|
static getDerivedStateFromError(error: unknown): Partial<UnauthorizedBoundaryState>;
|
|
116
|
-
render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> |
|
|
126
|
+
render(): string | number | bigint | boolean | React.JSX.Element | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | null | undefined;
|
|
117
127
|
}
|
|
118
128
|
declare function UnauthorizedBoundary({
|
|
119
129
|
fallback,
|
|
@@ -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 };
|
|
@@ -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
|
*
|
|
@@ -110,4 +123,4 @@ declare function ensureFetchPatch(): void;
|
|
|
110
123
|
*/
|
|
111
124
|
declare function getOriginalFetch(): typeof globalThis.fetch;
|
|
112
125
|
//#endregion
|
|
113
|
-
export { FetchCacheMode, FetchCacheState, _resetPendingRefetches, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, getOriginalFetch, peekCacheableFetchObservations, peekDynamicFetchObservations, runWithFetchCache, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, withFetchCache };
|
|
126
|
+
export { FetchCacheMode, FetchCacheState, _resetPendingRefetches, addCollectedRequestTags, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, 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
|
|
@@ -711,4 +728,4 @@ function getOriginalFetch() {
|
|
|
711
728
|
return originalFetch;
|
|
712
729
|
}
|
|
713
730
|
//#endregion
|
|
714
|
-
export { _resetPendingRefetches, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, getOriginalFetch, peekCacheableFetchObservations, peekDynamicFetchObservations, runWithFetchCache, runWithFetchDedupe, setCurrentFetchCacheMode, setCurrentFetchSoftTags, withFetchCache };
|
|
731
|
+
export { _resetPendingRefetches, addCollectedRequestTags, consumeDynamicFetchObservations, ensureFetchPatch, getCollectedFetchTags, 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
|
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/shims/internal/link-status-registry.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Link-status pending registry.
|
|
4
|
+
*
|
|
5
|
+
* Tracks the single <Link> that started the most recent App Router navigation
|
|
6
|
+
* so its `useLinkStatus()` pending state can be reset when a *different*
|
|
7
|
+
* navigation begins — a different <Link> click, `router.push`/`router.replace`,
|
|
8
|
+
* a form submission, shallow routing via raw `history.pushState`, or browser
|
|
9
|
+
* back/forward. Without this, a Link's pending indicator stays "sticky" after
|
|
10
|
+
* an interrupting navigation, because the Link's own completion handler is the
|
|
11
|
+
* only thing that would otherwise clear it.
|
|
12
|
+
*
|
|
13
|
+
* Mirrors Next.js's `linkForMostRecentNavigation` /
|
|
14
|
+
* `setLinkForCurrentNavigation` in
|
|
15
|
+
* packages/next/src/client/components/links.ts, adapted to vinext's per-<Link>
|
|
16
|
+
* React state model: instead of an optimistic-status dispatcher, we hold the
|
|
17
|
+
* link's `setPending` setter.
|
|
18
|
+
*/
|
|
19
|
+
type PendingLinkSetter = (pending: boolean) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Mark `setter` as the link that started the most recent navigation, resetting
|
|
22
|
+
* the previously-tracked link's pending state to idle so only the last-clicked
|
|
23
|
+
* link shows a pending state.
|
|
24
|
+
*/
|
|
25
|
+
declare function setLinkForCurrentNavigation(setter: PendingLinkSetter): void;
|
|
26
|
+
/**
|
|
27
|
+
* Stop tracking `setter` if it is the current navigation link. Called when a
|
|
28
|
+
* <Link> finishes its own navigation or unmounts so we never hold a stale
|
|
29
|
+
* reference to an unmounted component's setter.
|
|
30
|
+
*/
|
|
31
|
+
declare function clearLinkForCurrentNavigation(setter: PendingLinkSetter): void;
|
|
32
|
+
/**
|
|
33
|
+
* Reset any link that is currently showing a pending state. Invoked at the
|
|
34
|
+
* start of every App Router navigation so that navigations not initiated by the
|
|
35
|
+
* tracked link — `router.push`/`router.replace`, form submissions, shallow
|
|
36
|
+
* routing, and browser back/forward — clear a stale pending indicator. A
|
|
37
|
+
* link-initiated navigation registers itself first via
|
|
38
|
+
* `setLinkForCurrentNavigation`; the matching call here consumes that marker and
|
|
39
|
+
* keeps the link pending.
|
|
40
|
+
*/
|
|
41
|
+
declare function notifyLinkNavigationStart(): void;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { PendingLinkSetter, clearLinkForCurrentNavigation, notifyLinkNavigationStart, setLinkForCurrentNavigation };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region src/shims/internal/link-status-registry.ts
|
|
2
|
+
let linkSetterForMostRecentNavigation = null;
|
|
3
|
+
let currentNavigationIsLinkInitiated = false;
|
|
4
|
+
/**
|
|
5
|
+
* Mark `setter` as the link that started the most recent navigation, resetting
|
|
6
|
+
* the previously-tracked link's pending state to idle so only the last-clicked
|
|
7
|
+
* link shows a pending state.
|
|
8
|
+
*/
|
|
9
|
+
function setLinkForCurrentNavigation(setter) {
|
|
10
|
+
if (linkSetterForMostRecentNavigation && linkSetterForMostRecentNavigation !== setter) linkSetterForMostRecentNavigation(false);
|
|
11
|
+
linkSetterForMostRecentNavigation = setter;
|
|
12
|
+
currentNavigationIsLinkInitiated = true;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Stop tracking `setter` if it is the current navigation link. Called when a
|
|
16
|
+
* <Link> finishes its own navigation or unmounts so we never hold a stale
|
|
17
|
+
* reference to an unmounted component's setter.
|
|
18
|
+
*/
|
|
19
|
+
function clearLinkForCurrentNavigation(setter) {
|
|
20
|
+
if (linkSetterForMostRecentNavigation === setter) linkSetterForMostRecentNavigation = null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Reset any link that is currently showing a pending state. Invoked at the
|
|
24
|
+
* start of every App Router navigation so that navigations not initiated by the
|
|
25
|
+
* tracked link — `router.push`/`router.replace`, form submissions, shallow
|
|
26
|
+
* routing, and browser back/forward — clear a stale pending indicator. A
|
|
27
|
+
* link-initiated navigation registers itself first via
|
|
28
|
+
* `setLinkForCurrentNavigation`; the matching call here consumes that marker and
|
|
29
|
+
* keeps the link pending.
|
|
30
|
+
*/
|
|
31
|
+
function notifyLinkNavigationStart() {
|
|
32
|
+
if (currentNavigationIsLinkInitiated) {
|
|
33
|
+
currentNavigationIsLinkInitiated = false;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (linkSetterForMostRecentNavigation) {
|
|
37
|
+
linkSetterForMostRecentNavigation(false);
|
|
38
|
+
linkSetterForMostRecentNavigation = null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { clearLinkForCurrentNavigation, notifyLinkNavigationStart, setLinkForCurrentNavigation };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/shims/internal/route-pattern-for-warning.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Side-effect-free accessor for the current render's route pattern, used by
|
|
4
|
+
* Next.js-parity diagnostics such as the Link shim's
|
|
5
|
+
* "Invalid href ... in page: '...'" `console.error`.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors Next.js's `router.pathname`, which is the *route pattern* (e.g.
|
|
8
|
+
* `/posts/[id]`), not the resolved URL. During Pages Router SSR the route
|
|
9
|
+
* pattern lives on the request-scoped SSR context; the server-only
|
|
10
|
+
* `router-state.ts` publishes an accessor for it under a well-known
|
|
11
|
+
* `Symbol.for` handle. We read it through that handle rather than importing
|
|
12
|
+
* `router.ts` directly — importing `router.ts` would pull its browser-only
|
|
13
|
+
* `installWindowNext()` side effect into every consumer of the Link shim
|
|
14
|
+
* (including the App Router client bundle), clobbering `window.next.router`.
|
|
15
|
+
*
|
|
16
|
+
* On the client (or when no accessor is registered, e.g. App Router) we fall
|
|
17
|
+
* back to `window.location.pathname`, then to `"/"`.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Register the server-side route-pattern accessor. Called once by the
|
|
21
|
+
* server-only router-state module on import. Idempotent.
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
declare function registerRoutePatternForWarningAccessor(accessor: () => string | null): void;
|
|
25
|
+
declare function getCurrentRoutePathnameForWarning(): string;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { getCurrentRoutePathnameForWarning, registerRoutePatternForWarningAccessor };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/shims/internal/route-pattern-for-warning.ts
|
|
2
|
+
/**
|
|
3
|
+
* Side-effect-free accessor for the current render's route pattern, used by
|
|
4
|
+
* Next.js-parity diagnostics such as the Link shim's
|
|
5
|
+
* "Invalid href ... in page: '...'" `console.error`.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors Next.js's `router.pathname`, which is the *route pattern* (e.g.
|
|
8
|
+
* `/posts/[id]`), not the resolved URL. During Pages Router SSR the route
|
|
9
|
+
* pattern lives on the request-scoped SSR context; the server-only
|
|
10
|
+
* `router-state.ts` publishes an accessor for it under a well-known
|
|
11
|
+
* `Symbol.for` handle. We read it through that handle rather than importing
|
|
12
|
+
* `router.ts` directly — importing `router.ts` would pull its browser-only
|
|
13
|
+
* `installWindowNext()` side effect into every consumer of the Link shim
|
|
14
|
+
* (including the App Router client bundle), clobbering `window.next.router`.
|
|
15
|
+
*
|
|
16
|
+
* On the client (or when no accessor is registered, e.g. App Router) we fall
|
|
17
|
+
* back to `window.location.pathname`, then to `"/"`.
|
|
18
|
+
*/
|
|
19
|
+
const ROUTE_PATTERN_FOR_WARNING_ACCESSOR_KEY = Symbol.for("vinext.router.routePatternForWarningAccessor");
|
|
20
|
+
/**
|
|
21
|
+
* Register the server-side route-pattern accessor. Called once by the
|
|
22
|
+
* server-only router-state module on import. Idempotent.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
function registerRoutePatternForWarningAccessor(accessor) {
|
|
26
|
+
globalThis[ROUTE_PATTERN_FOR_WARNING_ACCESSOR_KEY] = accessor;
|
|
27
|
+
}
|
|
28
|
+
function getCurrentRoutePathnameForWarning() {
|
|
29
|
+
if (typeof window === "undefined") {
|
|
30
|
+
const accessor = globalThis[ROUTE_PATTERN_FOR_WARNING_ACCESSOR_KEY];
|
|
31
|
+
if (accessor) try {
|
|
32
|
+
const pattern = accessor();
|
|
33
|
+
if (pattern) return pattern;
|
|
34
|
+
} catch {}
|
|
35
|
+
return "/";
|
|
36
|
+
}
|
|
37
|
+
return window.location?.pathname ?? "/";
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { getCurrentRoutePathnameForWarning, registerRoutePatternForWarningAccessor };
|
package/dist/shims/link.js
CHANGED
|
@@ -12,12 +12,14 @@ import { markAppRouteDetectedOnPrefetch } from "./internal/app-route-detection.j
|
|
|
12
12
|
import { isAbsoluteOrProtocolRelativeUrl, normalizePathTrailingSlash, resolveRelativeHref, toBrowserNavigationHref, toSameOriginAppPath, withBasePath } from "./url-utils.js";
|
|
13
13
|
import { appendSearchParamsToUrl, urlQueryToSearchParams } from "../utils/query.js";
|
|
14
14
|
import { getCurrentBrowserLocale } from "./client-locale.js";
|
|
15
|
+
import { getCurrentRoutePathnameForWarning } from "./internal/route-pattern-for-warning.js";
|
|
15
16
|
import { getNavigationRuntime, hasAppNavigationRuntime, registerNavigationRuntimeFunctions } from "../client/navigation-runtime.js";
|
|
16
17
|
import { createRscRequestHeaders, createRscRequestUrl, stripRscCacheBustingSearchParam, stripRscSuffix } from "../server/app-rsc-cache-busting.js";
|
|
17
18
|
import { getMountedSlotsHeader, getPrefetchCache, getPrefetchInterceptionContext, getPrefetchedUrls, hasPrefetchCacheEntryForNavigation, navigateClientSide, prefetchRscResponse } from "./navigation.js";
|
|
18
19
|
import { navigatePagesRouterLink } from "../client/pages-router-link-navigation.js";
|
|
19
20
|
import { getI18nContext } from "./i18n-context.js";
|
|
20
21
|
import { canLinkIntentPrefetch, canLinkPrefetch, getLinkPrefetchHref } from "./link-prefetch.js";
|
|
22
|
+
import { clearLinkForCurrentNavigation, notifyLinkNavigationStart, setLinkForCurrentNavigation } from "./internal/link-status-registry.js";
|
|
21
23
|
import React, { createContext, forwardRef, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
22
24
|
import { jsx } from "react/jsx-runtime";
|
|
23
25
|
//#region src/shims/link.tsx
|
|
@@ -37,6 +39,7 @@ const LinkStatusContext = createContext({ pending: false });
|
|
|
37
39
|
function useLinkStatus() {
|
|
38
40
|
return useContext(LinkStatusContext);
|
|
39
41
|
}
|
|
42
|
+
if (typeof window !== "undefined") registerNavigationRuntimeFunctions({ notifyLinkNavigationStart });
|
|
40
43
|
/** basePath from next.config.js, injected by the plugin at build time */
|
|
41
44
|
const __basePath = process.env.__NEXT_ROUTER_BASEPATH ?? "";
|
|
42
45
|
/** trailingSlash from next.config.js, injected by the plugin at build time */
|
|
@@ -45,7 +48,7 @@ const __prefetchInlining = process.env.__VINEXT_PREFETCH_INLINING === "true";
|
|
|
45
48
|
const linkPrefetchRouteTrieCache = createRouteTrieCache();
|
|
46
49
|
function resolveHref(href) {
|
|
47
50
|
if (typeof href === "string") return href;
|
|
48
|
-
let url = href.pathname ?? "
|
|
51
|
+
let url = href.pathname ?? "";
|
|
49
52
|
if (href.query) {
|
|
50
53
|
const params = urlQueryToSearchParams(href.query);
|
|
51
54
|
url = appendSearchParamsToUrl(url, params);
|
|
@@ -81,17 +84,19 @@ function normalizeRepeatedSlashes(url) {
|
|
|
81
84
|
* `resolveHref`. We mirror that behaviour (no dedup) for exact parity.
|
|
82
85
|
*
|
|
83
86
|
* Note: Next.js uses `router.pathname` (the route pattern, e.g.
|
|
84
|
-
* `/posts/[id]`) for the "in page" segment of the message.
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
87
|
+
* `/posts/[id]`) for the "in page" segment of the message. The Next.js
|
|
88
|
+
* compat test asserts this exact text (`in page: '/my/path/[name]'`), so we
|
|
89
|
+
* source it from the current render's route pattern via
|
|
90
|
+
* `getCurrentRoutePathnameForWarning()`: the Pages Router SSR context's route
|
|
91
|
+
* pattern on the server, `window.location.pathname` on the client, falling
|
|
92
|
+
* back to `"/"`.
|
|
88
93
|
*/
|
|
89
94
|
function warnAndNormalizeRepeatedSlashesInHref(urlAsString) {
|
|
90
95
|
if (urlAsString.startsWith("//")) return urlAsString;
|
|
91
96
|
const urlProtoMatch = urlAsString.match(/^[a-z][a-z0-9+.-]*:\/\//i);
|
|
92
97
|
const urlAsStringNoProto = urlProtoMatch ? urlAsString.slice(urlProtoMatch[0].length) : urlAsString;
|
|
93
98
|
if (!(urlAsStringNoProto.split("?", 1)[0] || "").match(/(\/\/|\\)/)) return urlAsString;
|
|
94
|
-
const pathname =
|
|
99
|
+
const pathname = getCurrentRoutePathnameForWarning();
|
|
95
100
|
console.error(`Invalid href '${urlAsString}' passed to next/router in page: '${pathname}'. Repeated forward-slashes (//) or backslashes \\ are not valid in the href.`);
|
|
96
101
|
const normalizedNoProto = normalizeRepeatedSlashes(urlAsStringNoProto);
|
|
97
102
|
return (urlProtoMatch ? urlProtoMatch[0] : "") + normalizedNoProto;
|
|
@@ -381,10 +386,16 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
|
|
|
381
386
|
const fullHref = normalizePathTrailingSlash(withBasePath(normalizedHref, __basePath), __trailingSlash);
|
|
382
387
|
const [pending, setPending] = useState(false);
|
|
383
388
|
const mountedRef = useRef(true);
|
|
389
|
+
const setPendingRef = useRef(null);
|
|
390
|
+
if (setPendingRef.current === null) setPendingRef.current = (next) => {
|
|
391
|
+
if (mountedRef.current) setPending(next);
|
|
392
|
+
};
|
|
384
393
|
useEffect(() => {
|
|
385
394
|
mountedRef.current = true;
|
|
395
|
+
const setter = setPendingRef.current;
|
|
386
396
|
return () => {
|
|
387
397
|
mountedRef.current = false;
|
|
398
|
+
if (setter) clearLinkForCurrentNavigation(setter);
|
|
388
399
|
};
|
|
389
400
|
}, []);
|
|
390
401
|
const internalRef = useRef(null);
|
|
@@ -497,10 +508,13 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
|
|
|
497
508
|
if (navEvent.defaultPrevented) return;
|
|
498
509
|
} catch {}
|
|
499
510
|
if (getNavigationRuntime()?.functions.navigate) {
|
|
511
|
+
const setter = setPendingRef.current;
|
|
512
|
+
if (setter) setLinkForCurrentNavigation(setter);
|
|
500
513
|
setPending(true);
|
|
501
514
|
React.startTransition(() => {
|
|
502
515
|
navigateClientSide(navigateHref, replace ? "replace" : "push", scroll, true).finally(() => {
|
|
503
516
|
if (mountedRef.current) setPending(false);
|
|
517
|
+
if (setter) clearLinkForCurrentNavigation(setter);
|
|
504
518
|
});
|
|
505
519
|
});
|
|
506
520
|
return;
|
|
@@ -225,7 +225,7 @@ declare function clearPendingPathname(navId: number): void;
|
|
|
225
225
|
* Returns the current pathname.
|
|
226
226
|
* Server: from request context. Client: from window.location.
|
|
227
227
|
*/
|
|
228
|
-
declare function usePathname(): string;
|
|
228
|
+
declare function usePathname(): string | null;
|
|
229
229
|
/**
|
|
230
230
|
* Returns the current search params as a read-only URLSearchParams.
|
|
231
231
|
*/
|
|
@@ -233,7 +233,7 @@ declare function useSearchParams(): ReadonlyURLSearchParams;
|
|
|
233
233
|
/**
|
|
234
234
|
* Returns the dynamic params for the current route.
|
|
235
235
|
*/
|
|
236
|
-
declare function useParams<T extends Record<string, string | string[]> = Record<string, string | string[]>>(): T;
|
|
236
|
+
declare function useParams<T extends Record<string, string | string[]> = Record<string, string | string[]>>(): T | null;
|
|
237
237
|
/**
|
|
238
238
|
* Commit pending client navigation state to committed snapshots.
|
|
239
239
|
*
|