@ivogt/rsc-router 0.0.0-experimental.2 → 0.0.0-experimental.5
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/package.json +1 -1
- package/src/vite/index.ts +168 -12
package/package.json
CHANGED
package/src/vite/index.ts
CHANGED
|
@@ -22,23 +22,95 @@ export { exposeLocationStateId } from "./expose-location-state-id.ts";
|
|
|
22
22
|
// Virtual module type declarations in ./version.d.ts
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* Modules that must be excluded from Vite's dependency optimization.
|
|
26
|
+
*
|
|
27
|
+
* When rsc-router is installed from npm, Vite's dep optimizer bundles these modules
|
|
28
|
+
* into separate chunks. However, @vitejs/plugin-rsc creates virtual proxy modules
|
|
29
|
+
* for client components that import from the original source paths.
|
|
30
|
+
*
|
|
31
|
+
* This creates two different module instances:
|
|
32
|
+
* 1. Bundled version in /node_modules/.vite/deps/
|
|
33
|
+
* 2. Original source via plugin-rsc proxy
|
|
34
|
+
*
|
|
35
|
+
* When both instances create React Contexts (e.g., OutletContext), React sees them
|
|
36
|
+
* as different contexts, causing useContext to return undefined even when a Provider
|
|
37
|
+
* exists in the tree.
|
|
38
|
+
*
|
|
39
|
+
* By excluding these modules, we ensure a single module instance is used everywhere.
|
|
40
|
+
*
|
|
41
|
+
* We include both the scoped package name (@ivogt/rsc-router) and the aliased paths
|
|
42
|
+
* (rsc-router) because Vite's optimizer runs before alias resolution.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* esbuild plugin to provide rsc-router:version virtual module during optimization.
|
|
46
|
+
* This is needed because esbuild runs during Vite's dependency optimization phase,
|
|
47
|
+
* before Vite's plugin system can handle virtual modules.
|
|
48
|
+
*/
|
|
49
|
+
const versionEsbuildPlugin = {
|
|
50
|
+
name: "rsc-router-version",
|
|
51
|
+
setup(build: any) {
|
|
52
|
+
build.onResolve({ filter: /^rsc-router:version$/ }, (args: any) => ({
|
|
53
|
+
path: args.path,
|
|
54
|
+
namespace: "rsc-router-virtual",
|
|
55
|
+
}));
|
|
56
|
+
build.onLoad({ filter: /.*/, namespace: "rsc-router-virtual" }, () => ({
|
|
57
|
+
contents: `export const VERSION = "dev";`,
|
|
58
|
+
loader: "js",
|
|
59
|
+
}));
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Shared esbuild options for dependency optimization.
|
|
65
|
+
* Includes the version stub plugin for all environments.
|
|
66
|
+
*/
|
|
67
|
+
const sharedEsbuildOptions = {
|
|
68
|
+
plugins: [versionEsbuildPlugin],
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const RSC_ROUTER_EXCLUDE_DEPS = [
|
|
72
|
+
// Scoped package paths
|
|
73
|
+
"@ivogt/rsc-router",
|
|
74
|
+
"@ivogt/rsc-router/browser",
|
|
75
|
+
"@ivogt/rsc-router/client",
|
|
76
|
+
"@ivogt/rsc-router/server",
|
|
77
|
+
"@ivogt/rsc-router/rsc",
|
|
78
|
+
"@ivogt/rsc-router/ssr",
|
|
79
|
+
"@ivogt/rsc-router/internal/deps/browser",
|
|
80
|
+
"@ivogt/rsc-router/internal/deps/html-stream-client",
|
|
81
|
+
"@ivogt/rsc-router/internal/deps/ssr",
|
|
82
|
+
"@ivogt/rsc-router/internal/deps/rsc",
|
|
83
|
+
// Aliased paths (before alias resolution)
|
|
84
|
+
"rsc-router/browser",
|
|
85
|
+
"rsc-router/client",
|
|
86
|
+
"rsc-router/server",
|
|
87
|
+
"rsc-router/rsc",
|
|
88
|
+
"rsc-router/ssr",
|
|
89
|
+
"rsc-router/internal/deps/browser",
|
|
90
|
+
"rsc-router/internal/deps/html-stream-client",
|
|
91
|
+
"rsc-router/internal/deps/ssr",
|
|
92
|
+
"rsc-router/internal/deps/rsc",
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Plugin to transform CJS react-server-dom vendor files to ESM.
|
|
26
97
|
* The @vitejs/plugin-rsc package ships client.browser.js as CommonJS
|
|
27
|
-
* which doesn't work in the browser. This transforms
|
|
98
|
+
* which doesn't work in the browser. This transforms both:
|
|
99
|
+
* 1. The entry point (client.browser.js) to re-export from the CJS file
|
|
100
|
+
* 2. The actual CJS file content to ESM syntax
|
|
28
101
|
*/
|
|
29
102
|
function createCjsToEsmPlugin(): Plugin {
|
|
30
103
|
return {
|
|
31
104
|
name: "rsc-router:cjs-to-esm",
|
|
32
105
|
enforce: "pre",
|
|
33
106
|
transform(code, id) {
|
|
34
|
-
|
|
35
|
-
|
|
107
|
+
const cleanId = id.split("?")[0];
|
|
108
|
+
|
|
109
|
+
// Transform the client.browser.js entry point to re-export from CJS
|
|
36
110
|
if (
|
|
37
|
-
|
|
38
|
-
|
|
111
|
+
cleanId.includes("vendor/react-server-dom/client.browser.js") ||
|
|
112
|
+
cleanId.includes("vendor\\react-server-dom\\client.browser.js")
|
|
39
113
|
) {
|
|
40
|
-
// The original file uses: module.exports = require('./cjs/...')
|
|
41
|
-
// Transform to ESM re-export from the development or production CJS file
|
|
42
114
|
const isProd = process.env.NODE_ENV === "production";
|
|
43
115
|
const cjsFile = isProd
|
|
44
116
|
? "./cjs/react-server-dom-webpack-client.browser.production.js"
|
|
@@ -49,6 +121,60 @@ function createCjsToEsmPlugin(): Plugin {
|
|
|
49
121
|
map: null,
|
|
50
122
|
};
|
|
51
123
|
}
|
|
124
|
+
|
|
125
|
+
// Transform the actual CJS files to ESM
|
|
126
|
+
if (
|
|
127
|
+
(cleanId.includes("vendor/react-server-dom/cjs/") ||
|
|
128
|
+
cleanId.includes("vendor\\react-server-dom\\cjs\\")) &&
|
|
129
|
+
cleanId.includes("client.browser")
|
|
130
|
+
) {
|
|
131
|
+
let transformed = code;
|
|
132
|
+
|
|
133
|
+
// Extract the license comment to preserve it
|
|
134
|
+
const licenseMatch = transformed.match(/^\/\*\*[\s\S]*?\*\//);
|
|
135
|
+
const license = licenseMatch ? licenseMatch[0] : "";
|
|
136
|
+
if (license) {
|
|
137
|
+
transformed = transformed.slice(license.length);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Remove "use strict" and the conditional IIFE wrapper
|
|
141
|
+
// Pattern: "use strict"; "production" !== process.env.NODE_ENV && (function() { ... })();
|
|
142
|
+
transformed = transformed.replace(
|
|
143
|
+
/^\s*["']use strict["'];\s*["']production["']\s*!==\s*process\.env\.NODE_ENV\s*&&\s*\(function\s*\(\)\s*\{/,
|
|
144
|
+
""
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Remove the closing of the conditional IIFE at the end: })();
|
|
148
|
+
transformed = transformed.replace(/\}\)\(\);?\s*$/, "");
|
|
149
|
+
|
|
150
|
+
// Replace require('react') and require('react-dom') with imports
|
|
151
|
+
// The pattern spans multiple lines with whitespace
|
|
152
|
+
transformed = transformed.replace(
|
|
153
|
+
/var\s+React\s*=\s*require\s*\(\s*["']react["']\s*\)\s*,[\s\n]+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
|
|
154
|
+
'import React from "react";\nimport ReactDOM from "react-dom";\nvar '
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Transform exports.xyz = function() to export function xyz()
|
|
158
|
+
transformed = transformed.replace(
|
|
159
|
+
/exports\.(\w+)\s*=\s*function\s*\(/g,
|
|
160
|
+
"export function $1("
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Transform exports.xyz = value to export const xyz = value
|
|
164
|
+
transformed = transformed.replace(
|
|
165
|
+
/exports\.(\w+)\s*=/g,
|
|
166
|
+
"export const $1 ="
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Reconstruct with license at the top
|
|
170
|
+
transformed = license + "\n" + transformed;
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
code: transformed,
|
|
174
|
+
map: null,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
52
178
|
return null;
|
|
53
179
|
},
|
|
54
180
|
};
|
|
@@ -455,6 +581,12 @@ export async function rscRouter(
|
|
|
455
581
|
config() {
|
|
456
582
|
// Configure environments for cloudflare deployment
|
|
457
583
|
return {
|
|
584
|
+
// Exclude rsc-router modules from optimization to prevent module duplication
|
|
585
|
+
// This ensures the same Context instance is used by both browser entry and RSC proxy modules
|
|
586
|
+
optimizeDeps: {
|
|
587
|
+
exclude: RSC_ROUTER_EXCLUDE_DEPS,
|
|
588
|
+
esbuildOptions: sharedEsbuildOptions,
|
|
589
|
+
},
|
|
458
590
|
resolve: {
|
|
459
591
|
alias: {
|
|
460
592
|
// Map rsc-router/* to @ivogt/rsc-router/* for virtual entries
|
|
@@ -486,8 +618,11 @@ export async function rscRouter(
|
|
|
486
618
|
},
|
|
487
619
|
},
|
|
488
620
|
// Pre-bundle rsc-html-stream to prevent discovery during first request
|
|
621
|
+
// Exclude rsc-router modules to ensure same Context instance
|
|
489
622
|
optimizeDeps: {
|
|
490
623
|
include: ["rsc-html-stream/client"],
|
|
624
|
+
exclude: RSC_ROUTER_EXCLUDE_DEPS,
|
|
625
|
+
esbuildOptions: sharedEsbuildOptions,
|
|
491
626
|
},
|
|
492
627
|
},
|
|
493
628
|
ssr: {
|
|
@@ -500,6 +635,7 @@ export async function rscRouter(
|
|
|
500
635
|
dedupe: ["react", "react-dom"],
|
|
501
636
|
},
|
|
502
637
|
// Pre-bundle SSR entry and React for proper module linking with childEnvironments
|
|
638
|
+
// Exclude rsc-router modules to ensure same Context instance
|
|
503
639
|
optimizeDeps: {
|
|
504
640
|
entries: [finalEntries.ssr],
|
|
505
641
|
include: [
|
|
@@ -508,6 +644,14 @@ export async function rscRouter(
|
|
|
508
644
|
"react/jsx-runtime",
|
|
509
645
|
"rsc-html-stream/server",
|
|
510
646
|
],
|
|
647
|
+
exclude: RSC_ROUTER_EXCLUDE_DEPS,
|
|
648
|
+
esbuildOptions: sharedEsbuildOptions,
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
rsc: {
|
|
652
|
+
// RSC environment also needs esbuild options for version virtual module
|
|
653
|
+
optimizeDeps: {
|
|
654
|
+
esbuildOptions: sharedEsbuildOptions,
|
|
511
655
|
},
|
|
512
656
|
},
|
|
513
657
|
},
|
|
@@ -569,6 +713,12 @@ export async function rscRouter(
|
|
|
569
713
|
const useVirtualRSC = finalEntries.rsc === VIRTUAL_IDS.rsc;
|
|
570
714
|
|
|
571
715
|
return {
|
|
716
|
+
// Exclude rsc-router modules from optimization to prevent module duplication
|
|
717
|
+
// This ensures the same Context instance is used by both browser entry and RSC proxy modules
|
|
718
|
+
optimizeDeps: {
|
|
719
|
+
exclude: RSC_ROUTER_EXCLUDE_DEPS,
|
|
720
|
+
esbuildOptions: sharedEsbuildOptions,
|
|
721
|
+
},
|
|
572
722
|
resolve: {
|
|
573
723
|
alias: {
|
|
574
724
|
// Map rsc-router/* to @ivogt/rsc-router/* for virtual entries
|
|
@@ -599,12 +749,15 @@ export async function rscRouter(
|
|
|
599
749
|
},
|
|
600
750
|
},
|
|
601
751
|
},
|
|
602
|
-
|
|
603
|
-
|
|
752
|
+
// Always exclude rsc-router modules, conditionally add virtual entry
|
|
753
|
+
optimizeDeps: {
|
|
754
|
+
exclude: RSC_ROUTER_EXCLUDE_DEPS,
|
|
755
|
+
esbuildOptions: sharedEsbuildOptions,
|
|
756
|
+
...(useVirtualClient && {
|
|
604
757
|
// Tell Vite to scan the virtual entry for dependencies
|
|
605
758
|
entries: [VIRTUAL_IDS.browser],
|
|
606
|
-
},
|
|
607
|
-
}
|
|
759
|
+
}),
|
|
760
|
+
},
|
|
608
761
|
},
|
|
609
762
|
...(useVirtualSSR && {
|
|
610
763
|
ssr: {
|
|
@@ -612,6 +765,8 @@ export async function rscRouter(
|
|
|
612
765
|
entries: [VIRTUAL_IDS.ssr],
|
|
613
766
|
// Pre-bundle React for SSR to ensure single instance
|
|
614
767
|
include: ["react", "react-dom/server.edge", "react/jsx-runtime"],
|
|
768
|
+
exclude: RSC_ROUTER_EXCLUDE_DEPS,
|
|
769
|
+
esbuildOptions: sharedEsbuildOptions,
|
|
615
770
|
},
|
|
616
771
|
},
|
|
617
772
|
}),
|
|
@@ -621,6 +776,7 @@ export async function rscRouter(
|
|
|
621
776
|
entries: [VIRTUAL_IDS.rsc],
|
|
622
777
|
// Pre-bundle React for RSC to ensure single instance
|
|
623
778
|
include: ["react", "react/jsx-runtime"],
|
|
779
|
+
esbuildOptions: sharedEsbuildOptions,
|
|
624
780
|
},
|
|
625
781
|
},
|
|
626
782
|
}),
|