vinext 0.1.5 → 0.1.6
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 -2
- package/dist/build/google-fonts/find-font-files-in-css.d.ts +16 -0
- package/dist/build/google-fonts/find-font-files-in-css.js +28 -0
- package/dist/build/report.d.ts +1 -2
- package/dist/build/report.js +2 -11
- package/dist/build/run-prerender.js +1 -1
- package/dist/check.js +3 -3
- package/dist/client/pages-router-link-navigation.d.ts +10 -0
- package/dist/client/pages-router-link-navigation.js +3 -3
- package/dist/deploy.js +3 -3
- package/dist/entries/app-rsc-entry.js +1 -0
- package/dist/entries/app-rsc-manifest.js +76 -46
- package/dist/entries/pages-server-entry.js +1 -0
- package/dist/index.js +69 -54
- package/dist/init.d.ts +5 -4
- package/dist/init.js +27 -8
- package/dist/plugins/fonts.js +3 -1
- package/dist/plugins/middleware-export-validation.d.ts +5 -0
- package/dist/plugins/middleware-export-validation.js +36 -0
- package/dist/plugins/og-asset-ownership.d.ts +27 -0
- package/dist/plugins/og-asset-ownership.js +285 -0
- package/dist/plugins/og-assets.js +22 -4
- package/dist/server/accept-encoding.d.ts +29 -0
- package/dist/server/accept-encoding.js +95 -0
- package/dist/server/app-browser-entry.js +8 -5
- package/dist/server/app-browser-navigation-controller.js +1 -1
- package/dist/server/app-browser-rsc-redirect.d.ts +2 -1
- package/dist/server/app-browser-rsc-redirect.js +8 -1
- package/dist/server/app-browser-state.js +1 -1
- package/dist/server/app-browser-visible-commit.js +1 -1
- package/dist/server/app-middleware.d.ts +1 -0
- package/dist/server/app-middleware.js +1 -0
- package/dist/server/app-page-dispatch.d.ts +2 -2
- package/dist/server/app-page-head.js +2 -2
- package/dist/server/app-page-request.d.ts +7 -0
- package/dist/server/app-page-request.js +19 -1
- package/dist/server/app-prerender-endpoints.d.ts +3 -3
- package/dist/server/app-prerender-endpoints.js +3 -4
- package/dist/server/app-route-module-loader.d.ts +62 -14
- package/dist/server/app-route-module-loader.js +61 -10
- package/dist/server/app-rsc-handler.d.ts +7 -6
- package/dist/server/app-rsc-handler.js +31 -26
- package/dist/server/app-rsc-route-matching.d.ts +9 -0
- package/dist/server/app-rsc-route-matching.js +13 -0
- package/dist/server/app-ssr-entry.js +2 -2
- package/dist/server/headers.d.ts +5 -1
- package/dist/server/headers.js +5 -1
- package/dist/server/metadata-route-response.d.ts +3 -3
- package/dist/server/middleware-runtime.d.ts +2 -1
- package/dist/server/middleware-runtime.js +19 -8
- package/dist/server/middleware.d.ts +2 -2
- package/dist/server/middleware.js +26 -11
- package/dist/server/pages-request-pipeline.d.ts +3 -1
- package/dist/server/pages-request-pipeline.js +4 -2
- package/dist/server/prod-server.d.ts +1 -1
- package/dist/server/prod-server.js +85 -72
- package/dist/shims/font-google-base.d.ts +1 -0
- package/dist/shims/font-google-base.js +6 -21
- package/dist/shims/internal/interpolate-as.d.ts +25 -0
- package/dist/shims/internal/interpolate-as.js +196 -0
- package/dist/shims/link.js +24 -1
- package/dist/shims/navigation.js +1 -1
- package/dist/shims/navigation.react-server.js +1 -1
- package/dist/shims/router.d.ts +1 -3
- package/dist/shims/router.js +62 -18
- package/dist/typegen.js +2 -10
- package/dist/utils/project.d.ts +7 -1
- package/dist/utils/project.js +16 -2
- package/package.json +3 -3
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { removeTrailingSlash } from "../../utils/base-path.js";
|
|
2
|
+
//#region src/shims/internal/interpolate-as.ts
|
|
3
|
+
/**
|
|
4
|
+
* Port of Next.js' Pages Router `interpolateAs` helper, plus the minimal
|
|
5
|
+
* subset of its transitive dependencies (`getRouteRegex` + `getRouteMatcher`
|
|
6
|
+
* and the parameter-pattern parsers they rely on).
|
|
7
|
+
*
|
|
8
|
+
* Upstream sources:
|
|
9
|
+
* - packages/next/src/shared/lib/router/utils/interpolate-as.ts
|
|
10
|
+
* - packages/next/src/shared/lib/router/utils/route-regex.ts
|
|
11
|
+
* - packages/next/src/shared/lib/router/utils/route-matcher.ts
|
|
12
|
+
* - packages/next/src/shared/lib/router/utils/get-dynamic-param.ts
|
|
13
|
+
* - packages/next/src/shared/lib/router/utils/remove-trailing-slash.ts
|
|
14
|
+
* - packages/next/src/shared/lib/escape-regexp.ts
|
|
15
|
+
*
|
|
16
|
+
* Used by the Pages Router shim to project a route pattern + `as` pathname +
|
|
17
|
+
* query back into a fully-interpolated browser URL (the `<Link href as>`
|
|
18
|
+
* masking path). Interception-route markers (`(.)`, `(..)`, `(...)`,
|
|
19
|
+
* `(..)(..)`) are intentionally omitted — they only apply to App Router and
|
|
20
|
+
* never appear in Pages Router patterns.
|
|
21
|
+
*/
|
|
22
|
+
function normalizeQuery(query) {
|
|
23
|
+
const normalized = {};
|
|
24
|
+
if (!query) return normalized;
|
|
25
|
+
for (const [key, value] of Object.entries(query)) {
|
|
26
|
+
if (value === void 0) continue;
|
|
27
|
+
normalized[key] = Array.isArray(value) ? value.map(String) : String(value);
|
|
28
|
+
}
|
|
29
|
+
return normalized;
|
|
30
|
+
}
|
|
31
|
+
function parseRouteHrefQuery(routeHref, queryIndex, hashIndex) {
|
|
32
|
+
const query = {};
|
|
33
|
+
if (queryIndex === -1 || hashIndex !== -1 && queryIndex > hashIndex) return query;
|
|
34
|
+
const searchEnd = hashIndex === -1 ? routeHref.length : hashIndex;
|
|
35
|
+
for (const [key, value] of new URLSearchParams(routeHref.slice(queryIndex + 1, searchEnd))) {
|
|
36
|
+
const existing = query[key];
|
|
37
|
+
if (existing === void 0) query[key] = value;
|
|
38
|
+
else if (Array.isArray(existing)) existing.push(value);
|
|
39
|
+
else query[key] = [existing, value];
|
|
40
|
+
}
|
|
41
|
+
return query;
|
|
42
|
+
}
|
|
43
|
+
const reHasRegExp = /[|\\{}()[\]^$+*?.-]/;
|
|
44
|
+
const reReplaceRegExp = /[|\\{}()[\]^$+*?.-]/g;
|
|
45
|
+
function escapeStringRegexp(str) {
|
|
46
|
+
if (reHasRegExp.test(str)) return str.replace(reReplaceRegExp, "\\$&");
|
|
47
|
+
return str;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Regular expression pattern used to match route parameters.
|
|
51
|
+
* Matches both single parameters and parameter groups.
|
|
52
|
+
* Examples:
|
|
53
|
+
* - `[[...slug]]` matches parameter group with key 'slug', repeat: true, optional: true
|
|
54
|
+
* - `[...slug]` matches parameter group with key 'slug', repeat: true, optional: false
|
|
55
|
+
* - `[[foo]]` matches parameter with key 'foo', repeat: false, optional: true
|
|
56
|
+
* - `[bar]` matches parameter with key 'bar', repeat: false, optional: false
|
|
57
|
+
*/
|
|
58
|
+
const PARAMETER_PATTERN = /^([^[]*)\[((?:\[[^\]]*\])|[^\]]+)\](.*)$/;
|
|
59
|
+
/**
|
|
60
|
+
* Parses a matched parameter from the PARAMETER_PATTERN regex.
|
|
61
|
+
* Examples:
|
|
62
|
+
* - `[...slug]` -> `{ key: 'slug', repeat: true, optional: true }`
|
|
63
|
+
* - `...slug` -> `{ key: 'slug', repeat: true, optional: false }`
|
|
64
|
+
* - `[foo]` -> `{ key: 'foo', repeat: false, optional: true }`
|
|
65
|
+
* - `bar` -> `{ key: 'bar', repeat: false, optional: false }`
|
|
66
|
+
*/
|
|
67
|
+
function parseMatchedParameter(param) {
|
|
68
|
+
const optional = param.startsWith("[") && param.endsWith("]");
|
|
69
|
+
if (optional) param = param.slice(1, -1);
|
|
70
|
+
const repeat = param.startsWith("...");
|
|
71
|
+
if (repeat) param = param.slice(3);
|
|
72
|
+
return {
|
|
73
|
+
key: param,
|
|
74
|
+
repeat,
|
|
75
|
+
optional
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function getParametrizedRoute(route) {
|
|
79
|
+
const groups = {};
|
|
80
|
+
let groupIndex = 1;
|
|
81
|
+
const segments = [];
|
|
82
|
+
for (const segment of removeTrailingSlash(route).slice(1).split("/")) {
|
|
83
|
+
const paramMatches = segment.match(PARAMETER_PATTERN);
|
|
84
|
+
if (paramMatches && paramMatches[2]) {
|
|
85
|
+
const { key, repeat, optional } = parseMatchedParameter(paramMatches[2]);
|
|
86
|
+
groups[key] = {
|
|
87
|
+
pos: groupIndex++,
|
|
88
|
+
repeat,
|
|
89
|
+
optional
|
|
90
|
+
};
|
|
91
|
+
const s = repeat ? optional ? "(?:/(.+?))?" : "/(.+?)" : "/([^/]+?)";
|
|
92
|
+
segments.push(s);
|
|
93
|
+
} else segments.push(`/${escapeStringRegexp(segment)}`);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
parameterizedRoute: segments.join(""),
|
|
97
|
+
groups
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* From a normalized route this function generates a regular expression and a
|
|
102
|
+
* corresponding groups object intended to be used to store matching groups
|
|
103
|
+
* from the regular expression.
|
|
104
|
+
*/
|
|
105
|
+
function getRouteRegex(normalizedRoute) {
|
|
106
|
+
const { parameterizedRoute, groups } = getParametrizedRoute(normalizedRoute);
|
|
107
|
+
return {
|
|
108
|
+
re: new RegExp(`^${parameterizedRoute}(?:/)?$`),
|
|
109
|
+
groups
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Compile a route regex into a function that extracts decoded params from a
|
|
114
|
+
* pathname. Returns `false` if the pathname does not match the route.
|
|
115
|
+
*
|
|
116
|
+
* The `safeRouteMatcher`/`stripParameterSeparators` wrapper that Next.js
|
|
117
|
+
* applies (for adjacent-parameter normalization) is omitted — Pages Router
|
|
118
|
+
* patterns never contain adjacent parameters, so the wrapper is a no-op for
|
|
119
|
+
* this caller.
|
|
120
|
+
*/
|
|
121
|
+
function getRouteMatcher({ re, groups }) {
|
|
122
|
+
return (pathname) => {
|
|
123
|
+
const routeMatch = re.exec(pathname);
|
|
124
|
+
if (!routeMatch) return false;
|
|
125
|
+
const decode = (param) => {
|
|
126
|
+
try {
|
|
127
|
+
return decodeURIComponent(param);
|
|
128
|
+
} catch {
|
|
129
|
+
throw new Error("failed to decode param");
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const params = {};
|
|
133
|
+
for (const [key, group] of Object.entries(groups)) {
|
|
134
|
+
const match = routeMatch[group.pos];
|
|
135
|
+
if (match !== void 0) if (group.repeat) params[key] = match.split("/").map((entry) => decode(entry));
|
|
136
|
+
else params[key] = decode(match);
|
|
137
|
+
}
|
|
138
|
+
return params;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Project a `(route, asPathname, query)` triple back into a fully-interpolated
|
|
143
|
+
* browser URL. Used by the `<Link href as>` masking path in the Pages Router
|
|
144
|
+
* shim: extract param values from the rendered `as` path when it differs from
|
|
145
|
+
* the route pattern, otherwise fall back to reading them from the href query.
|
|
146
|
+
*
|
|
147
|
+
* Returns `{ params, result }`. `result` is the interpolated URL, or the empty
|
|
148
|
+
* string when one or more required params could not be resolved. Callers warn
|
|
149
|
+
* (in dev) on the empty-string case; this helper deliberately stays silent —
|
|
150
|
+
* matching Next.js' behavior, where `<Link>` itself owns the dev warning.
|
|
151
|
+
*/
|
|
152
|
+
function interpolateAs(route, asPathname, query) {
|
|
153
|
+
let interpolatedRoute = "";
|
|
154
|
+
const dynamicRegex = getRouteRegex(route);
|
|
155
|
+
const dynamicGroups = dynamicRegex.groups;
|
|
156
|
+
const dynamicMatches = (asPathname !== route ? getRouteMatcher(dynamicRegex)(asPathname) : "") || query;
|
|
157
|
+
interpolatedRoute = route;
|
|
158
|
+
const params = Object.keys(dynamicGroups);
|
|
159
|
+
if (!params.every((param) => {
|
|
160
|
+
let value = dynamicMatches[param] || "";
|
|
161
|
+
const { repeat, optional } = dynamicGroups[param];
|
|
162
|
+
let replaced = `[${repeat ? "..." : ""}${param}]`;
|
|
163
|
+
if (optional) replaced = `${!value ? "/" : ""}[${replaced}]`;
|
|
164
|
+
if (repeat && !Array.isArray(value)) value = [value];
|
|
165
|
+
return (optional || param in dynamicMatches) && (interpolatedRoute = interpolatedRoute.replace(replaced, repeat ? value.map((segment) => encodeURIComponent(segment)).join("/") : encodeURIComponent(value)) || "/");
|
|
166
|
+
})) interpolatedRoute = "";
|
|
167
|
+
return {
|
|
168
|
+
params,
|
|
169
|
+
result: interpolatedRoute
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Resolve a bracket-pattern route href against its displayed href. Query
|
|
174
|
+
* values can be supplied directly (object-form hrefs) or parsed from the route
|
|
175
|
+
* href (string-form hrefs). A `?` after `#` is part of the fragment, not a
|
|
176
|
+
* query delimiter.
|
|
177
|
+
*/
|
|
178
|
+
function interpolateDynamicRouteHref(routeHref, asHref, queryInput) {
|
|
179
|
+
if (!routeHref.includes("[")) return null;
|
|
180
|
+
const hashIndex = routeHref.indexOf("#");
|
|
181
|
+
const queryIndex = routeHref.indexOf("?");
|
|
182
|
+
const pathEnd = [hashIndex, queryIndex].filter((index) => index !== -1).reduce((earliest, index) => Math.min(earliest, index), routeHref.length);
|
|
183
|
+
const routePathname = routeHref.slice(0, pathEnd);
|
|
184
|
+
const trailing = routeHref.slice(pathEnd);
|
|
185
|
+
const asPathname = asHref.split(/[?#]/, 1)[0];
|
|
186
|
+
const query = queryInput ? normalizeQuery(queryInput) : parseRouteHrefQuery(routeHref, queryIndex, hashIndex);
|
|
187
|
+
const { result, params } = interpolateAs(routePathname, asPathname, query);
|
|
188
|
+
return {
|
|
189
|
+
href: result ? `${result}${trailing}` : "",
|
|
190
|
+
params,
|
|
191
|
+
query,
|
|
192
|
+
routePathname
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
196
|
+
export { interpolateDynamicRouteHref };
|
package/dist/shims/link.js
CHANGED
|
@@ -12,6 +12,7 @@ import { appendSearchParamsToUrl, urlQueryToSearchParams } from "../utils/query.
|
|
|
12
12
|
import { resolveHybridClientRouteOwner } from "./internal/hybrid-client-route-owner.js";
|
|
13
13
|
import { markAppRouteDetectedOnPrefetch } from "./internal/app-route-detection.js";
|
|
14
14
|
import { isAbsoluteOrProtocolRelativeUrl, normalizePathTrailingSlash, toBrowserNavigationHref, toSameOriginAppPath, withBasePath } from "./url-utils.js";
|
|
15
|
+
import { interpolateDynamicRouteHref } from "./internal/interpolate-as.js";
|
|
15
16
|
import { getCurrentBrowserLocale } from "./client-locale.js";
|
|
16
17
|
import { getCurrentRoutePathnameForWarning } from "./internal/route-pattern-for-warning.js";
|
|
17
18
|
import { createRscRequestHeaders, createRscRequestUrl, stripRscCacheBustingSearchParam, stripRscSuffix } from "../server/app-rsc-cache-busting.js";
|
|
@@ -391,11 +392,30 @@ function applyLocaleToHref(href, locale) {
|
|
|
391
392
|
}
|
|
392
393
|
return addLocalePrefix(href, resolvedLocale, defaultLocale);
|
|
393
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
* For the `<Link href="/blog/[slug]" as="/blog/test-post">` case, project the
|
|
397
|
+
* bracket-pattern href + the resolved `as` back into a concrete route URL the
|
|
398
|
+
* Pages Router can fetch (`/blog/test-post`). Returns null when:
|
|
399
|
+
* - `href` has no bracket params (already concrete; the existing forwarding
|
|
400
|
+
* path works as-is)
|
|
401
|
+
* - interpolation fails because a required param could not be resolved
|
|
402
|
+
* (caller falls back to `as`, matching pre-PR behavior)
|
|
403
|
+
*
|
|
404
|
+
* The query for interpolation is the href's own query — `as` is the matcher
|
|
405
|
+
* input rather than the source of param values. For string hrefs the search
|
|
406
|
+
* portion is parsed into the query record; for object hrefs we take
|
|
407
|
+
* `href.query` directly.
|
|
408
|
+
*/
|
|
409
|
+
function resolveConcreteRouteHref(href, as) {
|
|
410
|
+
if (typeof as !== "string") return null;
|
|
411
|
+
return interpolateDynamicRouteHref(typeof href === "string" ? href : resolveHref(href), as, typeof href === "string" ? void 0 : href.query)?.href || null;
|
|
412
|
+
}
|
|
394
413
|
const Link = forwardRef(function Link({ href, as, replace = false, prefetch: prefetchProp, scroll = true, shallow = false, children: childrenProp, onClick, onMouseEnter, onTouchStart, onNavigate, unstable_dynamicOnHover = false, legacyBehavior = false, passHref = false, ...rest }, forwardedRef) {
|
|
395
414
|
const { locale, ...restWithoutLocale } = rest;
|
|
396
415
|
let children = childrenProp;
|
|
397
416
|
if (legacyBehavior && (typeof childrenProp === "string" || typeof childrenProp === "number")) children = React.createElement("a", null, childrenProp);
|
|
398
417
|
const rawResolvedHref = as ?? resolveHref(href);
|
|
418
|
+
const routeHrefRaw = resolveConcreteRouteHref(href, as) ?? (typeof href === "string" ? href : resolveHref(href));
|
|
399
419
|
const resolvedHref = typeof rawResolvedHref === "string" ? warnAndNormalizeRepeatedSlashesInHref(rawResolvedHref) : rawResolvedHref;
|
|
400
420
|
const isDangerous = typeof resolvedHref === "string" && isDangerousScheme(resolvedHref);
|
|
401
421
|
const normalizedHref = normalizePathTrailingSlash(applyLocaleToHref(isDangerous ? "/" : resolvedHref, locale), __trailingSlash);
|
|
@@ -508,6 +528,8 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
|
|
|
508
528
|
e.preventDefault();
|
|
509
529
|
const hasAppNavigationRuntime = Boolean(getNavigationRuntime()?.functions.navigate);
|
|
510
530
|
const pagesNavigateHref = resolvedHref.startsWith("?") ? resolvePagesLinkNavigationHref(resolvedHref, locale) : navigateHref;
|
|
531
|
+
const pagesAsForLink = typeof as === "string" && typeof routeHrefRaw === "string" && as !== routeHrefRaw ? pagesNavigateHref : void 0;
|
|
532
|
+
const pagesHrefForLink = pagesAsForLink === void 0 ? pagesNavigateHref : routeHrefRaw;
|
|
511
533
|
const absoluteFullHref = toBrowserNavigationHref(hasAppNavigationRuntime ? navigateHref : pagesNavigateHref, window.location.href, __basePath);
|
|
512
534
|
if (onNavigate) try {
|
|
513
535
|
const navUrl = new URL(absoluteFullHref, window.location.origin);
|
|
@@ -546,7 +568,8 @@ const Link = forwardRef(function Link({ href, as, replace = false, prefetch: pre
|
|
|
546
568
|
router: Router && "reload" in Router ? Router : void 0,
|
|
547
569
|
loadRouter: async () => (await import("next/router.js")).default,
|
|
548
570
|
navigation: {
|
|
549
|
-
href:
|
|
571
|
+
href: pagesHrefForLink,
|
|
572
|
+
as: pagesAsForLink,
|
|
550
573
|
replace,
|
|
551
574
|
scroll,
|
|
552
575
|
shallow,
|
package/dist/shims/navigation.js
CHANGED
|
@@ -16,8 +16,8 @@ import { VINEXT_RSC_COMPATIBILITY_ID_HEADER, createRscRequestHeaders, createRscR
|
|
|
16
16
|
import { getNavigationRuntime, hasAppNavigationRuntime } from "../client/navigation-runtime.js";
|
|
17
17
|
import { notifyAppRouterTransitionStart } from "../client/instrumentation-client-state.js";
|
|
18
18
|
import { PUBLIC_INITIAL_BFCACHE_ID } from "../server/app-bfcache-id.js";
|
|
19
|
-
import { createExternalHistoryStatePreservingMetadata, createHashOnlyHistoryStatePreservingNavigationMetadata } from "../server/app-history-state.js";
|
|
20
19
|
import { resolveManifestNavigationInterceptionContext } from "../server/app-browser-interception-context.js";
|
|
20
|
+
import { createExternalHistoryStatePreservingMetadata, createHashOnlyHistoryStatePreservingNavigationMetadata } from "../server/app-history-state.js";
|
|
21
21
|
import { hasPendingAppRouterPageRedirect } from "../server/app-browser-mpa-navigation.js";
|
|
22
22
|
import { navigationPlanner } from "../server/navigation-planner.js";
|
|
23
23
|
import { ReadonlyURLSearchParams } from "./readonly-url-search-params.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { GLOBAL_ACCESSORS_KEY, ServerInsertedHTMLContext, _registerStateAccessors, clearServerInsertedHTML, flushServerInsertedHTML, getLayoutSegmentContext, getNavigationContext, renderServerInsertedHTML, setNavigationContext } from "./navigation-context-state.js";
|
|
2
2
|
import { BailoutToCSRError, DynamicServerError, HTTP_ERROR_FALLBACK_ERROR_CODE, RedirectType, forbidden, getAccessFallbackHTTPStatus, isBailoutToCSRError, isDynamicServerError, isHTTPAccessFallbackError, isNextRouterError, isRedirectError, notFound, permanentRedirect, redirect, unauthorized, unstable_rethrow } from "./navigation-errors.js";
|
|
3
3
|
import "./navigation-server.js";
|
|
4
|
-
import { ReadonlyURLSearchParams } from "./readonly-url-search-params.js";
|
|
5
4
|
import { throwClientHookError } from "./client-hook-error.js";
|
|
5
|
+
import { ReadonlyURLSearchParams } from "./readonly-url-search-params.js";
|
|
6
6
|
//#region src/shims/navigation.react-server.ts
|
|
7
7
|
function usePathname() {
|
|
8
8
|
throwClientHookError("usePathname()");
|
package/dist/shims/router.d.ts
CHANGED
|
@@ -6,9 +6,7 @@ import { ComponentType, ReactElement } from "react";
|
|
|
6
6
|
type BeforePopStateCallback = (state: {
|
|
7
7
|
url: string;
|
|
8
8
|
as: string;
|
|
9
|
-
options:
|
|
10
|
-
shallow: boolean;
|
|
11
|
-
};
|
|
9
|
+
options: TransitionOptions;
|
|
12
10
|
}) => boolean;
|
|
13
11
|
type NextRouter = {
|
|
14
12
|
/** Current pathname */pathname: string; /** Current route pattern (e.g., "/posts/[id]") */
|
package/dist/shims/router.js
CHANGED
|
@@ -19,6 +19,7 @@ import { installWindowNext } from "../client/window-next.js";
|
|
|
19
19
|
import { getWindowOrigin, isAbsoluteOrProtocolRelativeUrl, isHashOnlyBrowserUrlChange, normalizePathTrailingSlash, toBrowserNavigationHref, toSameOriginAppPath, withBasePath } from "./url-utils.js";
|
|
20
20
|
import { scrollToHashTarget } from "./hash-scroll.js";
|
|
21
21
|
import { installPagesRouterRuntime, setPagesRouterPopStateHandler, setStampInitialHistoryState } from "./pages-router-runtime.js";
|
|
22
|
+
import { interpolateDynamicRouteHref } from "./internal/interpolate-as.js";
|
|
22
23
|
import { getCurrentBrowserLocale } from "./client-locale.js";
|
|
23
24
|
import { Component, Fragment, createElement, useContext, useEffect, useLayoutEffect, useMemo, useState } from "react";
|
|
24
25
|
//#region src/shims/router.ts
|
|
@@ -1118,7 +1119,7 @@ async function navigateClientHtml(url, fetchUrl, controller, navId, assertStillC
|
|
|
1118
1119
|
* fixups). The JSON path derives its own URL from the browser-facing `url`
|
|
1119
1120
|
* because the data endpoint speaks the unprefixed path.
|
|
1120
1121
|
*/
|
|
1121
|
-
async function navigateClient(url, fetchUrl = url, options = {}) {
|
|
1122
|
+
async function navigateClient(url, fetchUrl = url, options = {}, routeUrl = url) {
|
|
1122
1123
|
if (typeof window === "undefined") return;
|
|
1123
1124
|
const previousAbortController = routerRuntimeState.activeAbortController;
|
|
1124
1125
|
if (previousAbortController) queueMicrotask(() => previousAbortController.abort());
|
|
@@ -1135,7 +1136,7 @@ async function navigateClient(url, fetchUrl = url, options = {}) {
|
|
|
1135
1136
|
else {
|
|
1136
1137
|
let browserUrl = url;
|
|
1137
1138
|
let htmlFetchUrl = fetchUrl;
|
|
1138
|
-
let dataTarget = resolvePagesDataNavigationTarget(
|
|
1139
|
+
let dataTarget = resolvePagesDataNavigationTarget(routeUrl, __basePath);
|
|
1139
1140
|
let middlewareDataResponse;
|
|
1140
1141
|
if (!dataTarget) {
|
|
1141
1142
|
let middlewareEffect;
|
|
@@ -1178,9 +1179,9 @@ async function navigateClient(url, fetchUrl = url, options = {}) {
|
|
|
1178
1179
|
* - "failed" — genuine error, caller should return false (hard nav is already
|
|
1179
1180
|
* scheduled as recovery)
|
|
1180
1181
|
*/
|
|
1181
|
-
async function runNavigateClient(fullUrl, resolvedUrl, fetchUrl = fullUrl, options = {}) {
|
|
1182
|
+
async function runNavigateClient(fullUrl, resolvedUrl, fetchUrl = fullUrl, options = {}, routeUrl = fullUrl) {
|
|
1182
1183
|
try {
|
|
1183
|
-
await navigateClient(fullUrl, fetchUrl, options);
|
|
1184
|
+
await navigateClient(fullUrl, fetchUrl, options, routeUrl);
|
|
1184
1185
|
return "completed";
|
|
1185
1186
|
} catch (err) {
|
|
1186
1187
|
routerEvents.emit("routeChangeError", err, resolvedUrl, { shallow: false });
|
|
@@ -1252,9 +1253,9 @@ function updateHistory(mode, fullUrl, navState) {
|
|
|
1252
1253
|
const previousKey = getRouterStateKey(window.history.state);
|
|
1253
1254
|
const key = mode === "push" ? createHistoryKey() : previousKey ?? routerRuntimeState.currentHistoryKey ?? createHistoryKey();
|
|
1254
1255
|
const state = {
|
|
1255
|
-
url: navState
|
|
1256
|
-
as: navState
|
|
1257
|
-
options: navState
|
|
1256
|
+
url: navState.url,
|
|
1257
|
+
as: navState.as,
|
|
1258
|
+
options: navState.options,
|
|
1258
1259
|
__N: true,
|
|
1259
1260
|
key
|
|
1260
1261
|
};
|
|
@@ -1300,8 +1301,13 @@ async function performNavigation(url, as, options, mode, onStateUpdate) {
|
|
|
1300
1301
|
assertSafeNavigationUrl(resolveUrl(url));
|
|
1301
1302
|
if (as !== void 0) assertSafeNavigationUrl(String(as));
|
|
1302
1303
|
const navigationLocale = resolveTransitionLocale(options?.locale);
|
|
1303
|
-
|
|
1304
|
-
|
|
1304
|
+
const replaceInheritedLocale = as === void 0 && options?.locale !== void 0 && typeof url !== "string" && url.pathname === void 0 && (url.query !== void 0 && Object.keys(url.query).length > 0 || typeof url.search === "string" && url.search.length > 0 || typeof url.hash === "string" && url.hash.length > 0);
|
|
1305
|
+
let resolved = resolveNavigationTarget(url, as, navigationLocale, replaceInheritedLocale);
|
|
1306
|
+
let resolvedRoute = applyNavigationLocale(resolveUrl(url), navigationLocale, replaceInheritedLocale);
|
|
1307
|
+
if (as === void 0 && (typeof url === "string" && options?._vinextInterpolateDynamicRoute === true || typeof url !== "string" && url.pathname === void 0 && (url.query !== void 0 && Object.keys(url.query).length > 0 || typeof url.search === "string" && url.search.length > 0))) {
|
|
1308
|
+
resolved = interpolateCurrentDynamicRoute(resolved);
|
|
1309
|
+
resolvedRoute = interpolateCurrentDynamicRoute(resolvedRoute);
|
|
1310
|
+
}
|
|
1305
1311
|
if (isExternalUrl(resolved)) {
|
|
1306
1312
|
const localPath = toSameOriginAppPath(resolved, __basePath);
|
|
1307
1313
|
if (localPath == null) {
|
|
@@ -1311,10 +1317,36 @@ async function performNavigation(url, as, options, mode, onStateUpdate) {
|
|
|
1311
1317
|
}
|
|
1312
1318
|
resolved = localPath;
|
|
1313
1319
|
}
|
|
1320
|
+
if (isExternalUrl(resolvedRoute)) {
|
|
1321
|
+
const localPath = toSameOriginAppPath(resolvedRoute, __basePath);
|
|
1322
|
+
if (localPath != null) resolvedRoute = localPath;
|
|
1323
|
+
}
|
|
1314
1324
|
resolved = normalizePathTrailingSlash(resolved, __trailingSlash);
|
|
1325
|
+
resolvedRoute = normalizePathTrailingSlash(resolvedRoute, __trailingSlash);
|
|
1326
|
+
let interpolatedRoute = resolvedRoute;
|
|
1327
|
+
if (resolvedRoute.includes("[")) {
|
|
1328
|
+
const projection = interpolateDynamicRouteHref(resolvedRoute, resolved, typeof url === "string" ? void 0 : url.query);
|
|
1329
|
+
if (projection?.href) {
|
|
1330
|
+
interpolatedRoute = projection.href;
|
|
1331
|
+
if (as === void 0 && stripHash(resolved).split("?", 1)[0] === projection.routePathname) {
|
|
1332
|
+
const remaining = new URLSearchParams();
|
|
1333
|
+
const consumed = new Set(projection.params);
|
|
1334
|
+
for (const [k, v] of Object.entries(projection.query)) {
|
|
1335
|
+
if (consumed.has(k)) continue;
|
|
1336
|
+
if (v === void 0) continue;
|
|
1337
|
+
if (Array.isArray(v)) v.forEach((entry) => remaining.append(k, entry));
|
|
1338
|
+
else remaining.append(k, v);
|
|
1339
|
+
}
|
|
1340
|
+
const searchStr = remaining.toString();
|
|
1341
|
+
const hashStr = extractHash(resolved);
|
|
1342
|
+
resolved = normalizePathTrailingSlash(`${projection.href.split(/[?#]/, 1)[0]}${searchStr ? `?${searchStr}` : ""}${hashStr}`, __trailingSlash);
|
|
1343
|
+
}
|
|
1344
|
+
} else interpolatedRoute = resolved;
|
|
1345
|
+
}
|
|
1315
1346
|
const full = normalizePathTrailingSlash(toBrowserNavigationHref(resolved, window.location.href, __basePath), __trailingSlash);
|
|
1347
|
+
const fullRouteUrl = interpolatedRoute !== resolved ? normalizePathTrailingSlash(toBrowserNavigationHref(interpolatedRoute, window.location.href, __basePath), __trailingSlash) : full;
|
|
1316
1348
|
const errorRouteHtmlFetchUrl = resolvePagesErrorHtmlFetchUrl(url, navigationLocale);
|
|
1317
|
-
const htmlFetchUrl = errorRouteHtmlFetchUrl ?? getPagesHtmlFetchUrl(
|
|
1349
|
+
const htmlFetchUrl = errorRouteHtmlFetchUrl ?? getPagesHtmlFetchUrl(fullRouteUrl, navigationLocale);
|
|
1318
1350
|
const shallow = options?.shallow ?? false;
|
|
1319
1351
|
const doScroll = options?.scroll !== false;
|
|
1320
1352
|
const hash = extractHash(resolved);
|
|
@@ -1330,15 +1362,17 @@ async function performNavigation(url, as, options, mode, onStateUpdate) {
|
|
|
1330
1362
|
mode,
|
|
1331
1363
|
scroll: scrollTarget
|
|
1332
1364
|
};
|
|
1365
|
+
const currentLocale = getCurrentUrlLocale();
|
|
1366
|
+
if (mode === "push" && interpolatedRoute !== resolved && stripHash(full) === routerRuntimeState.lastPathnameAndSearch && navigationLocale === currentLocale) mode = "replace";
|
|
1333
1367
|
const navStateOptions = { shallow };
|
|
1334
1368
|
if (navigationLocale !== void 0) navStateOptions.locale = navigationLocale;
|
|
1335
1369
|
const resolvedNoHash = stripHash(resolved);
|
|
1336
1370
|
const navState = {
|
|
1337
|
-
url:
|
|
1371
|
+
url: stripHash(interpolatedRoute),
|
|
1338
1372
|
as: resolvedNoHash,
|
|
1339
1373
|
options: navStateOptions
|
|
1340
1374
|
};
|
|
1341
|
-
if (options?._h !== 1 && isHashOnlyChange(full)) {
|
|
1375
|
+
if (options?._h !== 1 && interpolatedRoute === resolved && isHashOnlyChange(full)) {
|
|
1342
1376
|
if (mode === "push") saveScrollPosition();
|
|
1343
1377
|
const eventUrl = resolveHashUrl(full);
|
|
1344
1378
|
routerEvents.emit("hashChangeStart", eventUrl, { shallow });
|
|
@@ -1363,7 +1397,7 @@ async function performNavigation(url, as, options, mode, onStateUpdate) {
|
|
|
1363
1397
|
routerEvents.emit("beforeHistoryChange", resolved, { shallow });
|
|
1364
1398
|
updateHistory(mode, full, navState);
|
|
1365
1399
|
if (!shallow) {
|
|
1366
|
-
const result = await runNavigateClient(full, resolved, htmlFetchUrl, navigateOptions);
|
|
1400
|
+
const result = await runNavigateClient(full, resolved, htmlFetchUrl, navigateOptions, fullRouteUrl);
|
|
1367
1401
|
if (result === "cancelled") return true;
|
|
1368
1402
|
if (result === "failed") return false;
|
|
1369
1403
|
} else if (doScroll) if (hash) scrollToHashTarget(hash);
|
|
@@ -1479,7 +1513,7 @@ function PagesRouterProvider({ children }) {
|
|
|
1479
1513
|
return AppRouterContext ? createElement(AppRouterContext.Provider, { value: appRouter }, content) : content;
|
|
1480
1514
|
}
|
|
1481
1515
|
function isNextRouterState(state) {
|
|
1482
|
-
return typeof state === "object" && state !== null && "__N" in state && state.__N === true;
|
|
1516
|
+
return typeof state === "object" && state !== null && "__N" in state && state.__N === true && "url" in state && typeof state.url === "string" && "as" in state && typeof state.as === "string" && "options" in state && typeof state.options === "object" && state.options !== null;
|
|
1483
1517
|
}
|
|
1484
1518
|
function getRouterStateKey(state) {
|
|
1485
1519
|
if (!isNextRouterState(state)) return void 0;
|
|
@@ -1508,11 +1542,16 @@ function handlePagesRouterPopState(e) {
|
|
|
1508
1542
|
};
|
|
1509
1543
|
}
|
|
1510
1544
|
if (routerRuntimeState.beforePopStateCb !== void 0) {
|
|
1511
|
-
|
|
1545
|
+
const beforePopStateState = isNextRouterState(state) ? {
|
|
1546
|
+
url: state.url,
|
|
1547
|
+
as: state.as,
|
|
1548
|
+
options: state.options
|
|
1549
|
+
} : {
|
|
1512
1550
|
url: appUrl,
|
|
1513
1551
|
as: appUrl,
|
|
1514
1552
|
options: { shallow: false }
|
|
1515
|
-
}
|
|
1553
|
+
};
|
|
1554
|
+
if (!routerRuntimeState.beforePopStateCb({ ...beforePopStateState })) return;
|
|
1516
1555
|
}
|
|
1517
1556
|
if (targetKey !== void 0) routerRuntimeState.currentHistoryKey = targetKey;
|
|
1518
1557
|
routerRuntimeState.lastPathnameAndSearch = browserUrl;
|
|
@@ -1524,7 +1563,8 @@ function handlePagesRouterPopState(e) {
|
|
|
1524
1563
|
dispatchNavigateEvent();
|
|
1525
1564
|
return;
|
|
1526
1565
|
}
|
|
1527
|
-
const
|
|
1566
|
+
const stateLocale = isNextRouterState(state) ? state.options?.locale : void 0;
|
|
1567
|
+
const effectiveLocale = typeof stateLocale === "string" ? stateLocale : window.__VINEXT_LOCALE__;
|
|
1528
1568
|
const fullAppUrl = appUrl + window.location.hash;
|
|
1529
1569
|
routerEvents.emit("routeChangeStart", fullAppUrl, { shallow: false });
|
|
1530
1570
|
routerEvents.emit("beforeHistoryChange", fullAppUrl, { shallow: false });
|
|
@@ -1533,7 +1573,11 @@ function handlePagesRouterPopState(e) {
|
|
|
1533
1573
|
x: 0,
|
|
1534
1574
|
y: 0
|
|
1535
1575
|
} : readScrollPosition(state);
|
|
1536
|
-
|
|
1576
|
+
const stateRouteUrl = (() => {
|
|
1577
|
+
if (isNextRouterState(state) && typeof state.url === "string" && typeof state.as === "string" && state.url !== state.as) return normalizePathTrailingSlash(withBasePath(state.url, __basePath), __trailingSlash);
|
|
1578
|
+
return browserUrl;
|
|
1579
|
+
})();
|
|
1580
|
+
if (await runNavigateClient(browserUrl, fullAppUrl, getPagesHtmlFetchUrl(stateRouteUrl, effectiveLocale), { scroll: scrollTarget }, stateRouteUrl) === "completed") {
|
|
1537
1581
|
routerEvents.emit("routeChangeComplete", fullAppUrl, { shallow: false });
|
|
1538
1582
|
dispatchNavigateEvent();
|
|
1539
1583
|
}
|
package/dist/typegen.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findDir } from "./utils/project.js";
|
|
1
2
|
import { decodeRouteSegment, isInvisibleSegment } from "./routing/utils.js";
|
|
2
3
|
import { patternToNextFormat } from "./routing/route-validation.js";
|
|
3
4
|
import { compareStrings } from "./utils/compare.js";
|
|
@@ -15,7 +16,7 @@ import "./.next/types/routes.d.ts";
|
|
|
15
16
|
`;
|
|
16
17
|
async function generateRouteTypes(options) {
|
|
17
18
|
const root = path.resolve(options.root);
|
|
18
|
-
const appDir = options.appDir ? path.resolve(options.appDir) :
|
|
19
|
+
const appDir = options.appDir ? path.resolve(options.appDir) : findDir(root, "app", path.join("src", "app"));
|
|
19
20
|
const outPath = path.join(root, ".next", "types", "routes.d.ts");
|
|
20
21
|
const content = appDir ? renderRouteTypes(await collectRouteTypeModel(appDir, options.pageExtensions)) : renderRouteTypes(emptyRouteTypeModel());
|
|
21
22
|
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
@@ -84,15 +85,6 @@ async function collectRouteTypeModel(appDir, pageExtensions) {
|
|
|
84
85
|
for (const slotNames of model.layoutSlots.values()) slotNames.sort(compareStrings);
|
|
85
86
|
return model;
|
|
86
87
|
}
|
|
87
|
-
async function findAppDir(root) {
|
|
88
|
-
for (const rel of ["app", path.join("src", "app")]) {
|
|
89
|
-
const candidate = path.join(root, rel);
|
|
90
|
-
try {
|
|
91
|
-
if ((await fs.stat(candidate)).isDirectory()) return candidate;
|
|
92
|
-
} catch {}
|
|
93
|
-
}
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
88
|
function renderRouteTypes(model) {
|
|
97
89
|
const allRoutes = uniqueSorted([
|
|
98
90
|
...model.pageRoutes,
|
package/dist/utils/project.d.ts
CHANGED
|
@@ -62,9 +62,15 @@ declare function findInNodeModules(start: string, subPath: string): string | nul
|
|
|
62
62
|
* Check if a vite.config file exists in the project root.
|
|
63
63
|
*/
|
|
64
64
|
declare function hasViteConfig(root: string): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Return the first candidate directory (resolved against `root`) that exists,
|
|
67
|
+
* or null. Used to locate conventional `app`/`pages` directories that may live
|
|
68
|
+
* at the root or under `src/`.
|
|
69
|
+
*/
|
|
70
|
+
declare function findDir(root: string, ...candidates: string[]): string | null;
|
|
65
71
|
/**
|
|
66
72
|
* Check if the project uses App Router (has an app/ directory).
|
|
67
73
|
*/
|
|
68
74
|
declare function hasAppDir(root: string): boolean;
|
|
69
75
|
//#endregion
|
|
70
|
-
export { detectPackageManager, detectPackageManagerName, ensureESModule, ensureViteConfigCompatibility, findInNodeModules, hasAppDir, hasViteConfig, renameCJSConfigs };
|
|
76
|
+
export { detectPackageManager, detectPackageManagerName, ensureESModule, ensureViteConfigCompatibility, findDir, findInNodeModules, hasAppDir, hasViteConfig, renameCJSConfigs };
|
package/dist/utils/project.js
CHANGED
|
@@ -190,10 +190,24 @@ function hasViteConfig(root) {
|
|
|
190
190
|
return fs.existsSync(path.join(root, "vite.config.ts")) || fs.existsSync(path.join(root, "vite.config.js")) || fs.existsSync(path.join(root, "vite.config.mjs"));
|
|
191
191
|
}
|
|
192
192
|
/**
|
|
193
|
+
* Return the first candidate directory (resolved against `root`) that exists,
|
|
194
|
+
* or null. Used to locate conventional `app`/`pages` directories that may live
|
|
195
|
+
* at the root or under `src/`.
|
|
196
|
+
*/
|
|
197
|
+
function findDir(root, ...candidates) {
|
|
198
|
+
for (const candidate of candidates) {
|
|
199
|
+
const full = path.join(root, candidate);
|
|
200
|
+
try {
|
|
201
|
+
if (fs.statSync(full).isDirectory()) return full;
|
|
202
|
+
} catch {}
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
193
207
|
* Check if the project uses App Router (has an app/ directory).
|
|
194
208
|
*/
|
|
195
209
|
function hasAppDir(root) {
|
|
196
|
-
return
|
|
210
|
+
return findDir(root, "app", path.join("src", "app")) !== null;
|
|
197
211
|
}
|
|
198
212
|
//#endregion
|
|
199
|
-
export { detectPackageManager, detectPackageManagerName, ensureESModule, ensureViteConfigCompatibility, findInNodeModules, hasAppDir, hasViteConfig, renameCJSConfigs };
|
|
213
|
+
export { detectPackageManager, detectPackageManagerName, ensureESModule, ensureViteConfigCompatibility, findDir, findInNodeModules, hasAppDir, hasViteConfig, renameCJSConfigs };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vinext",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Run Next.js apps on Vite. Drop-in replacement for the next CLI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -103,8 +103,8 @@
|
|
|
103
103
|
"@vitejs/plugin-react": "^6.0.1",
|
|
104
104
|
"@vitejs/plugin-rsc": "^0.5.27",
|
|
105
105
|
"react-server-dom-webpack": "^19.2.7",
|
|
106
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@0.1
|
|
107
|
-
"vite-plus": "0.1
|
|
106
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.2.1",
|
|
107
|
+
"vite-plus": "0.2.1",
|
|
108
108
|
"@vinext/cloudflare": "0.1.2"
|
|
109
109
|
},
|
|
110
110
|
"peerDependencies": {
|