@rangojs/router 0.12.0 → 0.12.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/testing/vitest.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
// src/testing/vitest.ts
|
|
2
|
-
import {
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
4
|
function here(relativeFromRoot) {
|
|
4
5
|
return fileURLToPath(new URL(`../../${relativeFromRoot}`, import.meta.url));
|
|
5
6
|
}
|
|
7
|
+
var RSD_SERVER_EDGE_SPEC = "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge";
|
|
8
|
+
var requireFromHere = createRequire(
|
|
9
|
+
import.meta.url
|
|
10
|
+
);
|
|
11
|
+
function resolveFromRouter(spec) {
|
|
12
|
+
try {
|
|
13
|
+
return requireFromHere.resolve(spec);
|
|
14
|
+
} catch {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
var rsdServerEdgePath = resolveFromRouter(RSD_SERVER_EDGE_SPEC);
|
|
6
19
|
function rangoTestAliases(opts = {}) {
|
|
7
20
|
const aliases = [
|
|
8
21
|
// Real impls (index.rsc.ts) for the bare specifier ONLY — exact regex so
|
|
@@ -18,6 +31,12 @@ function rangoTestAliases(opts = {}) {
|
|
|
18
31
|
replacement: here("src/testing/vitest-stubs/plugin-rsc.ts")
|
|
19
32
|
}
|
|
20
33
|
];
|
|
34
|
+
if (rsdServerEdgePath) {
|
|
35
|
+
aliases.push({
|
|
36
|
+
find: RSD_SERVER_EDGE_SPEC,
|
|
37
|
+
replacement: rsdServerEdgePath
|
|
38
|
+
});
|
|
39
|
+
}
|
|
21
40
|
if (opts.preset === "cloudflare") {
|
|
22
41
|
aliases.push(
|
|
23
42
|
{
|
|
@@ -63,8 +82,9 @@ function rangoUseClientTransform() {
|
|
|
63
82
|
});
|
|
64
83
|
if (!result) return void 0;
|
|
65
84
|
const { output } = result;
|
|
85
|
+
const rsdHref = rsdServerEdgePath ? pathToFileURL(rsdServerEdgePath).href : RSD_SERVER_EDGE_SPEC;
|
|
66
86
|
output.prepend(
|
|
67
|
-
`import * as $$RangoRSD from
|
|
87
|
+
`import * as $$RangoRSD from ${JSON.stringify(rsdHref)};
|
|
68
88
|
`
|
|
69
89
|
);
|
|
70
90
|
return {
|
|
@@ -28,9 +28,11 @@
|
|
|
28
28
|
* type:module in scope the deployed (isolated) function loads them as
|
|
29
29
|
* CommonJS and fails on the first `import`.
|
|
30
30
|
*
|
|
31
|
-
* The launcher is bundled with
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* The launcher is bundled with rolldown (Vite 8's bundler — a production
|
|
32
|
+
* dependency of `vite`, resolved through the app's vite install) so a
|
|
33
|
+
* standalone consumer does not need `esbuild`. srvx (the Web->Node streaming
|
|
34
|
+
* bridge, a @rangojs/router dependency) and @vercel/functions (resolved from
|
|
35
|
+
* the app) are inlined; the RSC bundle stays a runtime-relative external.
|
|
34
36
|
*
|
|
35
37
|
* Timing: this runs in the `buildApp` hook (order "post"), which fires once
|
|
36
38
|
* after every environment has built, so dist/{client,rsc,ssr} all exist.
|
|
@@ -41,6 +43,23 @@
|
|
|
41
43
|
*/
|
|
42
44
|
import type { Plugin } from "vite";
|
|
43
45
|
import type { RangoVercelOptions, VercelPresetOptions } from "../plugin-types.js";
|
|
46
|
+
/**
|
|
47
|
+
* Resolve rolldown through the app's vite install (rolldown is a production
|
|
48
|
+
* dependency of Vite 8, unlike esbuild which is only an optional peer). Fall
|
|
49
|
+
* back to the app root, then the plugin's own vite, so a hoisted or
|
|
50
|
+
* workspace copy still works. Issue #785: the previous "esbuild ships with
|
|
51
|
+
* Vite" path broke standalone consumers on Vite 8.
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveRolldownPath(root: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Bundle the Node launcher into `funcDir/index.mjs`: srvx + @vercel/functions
|
|
56
|
+
* inlined, `./rsc/index.js` left as a runtime-relative external.
|
|
57
|
+
*/
|
|
58
|
+
export declare function bundleVercelLauncher(opts: {
|
|
59
|
+
root: string;
|
|
60
|
+
funcDir: string;
|
|
61
|
+
srvxNodePath: string;
|
|
62
|
+
}): Promise<void>;
|
|
44
63
|
/**
|
|
45
64
|
* Reject a non-Node runtime for the vercel preset. The preset only emits a Node
|
|
46
65
|
* serverless function (launcherType "Nodejs", bundled Node APIs, response
|
package/dist/vite/index.js
CHANGED
|
@@ -3746,7 +3746,7 @@ import { resolve } from "node:path";
|
|
|
3746
3746
|
// package.json
|
|
3747
3747
|
var package_default = {
|
|
3748
3748
|
name: "@rangojs/router",
|
|
3749
|
-
version: "0.12.
|
|
3749
|
+
version: "0.12.1",
|
|
3750
3750
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
3751
3751
|
keywords: [
|
|
3752
3752
|
"react",
|
|
@@ -5682,6 +5682,7 @@ import { existsSync as existsSync5 } from "node:fs";
|
|
|
5682
5682
|
import { resolve as resolve5, join as join4 } from "node:path";
|
|
5683
5683
|
import { createRequire as createRequire2 } from "node:module";
|
|
5684
5684
|
import { pathToFileURL } from "node:url";
|
|
5685
|
+
var VIRTUAL_LAUNCHER_ID = "\0rango-vercel-launcher";
|
|
5685
5686
|
var LAUNCHER_SOURCE = `import { toNodeHandler } from "srvx/node";
|
|
5686
5687
|
import { waitUntil } from "@vercel/functions";
|
|
5687
5688
|
import rscHandler from "./rsc/index.js";
|
|
@@ -5701,6 +5702,99 @@ const fetchHandler = (request) =>
|
|
|
5701
5702
|
|
|
5702
5703
|
export default toNodeHandler(fetchHandler);
|
|
5703
5704
|
`;
|
|
5705
|
+
function resolveRolldownPath(root) {
|
|
5706
|
+
const appRequire = createRequire2(join4(root, "package.json"));
|
|
5707
|
+
const rangoRequire = createRequire2(import.meta.url);
|
|
5708
|
+
const attempts = [
|
|
5709
|
+
() => createRequire2(appRequire.resolve("vite")).resolve("rolldown"),
|
|
5710
|
+
() => appRequire.resolve("rolldown"),
|
|
5711
|
+
() => createRequire2(rangoRequire.resolve("vite")).resolve("rolldown"),
|
|
5712
|
+
() => rangoRequire.resolve("rolldown")
|
|
5713
|
+
];
|
|
5714
|
+
for (const attempt of attempts) {
|
|
5715
|
+
try {
|
|
5716
|
+
return attempt();
|
|
5717
|
+
} catch {
|
|
5718
|
+
}
|
|
5719
|
+
}
|
|
5720
|
+
throw new Error(
|
|
5721
|
+
'[rango] preset "vercel" requires "rolldown" to bundle the function launcher. Vite 8 depends on it; reinstall dependencies.'
|
|
5722
|
+
);
|
|
5723
|
+
}
|
|
5724
|
+
async function bundleVercelLauncher(opts) {
|
|
5725
|
+
const { root, funcDir, srvxNodePath } = opts;
|
|
5726
|
+
const appRequire = createRequire2(join4(root, "package.json"));
|
|
5727
|
+
let vercelFunctionsPath;
|
|
5728
|
+
try {
|
|
5729
|
+
vercelFunctionsPath = appRequire.resolve("@vercel/functions");
|
|
5730
|
+
} catch {
|
|
5731
|
+
throw new Error(
|
|
5732
|
+
'[rango] preset "vercel": could not resolve "@vercel/functions". Add it to your app dependencies (it also backs VercelCacheStore).'
|
|
5733
|
+
);
|
|
5734
|
+
}
|
|
5735
|
+
let rolldownModule;
|
|
5736
|
+
try {
|
|
5737
|
+
rolldownModule = await import(pathToFileURL(resolveRolldownPath(root)).href);
|
|
5738
|
+
} catch {
|
|
5739
|
+
throw new Error(
|
|
5740
|
+
'[rango] preset "vercel" requires "rolldown" to bundle the function launcher. Vite 8 depends on it; reinstall dependencies.'
|
|
5741
|
+
);
|
|
5742
|
+
}
|
|
5743
|
+
const rolldownFn = rolldownModule.rolldown ?? rolldownModule.default?.rolldown;
|
|
5744
|
+
if (typeof rolldownFn !== "function") {
|
|
5745
|
+
throw new Error('[rango] preset "vercel": could not load rolldown().');
|
|
5746
|
+
}
|
|
5747
|
+
let bundle;
|
|
5748
|
+
try {
|
|
5749
|
+
bundle = await rolldownFn({
|
|
5750
|
+
input: VIRTUAL_LAUNCHER_ID,
|
|
5751
|
+
cwd: root,
|
|
5752
|
+
platform: "node",
|
|
5753
|
+
logLevel: "silent",
|
|
5754
|
+
resolve: {
|
|
5755
|
+
alias: {
|
|
5756
|
+
"srvx/node": srvxNodePath,
|
|
5757
|
+
"@vercel/functions": vercelFunctionsPath
|
|
5758
|
+
}
|
|
5759
|
+
},
|
|
5760
|
+
plugins: [
|
|
5761
|
+
{
|
|
5762
|
+
name: "rango-vercel-launcher",
|
|
5763
|
+
resolveId(id) {
|
|
5764
|
+
if (id === VIRTUAL_LAUNCHER_ID) return VIRTUAL_LAUNCHER_ID;
|
|
5765
|
+
if (id === "./rsc/index.js") {
|
|
5766
|
+
return { id: "./rsc/index.js", external: true };
|
|
5767
|
+
}
|
|
5768
|
+
return null;
|
|
5769
|
+
},
|
|
5770
|
+
load(id) {
|
|
5771
|
+
if (id === VIRTUAL_LAUNCHER_ID) return LAUNCHER_SOURCE;
|
|
5772
|
+
return null;
|
|
5773
|
+
}
|
|
5774
|
+
}
|
|
5775
|
+
]
|
|
5776
|
+
});
|
|
5777
|
+
await bundle.write({
|
|
5778
|
+
file: join4(funcDir, "index.mjs"),
|
|
5779
|
+
format: "esm",
|
|
5780
|
+
exports: "default",
|
|
5781
|
+
// @vercel/functions (and srvx) may contain dynamic import(); the
|
|
5782
|
+
// launcher must stay a single index.mjs — Vercel's handler field
|
|
5783
|
+
// points at that one file.
|
|
5784
|
+
codeSplitting: false
|
|
5785
|
+
});
|
|
5786
|
+
} catch (error) {
|
|
5787
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5788
|
+
if (/@vercel\/functions/.test(message)) {
|
|
5789
|
+
throw new Error(
|
|
5790
|
+
'[rango] preset "vercel": could not resolve "@vercel/functions". Add it to your app dependencies (it also backs VercelCacheStore).\n' + message
|
|
5791
|
+
);
|
|
5792
|
+
}
|
|
5793
|
+
throw error;
|
|
5794
|
+
} finally {
|
|
5795
|
+
await bundle?.close();
|
|
5796
|
+
}
|
|
5797
|
+
}
|
|
5704
5798
|
function assertVercelNodeRuntime(runtime2) {
|
|
5705
5799
|
if (runtime2 != null && !runtime2.startsWith("nodejs")) {
|
|
5706
5800
|
throw new Error(
|
|
@@ -5788,66 +5882,7 @@ async function assemble(root, options, assetsDir, publicDir) {
|
|
|
5788
5882
|
'[rango] preset "vercel" requires "srvx" (a dependency of @rangojs/router). Reinstall dependencies.'
|
|
5789
5883
|
);
|
|
5790
5884
|
}
|
|
5791
|
-
|
|
5792
|
-
const resolveEsbuildPath = () => {
|
|
5793
|
-
try {
|
|
5794
|
-
const viteRequire = createRequire2(appRequire.resolve("vite"));
|
|
5795
|
-
return viteRequire.resolve("esbuild");
|
|
5796
|
-
} catch {
|
|
5797
|
-
}
|
|
5798
|
-
try {
|
|
5799
|
-
return appRequire.resolve("esbuild");
|
|
5800
|
-
} catch {
|
|
5801
|
-
}
|
|
5802
|
-
return rangoRequire.resolve("esbuild");
|
|
5803
|
-
};
|
|
5804
|
-
let esbuildModule;
|
|
5805
|
-
try {
|
|
5806
|
-
esbuildModule = await import(pathToFileURL(resolveEsbuildPath()).href);
|
|
5807
|
-
} catch {
|
|
5808
|
-
throw new Error(
|
|
5809
|
-
'[rango] preset "vercel" requires "esbuild" to bundle the function launcher. It ships with Vite; reinstall dependencies (or add esbuild to your app dependencies).'
|
|
5810
|
-
);
|
|
5811
|
-
}
|
|
5812
|
-
const esbuildBuild = esbuildModule.build ?? esbuildModule.default?.build;
|
|
5813
|
-
if (typeof esbuildBuild !== "function") {
|
|
5814
|
-
throw new Error('[rango] preset "vercel": could not load esbuild.build.');
|
|
5815
|
-
}
|
|
5816
|
-
try {
|
|
5817
|
-
await esbuildBuild({
|
|
5818
|
-
stdin: {
|
|
5819
|
-
contents: LAUNCHER_SOURCE,
|
|
5820
|
-
resolveDir: root,
|
|
5821
|
-
sourcefile: "func-entry.mjs",
|
|
5822
|
-
loader: "js"
|
|
5823
|
-
},
|
|
5824
|
-
outfile: join4(funcDir, "index.mjs"),
|
|
5825
|
-
bundle: true,
|
|
5826
|
-
format: "esm",
|
|
5827
|
-
platform: "node",
|
|
5828
|
-
target: "node18",
|
|
5829
|
-
alias: { "srvx/node": srvxNodePath },
|
|
5830
|
-
plugins: [
|
|
5831
|
-
{
|
|
5832
|
-
name: "external-rsc-entry",
|
|
5833
|
-
setup(b) {
|
|
5834
|
-
b.onResolve({ filter: /^\.\/rsc\/index\.js$/ }, () => ({
|
|
5835
|
-
path: "./rsc/index.js",
|
|
5836
|
-
external: true
|
|
5837
|
-
}));
|
|
5838
|
-
}
|
|
5839
|
-
}
|
|
5840
|
-
]
|
|
5841
|
-
});
|
|
5842
|
-
} catch (error) {
|
|
5843
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
5844
|
-
if (/@vercel\/functions/.test(message)) {
|
|
5845
|
-
throw new Error(
|
|
5846
|
-
'[rango] preset "vercel": could not resolve "@vercel/functions". Add it to your app dependencies (it also backs VercelCacheStore).\n' + message
|
|
5847
|
-
);
|
|
5848
|
-
}
|
|
5849
|
-
throw error;
|
|
5850
|
-
}
|
|
5885
|
+
await bundleVercelLauncher({ root, funcDir, srvxNodePath });
|
|
5851
5886
|
await writeFile(
|
|
5852
5887
|
join4(funcDir, "package.json"),
|
|
5853
5888
|
JSON.stringify({ type: "module" }, null, 2) + "\n"
|
package/package.json
CHANGED
package/skills/testing/setup.md
CHANGED
|
@@ -114,7 +114,7 @@ Scripts:
|
|
|
114
114
|
- The forked rsc worker (`pool: "forks"`) must force the condition via `execArgv: ["--conditions=react-server"]`, or React throws "the react-server condition must be enabled".
|
|
115
115
|
- The `@rangojs/router:version` and `@vitejs/plugin-rsc/rsc` (`/rsc/server`, `/rsc/client`) virtuals must be stubbed; the preset does it. A bare router import without stubbing throws.
|
|
116
116
|
- The rango fragment goes under `test` (`test.alias` + `test.server.deps.inline`, both returned by `rangoTestConfig`), NOT under top-level `resolve`.
|
|
117
|
-
- Wire `rangoUseClientTransform()` into the rsc project `plugins` so islands auto-discover from the server tree imports (see `./server-tree.md`); without it, register islands explicitly with `clientComponents`.
|
|
117
|
+
- Wire `rangoUseClientTransform()` into the rsc project `plugins` so islands auto-discover from the server tree imports (see `./server-tree.md`); without it, register islands explicitly with `clientComponents`. The transform's `registerClientReference` import is resolved from `@rangojs/router`'s `@vitejs/plugin-rsc` — the consumer does not need a direct plugin-rsc dependency.
|
|
118
118
|
|
|
119
119
|
## See also
|
|
120
120
|
|
package/src/testing/vitest.ts
CHANGED
|
@@ -76,7 +76,8 @@
|
|
|
76
76
|
* a focused include, or use e2e.
|
|
77
77
|
*/
|
|
78
78
|
|
|
79
|
-
import {
|
|
79
|
+
import { createRequire } from "node:module";
|
|
80
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
80
81
|
|
|
81
82
|
/** A single Vite/Vitest resolve alias entry. Structurally a Vite `Alias`. */
|
|
82
83
|
export interface TestAlias {
|
|
@@ -108,6 +109,32 @@ function here(relativeFromRoot: string): string {
|
|
|
108
109
|
return fileURLToPath(new URL(`../../${relativeFromRoot}`, import.meta.url));
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Spec `rangoUseClientTransform` injects into `"use client"` modules. A
|
|
114
|
+
* consumer app does not depend on `@vitejs/plugin-rsc`, so the bare specifier
|
|
115
|
+
* is unresolvable from their `"use client"` files (cloudflare-basic
|
|
116
|
+
* `server-tree.rsc-test.tsx` on plugin-rsc 0.5.34). Resolve it from THIS
|
|
117
|
+
* module — `@rangojs/router` does depend on plugin-rsc — and alias / inject
|
|
118
|
+
* the absolute path.
|
|
119
|
+
*/
|
|
120
|
+
const RSD_SERVER_EDGE_SPEC: string =
|
|
121
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/server.edge";
|
|
122
|
+
|
|
123
|
+
const requireFromHere: ReturnType<typeof createRequire> = createRequire(
|
|
124
|
+
import.meta.url,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
function resolveFromRouter(spec: string): string | undefined {
|
|
128
|
+
try {
|
|
129
|
+
return requireFromHere.resolve(spec);
|
|
130
|
+
} catch {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const rsdServerEdgePath: string | undefined =
|
|
136
|
+
resolveFromRouter(RSD_SERVER_EDGE_SPEC);
|
|
137
|
+
|
|
111
138
|
/**
|
|
112
139
|
* Build the `resolve.alias` entries a consumer's node/DOM Vitest project needs to
|
|
113
140
|
* import a real @rangojs/router app's router/loaders/middleware. Spread into a
|
|
@@ -131,6 +158,12 @@ export function rangoTestAliases(
|
|
|
131
158
|
replacement: here("src/testing/vitest-stubs/plugin-rsc.ts"),
|
|
132
159
|
},
|
|
133
160
|
];
|
|
161
|
+
if (rsdServerEdgePath) {
|
|
162
|
+
aliases.push({
|
|
163
|
+
find: RSD_SERVER_EDGE_SPEC,
|
|
164
|
+
replacement: rsdServerEdgePath,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
134
167
|
|
|
135
168
|
if (opts.preset === "cloudflare") {
|
|
136
169
|
aliases.push(
|
|
@@ -292,10 +325,13 @@ export function rangoUseClientTransform(): FlightTransformPlugin {
|
|
|
292
325
|
});
|
|
293
326
|
if (!result) return undefined;
|
|
294
327
|
const { output } = result;
|
|
295
|
-
//
|
|
296
|
-
//
|
|
328
|
+
// Absolute file URL, not the bare specifier: the consumer's "use client"
|
|
329
|
+
// module cannot resolve @vitejs/plugin-rsc (it is a router dependency).
|
|
330
|
+
const rsdHref = rsdServerEdgePath
|
|
331
|
+
? pathToFileURL(rsdServerEdgePath).href
|
|
332
|
+
: RSD_SERVER_EDGE_SPEC;
|
|
297
333
|
output.prepend(
|
|
298
|
-
`import * as $$RangoRSD from
|
|
334
|
+
`import * as $$RangoRSD from ${JSON.stringify(rsdHref)};\n`,
|
|
299
335
|
);
|
|
300
336
|
return {
|
|
301
337
|
code: output.toString(),
|
|
@@ -28,9 +28,11 @@
|
|
|
28
28
|
* type:module in scope the deployed (isolated) function loads them as
|
|
29
29
|
* CommonJS and fails on the first `import`.
|
|
30
30
|
*
|
|
31
|
-
* The launcher is bundled with
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* The launcher is bundled with rolldown (Vite 8's bundler — a production
|
|
32
|
+
* dependency of `vite`, resolved through the app's vite install) so a
|
|
33
|
+
* standalone consumer does not need `esbuild`. srvx (the Web->Node streaming
|
|
34
|
+
* bridge, a @rangojs/router dependency) and @vercel/functions (resolved from
|
|
35
|
+
* the app) are inlined; the RSC bundle stays a runtime-relative external.
|
|
34
36
|
*
|
|
35
37
|
* Timing: this runs in the `buildApp` hook (order "post"), which fires once
|
|
36
38
|
* after every environment has built, so dist/{client,rsc,ssr} all exist.
|
|
@@ -52,19 +54,30 @@ import type {
|
|
|
52
54
|
VercelPresetOptions,
|
|
53
55
|
} from "../plugin-types.js";
|
|
54
56
|
|
|
55
|
-
// Minimal structural types for the
|
|
56
|
-
// the app so @rangojs/router does not depend on
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
): void;
|
|
57
|
+
// Minimal structural types for the rolldown API we use. Resolved dynamically
|
|
58
|
+
// from the app's vite install so @rangojs/router does not depend on rolldown's
|
|
59
|
+
// type package (same stance the previous esbuild path took).
|
|
60
|
+
interface RolldownResolveResult {
|
|
61
|
+
id: string;
|
|
62
|
+
external?: boolean;
|
|
62
63
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
interface RolldownBundle {
|
|
65
|
+
write: (output: {
|
|
66
|
+
file: string;
|
|
67
|
+
format: string;
|
|
68
|
+
exports: string;
|
|
69
|
+
codeSplitting?: boolean;
|
|
70
|
+
}) => Promise<unknown>;
|
|
71
|
+
close: () => Promise<void>;
|
|
67
72
|
}
|
|
73
|
+
interface RolldownModule {
|
|
74
|
+
rolldown?: (options: Record<string, unknown>) => Promise<RolldownBundle>;
|
|
75
|
+
default?: {
|
|
76
|
+
rolldown?: (options: Record<string, unknown>) => Promise<RolldownBundle>;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const VIRTUAL_LAUNCHER_ID = "\0rango-vercel-launcher";
|
|
68
81
|
|
|
69
82
|
const LAUNCHER_SOURCE = `import { toNodeHandler } from "srvx/node";
|
|
70
83
|
import { waitUntil } from "@vercel/functions";
|
|
@@ -86,6 +99,122 @@ const fetchHandler = (request) =>
|
|
|
86
99
|
export default toNodeHandler(fetchHandler);
|
|
87
100
|
`;
|
|
88
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Resolve rolldown through the app's vite install (rolldown is a production
|
|
104
|
+
* dependency of Vite 8, unlike esbuild which is only an optional peer). Fall
|
|
105
|
+
* back to the app root, then the plugin's own vite, so a hoisted or
|
|
106
|
+
* workspace copy still works. Issue #785: the previous "esbuild ships with
|
|
107
|
+
* Vite" path broke standalone consumers on Vite 8.
|
|
108
|
+
*/
|
|
109
|
+
export function resolveRolldownPath(root: string): string {
|
|
110
|
+
const appRequire = createRequire(join(root, "package.json"));
|
|
111
|
+
const rangoRequire = createRequire(import.meta.url);
|
|
112
|
+
const attempts: Array<() => string> = [
|
|
113
|
+
() => createRequire(appRequire.resolve("vite")).resolve("rolldown"),
|
|
114
|
+
() => appRequire.resolve("rolldown"),
|
|
115
|
+
() => createRequire(rangoRequire.resolve("vite")).resolve("rolldown"),
|
|
116
|
+
() => rangoRequire.resolve("rolldown"),
|
|
117
|
+
];
|
|
118
|
+
for (const attempt of attempts) {
|
|
119
|
+
try {
|
|
120
|
+
return attempt();
|
|
121
|
+
} catch {
|
|
122
|
+
// Intentionally empty: try the next resolver.
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
throw new Error(
|
|
126
|
+
'[rango] preset "vercel" requires "rolldown" to bundle the function launcher. Vite 8 depends on it; reinstall dependencies.',
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Bundle the Node launcher into `funcDir/index.mjs`: srvx + @vercel/functions
|
|
132
|
+
* inlined, `./rsc/index.js` left as a runtime-relative external.
|
|
133
|
+
*/
|
|
134
|
+
export async function bundleVercelLauncher(opts: {
|
|
135
|
+
root: string;
|
|
136
|
+
funcDir: string;
|
|
137
|
+
srvxNodePath: string;
|
|
138
|
+
}): Promise<void> {
|
|
139
|
+
const { root, funcDir, srvxNodePath } = opts;
|
|
140
|
+
const appRequire = createRequire(join(root, "package.json"));
|
|
141
|
+
let vercelFunctionsPath: string;
|
|
142
|
+
try {
|
|
143
|
+
vercelFunctionsPath = appRequire.resolve("@vercel/functions");
|
|
144
|
+
} catch {
|
|
145
|
+
throw new Error(
|
|
146
|
+
'[rango] preset "vercel": could not resolve "@vercel/functions". Add it to your app dependencies (it also backs VercelCacheStore).',
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
let rolldownModule: RolldownModule;
|
|
150
|
+
try {
|
|
151
|
+
rolldownModule = (await import(
|
|
152
|
+
pathToFileURL(resolveRolldownPath(root)).href
|
|
153
|
+
)) as RolldownModule;
|
|
154
|
+
} catch {
|
|
155
|
+
throw new Error(
|
|
156
|
+
'[rango] preset "vercel" requires "rolldown" to bundle the function launcher. Vite 8 depends on it; reinstall dependencies.',
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const rolldownFn =
|
|
160
|
+
rolldownModule.rolldown ?? rolldownModule.default?.rolldown;
|
|
161
|
+
if (typeof rolldownFn !== "function") {
|
|
162
|
+
throw new Error('[rango] preset "vercel": could not load rolldown().');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let bundle: RolldownBundle | undefined;
|
|
166
|
+
try {
|
|
167
|
+
bundle = await rolldownFn({
|
|
168
|
+
input: VIRTUAL_LAUNCHER_ID,
|
|
169
|
+
cwd: root,
|
|
170
|
+
platform: "node",
|
|
171
|
+
logLevel: "silent",
|
|
172
|
+
resolve: {
|
|
173
|
+
alias: {
|
|
174
|
+
"srvx/node": srvxNodePath,
|
|
175
|
+
"@vercel/functions": vercelFunctionsPath,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
plugins: [
|
|
179
|
+
{
|
|
180
|
+
name: "rango-vercel-launcher",
|
|
181
|
+
resolveId(id: string): string | RolldownResolveResult | null {
|
|
182
|
+
if (id === VIRTUAL_LAUNCHER_ID) return VIRTUAL_LAUNCHER_ID;
|
|
183
|
+
if (id === "./rsc/index.js") {
|
|
184
|
+
return { id: "./rsc/index.js", external: true };
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
},
|
|
188
|
+
load(id: string): string | null {
|
|
189
|
+
if (id === VIRTUAL_LAUNCHER_ID) return LAUNCHER_SOURCE;
|
|
190
|
+
return null;
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
});
|
|
195
|
+
await bundle.write({
|
|
196
|
+
file: join(funcDir, "index.mjs"),
|
|
197
|
+
format: "esm",
|
|
198
|
+
exports: "default",
|
|
199
|
+
// @vercel/functions (and srvx) may contain dynamic import(); the
|
|
200
|
+
// launcher must stay a single index.mjs — Vercel's handler field
|
|
201
|
+
// points at that one file.
|
|
202
|
+
codeSplitting: false,
|
|
203
|
+
});
|
|
204
|
+
} catch (error) {
|
|
205
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
206
|
+
if (/@vercel\/functions/.test(message)) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
'[rango] preset "vercel": could not resolve "@vercel/functions". Add it to your app dependencies (it also backs VercelCacheStore).\n' +
|
|
209
|
+
message,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
throw error;
|
|
213
|
+
} finally {
|
|
214
|
+
await bundle?.close();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
89
218
|
/**
|
|
90
219
|
* Reject a non-Node runtime for the vercel preset. The preset only emits a Node
|
|
91
220
|
* serverless function (launcherType "Nodejs", bundled Node APIs, response
|
|
@@ -237,7 +366,8 @@ async function assemble(
|
|
|
237
366
|
|
|
238
367
|
// 3. Bundle the Node launcher. srvx (a @rangojs/router dependency) is aliased
|
|
239
368
|
// to its resolved path; @vercel/functions resolves from the app; the RSC
|
|
240
|
-
// server bundle stays a runtime-relative external.
|
|
369
|
+
// server bundle stays a runtime-relative external. Rolldown (Vite 8's
|
|
370
|
+
// bundler) is resolved through the app's vite install — #785.
|
|
241
371
|
const rangoRequire = createRequire(import.meta.url);
|
|
242
372
|
let srvxNodePath: string;
|
|
243
373
|
try {
|
|
@@ -247,79 +377,7 @@ async function assemble(
|
|
|
247
377
|
'[rango] preset "vercel" requires "srvx" (a dependency of @rangojs/router). Reinstall dependencies.',
|
|
248
378
|
);
|
|
249
379
|
}
|
|
250
|
-
|
|
251
|
-
// esbuild ships with Vite, so we never add it as a @rangojs/router dependency.
|
|
252
|
-
// It is a DIRECT dependency of Vite but only a TRANSITIVE one from the app's
|
|
253
|
-
// view, so under strict pnpm it is NOT resolvable from the app root. Resolve it
|
|
254
|
-
// through Vite's module location (Vite is a direct app dependency, and esbuild
|
|
255
|
-
// is a direct dependency of Vite). Minimal structural types avoid coupling to
|
|
256
|
-
// esbuild's type package at compile time.
|
|
257
|
-
const appRequire = createRequire(join(root, "package.json"));
|
|
258
|
-
const resolveEsbuildPath = (): string => {
|
|
259
|
-
try {
|
|
260
|
-
const viteRequire = createRequire(appRequire.resolve("vite"));
|
|
261
|
-
return viteRequire.resolve("esbuild");
|
|
262
|
-
} catch {
|
|
263
|
-
// Intentionally empty: fall through to the app/rango fallbacks below.
|
|
264
|
-
}
|
|
265
|
-
try {
|
|
266
|
-
return appRequire.resolve("esbuild");
|
|
267
|
-
} catch {
|
|
268
|
-
// Intentionally empty: last resort is @rangojs/router's own resolver.
|
|
269
|
-
}
|
|
270
|
-
return rangoRequire.resolve("esbuild");
|
|
271
|
-
};
|
|
272
|
-
let esbuildModule: EsbuildModule;
|
|
273
|
-
try {
|
|
274
|
-
esbuildModule = (await import(
|
|
275
|
-
pathToFileURL(resolveEsbuildPath()).href
|
|
276
|
-
)) as EsbuildModule;
|
|
277
|
-
} catch {
|
|
278
|
-
throw new Error(
|
|
279
|
-
'[rango] preset "vercel" requires "esbuild" to bundle the function launcher. It ships with Vite; reinstall dependencies (or add esbuild to your app dependencies).',
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
const esbuildBuild = esbuildModule.build ?? esbuildModule.default?.build;
|
|
283
|
-
if (typeof esbuildBuild !== "function") {
|
|
284
|
-
throw new Error('[rango] preset "vercel": could not load esbuild.build.');
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
try {
|
|
288
|
-
await esbuildBuild({
|
|
289
|
-
stdin: {
|
|
290
|
-
contents: LAUNCHER_SOURCE,
|
|
291
|
-
resolveDir: root,
|
|
292
|
-
sourcefile: "func-entry.mjs",
|
|
293
|
-
loader: "js",
|
|
294
|
-
},
|
|
295
|
-
outfile: join(funcDir, "index.mjs"),
|
|
296
|
-
bundle: true,
|
|
297
|
-
format: "esm",
|
|
298
|
-
platform: "node",
|
|
299
|
-
target: "node18",
|
|
300
|
-
alias: { "srvx/node": srvxNodePath },
|
|
301
|
-
plugins: [
|
|
302
|
-
{
|
|
303
|
-
name: "external-rsc-entry",
|
|
304
|
-
setup(b: EsbuildPluginBuild) {
|
|
305
|
-
b.onResolve({ filter: /^\.\/rsc\/index\.js$/ }, () => ({
|
|
306
|
-
path: "./rsc/index.js",
|
|
307
|
-
external: true,
|
|
308
|
-
}));
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
],
|
|
312
|
-
});
|
|
313
|
-
} catch (error) {
|
|
314
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
315
|
-
if (/@vercel\/functions/.test(message)) {
|
|
316
|
-
throw new Error(
|
|
317
|
-
'[rango] preset "vercel": could not resolve "@vercel/functions". Add it to your app dependencies (it also backs VercelCacheStore).\n' +
|
|
318
|
-
message,
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
|
-
throw error;
|
|
322
|
-
}
|
|
380
|
+
await bundleVercelLauncher({ root, funcDir, srvxNodePath });
|
|
323
381
|
|
|
324
382
|
// 3b. Mark the function as ESM. The rsc/ssr bundles are .js ESM files with no
|
|
325
383
|
// package.json in scope on the deployed function (it is isolated at
|