@rangojs/router 0.12.0 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/testing/vitest.js +22 -2
- package/dist/types/vite/plugins/vercel-output.d.ts +22 -3
- package/dist/vite/index.js +96 -61
- package/package.json +1 -1
- package/skills/catalog.json +1 -1
- package/skills/rango/SKILL.md +1 -1
- package/skills/react-compiler/SKILL.md +106 -68
- package/skills/testing/setup.md +1 -1
- package/src/testing/vitest.ts +40 -4
- package/src/vite/plugins/vercel-output.ts +146 -88
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.2",
|
|
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/catalog.json
CHANGED
|
@@ -197,7 +197,7 @@
|
|
|
197
197
|
},
|
|
198
198
|
{
|
|
199
199
|
"name": "react-compiler",
|
|
200
|
-
"description": "Enable the React Compiler in a Rango app
|
|
200
|
+
"description": "Enable the React Compiler in a Rango app with @vitejs/plugin-react 6.1's native `compiler` option (oxc-transform-react) — one flag, client components only, no Babel. Use when a consumer wants to turn React Compiler on, hits the dead plugin-react v6 `react({ babel })` path, sees an unmet-peer / ERESOLVE on oxc-transform-react, or is unsure why server components aren't being compiled.",
|
|
201
201
|
"argumentHint": "",
|
|
202
202
|
"path": "skills/react-compiler/SKILL.md"
|
|
203
203
|
},
|
package/skills/rango/SKILL.md
CHANGED
|
@@ -289,7 +289,7 @@ Grouped by concern — read when you need to…
|
|
|
289
289
|
| `/view-transitions` | React View Transitions on layouts, routes, and parallel slots |
|
|
290
290
|
| `/defer-hydration` | Full body HTML in the PPR shell + hydration off the critical path (gated Suspense boundary, content-as-fallback) |
|
|
291
291
|
| `/breadcrumbs` | Built-in Breadcrumbs handle for breadcrumb navigation |
|
|
292
|
-
| `/react-compiler` | Enable React Compiler (opt-in)
|
|
292
|
+
| `/react-compiler` | Enable React Compiler (opt-in) via plugin-react's native `compiler` option; client-only scope |
|
|
293
293
|
|
|
294
294
|
**Observability & production health**:
|
|
295
295
|
|
|
@@ -1,45 +1,49 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: react-compiler
|
|
3
|
-
description: Enable the React Compiler in a Rango app
|
|
3
|
+
description: Enable the React Compiler in a Rango app with @vitejs/plugin-react 6.1's native `compiler` option (oxc-transform-react) — one flag, client components only, no Babel. Use when a consumer wants to turn React Compiler on, hits the dead plugin-react v6 `react({ babel })` path, sees an unmet-peer / ERESOLVE on oxc-transform-react, or is unsure why server components aren't being compiled.
|
|
4
4
|
argument-hint:
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# React Compiler
|
|
8
8
|
|
|
9
|
-
React Compiler is **opt-in** in Rango
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
React Compiler is **opt-in** in Rango, and it is one option on the plugin you
|
|
10
|
+
already have: `@vitejs/plugin-react` 6.1 ships a native React Compiler behind
|
|
11
|
+
`react({ compiler: true })`, backed by
|
|
12
|
+
[`oxc-transform-react`](https://www.npmjs.com/package/oxc-transform-react),
|
|
13
|
+
Oxc's Rust port of the compiler. No Babel, no extra plugin, no ordering rules.
|
|
14
|
+
Upstream marks the option experimental; the previous Babel wiring still works
|
|
15
|
+
and is kept below as the [fallback](#babel-fallback).
|
|
15
16
|
|
|
16
17
|
## The shape (read first)
|
|
17
18
|
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
app that plugin is `rango()` itself; in a Cloudflare app it is
|
|
24
|
-
`@cloudflare/vite-plugin`.
|
|
25
|
-
- **It is client-only.** `reactCompilerPreset()` gates itself to the client
|
|
26
|
-
environment. Server/RSC components are not compiled, and that is the upstream
|
|
27
|
-
example's behavior — not a Rango limitation. See
|
|
19
|
+
- **One option:** `react({ compiler: true })`. Needs `@vitejs/plugin-react` 6.1+
|
|
20
|
+
and its optional peer `oxc-transform-react`.
|
|
21
|
+
- **It is client-only.** plugin-react runs the compiler only in environments
|
|
22
|
+
whose `consumer` is not `"server"`. Server/RSC components are not compiled,
|
|
23
|
+
and that is plugin-react's contract — not a Rango limitation. See
|
|
28
24
|
[What gets compiled](#what-gets-compiled-client-only).
|
|
29
|
-
- **
|
|
30
|
-
|
|
25
|
+
- **It owns JSX and Fast Refresh too.** With `compiler` on, plugin-react hands
|
|
26
|
+
TypeScript, JSX and Fast Refresh to the same native pass and disables Vite's
|
|
27
|
+
built-in refresh injection. Nothing to configure, and dev line numbers still
|
|
28
|
+
point at your source.
|
|
29
|
+
- **Rango's build-time prerender is unaffected.** See
|
|
30
|
+
[Prerender](#interaction-with-build-time-prerender).
|
|
31
31
|
|
|
32
32
|
## Step 1: Install
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
pnpm add -D
|
|
36
|
-
# TypeScript users also want the Babel core types:
|
|
37
|
-
pnpm add -D @types/babel__core
|
|
35
|
+
pnpm add -D oxc-transform-react@^0.145.0
|
|
38
36
|
```
|
|
39
37
|
|
|
38
|
+
Take the range from `@vitejs/plugin-react`'s `peerDependencies` (`^0.145.0` for
|
|
39
|
+
6.1.x) rather than npm's latest. `oxc-transform-react` cuts a new minor every
|
|
40
|
+
couple of weeks and plugin-react widens its range in its own releases, so a newer
|
|
41
|
+
binding shows up as an unmet-peer warning under pnpm and an `ERESOLVE` error
|
|
42
|
+
under npm (vitejs/vite-plugin-react#1437). Bump both together.
|
|
43
|
+
|
|
40
44
|
React 19 ships `react/compiler-runtime` in-tree, so there is **no** extra runtime
|
|
41
|
-
to install and **no** `target` option to set. Only pass `target: '17' | '18'`
|
|
42
|
-
|
|
45
|
+
to install and **no** `target` option to set. Only pass `target: '17' | '18'` if
|
|
46
|
+
you are on an older React.
|
|
43
47
|
|
|
44
48
|
## Step 2: Wire it in
|
|
45
49
|
|
|
@@ -48,16 +52,11 @@ to install and **no** `target` option to set. Only pass `target: '17' | '18'` to
|
|
|
48
52
|
```ts
|
|
49
53
|
// vite.config.ts
|
|
50
54
|
import { defineConfig } from "vite";
|
|
51
|
-
import react
|
|
52
|
-
import babel from "@rolldown/plugin-babel";
|
|
55
|
+
import react from "@vitejs/plugin-react";
|
|
53
56
|
import { rango } from "@rangojs/router/vite";
|
|
54
57
|
|
|
55
58
|
export default defineConfig({
|
|
56
|
-
plugins: [
|
|
57
|
-
react(),
|
|
58
|
-
babel({ presets: [reactCompilerPreset()] }),
|
|
59
|
-
rango(), // supplies @vitejs/plugin-rsc
|
|
60
|
-
],
|
|
59
|
+
plugins: [react({ compiler: true }), rango()],
|
|
61
60
|
});
|
|
62
61
|
```
|
|
63
62
|
|
|
@@ -66,29 +65,29 @@ export default defineConfig({
|
|
|
66
65
|
```ts
|
|
67
66
|
// vite.config.ts
|
|
68
67
|
import { cloudflare } from "@cloudflare/vite-plugin";
|
|
69
|
-
import react
|
|
70
|
-
import babel from "@rolldown/plugin-babel";
|
|
68
|
+
import react from "@vitejs/plugin-react";
|
|
71
69
|
import { defineConfig } from "vite";
|
|
72
70
|
import { rango } from "@rangojs/router/vite";
|
|
73
71
|
|
|
74
72
|
export default defineConfig({
|
|
75
73
|
plugins: [
|
|
76
|
-
react(),
|
|
77
|
-
babel({ presets: [reactCompilerPreset()] }),
|
|
74
|
+
react({ compiler: true }),
|
|
78
75
|
rango({ preset: "cloudflare" }),
|
|
79
76
|
cloudflare({
|
|
80
77
|
/* ... */
|
|
81
|
-
}),
|
|
78
|
+
}),
|
|
82
79
|
],
|
|
83
80
|
});
|
|
84
81
|
```
|
|
85
82
|
|
|
83
|
+
Both layouts keep `react()` ahead of `rango()` / `cloudflare()`, which is what
|
|
84
|
+
Rango's own e2e apps run.
|
|
85
|
+
|
|
86
86
|
## What gets compiled (client-only)
|
|
87
87
|
|
|
88
|
-
`
|
|
89
|
-
`
|
|
90
|
-
|
|
91
|
-
`client` environment**:
|
|
88
|
+
plugin-react's `vite:react-compiler` transform runs in every environment, but
|
|
89
|
+
passes `reactCompiler: false` whenever `environment.config.consumer === "server"`.
|
|
90
|
+
Server environments only get the TypeScript/JSX pass:
|
|
92
91
|
|
|
93
92
|
| Environment | `consumer` | Compiled? |
|
|
94
93
|
| ----------- | ---------- | --------- |
|
|
@@ -96,43 +95,53 @@ even though the babel plugin is top-level, the transform runs **only in the
|
|
|
96
95
|
| ssr | `server` | No |
|
|
97
96
|
| rsc | `server` | No |
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
`babel-plugin-react-compiler` yourself without the preset's
|
|
102
|
-
`applyToEnvironmentHook` — that is outside what the example does and is not
|
|
98
|
+
If you genuinely need to compile **server** components, the native option cannot
|
|
99
|
+
do it; you would have to run `babel-plugin-react-compiler` yourself. That is not
|
|
103
100
|
covered here.
|
|
104
101
|
|
|
105
102
|
## Options
|
|
106
103
|
|
|
107
|
-
`
|
|
104
|
+
`compiler` takes `true` or the React Compiler configuration plus one plugin-level
|
|
105
|
+
flag:
|
|
106
|
+
|
|
107
|
+
| Option | Effect |
|
|
108
|
+
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
109
|
+
| `compilationMode: 'annotation'` | Compile only components marked with the `"use memo"` directive, not every eligible one |
|
|
110
|
+
| `target: '17' \| '18'` | Emit `react-compiler-runtime` calls for React < 19. Omit on React 19+. |
|
|
111
|
+
| `logDiagnostics: true` | Log recoverable compiler diagnostics (why a component was skipped) through Vite. Default `false`. Fatal diagnostics always fail the transform. |
|
|
108
112
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
`logDiagnostics` can only relay what the binding reports. `oxc-transform-react`
|
|
114
|
+
0.145.x, the range plugin-react 6.1 declares, reports bail-out reasons. 0.148.0
|
|
115
|
+
dropped them (oxc-project/oxc#26318), so the flag goes silent once plugin-react's
|
|
116
|
+
peer range moves past it; 0.146 and 0.147 still report but need a later
|
|
117
|
+
plugin-react. Callback-valued Babel
|
|
118
|
+
options (`logger`, function-valued `sources`) do not exist on the native path;
|
|
119
|
+
`sources` takes an array of filename substrings.
|
|
113
120
|
|
|
114
121
|
## Interaction with build-time prerender
|
|
115
122
|
|
|
116
123
|
Nothing to configure. Rango's discovery/prerender step runs a throwaway temp Vite
|
|
117
124
|
server (`createTempRscServer`) that forwards only your **resolution** plugins
|
|
118
|
-
(`resolveId` / `load`)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
(`resolveId` / `load`) and denies every `vite:*` plugin, so `vite:react-compiler`
|
|
126
|
+
never runs there. That is correct: the temp runner only produces **data**
|
|
127
|
+
(serialized Flight payloads + the route manifest), not shipped code, and React
|
|
128
|
+
Compiler is a memoization-only transform that does not change rendered output.
|
|
129
|
+
Your shipped client bundle is compiled by the `react({ compiler: true })` in your
|
|
130
|
+
app's own plugin array.
|
|
124
131
|
|
|
125
132
|
## Step 3: Verify the compiler actually ran
|
|
126
133
|
|
|
127
|
-
|
|
134
|
+
The native compiler emits the same shape as `babel-plugin-react-compiler`. A
|
|
135
|
+
compiled module imports the cache allocator from `react/compiler-runtime` and
|
|
128
136
|
calls `_c(n)`. Those two appear in **every** compiled module, so they are the
|
|
129
|
-
reliable per-module signal in dev
|
|
137
|
+
reliable per-module signal in dev, and a compiled module also carries Fast
|
|
138
|
+
Refresh's `$RefreshReg$(...)` registration:
|
|
130
139
|
|
|
131
140
|
```bash
|
|
132
141
|
pnpm dev
|
|
133
142
|
# fetch any client component module straight from Vite and look for the markers:
|
|
134
143
|
curl -s "http://localhost:5173/src/components/SomeClientComponent.tsx" \
|
|
135
|
-
| grep -E "compiler-runtime|_c\("
|
|
144
|
+
| grep -E "compiler-runtime|_c\(|\\\$RefreshReg\\\$\("
|
|
136
145
|
```
|
|
137
146
|
|
|
138
147
|
For a production build, grep the built client bundle for the compiler's
|
|
@@ -150,19 +159,48 @@ also defines that symbol once with a single `=` assignment, so count comparisons
|
|
|
150
159
|
not the bare string.) Run the same grep over `dist/rsc` / `dist/ssr` and you
|
|
151
160
|
should find **none** — that is the client-only contract.
|
|
152
161
|
|
|
162
|
+
Output is not byte-identical to Babel's: the port tracks React's experimental
|
|
163
|
+
compiler channel, so a handful of components memoize a different number of
|
|
164
|
+
values, and comments inside a compiled function body are dropped. Neither changes
|
|
165
|
+
rendered output.
|
|
166
|
+
|
|
167
|
+
## Babel fallback
|
|
168
|
+
|
|
169
|
+
On `@vitejs/plugin-react` < 6.1, or if you need a Babel-only compiler option, the
|
|
170
|
+
previous wiring still works: a top-level
|
|
171
|
+
[`@rolldown/plugin-babel`](https://www.npmjs.com/package/@rolldown/plugin-babel)
|
|
172
|
+
running `reactCompilerPreset()` from `@vitejs/plugin-react`, placed **after
|
|
173
|
+
`react()`** and **before the plugin that supplies `@vitejs/plugin-rsc`**. The
|
|
174
|
+
preset gates itself to `consumer === "client"`, so the client-only contract is
|
|
175
|
+
the same. Do not combine it with `compiler: true`.
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
pnpm add -D @rolldown/plugin-babel @babel/core babel-plugin-react-compiler @types/babel__core
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import react, { reactCompilerPreset } from "@vitejs/plugin-react";
|
|
183
|
+
import babel from "@rolldown/plugin-babel";
|
|
184
|
+
|
|
185
|
+
// plugins: [react(), babel({ presets: [reactCompilerPreset()] }), rango()]
|
|
186
|
+
```
|
|
187
|
+
|
|
153
188
|
## Troubleshooting
|
|
154
189
|
|
|
155
|
-
| Symptom
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
|
|
|
159
|
-
| `
|
|
160
|
-
|
|
|
161
|
-
|
|
|
190
|
+
| Symptom | Cause / fix |
|
|
191
|
+
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
192
|
+
| `React Compiler requires the optional oxc-transform-react package` on startup | Install it (Step 1). plugin-react checks in its `config` hook, so the error is immediate. |
|
|
193
|
+
| Nothing is compiled; no `compiler-runtime` import anywhere | You used `react({ babel: { plugins: [...] } })`. plugin-react v6 has no internal Babel — use `react({ compiler: true })`. |
|
|
194
|
+
| pnpm warns `unmet peer oxc-transform-react`, npm fails with `ERESOLVE` | The binding is newer than plugin-react's peer range. Pin the range plugin-react declares. |
|
|
195
|
+
| Client compiled, but server/RSC components are not | Expected. The option is client-only (see the table). Not a bug. |
|
|
196
|
+
| `logDiagnostics: true` prints nothing | You are on `oxc-transform-react` 0.148.0 or later, which dropped recoverable diagnostics. On plugin-react 6.1.x stay on 0.145.x; 0.146 and 0.147 also report, but need a later plugin-react peer range. |
|
|
197
|
+
| Build pulls in `react-compiler-runtime` | You set `target: '17'`/`'18'` on React 19. Drop `target` — React 19 ships `react/compiler-runtime` in-tree. |
|
|
198
|
+
| Output looks compiled but a component misbehaves | The component likely breaks the Rules of React. Fix the component, or scope the compiler with `compilationMode: 'annotation'` while you do. |
|
|
162
199
|
|
|
163
200
|
## Reference
|
|
164
201
|
|
|
165
202
|
A worked, tested wiring (dev + production e2e markers, incl. the client-only
|
|
166
|
-
contract) lives in the `@rangojs/router` repository —
|
|
167
|
-
package: `docs/react-compiler.md` and the
|
|
168
|
-
`e2e/e2e-basic`, `tests/cloudflare-basic`,
|
|
203
|
+
contract and the Fast Refresh check) lives in the `@rangojs/router` repository —
|
|
204
|
+
not shipped in this package: `docs/react-compiler.md` and the
|
|
205
|
+
`react-compiler.test.ts` files under `e2e/e2e-basic`, `tests/cloudflare-basic`,
|
|
206
|
+
and `tests/vite-rsc-demo`.
|
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
|