@rangojs/router 0.26.4 → 0.26.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/dist/bin/rango.js +2 -2
- package/dist/types/browser/document-flight-stream.d.ts +15 -0
- package/dist/types/browser/rsc-router.d.ts +3 -2
- package/dist/types/deps/html-stream-client.d.ts +1 -1
- package/dist/types/ssr/index.d.ts +9 -7
- package/dist/types/ssr/ssr-root.d.ts +5 -5
- package/dist/types/vite/discovery/state.d.ts +4 -3
- package/dist/types/vite/plugin-types.d.ts +6 -6
- package/dist/types/vite/plugins/server-ref-hashing.d.ts +8 -1
- package/dist/vite/index.js +25 -14
- package/package.json +2 -2
- package/skills/loader/SKILL.md +4 -5
- package/skills/react-compiler/SKILL.md +4 -3
- package/src/browser/document-flight-stream.ts +53 -0
- package/src/browser/rsc-router.tsx +3 -2
- package/src/deps/html-stream-client.ts +11 -2
- package/src/ssr/index.tsx +51 -24
- package/src/ssr/ssr-root.tsx +5 -5
- package/src/vite/discovery/shell-prerender-phase.ts +2 -1
- package/src/vite/discovery/state.ts +4 -3
- package/src/vite/plugin-types.ts +6 -6
- package/src/vite/plugins/server-ref-hashing.ts +16 -5
- package/src/vite/rango.ts +0 -2
- package/src/vite/router-discovery.ts +5 -3
- package/src/vite/utils/forward-user-plugins.ts +31 -8
package/dist/bin/rango.js
CHANGED
|
@@ -1644,7 +1644,7 @@ var init_package = __esm({
|
|
|
1644
1644
|
"package.json"() {
|
|
1645
1645
|
package_default = {
|
|
1646
1646
|
name: "@rangojs/router",
|
|
1647
|
-
version: "0.26.
|
|
1647
|
+
version: "0.26.6",
|
|
1648
1648
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
1649
1649
|
keywords: [
|
|
1650
1650
|
"react",
|
|
@@ -1852,7 +1852,7 @@ var init_package = __esm({
|
|
|
1852
1852
|
},
|
|
1853
1853
|
dependencies: {
|
|
1854
1854
|
"@types/debug": "^4.1.12",
|
|
1855
|
-
"@vitejs/plugin-rsc": "^0.5.
|
|
1855
|
+
"@vitejs/plugin-rsc": "^0.5.38",
|
|
1856
1856
|
debug: "^4.4.1",
|
|
1857
1857
|
"magic-string": "^0.30.17",
|
|
1858
1858
|
picomatch: "^4.0.4",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type FlightChunk = string | Uint8Array;
|
|
2
|
+
/** Where the document's inline scripts push Flight chunks (`self`). */
|
|
3
|
+
export interface FlightDataHost {
|
|
4
|
+
__FLIGHT_DATA?: FlightChunk[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The document's Flight payload as a stream: the server's inline scripts push
|
|
8
|
+
* chunks onto `self.__FLIGHT_DATA` (rsc-html-stream/server, or
|
|
9
|
+
* ssr/inject-rsc-eager.ts), and the browser's own DOMContentLoaded
|
|
10
|
+
* (`isTrusted`) closes it after the last of them ran. rsc-html-stream/client
|
|
11
|
+
* closed on any DOMContentLoaded, so a script re-dispatching the event cut a
|
|
12
|
+
* streaming page short and threw after the load (#1205).
|
|
13
|
+
*/
|
|
14
|
+
export declare function createDocumentFlightStream(doc: Pick<Document, "readyState" | "addEventListener" | "removeEventListener">, host: FlightDataHost): ReadableStream<Uint8Array>;
|
|
15
|
+
export {};
|
|
@@ -7,7 +7,8 @@ import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
|
7
7
|
*/
|
|
8
8
|
export interface InitBrowserAppOptions {
|
|
9
9
|
/**
|
|
10
|
-
* RSC stream containing the initial payload
|
|
10
|
+
* RSC stream containing the initial payload
|
|
11
|
+
* (`@rangojs/router/internal/deps/html-stream-client`)
|
|
11
12
|
*/
|
|
12
13
|
rscStream: ReadableStream<Uint8Array>;
|
|
13
14
|
/**
|
|
@@ -133,7 +134,7 @@ export interface RangoProps {
|
|
|
133
134
|
* @example
|
|
134
135
|
* ```tsx
|
|
135
136
|
* import { initBrowserApp, Rango } from "@rangojs/router/browser";
|
|
136
|
-
* import { rscStream } from "
|
|
137
|
+
* import { rscStream } from "@rangojs/router/internal/deps/html-stream-client";
|
|
137
138
|
* import * as rscBrowser from "@vitejs/plugin-rsc/browser";
|
|
138
139
|
*
|
|
139
140
|
* async function main() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare const rscStream: ReadableStream<Uint8Array>;
|
|
@@ -169,13 +169,15 @@ export interface SSRDependencies<TEnv = unknown> {
|
|
|
169
169
|
* the `rango({ progressiveChunkSize })` plugin option here (canonical docs
|
|
170
170
|
* on `RangoBaseOptions.progressiveChunkSize` in vite/plugin-types.ts).
|
|
171
171
|
*
|
|
172
|
-
* When UNSET, createSSRHandler auto-
|
|
173
|
-
* document renders
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
172
|
+
* When UNSET, createSSRHandler and createShellCaptureHandler auto-raise to
|
|
173
|
+
* Number.MAX_SAFE_INTEGER for document renders and shell captures whose
|
|
174
|
+
* matched chain has a `loader(Def, { ssr: false })` entry (payload segments
|
|
175
|
+
* carry `awaitBeforeFlush`): the loader was awaited before first flush
|
|
176
|
+
* precisely so its content ships in-place, and React 19.2's Fizz would
|
|
177
|
+
* otherwise move any completed boundary over ~500 bytes to an end-of-stream
|
|
178
|
+
* `<div hidden>` + $RC reveal once the shell saturates the default 12800
|
|
179
|
+
* budget. A shell HIT inherits the raise (prelude and resumed holes), so it
|
|
180
|
+
* matches the live render. An explicit value disables the auto-raise.
|
|
179
181
|
*/
|
|
180
182
|
progressiveChunkSize?: number;
|
|
181
183
|
/**
|
|
@@ -28,11 +28,11 @@ export interface RscPayload {
|
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Deserialize the Flight stream branch into the payload SsrRoot renders,
|
|
31
|
-
* fragment expansion included. Factored out of SsrRoot so createSSRHandler
|
|
32
|
-
* create the promise BEFORE fizz starts and read
|
|
33
|
-
* the fizz call itself (the ssr:false
|
|
34
|
-
* awaiting it there is latency-neutral
|
|
35
|
-
* until this same promise settles.
|
|
31
|
+
* fragment expansion included. Factored out of SsrRoot so createSSRHandler and
|
|
32
|
+
* createShellCaptureHandler can create the promise BEFORE fizz starts and read
|
|
33
|
+
* metadata that must influence the fizz call itself (the ssr:false
|
|
34
|
+
* progressiveChunkSize auto-raise) — awaiting it there is latency-neutral
|
|
35
|
+
* because fizz cannot emit even <html> until this same promise settles.
|
|
36
36
|
*/
|
|
37
37
|
export declare function deserializeSsrPayload(createFromReadableStream: CreateFromReadableStream, rscStream: ReadableStream<Uint8Array>): Promise<RscPayload>;
|
|
38
38
|
/**
|
|
@@ -126,9 +126,10 @@ export interface DiscoveryState {
|
|
|
126
126
|
*/
|
|
127
127
|
userRunnerConfig: import("../utils/forward-user-plugins.js").ForwardedRunnerConfig | undefined;
|
|
128
128
|
/**
|
|
129
|
-
* User resolution plugins (resolveId/load
|
|
130
|
-
* surface, forwarded into the discovery temp
|
|
131
|
-
* resolvers such as vite-tsconfig-paths participate
|
|
129
|
+
* User resolution plugins (resolveId/load, plus import rewriters' transform),
|
|
130
|
+
* stripped to their resolution surface, forwarded into the discovery temp
|
|
131
|
+
* server. Lets third-party resolvers such as vite-tsconfig-paths participate
|
|
132
|
+
* in discovery.
|
|
132
133
|
*/
|
|
133
134
|
userResolvePlugins: import("vite").Plugin[];
|
|
134
135
|
scanFilter: ScanFilter | undefined;
|
|
@@ -165,12 +165,12 @@ interface RangoBaseOptions {
|
|
|
165
165
|
* the CSS. Trade-off: inline content delays later shell bytes behind it,
|
|
166
166
|
* which is why React outlines by default.
|
|
167
167
|
*
|
|
168
|
-
* When UNSET, document renders whose matched chain
|
|
169
|
-
* `loader(Def, { ssr: false })` entry auto-raise to MAX_SAFE_INTEGER —
|
|
170
|
-
* loader was awaited before first flush precisely so its content ships
|
|
171
|
-
* in-place, and outlining the boundary it feeds would defeat that.
|
|
172
|
-
*
|
|
173
|
-
*
|
|
168
|
+
* When UNSET, document renders and PPR shell captures whose matched chain
|
|
169
|
+
* has a `loader(Def, { ssr: false })` entry auto-raise to MAX_SAFE_INTEGER —
|
|
170
|
+
* the loader was awaited before first flush precisely so its content ships
|
|
171
|
+
* in-place, and outlining the boundary it feeds would defeat that. A shell
|
|
172
|
+
* HIT inherits the captured value, so it matches the live render. Setting
|
|
173
|
+
* an explicit value disables the auto-raise.
|
|
174
174
|
*
|
|
175
175
|
* Apps with a custom SSR entry set this per-handler via
|
|
176
176
|
* `SSRDependencies.progressiveChunkSize`.
|
|
@@ -3,7 +3,14 @@ import type { Plugin } from "vite";
|
|
|
3
3
|
* Replace dev-style server-reference ids with their production hashes. The temp
|
|
4
4
|
* server renders Static/Prerender in a dev/serve Vite server, so plugin-rsc mints
|
|
5
5
|
* a dev-style id (e.g. "/src/urls/prerender.tsx"); rewriting only the id (group 2)
|
|
6
|
-
* leaves the hoisted function
|
|
6
|
+
* leaves the hoisted function untouched.
|
|
7
|
+
*
|
|
8
|
+
* The same id also prefixes the action id ("<id>#<name>") that plugin-rsc
|
|
9
|
+
* >= 0.5.37 passes to encryptActionBoundArgs/decryptActionBoundArgs as AES-GCM
|
|
10
|
+
* additional data. The production bundle decrypts under "<hash>#<name>", so bound
|
|
11
|
+
* args encrypted at build time under the dev-style id fail with OperationError on
|
|
12
|
+
* invocation. Rewriting every `"<id>#` literal to `"<hash>#` binds them to the
|
|
13
|
+
* runtime's id.
|
|
7
14
|
* Exported for testing; used by hashServerRefs. Returns null if nothing changed.
|
|
8
15
|
*/
|
|
9
16
|
export declare function transformServerRefs(code: string, projectRoot: string): string | null;
|
package/dist/vite/index.js
CHANGED
|
@@ -7078,7 +7078,7 @@ function resolveVersionsFrom(table, routerId2) {
|
|
|
7078
7078
|
// package.json
|
|
7079
7079
|
var package_default = {
|
|
7080
7080
|
name: "@rangojs/router",
|
|
7081
|
-
version: "0.26.
|
|
7081
|
+
version: "0.26.6",
|
|
7082
7082
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
7083
7083
|
keywords: [
|
|
7084
7084
|
"react",
|
|
@@ -7286,7 +7286,7 @@ var package_default = {
|
|
|
7286
7286
|
},
|
|
7287
7287
|
dependencies: {
|
|
7288
7288
|
"@types/debug": "^4.1.12",
|
|
7289
|
-
"@vitejs/plugin-rsc": "^0.5.
|
|
7289
|
+
"@vitejs/plugin-rsc": "^0.5.38",
|
|
7290
7290
|
debug: "^4.4.1",
|
|
7291
7291
|
"magic-string": "^0.30.17",
|
|
7292
7292
|
picomatch: "^4.0.4",
|
|
@@ -10468,18 +10468,22 @@ init_client_ref_hashing();
|
|
|
10468
10468
|
var debug9 = createRangoDebugger(NS.transform);
|
|
10469
10469
|
function transformServerRefs(code, projectRoot) {
|
|
10470
10470
|
if (!code.includes("registerServerReference")) return null;
|
|
10471
|
-
|
|
10472
|
-
|
|
10471
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
10472
|
+
let result = code.replace(
|
|
10473
10473
|
registerServerReferenceRegex(),
|
|
10474
10474
|
(match, _value, refKey) => {
|
|
10475
10475
|
const cleanKey = refKey.split("?")[0];
|
|
10476
10476
|
const hash = computeProductionHash(projectRoot, cleanKey);
|
|
10477
10477
|
if (hash === cleanKey) return match;
|
|
10478
|
-
|
|
10478
|
+
hashes.set(refKey, hash);
|
|
10479
10479
|
return match.replace(`"${refKey}"`, `"${hash}"`);
|
|
10480
10480
|
}
|
|
10481
10481
|
);
|
|
10482
|
-
|
|
10482
|
+
if (hashes.size === 0) return null;
|
|
10483
|
+
for (const [refKey, hash] of hashes) {
|
|
10484
|
+
result = result.replaceAll(`"${refKey}#`, `"${hash}#`);
|
|
10485
|
+
}
|
|
10486
|
+
return result;
|
|
10483
10487
|
}
|
|
10484
10488
|
function hashServerRefs(projectRoot) {
|
|
10485
10489
|
const counter = createCounter(debug9, "hash-server-refs");
|
|
@@ -12227,7 +12231,8 @@ async function runShellPrerenderPhase(s, builder, versionsTable) {
|
|
|
12227
12231
|
prerender: reactDomStatic.prerender ?? reactDomStatic.default?.prerender,
|
|
12228
12232
|
injectRSCPayload: htmlStream.injectRSCPayload,
|
|
12229
12233
|
headScripts: "preinit",
|
|
12230
|
-
// Build-captured shells
|
|
12234
|
+
// Build-captured shells honor the explicit outlining budget, or the
|
|
12235
|
+
// ssr:false auto-raise without one (createShellCaptureHandler): the
|
|
12231
12236
|
// capture value rides the postponed state into every runtime resume().
|
|
12232
12237
|
...s.opts?.progressiveChunkSize !== void 0 && {
|
|
12233
12238
|
progressiveChunkSize: s.opts.progressiveChunkSize
|
|
@@ -12942,8 +12947,13 @@ function createDiscoveryGate(s, debug14) {
|
|
|
12942
12947
|
function isDenied(name) {
|
|
12943
12948
|
return name.startsWith("vite:") || name === "rsc" || name.startsWith("rsc:") || name.startsWith("@rangojs/router") || name.startsWith("@cloudflare/vite-plugin") || name.startsWith("vite-plugin-cloudflare");
|
|
12944
12949
|
}
|
|
12945
|
-
|
|
12946
|
-
|
|
12950
|
+
var IMPORT_REWRITE_PLUGINS = /* @__PURE__ */ new Set([
|
|
12951
|
+
"rolldown-plugin-transform-imports"
|
|
12952
|
+
]);
|
|
12953
|
+
function isForwardable(p) {
|
|
12954
|
+
return Boolean(
|
|
12955
|
+
p.resolveId || p.load || IMPORT_REWRITE_PLUGINS.has(p.name)
|
|
12956
|
+
);
|
|
12947
12957
|
}
|
|
12948
12958
|
function stripToResolutionHooks(p) {
|
|
12949
12959
|
const stripped = { name: p.name };
|
|
@@ -12952,6 +12962,8 @@ function stripToResolutionHooks(p) {
|
|
|
12952
12962
|
stripped.applyToEnvironment = p.applyToEnvironment;
|
|
12953
12963
|
if (p.resolveId) stripped.resolveId = p.resolveId;
|
|
12954
12964
|
if (p.load) stripped.load = p.load;
|
|
12965
|
+
if (IMPORT_REWRITE_PLUGINS.has(p.name))
|
|
12966
|
+
stripped.transform = p.transform;
|
|
12955
12967
|
return stripped;
|
|
12956
12968
|
}
|
|
12957
12969
|
function selectForwardableResolvePlugins(plugins) {
|
|
@@ -12960,7 +12972,7 @@ function selectForwardableResolvePlugins(plugins) {
|
|
|
12960
12972
|
for (const p of plugins) {
|
|
12961
12973
|
const name = p?.name;
|
|
12962
12974
|
if (!name || isDenied(name)) continue;
|
|
12963
|
-
if (!
|
|
12975
|
+
if (!isForwardable(p)) continue;
|
|
12964
12976
|
forwarded.push(stripToResolutionHooks(p));
|
|
12965
12977
|
}
|
|
12966
12978
|
return forwarded;
|
|
@@ -13110,7 +13122,8 @@ async function createTempRscServer(state, options = {}) {
|
|
|
13110
13122
|
}
|
|
13111
13123
|
},
|
|
13112
13124
|
// Forwarded user resolution plugins (e.g. vite-tsconfig-paths). Stripped
|
|
13113
|
-
// to resolveId/load
|
|
13125
|
+
// to resolveId/load (plus transform for import rewriters, see
|
|
13126
|
+
// IMPORT_REWRITE_PLUGINS) and placed last so framework resolution runs first;
|
|
13114
13127
|
// Vite re-sorts by `enforce`, so `enforce: "pre"` resolvers still lead.
|
|
13115
13128
|
...state.userResolvePlugins
|
|
13116
13129
|
]
|
|
@@ -14755,7 +14768,6 @@ async function rango(options) {
|
|
|
14755
14768
|
}
|
|
14756
14769
|
},
|
|
14757
14770
|
optimizeDeps: {
|
|
14758
|
-
include: [nested("rsc-html-stream/client")],
|
|
14759
14771
|
exclude: excludeDeps,
|
|
14760
14772
|
rolldownOptions: sharedRolldownOptions
|
|
14761
14773
|
}
|
|
@@ -14923,8 +14935,7 @@ If this is a multi-app host router, export a createHostRouter() instance and set
|
|
|
14923
14935
|
"react",
|
|
14924
14936
|
"react-dom",
|
|
14925
14937
|
"react/jsx-runtime",
|
|
14926
|
-
"react/jsx-dev-runtime"
|
|
14927
|
-
nested("rsc-html-stream/client")
|
|
14938
|
+
"react/jsx-dev-runtime"
|
|
14928
14939
|
],
|
|
14929
14940
|
exclude: excludeDeps,
|
|
14930
14941
|
rolldownOptions: sharedRolldownOptions,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rangojs/router",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.6",
|
|
4
4
|
"description": "Django-inspired RSC router with composable URL patterns",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -194,7 +194,7 @@
|
|
|
194
194
|
},
|
|
195
195
|
"dependencies": {
|
|
196
196
|
"@types/debug": "^4.1.12",
|
|
197
|
-
"@vitejs/plugin-rsc": "^0.5.
|
|
197
|
+
"@vitejs/plugin-rsc": "^0.5.38",
|
|
198
198
|
"debug": "^4.4.1",
|
|
199
199
|
"magic-string": "^0.30.17",
|
|
200
200
|
"picomatch": "^4.0.4",
|
package/skills/loader/SKILL.md
CHANGED
|
@@ -1136,8 +1136,9 @@ COMPLETED Suspense boundary over ~500 bytes to an end-of-stream
|
|
|
1136
1136
|
`progressiveChunkSize` budget — the bytes are in the HTML, but not at their
|
|
1137
1137
|
document position, and a consumer that doesn't execute scripts never sees
|
|
1138
1138
|
them revealed. When the matched chain has a flagged loader, the document
|
|
1139
|
-
render
|
|
1140
|
-
content renders where it belongs
|
|
1139
|
+
render and the `ppr` shell capture raise the budget to `MAX_SAFE_INTEGER`
|
|
1140
|
+
automatically, so the awaited content renders where it belongs, on a shell
|
|
1141
|
+
HIT too. Pin the budget yourself (in either
|
|
1141
1142
|
direction) with `rango({ progressiveChunkSize })` in the Vite config — an
|
|
1142
1143
|
explicit value disables the auto-raise. Boundaries hoisting stylesheets
|
|
1143
1144
|
still outline; their reveal must wait for the CSS.
|
|
@@ -1160,9 +1161,7 @@ The costs and constraints:
|
|
|
1160
1161
|
served from the shell and does not run (unless the capture marked it to run:
|
|
1161
1162
|
see `/ppr` → On a shell HIT); one whose return carries promises
|
|
1162
1163
|
runs on every HIT, with its baked parts overlaid. A `cookies()`, `headers()`,
|
|
1163
|
-
`ctx.request.headers` or `{ cache: false }` read in it refuses the capture.
|
|
1164
|
-
`progressiveChunkSize` auto-raise is live-document only; captured shells
|
|
1165
|
-
outline per the explicit option or React's default.
|
|
1164
|
+
`ctx.request.headers` or `{ cache: false }` read in it refuses the capture.
|
|
1166
1165
|
|
|
1167
1166
|
Also available in `clientUrls()` route groups (`/client-urls`), where the
|
|
1168
1167
|
loader-heavy shape makes it most useful.
|
|
@@ -122,9 +122,10 @@ options (`logger`, function-valued `sources`) do not exist on the native path;
|
|
|
122
122
|
|
|
123
123
|
Nothing to configure. Rango's discovery/prerender step runs a throwaway temp Vite
|
|
124
124
|
server (`createTempRscServer`) that forwards only your **resolution** plugins
|
|
125
|
-
(`resolveId` / `load
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
(`resolveId` / `load`, plus the import rewrite of
|
|
126
|
+
`@rolldown/plugin-transform-imports`) and denies every `vite:*` plugin, so
|
|
127
|
+
`vite:react-compiler` never runs there. That is correct: the temp runner only
|
|
128
|
+
produces **data** (serialized Flight payloads + the route manifest), not shipped code, and React
|
|
128
129
|
Compiler is a memoization-only transform that does not change rendered output.
|
|
129
130
|
Your shipped client bundle is compiled by the `react({ compiler: true })` in your
|
|
130
131
|
app's own plugin array.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
type FlightChunk = string | Uint8Array;
|
|
2
|
+
|
|
3
|
+
/** Where the document's inline scripts push Flight chunks (`self`). */
|
|
4
|
+
export interface FlightDataHost {
|
|
5
|
+
__FLIGHT_DATA?: FlightChunk[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The document's Flight payload as a stream: the server's inline scripts push
|
|
10
|
+
* chunks onto `self.__FLIGHT_DATA` (rsc-html-stream/server, or
|
|
11
|
+
* ssr/inject-rsc-eager.ts), and the browser's own DOMContentLoaded
|
|
12
|
+
* (`isTrusted`) closes it after the last of them ran. rsc-html-stream/client
|
|
13
|
+
* closed on any DOMContentLoaded, so a script re-dispatching the event cut a
|
|
14
|
+
* streaming page short and threw after the load (#1205).
|
|
15
|
+
*/
|
|
16
|
+
export function createDocumentFlightStream(
|
|
17
|
+
doc: Pick<
|
|
18
|
+
Document,
|
|
19
|
+
"readyState" | "addEventListener" | "removeEventListener"
|
|
20
|
+
>,
|
|
21
|
+
host: FlightDataHost,
|
|
22
|
+
): ReadableStream<Uint8Array> {
|
|
23
|
+
const encoder = new TextEncoder();
|
|
24
|
+
let controller!: ReadableStreamDefaultController<Uint8Array>;
|
|
25
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
26
|
+
start(c) {
|
|
27
|
+
controller = c;
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const enqueue = (chunk: FlightChunk): void => {
|
|
31
|
+
controller.enqueue(
|
|
32
|
+
typeof chunk === "string" ? encoder.encode(chunk) : chunk,
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
const data = (host.__FLIGHT_DATA ||= []);
|
|
36
|
+
data.forEach(enqueue);
|
|
37
|
+
data.push = (...chunks: FlightChunk[]): number => {
|
|
38
|
+
chunks.forEach(enqueue);
|
|
39
|
+
return data.length;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (doc.readyState !== "loading") {
|
|
43
|
+
controller.close();
|
|
44
|
+
} else {
|
|
45
|
+
const onLoaded = (event: Event): void => {
|
|
46
|
+
if (!event.isTrusted) return;
|
|
47
|
+
doc.removeEventListener("DOMContentLoaded", onLoaded);
|
|
48
|
+
controller.close();
|
|
49
|
+
};
|
|
50
|
+
doc.addEventListener("DOMContentLoaded", onLoaded);
|
|
51
|
+
}
|
|
52
|
+
return stream;
|
|
53
|
+
}
|
|
@@ -59,7 +59,8 @@ import { setActiveInterceptTargets } from "../client-urls/navigation.js";
|
|
|
59
59
|
*/
|
|
60
60
|
export interface InitBrowserAppOptions {
|
|
61
61
|
/**
|
|
62
|
-
* RSC stream containing the initial payload
|
|
62
|
+
* RSC stream containing the initial payload
|
|
63
|
+
* (`@rangojs/router/internal/deps/html-stream-client`)
|
|
63
64
|
*/
|
|
64
65
|
rscStream: ReadableStream<Uint8Array>;
|
|
65
66
|
|
|
@@ -697,7 +698,7 @@ export interface RangoProps {}
|
|
|
697
698
|
* @example
|
|
698
699
|
* ```tsx
|
|
699
700
|
* import { initBrowserApp, Rango } from "@rangojs/router/browser";
|
|
700
|
-
* import { rscStream } from "
|
|
701
|
+
* import { rscStream } from "@rangojs/router/internal/deps/html-stream-client";
|
|
701
702
|
* import * as rscBrowser from "@vitejs/plugin-rsc/browser";
|
|
702
703
|
*
|
|
703
704
|
* async function main() {
|
|
@@ -1,2 +1,11 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// The document's Flight stream for the generated browser entry. Outside a
|
|
2
|
+
// browser it stays empty and open, as rsc-html-stream/client's did.
|
|
3
|
+
import {
|
|
4
|
+
createDocumentFlightStream,
|
|
5
|
+
type FlightDataHost,
|
|
6
|
+
} from "../browser/document-flight-stream.js";
|
|
7
|
+
|
|
8
|
+
export const rscStream: ReadableStream<Uint8Array> =
|
|
9
|
+
typeof document === "undefined"
|
|
10
|
+
? new ReadableStream<Uint8Array>()
|
|
11
|
+
: createDocumentFlightStream(document, self as FlightDataHost);
|
package/src/ssr/index.tsx
CHANGED
|
@@ -230,13 +230,15 @@ export interface SSRDependencies<TEnv = unknown> {
|
|
|
230
230
|
* the `rango({ progressiveChunkSize })` plugin option here (canonical docs
|
|
231
231
|
* on `RangoBaseOptions.progressiveChunkSize` in vite/plugin-types.ts).
|
|
232
232
|
*
|
|
233
|
-
* When UNSET, createSSRHandler auto-
|
|
234
|
-
* document renders
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
233
|
+
* When UNSET, createSSRHandler and createShellCaptureHandler auto-raise to
|
|
234
|
+
* Number.MAX_SAFE_INTEGER for document renders and shell captures whose
|
|
235
|
+
* matched chain has a `loader(Def, { ssr: false })` entry (payload segments
|
|
236
|
+
* carry `awaitBeforeFlush`): the loader was awaited before first flush
|
|
237
|
+
* precisely so its content ships in-place, and React 19.2's Fizz would
|
|
238
|
+
* otherwise move any completed boundary over ~500 bytes to an end-of-stream
|
|
239
|
+
* `<div hidden>` + $RC reveal once the shell saturates the default 12800
|
|
240
|
+
* budget. A shell HIT inherits the raise (prelude and resumed holes), so it
|
|
241
|
+
* matches the live render. An explicit value disables the auto-raise.
|
|
240
242
|
*/
|
|
241
243
|
progressiveChunkSize?: number;
|
|
242
244
|
|
|
@@ -448,6 +450,18 @@ function hasAwaitBeforeFlush(payload: {
|
|
|
448
450
|
return payload.metadata?.segments?.some((s) => s.awaitBeforeFlush) === true;
|
|
449
451
|
}
|
|
450
452
|
|
|
453
|
+
/**
|
|
454
|
+
* The progressiveChunkSize renderHTML and captureShellHTML pass to Fizz (see
|
|
455
|
+
* SSRDependencies.progressiveChunkSize): the explicit option, else the
|
|
456
|
+
* ssr:false auto-raise.
|
|
457
|
+
*/
|
|
458
|
+
function documentChunkSize(
|
|
459
|
+
explicit: number | undefined,
|
|
460
|
+
awaitsBeforeFlush: boolean,
|
|
461
|
+
): number | undefined {
|
|
462
|
+
return explicit ?? (awaitsBeforeFlush ? Number.MAX_SAFE_INTEGER : undefined);
|
|
463
|
+
}
|
|
464
|
+
|
|
451
465
|
/**
|
|
452
466
|
* ssr: false documents only: before the first flush, give Fizz the turns a
|
|
453
467
|
* task waiting on an already-settling thenable needs, so a boundary that
|
|
@@ -955,9 +969,10 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
955
969
|
themeConfig: decoded.metadata?.themeConfig ?? null,
|
|
956
970
|
};
|
|
957
971
|
const awaitsBeforeFlush = hasAwaitBeforeFlush(decoded);
|
|
958
|
-
const progressiveChunkSize =
|
|
959
|
-
deps.progressiveChunkSize
|
|
960
|
-
|
|
972
|
+
const progressiveChunkSize = documentChunkSize(
|
|
973
|
+
deps.progressiveChunkSize,
|
|
974
|
+
awaitsBeforeFlush,
|
|
975
|
+
);
|
|
961
976
|
|
|
962
977
|
// Render React tree to HTML stream
|
|
963
978
|
// Pass formState for useActionState progressive enhancement if provided
|
|
@@ -1110,9 +1125,23 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
1110
1125
|
const payloadSettled = new Promise<void>((resolve) => {
|
|
1111
1126
|
settlePayload = resolve;
|
|
1112
1127
|
});
|
|
1128
|
+
// Bootstrap resolution raced against the deadline (see resolveBootstrap):
|
|
1129
|
+
// null means the deadline won — the bounded no-shell degrade.
|
|
1130
|
+
const bootstrap = await resolveBootstrap(deps, deadline.promise);
|
|
1131
|
+
if (bootstrap === null) {
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Decoded after the bootstrap, as SsrRoot's first render used to, so
|
|
1136
|
+
// the capture gate's quiet clock starts only once fizz is about to.
|
|
1137
|
+
const payload = deserializeSsrPayload(
|
|
1138
|
+
createFromReadableStream,
|
|
1139
|
+
rscStream,
|
|
1140
|
+
);
|
|
1113
1141
|
const SsrRoot = createSsrRootComponent({
|
|
1114
1142
|
createFromReadableStream,
|
|
1115
1143
|
rscStream,
|
|
1144
|
+
payload,
|
|
1116
1145
|
onPayloadSettled: settlePayload,
|
|
1117
1146
|
// The shell key's own search: static-part search reads render what
|
|
1118
1147
|
// the key names, and the resume pass seeds the identical string.
|
|
@@ -1120,12 +1149,17 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
1120
1149
|
origin: opts.origin,
|
|
1121
1150
|
});
|
|
1122
1151
|
|
|
1123
|
-
//
|
|
1124
|
-
//
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1152
|
+
// ssr:false auto-raise (SSRDependencies.progressiveChunkSize); resume()
|
|
1153
|
+
// inherits it from the postponed state. A rejected or late payload
|
|
1154
|
+
// skips it and fails or degrades below as before.
|
|
1155
|
+
const awaitsBeforeFlush = await Promise.race([
|
|
1156
|
+
payload.then(hasAwaitBeforeFlush, () => false),
|
|
1157
|
+
deadline.promise.then(() => false),
|
|
1158
|
+
]);
|
|
1159
|
+
const progressiveChunkSize = documentChunkSize(
|
|
1160
|
+
deps.progressiveChunkSize,
|
|
1161
|
+
awaitsBeforeFlush,
|
|
1162
|
+
);
|
|
1129
1163
|
|
|
1130
1164
|
// Start prerender first, then run the abort schedule concurrently. When
|
|
1131
1165
|
// holes are pending, prerender's promise settles only after abort(); when
|
|
@@ -1140,14 +1174,7 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
1140
1174
|
const prerenderPromise = prerender(<SsrRoot />, {
|
|
1141
1175
|
signal: controller.signal,
|
|
1142
1176
|
...bootstrap,
|
|
1143
|
-
|
|
1144
|
-
// (RangoBaseOptions.progressiveChunkSize documents the contract); the
|
|
1145
|
-
// capture handler starts prerender without deserializing the payload,
|
|
1146
|
-
// so segment metadata is not read here. The captured value rides the
|
|
1147
|
-
// postponed state into every later resume().
|
|
1148
|
-
...(deps.progressiveChunkSize !== undefined && {
|
|
1149
|
-
progressiveChunkSize: deps.progressiveChunkSize,
|
|
1150
|
-
}),
|
|
1177
|
+
...(progressiveChunkSize !== undefined && { progressiveChunkSize }),
|
|
1151
1178
|
// Abort is how capture WORKS: once the shell is quiet we abort() to
|
|
1152
1179
|
// freeze the prelude and let the still-pending holes postpone. React
|
|
1153
1180
|
// reports the abort reason for each pending boundary through onError.
|
package/src/ssr/ssr-root.tsx
CHANGED
|
@@ -147,11 +147,11 @@ function createSsrEventController(opts: {
|
|
|
147
147
|
|
|
148
148
|
/**
|
|
149
149
|
* Deserialize the Flight stream branch into the payload SsrRoot renders,
|
|
150
|
-
* fragment expansion included. Factored out of SsrRoot so createSSRHandler
|
|
151
|
-
* create the promise BEFORE fizz starts and read
|
|
152
|
-
* the fizz call itself (the ssr:false
|
|
153
|
-
* awaiting it there is latency-neutral
|
|
154
|
-
* until this same promise settles.
|
|
150
|
+
* fragment expansion included. Factored out of SsrRoot so createSSRHandler and
|
|
151
|
+
* createShellCaptureHandler can create the promise BEFORE fizz starts and read
|
|
152
|
+
* metadata that must influence the fizz call itself (the ssr:false
|
|
153
|
+
* progressiveChunkSize auto-raise) — awaiting it there is latency-neutral
|
|
154
|
+
* because fizz cannot emit even <html> until this same promise settles.
|
|
155
155
|
*/
|
|
156
156
|
export function deserializeSsrPayload(
|
|
157
157
|
createFromReadableStream: CreateFromReadableStream,
|
|
@@ -187,7 +187,8 @@ export async function runShellPrerenderPhase(
|
|
|
187
187
|
prerender: reactDomStatic.prerender ?? reactDomStatic.default?.prerender,
|
|
188
188
|
injectRSCPayload: htmlStream.injectRSCPayload,
|
|
189
189
|
headScripts: "preinit",
|
|
190
|
-
// Build-captured shells
|
|
190
|
+
// Build-captured shells honor the explicit outlining budget, or the
|
|
191
|
+
// ssr:false auto-raise without one (createShellCaptureHandler): the
|
|
191
192
|
// capture value rides the postponed state into every runtime resume().
|
|
192
193
|
...(s.opts?.progressiveChunkSize !== undefined && {
|
|
193
194
|
progressiveChunkSize: s.opts.progressiveChunkSize,
|
|
@@ -135,9 +135,10 @@ export interface DiscoveryState {
|
|
|
135
135
|
| import("../utils/forward-user-plugins.js").ForwardedRunnerConfig
|
|
136
136
|
| undefined;
|
|
137
137
|
/**
|
|
138
|
-
* User resolution plugins (resolveId/load
|
|
139
|
-
* surface, forwarded into the discovery temp
|
|
140
|
-
* resolvers such as vite-tsconfig-paths participate
|
|
138
|
+
* User resolution plugins (resolveId/load, plus import rewriters' transform),
|
|
139
|
+
* stripped to their resolution surface, forwarded into the discovery temp
|
|
140
|
+
* server. Lets third-party resolvers such as vite-tsconfig-paths participate
|
|
141
|
+
* in discovery.
|
|
141
142
|
*/
|
|
142
143
|
userResolvePlugins: import("vite").Plugin[];
|
|
143
144
|
scanFilter: ScanFilter | undefined;
|
package/src/vite/plugin-types.ts
CHANGED
|
@@ -189,12 +189,12 @@ interface RangoBaseOptions {
|
|
|
189
189
|
* the CSS. Trade-off: inline content delays later shell bytes behind it,
|
|
190
190
|
* which is why React outlines by default.
|
|
191
191
|
*
|
|
192
|
-
* When UNSET, document renders whose matched chain
|
|
193
|
-
* `loader(Def, { ssr: false })` entry auto-raise to MAX_SAFE_INTEGER —
|
|
194
|
-
* loader was awaited before first flush precisely so its content ships
|
|
195
|
-
* in-place, and outlining the boundary it feeds would defeat that.
|
|
196
|
-
*
|
|
197
|
-
*
|
|
192
|
+
* When UNSET, document renders and PPR shell captures whose matched chain
|
|
193
|
+
* has a `loader(Def, { ssr: false })` entry auto-raise to MAX_SAFE_INTEGER —
|
|
194
|
+
* the loader was awaited before first flush precisely so its content ships
|
|
195
|
+
* in-place, and outlining the boundary it feeds would defeat that. A shell
|
|
196
|
+
* HIT inherits the captured value, so it matches the live render. Setting
|
|
197
|
+
* an explicit value disables the auto-raise.
|
|
198
198
|
*
|
|
199
199
|
* Apps with a custom SSR entry set this per-handler via
|
|
200
200
|
* `SSRDependencies.progressiveChunkSize`.
|
|
@@ -9,7 +9,14 @@ const debug = createRangoDebugger(NS.transform);
|
|
|
9
9
|
* Replace dev-style server-reference ids with their production hashes. The temp
|
|
10
10
|
* server renders Static/Prerender in a dev/serve Vite server, so plugin-rsc mints
|
|
11
11
|
* a dev-style id (e.g. "/src/urls/prerender.tsx"); rewriting only the id (group 2)
|
|
12
|
-
* leaves the hoisted function
|
|
12
|
+
* leaves the hoisted function untouched.
|
|
13
|
+
*
|
|
14
|
+
* The same id also prefixes the action id ("<id>#<name>") that plugin-rsc
|
|
15
|
+
* >= 0.5.37 passes to encryptActionBoundArgs/decryptActionBoundArgs as AES-GCM
|
|
16
|
+
* additional data. The production bundle decrypts under "<hash>#<name>", so bound
|
|
17
|
+
* args encrypted at build time under the dev-style id fail with OperationError on
|
|
18
|
+
* invocation. Rewriting every `"<id>#` literal to `"<hash>#` binds them to the
|
|
19
|
+
* runtime's id.
|
|
13
20
|
* Exported for testing; used by hashServerRefs. Returns null if nothing changed.
|
|
14
21
|
*/
|
|
15
22
|
export function transformServerRefs(
|
|
@@ -18,8 +25,8 @@ export function transformServerRefs(
|
|
|
18
25
|
): string | null {
|
|
19
26
|
if (!code.includes("registerServerReference")) return null;
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
const hashes = new Map<string, string>();
|
|
29
|
+
let result = code.replace(
|
|
23
30
|
registerServerReferenceRegex(),
|
|
24
31
|
(match, _value: string, refKey: string) => {
|
|
25
32
|
// The temp server is not a long-lived HMR server, so ids are bare; strip a
|
|
@@ -27,12 +34,16 @@ export function transformServerRefs(
|
|
|
27
34
|
const cleanKey = refKey.split("?")[0]!;
|
|
28
35
|
const hash = computeProductionHash(projectRoot, cleanKey);
|
|
29
36
|
if (hash === cleanKey) return match;
|
|
30
|
-
|
|
37
|
+
hashes.set(refKey, hash);
|
|
31
38
|
return match.replace(`"${refKey}"`, `"${hash}"`);
|
|
32
39
|
},
|
|
33
40
|
);
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
if (hashes.size === 0) return null;
|
|
43
|
+
for (const [refKey, hash] of hashes) {
|
|
44
|
+
result = result.replaceAll(`"${refKey}#`, `"${hash}#`);
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
/**
|
package/src/vite/rango.ts
CHANGED
|
@@ -234,7 +234,6 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
234
234
|
},
|
|
235
235
|
},
|
|
236
236
|
optimizeDeps: {
|
|
237
|
-
include: [nested("rsc-html-stream/client")],
|
|
238
237
|
exclude: excludeDeps,
|
|
239
238
|
rolldownOptions: sharedRolldownOptions,
|
|
240
239
|
},
|
|
@@ -457,7 +456,6 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
457
456
|
"react-dom",
|
|
458
457
|
"react/jsx-runtime",
|
|
459
458
|
"react/jsx-dev-runtime",
|
|
460
|
-
nested("rsc-html-stream/client"),
|
|
461
459
|
],
|
|
462
460
|
exclude: excludeDeps,
|
|
463
461
|
rolldownOptions: sharedRolldownOptions,
|
|
@@ -350,7 +350,8 @@ export async function createTempRscServer(
|
|
|
350
350
|
},
|
|
351
351
|
},
|
|
352
352
|
// Forwarded user resolution plugins (e.g. vite-tsconfig-paths). Stripped
|
|
353
|
-
// to resolveId/load
|
|
353
|
+
// to resolveId/load (plus transform for import rewriters, see
|
|
354
|
+
// IMPORT_REWRITE_PLUGINS) and placed last so framework resolution runs first;
|
|
354
355
|
// Vite re-sorts by `enforce`, so `enforce: "pre"` resolvers still lead.
|
|
355
356
|
...state.userResolvePlugins,
|
|
356
357
|
],
|
|
@@ -592,8 +593,9 @@ export function createRouterDiscoveryPlugin(
|
|
|
592
593
|
// Capture user's resolve aliases for the temp server
|
|
593
594
|
s.userResolveAlias = config.resolve.alias;
|
|
594
595
|
// Capture the data-only resolution config (resolve.*, define, oxc) and
|
|
595
|
-
// the user's resolution plugins (resolveId/load
|
|
596
|
-
// server resolves modules the same way
|
|
596
|
+
// the user's resolution plugins (resolveId/load, plus import rewriters'
|
|
597
|
+
// transform) so the discovery temp server resolves modules the same way
|
|
598
|
+
// the real environment does.
|
|
597
599
|
// Without this, both flavors of user resolution are absent during
|
|
598
600
|
// discovery/prerender/static rendering even though they apply at request
|
|
599
601
|
// time: third-party resolvers (e.g. vite-tsconfig-paths, forwarded as
|
|
@@ -37,18 +37,39 @@ function isDenied(name: string): boolean {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
* Transform-only plugins forwarded with their `transform` hook. A plugin
|
|
41
|
+
* belongs here only if its transform changes the code or imports discovery
|
|
42
|
+
* evaluates, and reads only state built at construction or in
|
|
43
|
+
* `config`/`configResolved`. Neither shows in a hook's shape, so the list is
|
|
44
|
+
* by name: forwarding every transform would let e.g. @tailwindcss/vite cache
|
|
45
|
+
* per-environment state built from the temp server's config.
|
|
46
|
+
*
|
|
47
|
+
* `rolldown-plugin-transform-imports` (@rolldown/plugin-transform-imports)
|
|
48
|
+
* rewrites barrel imports to subpaths (vitejs/vite-plugin-react#1519). Dropped,
|
|
49
|
+
* discovery loads the whole barrel and fails on a leaf importing `client-only`.
|
|
50
|
+
*/
|
|
51
|
+
const IMPORT_REWRITE_PLUGINS: ReadonlySet<string> = new Set([
|
|
52
|
+
"rolldown-plugin-transform-imports",
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A plugin participates in resolution if it exposes `resolveId` or `load`, or
|
|
57
|
+
* rewrites imports (IMPORT_REWRITE_PLUGINS). Other plugins that only transform,
|
|
58
|
+
* configure the server, or hook into the build lifecycle are skipped to keep
|
|
43
59
|
* the forwarded surface minimal.
|
|
44
60
|
*/
|
|
45
|
-
function
|
|
46
|
-
return Boolean(
|
|
61
|
+
function isForwardable(p: Plugin): boolean {
|
|
62
|
+
return Boolean(
|
|
63
|
+
(p as any).resolveId ||
|
|
64
|
+
(p as any).load ||
|
|
65
|
+
IMPORT_REWRITE_PLUGINS.has(p.name),
|
|
66
|
+
);
|
|
47
67
|
}
|
|
48
68
|
|
|
49
69
|
/**
|
|
50
|
-
* Strip a resolved plugin instance down to its resolution hooks
|
|
51
|
-
* gating fields that decide whether/where it
|
|
70
|
+
* Strip a resolved plugin instance down to its resolution hooks (and an import
|
|
71
|
+
* rewriter's `transform`) plus the gating fields that decide whether/where it
|
|
72
|
+
* runs.
|
|
52
73
|
*
|
|
53
74
|
* We reuse the SAME instance objects captured from `config.plugins`. By the
|
|
54
75
|
* time `configResolved` fires on the discovery plugin, every plugin's own
|
|
@@ -78,6 +99,8 @@ function stripToResolutionHooks(p: Plugin): Plugin {
|
|
|
78
99
|
(stripped as any).applyToEnvironment = (p as any).applyToEnvironment;
|
|
79
100
|
if ((p as any).resolveId) (stripped as any).resolveId = (p as any).resolveId;
|
|
80
101
|
if ((p as any).load) (stripped as any).load = (p as any).load;
|
|
102
|
+
if (IMPORT_REWRITE_PLUGINS.has(p.name))
|
|
103
|
+
(stripped as any).transform = (p as any).transform;
|
|
81
104
|
return stripped;
|
|
82
105
|
}
|
|
83
106
|
|
|
@@ -95,7 +118,7 @@ export function selectForwardableResolvePlugins(
|
|
|
95
118
|
for (const p of plugins) {
|
|
96
119
|
const name = p?.name;
|
|
97
120
|
if (!name || isDenied(name)) continue;
|
|
98
|
-
if (!
|
|
121
|
+
if (!isForwardable(p)) continue;
|
|
99
122
|
forwarded.push(stripToResolutionHooks(p));
|
|
100
123
|
}
|
|
101
124
|
return forwarded;
|