@ivogt/rsc-router 0.0.0-experimental.11 → 0.0.0-experimental.13
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/vite/index.js +40 -35
- package/package.json +1 -1
- package/src/__tests__/component-utils.test.ts +76 -0
- package/src/cache/cf/cf-cache-store.ts +1 -1
- package/src/component-utils.ts +76 -0
- package/src/router.ts +4 -10
- package/src/rsc/handler.ts +1 -1
- package/src/server.ts +6 -0
- package/src/vite/__tests__/expose-loader-id.test.ts +7 -7
- package/src/vite/expose-action-id.ts +2 -2
- package/src/vite/expose-handle-id.ts +4 -4
- package/src/vite/expose-loader-id.ts +7 -7
- package/src/vite/expose-location-state-id.ts +4 -4
- package/src/vite/index.ts +24 -15
- package/src/vite/package-resolution.ts +3 -3
- package/src/vite/version.d.ts +1 -1
- package/src/vite/virtual-entries.ts +10 -10
package/dist/vite/index.js
CHANGED
|
@@ -102,7 +102,7 @@ function exposeActionId() {
|
|
|
102
102
|
let hashToFileMap;
|
|
103
103
|
let rscPluginApi;
|
|
104
104
|
return {
|
|
105
|
-
name: "rsc-router:expose-action-id",
|
|
105
|
+
name: "@ivogt/rsc-router:expose-action-id",
|
|
106
106
|
// Run after all other plugins (including RSC plugin's transforms)
|
|
107
107
|
enforce: "post",
|
|
108
108
|
configResolved(resolvedConfig) {
|
|
@@ -116,7 +116,7 @@ function exposeActionId() {
|
|
|
116
116
|
}
|
|
117
117
|
if (!rscPluginApi) {
|
|
118
118
|
throw new Error(
|
|
119
|
-
"[rsc-router] Could not find @vitejs/plugin-rsc. rsc-router requires the Vite RSC plugin.\nThe RSC plugin should be included automatically. If you disabled it with\nrscRouter({ rsc: false }), add rsc() before rscRouter() in your config."
|
|
119
|
+
"[rsc-router] Could not find @vitejs/plugin-rsc. @ivogt/rsc-router requires the Vite RSC plugin.\nThe RSC plugin should be included automatically. If you disabled it with\nrscRouter({ rsc: false }), add rsc() before rscRouter() in your config."
|
|
120
120
|
);
|
|
121
121
|
}
|
|
122
122
|
if (!isBuild) return;
|
|
@@ -189,7 +189,7 @@ function hashLoaderId(filePath, exportName) {
|
|
|
189
189
|
return `${hash.slice(0, 8)}#${exportName}`;
|
|
190
190
|
}
|
|
191
191
|
function hasCreateLoaderImport(code) {
|
|
192
|
-
const pattern = /import\s*\{[^}]*\bcreateLoader\b[^}]*\}\s*from\s*["']rsc-router(?:\/server)?["']/;
|
|
192
|
+
const pattern = /import\s*\{[^}]*\bcreateLoader\b[^}]*\}\s*from\s*["']@ivogt\/rsc-router(?:\/server)?["']/;
|
|
193
193
|
return pattern.test(code);
|
|
194
194
|
}
|
|
195
195
|
function countCreateLoaderArgs(code, startPos, endPos) {
|
|
@@ -265,7 +265,7 @@ function exposeLoaderId() {
|
|
|
265
265
|
const loaderRegistry = /* @__PURE__ */ new Map();
|
|
266
266
|
const pendingLoaderScans = /* @__PURE__ */ new Map();
|
|
267
267
|
return {
|
|
268
|
-
name: "rsc-router:expose-loader-id",
|
|
268
|
+
name: "@ivogt/rsc-router:expose-loader-id",
|
|
269
269
|
enforce: "post",
|
|
270
270
|
configResolved(resolvedConfig) {
|
|
271
271
|
config = resolvedConfig;
|
|
@@ -325,7 +325,7 @@ function exposeLoaderId() {
|
|
|
325
325
|
load(id) {
|
|
326
326
|
if (id === RESOLVED_VIRTUAL_LOADER_MANIFEST) {
|
|
327
327
|
if (!isBuild) {
|
|
328
|
-
return `import { setLoaderImports } from "rsc-router/server";
|
|
328
|
+
return `import { setLoaderImports } from "@ivogt/rsc-router/server";
|
|
329
329
|
|
|
330
330
|
// Dev mode: empty map, loaders are resolved dynamically via path parsing
|
|
331
331
|
setLoaderImports({});
|
|
@@ -338,13 +338,13 @@ setLoaderImports({});
|
|
|
338
338
|
);
|
|
339
339
|
}
|
|
340
340
|
if (lazyImports.length === 0) {
|
|
341
|
-
return `import { setLoaderImports } from "rsc-router/server";
|
|
341
|
+
return `import { setLoaderImports } from "@ivogt/rsc-router/server";
|
|
342
342
|
|
|
343
343
|
// No fetchable loaders discovered during build
|
|
344
344
|
setLoaderImports({});
|
|
345
345
|
`;
|
|
346
346
|
}
|
|
347
|
-
const code = `import { setLoaderImports } from "rsc-router/server";
|
|
347
|
+
const code = `import { setLoaderImports } from "@ivogt/rsc-router/server";
|
|
348
348
|
|
|
349
349
|
// Lazy import map - loaders are loaded on-demand when first requested
|
|
350
350
|
setLoaderImports({
|
|
@@ -394,7 +394,7 @@ function hashHandleId(filePath, exportName) {
|
|
|
394
394
|
return `${hash.slice(0, 8)}#${exportName}`;
|
|
395
395
|
}
|
|
396
396
|
function hasCreateHandleImport(code) {
|
|
397
|
-
const pattern = /import\s*\{[^}]*\bcreateHandle\b[^}]*\}\s*from\s*["']rsc-router(?:\/[^"']+)?["']/;
|
|
397
|
+
const pattern = /import\s*\{[^}]*\bcreateHandle\b[^}]*\}\s*from\s*["']@ivogt\/rsc-router(?:\/[^"']+)?["']/;
|
|
398
398
|
return pattern.test(code);
|
|
399
399
|
}
|
|
400
400
|
function analyzeCreateHandleArgs(code, startPos, endPos) {
|
|
@@ -462,7 +462,7 @@ function exposeHandleId() {
|
|
|
462
462
|
let config;
|
|
463
463
|
let isBuild = false;
|
|
464
464
|
return {
|
|
465
|
-
name: "rsc-router:expose-handle-id",
|
|
465
|
+
name: "@ivogt/rsc-router:expose-handle-id",
|
|
466
466
|
enforce: "post",
|
|
467
467
|
configResolved(resolvedConfig) {
|
|
468
468
|
config = resolvedConfig;
|
|
@@ -497,7 +497,7 @@ function hashLocationStateKey(filePath, exportName) {
|
|
|
497
497
|
return `${hash.slice(0, 8)}#${exportName}`;
|
|
498
498
|
}
|
|
499
499
|
function hasCreateLocationStateImport(code) {
|
|
500
|
-
const pattern = /import\s*\{[^}]*\bcreateLocationState\b[^}]*\}\s*from\s*["']rsc-router(?:\/[^"']+)?["']/;
|
|
500
|
+
const pattern = /import\s*\{[^}]*\bcreateLocationState\b[^}]*\}\s*from\s*["']@ivogt\/rsc-router(?:\/[^"']+)?["']/;
|
|
501
501
|
return pattern.test(code);
|
|
502
502
|
}
|
|
503
503
|
function transformLocationStateExports(code, filePath, sourceId, isBuild = false) {
|
|
@@ -554,7 +554,7 @@ function exposeLocationStateId() {
|
|
|
554
554
|
let config;
|
|
555
555
|
let isBuild = false;
|
|
556
556
|
return {
|
|
557
|
-
name: "rsc-router:expose-location-state-id",
|
|
557
|
+
name: "@ivogt/rsc-router:expose-location-state-id",
|
|
558
558
|
enforce: "post",
|
|
559
559
|
configResolved(resolvedConfig) {
|
|
560
560
|
config = resolvedConfig;
|
|
@@ -584,11 +584,11 @@ import {
|
|
|
584
584
|
setServerCallback,
|
|
585
585
|
encodeReply,
|
|
586
586
|
createTemporaryReferenceSet,
|
|
587
|
-
} from "rsc-router/internal/deps/browser";
|
|
587
|
+
} from "@ivogt/rsc-router/internal/deps/browser";
|
|
588
588
|
import { createElement, StrictMode } from "react";
|
|
589
589
|
import { hydrateRoot } from "react-dom/client";
|
|
590
|
-
import { rscStream } from "rsc-router/internal/deps/html-stream-client";
|
|
591
|
-
import { initBrowserApp, RSCRouter } from "rsc-router/browser";
|
|
590
|
+
import { rscStream } from "@ivogt/rsc-router/internal/deps/html-stream-client";
|
|
591
|
+
import { initBrowserApp, RSCRouter } from "@ivogt/rsc-router/browser";
|
|
592
592
|
|
|
593
593
|
async function initializeApp() {
|
|
594
594
|
const deps = {
|
|
@@ -610,10 +610,10 @@ async function initializeApp() {
|
|
|
610
610
|
initializeApp().catch(console.error);
|
|
611
611
|
`.trim();
|
|
612
612
|
var VIRTUAL_ENTRY_SSR = `
|
|
613
|
-
import { createFromReadableStream } from "rsc-router/internal/deps/ssr";
|
|
613
|
+
import { createFromReadableStream } from "@ivogt/rsc-router/internal/deps/ssr";
|
|
614
614
|
import { renderToReadableStream } from "react-dom/server.edge";
|
|
615
|
-
import { injectRSCPayload } from "rsc-router/internal/deps/html-stream-server";
|
|
616
|
-
import { createSSRHandler } from "rsc-router/ssr";
|
|
615
|
+
import { injectRSCPayload } from "@ivogt/rsc-router/internal/deps/html-stream-server";
|
|
616
|
+
import { createSSRHandler } from "@ivogt/rsc-router/ssr";
|
|
617
617
|
|
|
618
618
|
export const renderHTML = createSSRHandler({
|
|
619
619
|
createFromReadableStream,
|
|
@@ -632,10 +632,10 @@ import {
|
|
|
632
632
|
loadServerAction,
|
|
633
633
|
decodeAction,
|
|
634
634
|
decodeFormState,
|
|
635
|
-
} from "rsc-router/internal/deps/rsc";
|
|
635
|
+
} from "@ivogt/rsc-router/internal/deps/rsc";
|
|
636
636
|
import { router } from "${routerPath}";
|
|
637
|
-
import { createRSCHandler } from "rsc-router/rsc";
|
|
638
|
-
import { VERSION } from "rsc-router:version";
|
|
637
|
+
import { createRSCHandler } from "@ivogt/rsc-router/rsc";
|
|
638
|
+
import { VERSION } from "@ivogt/rsc-router:version";
|
|
639
639
|
|
|
640
640
|
// Import loader manifest to ensure all fetchable loaders are registered at startup
|
|
641
641
|
// This is critical for serverless/multi-process deployments where the loader module
|
|
@@ -662,7 +662,7 @@ var VIRTUAL_IDS = {
|
|
|
662
662
|
browser: "virtual:rsc-router/entry.browser.js",
|
|
663
663
|
ssr: "virtual:rsc-router/entry.ssr.js",
|
|
664
664
|
rsc: "virtual:rsc-router/entry.rsc.js",
|
|
665
|
-
version: "rsc-router:version"
|
|
665
|
+
version: "@ivogt/rsc-router:version"
|
|
666
666
|
};
|
|
667
667
|
function getVirtualVersionContent(version) {
|
|
668
668
|
return `export const VERSION = ${JSON.stringify(version)};`;
|
|
@@ -675,7 +675,7 @@ import { resolve } from "node:path";
|
|
|
675
675
|
// package.json
|
|
676
676
|
var package_default = {
|
|
677
677
|
name: "@ivogt/rsc-router",
|
|
678
|
-
version: "0.0.0-experimental.
|
|
678
|
+
version: "0.0.0-experimental.13",
|
|
679
679
|
type: "module",
|
|
680
680
|
description: "Type-safe RSC router with partial rendering support",
|
|
681
681
|
author: "Ivo Todorov",
|
|
@@ -810,7 +810,7 @@ var package_default = {
|
|
|
810
810
|
};
|
|
811
811
|
|
|
812
812
|
// src/vite/package-resolution.ts
|
|
813
|
-
var VIRTUAL_PACKAGE_NAME = "rsc-router";
|
|
813
|
+
var VIRTUAL_PACKAGE_NAME = "@ivogt/rsc-router";
|
|
814
814
|
function getPublishedPackageName() {
|
|
815
815
|
return package_default.name;
|
|
816
816
|
}
|
|
@@ -870,13 +870,13 @@ function getPackageAliases() {
|
|
|
870
870
|
|
|
871
871
|
// src/vite/index.ts
|
|
872
872
|
var versionEsbuildPlugin = {
|
|
873
|
-
name: "rsc-router-version",
|
|
873
|
+
name: "@ivogt/rsc-router-version",
|
|
874
874
|
setup(build) {
|
|
875
875
|
build.onResolve({ filter: /^rsc-router:version$/ }, (args) => ({
|
|
876
876
|
path: args.path,
|
|
877
|
-
namespace: "rsc-router-virtual"
|
|
877
|
+
namespace: "@ivogt/rsc-router-virtual"
|
|
878
878
|
}));
|
|
879
|
-
build.onLoad({ filter: /.*/, namespace: "rsc-router-virtual" }, () => ({
|
|
879
|
+
build.onLoad({ filter: /.*/, namespace: "@ivogt/rsc-router-virtual" }, () => ({
|
|
880
880
|
contents: `export const VERSION = "dev";`,
|
|
881
881
|
loader: "js"
|
|
882
882
|
}));
|
|
@@ -898,7 +898,7 @@ function createVirtualEntriesPlugin(entries, routerPath) {
|
|
|
898
898
|
virtualModules[VIRTUAL_IDS.rsc] = getVirtualEntryRSC(absoluteRouterPath);
|
|
899
899
|
}
|
|
900
900
|
return {
|
|
901
|
-
name: "rsc-router:virtual-entries",
|
|
901
|
+
name: "@ivogt/rsc-router:virtual-entries",
|
|
902
902
|
enforce: "pre",
|
|
903
903
|
resolveId(id) {
|
|
904
904
|
if (id in virtualModules) {
|
|
@@ -936,7 +936,7 @@ function createVersionPlugin() {
|
|
|
936
936
|
let isDev = false;
|
|
937
937
|
let server = null;
|
|
938
938
|
return {
|
|
939
|
-
name: "rsc-router:version",
|
|
939
|
+
name: "@ivogt/rsc-router:version",
|
|
940
940
|
enforce: "pre",
|
|
941
941
|
configResolved(config) {
|
|
942
942
|
isDev = config.command === "serve";
|
|
@@ -984,7 +984,7 @@ function createVersionInjectorPlugin(rscEntryPath) {
|
|
|
984
984
|
let projectRoot = "";
|
|
985
985
|
let resolvedEntryPath = "";
|
|
986
986
|
return {
|
|
987
|
-
name: "rsc-router:version-injector",
|
|
987
|
+
name: "@ivogt/rsc-router:version-injector",
|
|
988
988
|
enforce: "pre",
|
|
989
989
|
configResolved(config) {
|
|
990
990
|
projectRoot = config.root;
|
|
@@ -999,7 +999,7 @@ function createVersionInjectorPlugin(rscEntryPath) {
|
|
|
999
999
|
if (!code.includes("createRSCHandler")) {
|
|
1000
1000
|
return null;
|
|
1001
1001
|
}
|
|
1002
|
-
if (code.includes("rsc-router:version")) {
|
|
1002
|
+
if (code.includes("@ivogt/rsc-router:version")) {
|
|
1003
1003
|
return null;
|
|
1004
1004
|
}
|
|
1005
1005
|
const handlerCallMatch = code.match(/createRSCHandler\s*\(\s*\{/);
|
|
@@ -1020,7 +1020,7 @@ function createVersionInjectorPlugin(rscEntryPath) {
|
|
|
1020
1020
|
if (nextNewline === -1) break;
|
|
1021
1021
|
insertIndex = nextNewline + 1;
|
|
1022
1022
|
}
|
|
1023
|
-
const versionImport = `import { VERSION } from "rsc-router:version";
|
|
1023
|
+
const versionImport = `import { VERSION } from "@ivogt/rsc-router:version";
|
|
1024
1024
|
`;
|
|
1025
1025
|
let newCode = code.slice(0, insertIndex) + versionImport + code.slice(insertIndex);
|
|
1026
1026
|
newCode = newCode.replace(
|
|
@@ -1048,7 +1048,7 @@ async function rscRouter(options) {
|
|
|
1048
1048
|
ssr: VIRTUAL_IDS.ssr
|
|
1049
1049
|
};
|
|
1050
1050
|
plugins.push({
|
|
1051
|
-
name: "rsc-router:cloudflare-integration",
|
|
1051
|
+
name: "@ivogt/rsc-router:cloudflare-integration",
|
|
1052
1052
|
enforce: "pre",
|
|
1053
1053
|
config() {
|
|
1054
1054
|
return {
|
|
@@ -1137,7 +1137,7 @@ async function rscRouter(options) {
|
|
|
1137
1137
|
rscEntryPath = userEntries.rsc ?? null;
|
|
1138
1138
|
let hasWarnedDuplicate = false;
|
|
1139
1139
|
plugins.push({
|
|
1140
|
-
name: "rsc-router:rsc-integration",
|
|
1140
|
+
name: "@ivogt/rsc-router:rsc-integration",
|
|
1141
1141
|
enforce: "pre",
|
|
1142
1142
|
config() {
|
|
1143
1143
|
const useVirtualClient = finalEntries.client === VIRTUAL_IDS.browser;
|
|
@@ -1231,7 +1231,7 @@ async function rscRouter(options) {
|
|
|
1231
1231
|
}
|
|
1232
1232
|
function createCjsToEsmPlugin() {
|
|
1233
1233
|
return {
|
|
1234
|
-
name: "rsc-router:cjs-to-esm",
|
|
1234
|
+
name: "@ivogt/rsc-router:cjs-to-esm",
|
|
1235
1235
|
enforce: "pre",
|
|
1236
1236
|
transform(code, id) {
|
|
1237
1237
|
const cleanId = id.split("?")[0];
|
|
@@ -1250,8 +1250,9 @@ function createCjsToEsmPlugin() {
|
|
|
1250
1250
|
if (license) {
|
|
1251
1251
|
transformed = transformed.slice(license.length);
|
|
1252
1252
|
}
|
|
1253
|
+
transformed = transformed.replace(/^\s*["']use strict["'];\s*/, "");
|
|
1253
1254
|
transformed = transformed.replace(
|
|
1254
|
-
/^\s*["']
|
|
1255
|
+
/^\s*["']production["']\s*!==\s*process\.env\.NODE_ENV\s*&&\s*\(function\s*\(\)\s*\{/,
|
|
1255
1256
|
""
|
|
1256
1257
|
);
|
|
1257
1258
|
transformed = transformed.replace(/\}\)\(\);?\s*$/, "");
|
|
@@ -1259,6 +1260,10 @@ function createCjsToEsmPlugin() {
|
|
|
1259
1260
|
/var\s+React\s*=\s*require\s*\(\s*["']react["']\s*\)\s*,[\s\n]+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
|
|
1260
1261
|
'import React from "react";\nimport ReactDOM from "react-dom";\nvar '
|
|
1261
1262
|
);
|
|
1263
|
+
transformed = transformed.replace(
|
|
1264
|
+
/var\s+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
|
|
1265
|
+
'import ReactDOM from "react-dom";\nvar '
|
|
1266
|
+
);
|
|
1262
1267
|
transformed = transformed.replace(
|
|
1263
1268
|
/exports\.(\w+)\s*=\s*function\s*\(/g,
|
|
1264
1269
|
"export function $1("
|
package/package.json
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { isClientComponent, assertClientComponent } from "../component-utils";
|
|
3
|
+
|
|
4
|
+
describe("component-utils", () => {
|
|
5
|
+
describe("isClientComponent", () => {
|
|
6
|
+
it("should return false for regular functions", () => {
|
|
7
|
+
const ServerComponent = () => null;
|
|
8
|
+
expect(isClientComponent(ServerComponent)).toBe(false);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("should return false for non-functions", () => {
|
|
12
|
+
expect(isClientComponent(null)).toBe(false);
|
|
13
|
+
expect(isClientComponent(undefined)).toBe(false);
|
|
14
|
+
expect(isClientComponent("string")).toBe(false);
|
|
15
|
+
expect(isClientComponent(123)).toBe(false);
|
|
16
|
+
expect(isClientComponent({})).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should return true for functions with client reference marker", () => {
|
|
20
|
+
const ClientComponent = () => null;
|
|
21
|
+
// Simulate what the bundler does for "use client" components
|
|
22
|
+
(ClientComponent as any).$$typeof = Symbol.for("react.client.reference");
|
|
23
|
+
(ClientComponent as any).$$id = "src/components/MyComponent.tsx#default";
|
|
24
|
+
|
|
25
|
+
expect(isClientComponent(ClientComponent)).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should return false for functions with wrong $$typeof symbol", () => {
|
|
29
|
+
const Component = () => null;
|
|
30
|
+
(Component as any).$$typeof = Symbol.for("react.element");
|
|
31
|
+
|
|
32
|
+
expect(isClientComponent(Component)).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("assertClientComponent", () => {
|
|
37
|
+
it("should throw for non-function values", () => {
|
|
38
|
+
expect(() => assertClientComponent(null, "document")).toThrow(
|
|
39
|
+
'document must be a client component function with "use client" directive'
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(() => assertClientComponent({}, "document")).toThrow(
|
|
43
|
+
'document must be a client component function with "use client" directive'
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should throw for server components (no client marker)", () => {
|
|
48
|
+
const ServerComponent = () => null;
|
|
49
|
+
|
|
50
|
+
expect(() => assertClientComponent(ServerComponent, "document")).toThrow(
|
|
51
|
+
'document must be a client component with "use client" directive'
|
|
52
|
+
);
|
|
53
|
+
expect(() => assertClientComponent(ServerComponent, "document")).toThrow(
|
|
54
|
+
"cannot be serialized in the RSC payload"
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should not throw for client components", () => {
|
|
59
|
+
const ClientComponent = () => null;
|
|
60
|
+
(ClientComponent as any).$$typeof = Symbol.for("react.client.reference");
|
|
61
|
+
(ClientComponent as any).$$id = "src/document.tsx#default";
|
|
62
|
+
|
|
63
|
+
expect(() =>
|
|
64
|
+
assertClientComponent(ClientComponent, "document")
|
|
65
|
+
).not.toThrow();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should include component name in error message", () => {
|
|
69
|
+
const ServerComponent = () => null;
|
|
70
|
+
|
|
71
|
+
expect(() => assertClientComponent(ServerComponent, "myLayout")).toThrow(
|
|
72
|
+
"myLayout must be a client component"
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -19,7 +19,7 @@ import type {
|
|
|
19
19
|
CacheGetResult,
|
|
20
20
|
} from "../types.js";
|
|
21
21
|
import type { RequestContext } from "../../server/request-context.js";
|
|
22
|
-
import { VERSION } from "rsc-router:version";
|
|
22
|
+
import { VERSION } from "@ivogt/rsc-router:version";
|
|
23
23
|
|
|
24
24
|
// ============================================================================
|
|
25
25
|
// Constants
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component utilities for RSC
|
|
3
|
+
*
|
|
4
|
+
* Helpers for working with React Server Components and client components.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ComponentType } from "react";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Symbol used by React to mark client component references.
|
|
11
|
+
* When a file has "use client" directive, the bundler transforms the exports
|
|
12
|
+
* to include this symbol on $$typeof.
|
|
13
|
+
*/
|
|
14
|
+
const CLIENT_REFERENCE = Symbol.for("react.client.reference");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check if a component is a client component (has "use client" directive).
|
|
18
|
+
*
|
|
19
|
+
* Client components are marked by the bundler with:
|
|
20
|
+
* - $$typeof: Symbol.for("react.client.reference")
|
|
21
|
+
* - $$id: string (module identifier)
|
|
22
|
+
*
|
|
23
|
+
* @param component - The component to check
|
|
24
|
+
* @returns true if the component has client reference marker
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { MyComponent } from "./my-component"; // has "use client"
|
|
29
|
+
*
|
|
30
|
+
* if (!isClientComponent(MyComponent)) {
|
|
31
|
+
* throw new Error("MyComponent must be a client component");
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function isClientComponent(
|
|
36
|
+
component: ComponentType<unknown> | unknown
|
|
37
|
+
): boolean {
|
|
38
|
+
if (typeof component !== "function") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const withMeta = component as { $$typeof?: symbol };
|
|
42
|
+
return withMeta.$$typeof === CLIENT_REFERENCE;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Assert that a component is a client component.
|
|
47
|
+
* Throws a descriptive error if not.
|
|
48
|
+
*
|
|
49
|
+
* @param component - The component to check
|
|
50
|
+
* @param name - Name to use in error message (e.g., "document")
|
|
51
|
+
* @throws Error if the component is not a client component
|
|
52
|
+
*/
|
|
53
|
+
export function assertClientComponent(
|
|
54
|
+
component: ComponentType<unknown> | unknown,
|
|
55
|
+
name: string
|
|
56
|
+
): asserts component is ComponentType<unknown> {
|
|
57
|
+
if (typeof component !== "function") {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`${name} must be a client component function with "use client" directive. ` +
|
|
60
|
+
`Make sure to pass the component itself, not a JSX element: ` +
|
|
61
|
+
`${name}: My${capitalize(name)} (correct) vs ${name}: <My${capitalize(name)} /> (incorrect)`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!isClientComponent(component)) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`${name} must be a client component with "use client" directive at the top of the file. ` +
|
|
68
|
+
`Server components cannot be used as the ${name} because their function reference ` +
|
|
69
|
+
`cannot be serialized in the RSC payload. Add "use client" to your ${name} file.`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function capitalize(str: string): string {
|
|
75
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
76
|
+
}
|
package/src/router.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ComponentType } from "react";
|
|
|
2
2
|
import { type ReactNode } from "react";
|
|
3
3
|
import { CacheScope, createCacheScope } from "./cache/cache-scope.js";
|
|
4
4
|
import type { SegmentCacheStore } from "./cache/types.js";
|
|
5
|
+
import { assertClientComponent } from "./component-utils.js";
|
|
5
6
|
import { DefaultDocument } from "./components/DefaultDocument.js";
|
|
6
7
|
import { DefaultErrorFallback } from "./default-error-boundary.js";
|
|
7
8
|
import {
|
|
@@ -511,16 +512,9 @@ export function createRSCRouter<TEnv = any>(
|
|
|
511
512
|
invokeOnError(onError, error, phase, context, "Router");
|
|
512
513
|
}
|
|
513
514
|
|
|
514
|
-
// Validate document is a
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
// "Functions cannot be passed to Client Components" error.
|
|
518
|
-
if (documentOption !== undefined && typeof documentOption !== "function") {
|
|
519
|
-
throw new Error(
|
|
520
|
-
`document must be a client component function with "use client" directive. ` +
|
|
521
|
-
`Make sure to pass the component itself, not a JSX element: ` +
|
|
522
|
-
`document: MyDocument (correct) vs document: <MyDocument /> (incorrect)`
|
|
523
|
-
);
|
|
515
|
+
// Validate document is a client component
|
|
516
|
+
if (documentOption !== undefined) {
|
|
517
|
+
assertClientComponent(documentOption, "document");
|
|
524
518
|
}
|
|
525
519
|
|
|
526
520
|
// Use default document if none provided (keeps internal name as rootLayout)
|
package/src/rsc/handler.ts
CHANGED
|
@@ -32,7 +32,7 @@ import type {
|
|
|
32
32
|
} from "./types.js";
|
|
33
33
|
import { hasBodyContent, createResponseWithMergedHeaders } from "./helpers.js";
|
|
34
34
|
import { generateNonce } from "./nonce.js";
|
|
35
|
-
import { VERSION } from "rsc-router:version";
|
|
35
|
+
import { VERSION } from "@ivogt/rsc-router:version";
|
|
36
36
|
import type { ErrorPhase } from "../types.js";
|
|
37
37
|
import { invokeOnError } from "../router/error-handling.js";
|
|
38
38
|
|
package/src/server.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { describe, it, expect } from "vitest";
|
|
|
4
4
|
* Mock function to test createLoader detection patterns
|
|
5
5
|
*/
|
|
6
6
|
function hasCreateLoaderImport(code: string): boolean {
|
|
7
|
-
const pattern = /import\s*\{[^}]*\bcreateLoader\b[^}]*\}\s*from\s*["']rsc-router(?:\/server)?["']/;
|
|
7
|
+
const pattern = /import\s*\{[^}]*\bcreateLoader\b[^}]*\}\s*from\s*["']@ivogt\/rsc-router(?:\/server)?["']/;
|
|
8
8
|
return pattern.test(code);
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -24,22 +24,22 @@ function extractLoaderExports(code: string): string[] {
|
|
|
24
24
|
describe("exposeLoaderId plugin", () => {
|
|
25
25
|
describe("hasCreateLoaderImport", () => {
|
|
26
26
|
it("should detect direct import from rsc-router", () => {
|
|
27
|
-
const code = `import { createLoader } from "rsc-router";`;
|
|
27
|
+
const code = `import { createLoader } from "@ivogt/rsc-router";`;
|
|
28
28
|
expect(hasCreateLoaderImport(code)).toBe(true);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
it("should detect import from rsc-router/server", () => {
|
|
32
|
-
const code = `import { createLoader } from "rsc-router/server";`;
|
|
32
|
+
const code = `import { createLoader } from "@ivogt/rsc-router/server";`;
|
|
33
33
|
expect(hasCreateLoaderImport(code)).toBe(true);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
it("should detect createLoader with other imports", () => {
|
|
37
|
-
const code = `import { map, createLoader, route } from "rsc-router";`;
|
|
37
|
+
const code = `import { map, createLoader, route } from "@ivogt/rsc-router";`;
|
|
38
38
|
expect(hasCreateLoaderImport(code)).toBe(true);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
it("should NOT detect aliased import", () => {
|
|
42
|
-
const code = `import { createLoader as cl } from "rsc-router";`;
|
|
42
|
+
const code = `import { createLoader as cl } from "@ivogt/rsc-router";`;
|
|
43
43
|
// Our simple pattern doesn't support aliasing - this is intentional
|
|
44
44
|
expect(hasCreateLoaderImport(code)).toBe(true); // Still matches the word
|
|
45
45
|
});
|
|
@@ -50,12 +50,12 @@ describe("exposeLoaderId plugin", () => {
|
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
it("should NOT detect default import", () => {
|
|
53
|
-
const code = `import createLoader from "rsc-router";`;
|
|
53
|
+
const code = `import createLoader from "@ivogt/rsc-router";`;
|
|
54
54
|
expect(hasCreateLoaderImport(code)).toBe(false);
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
it("should NOT detect namespace import", () => {
|
|
58
|
-
const code = `import * as router from "rsc-router";`;
|
|
58
|
+
const code = `import * as router from "@ivogt/rsc-router";`;
|
|
59
59
|
expect(hasCreateLoaderImport(code)).toBe(false);
|
|
60
60
|
});
|
|
61
61
|
});
|
|
@@ -229,7 +229,7 @@ export function exposeActionId(): Plugin {
|
|
|
229
229
|
let rscPluginApi: RscPluginApi | undefined;
|
|
230
230
|
|
|
231
231
|
return {
|
|
232
|
-
name: "rsc-router:expose-action-id",
|
|
232
|
+
name: "@ivogt/rsc-router:expose-action-id",
|
|
233
233
|
// Run after all other plugins (including RSC plugin's transforms)
|
|
234
234
|
enforce: "post",
|
|
235
235
|
|
|
@@ -251,7 +251,7 @@ export function exposeActionId(): Plugin {
|
|
|
251
251
|
if (!rscPluginApi) {
|
|
252
252
|
throw new Error(
|
|
253
253
|
"[rsc-router] Could not find @vitejs/plugin-rsc. " +
|
|
254
|
-
"rsc-router requires the Vite RSC plugin.\n" +
|
|
254
|
+
"@ivogt/rsc-router requires the Vite RSC plugin.\n" +
|
|
255
255
|
"The RSC plugin should be included automatically. If you disabled it with\n" +
|
|
256
256
|
"rscRouter({ rsc: false }), add rsc() before rscRouter() in your config."
|
|
257
257
|
);
|
|
@@ -25,9 +25,9 @@ function hashHandleId(filePath: string, exportName: string): string {
|
|
|
25
25
|
* Check if file imports createHandle from rsc-router
|
|
26
26
|
*/
|
|
27
27
|
function hasCreateHandleImport(code: string): boolean {
|
|
28
|
-
// Match: import { createHandle } from "rsc-router" or "rsc-router/..."
|
|
28
|
+
// Match: import { createHandle } from "@ivogt/rsc-router" or "@ivogt/rsc-router/..."
|
|
29
29
|
const pattern =
|
|
30
|
-
/import\s*\{[^}]*\bcreateHandle\b[^}]*\}\s*from\s*["']rsc-router(?:\/[^"']+)?["']/;
|
|
30
|
+
/import\s*\{[^}]*\bcreateHandle\b[^}]*\}\s*from\s*["']@ivogt\/rsc-router(?:\/[^"']+)?["']/;
|
|
31
31
|
return pattern.test(code);
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -167,7 +167,7 @@ function transformHandleExports(
|
|
|
167
167
|
* The name is auto-generated from file path + export name.
|
|
168
168
|
*
|
|
169
169
|
* Requirements:
|
|
170
|
-
* - Must use direct import: import { createHandle } from "rsc-router"
|
|
170
|
+
* - Must use direct import: import { createHandle } from "@ivogt/rsc-router"
|
|
171
171
|
* - Must use named export: export const MyHandle = createHandle(...)
|
|
172
172
|
*/
|
|
173
173
|
export function exposeHandleId(): Plugin {
|
|
@@ -175,7 +175,7 @@ export function exposeHandleId(): Plugin {
|
|
|
175
175
|
let isBuild = false;
|
|
176
176
|
|
|
177
177
|
return {
|
|
178
|
-
name: "rsc-router:expose-handle-id",
|
|
178
|
+
name: "@ivogt/rsc-router:expose-handle-id",
|
|
179
179
|
enforce: "post",
|
|
180
180
|
|
|
181
181
|
configResolved(resolvedConfig) {
|
|
@@ -25,10 +25,10 @@ function hashLoaderId(filePath: string, exportName: string): string {
|
|
|
25
25
|
* Check if file imports createLoader from rsc-router
|
|
26
26
|
*/
|
|
27
27
|
function hasCreateLoaderImport(code: string): boolean {
|
|
28
|
-
// Match: import { createLoader } from "rsc-router" or "rsc-router/server"
|
|
28
|
+
// Match: import { createLoader } from "@ivogt/rsc-router" or "@ivogt/rsc-router/server"
|
|
29
29
|
// Must be exact - no aliasing support
|
|
30
30
|
const pattern =
|
|
31
|
-
/import\s*\{[^}]*\bcreateLoader\b[^}]*\}\s*from\s*["']rsc-router(?:\/server)?["']/;
|
|
31
|
+
/import\s*\{[^}]*\bcreateLoader\b[^}]*\}\s*from\s*["']@ivogt\/rsc-router(?:\/server)?["']/;
|
|
32
32
|
return pattern.test(code);
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -176,7 +176,7 @@ let manifestGenerated = false;
|
|
|
176
176
|
* The manifest can be imported by the RSC handler to get all loaders.
|
|
177
177
|
*
|
|
178
178
|
* Requirements:
|
|
179
|
-
* - Must use direct import: import { createLoader } from "rsc-router"
|
|
179
|
+
* - Must use direct import: import { createLoader } from "@ivogt/rsc-router"
|
|
180
180
|
* - No aliasing support (import { createLoader as cl } won't work)
|
|
181
181
|
* - Must use named export: export const MyLoader = createLoader(...)
|
|
182
182
|
*/
|
|
@@ -194,7 +194,7 @@ export function exposeLoaderId(): Plugin {
|
|
|
194
194
|
const pendingLoaderScans = new Map<string, Promise<void>>();
|
|
195
195
|
|
|
196
196
|
return {
|
|
197
|
-
name: "rsc-router:expose-loader-id",
|
|
197
|
+
name: "@ivogt/rsc-router:expose-loader-id",
|
|
198
198
|
enforce: "post",
|
|
199
199
|
|
|
200
200
|
configResolved(resolvedConfig) {
|
|
@@ -276,7 +276,7 @@ export function exposeLoaderId(): Plugin {
|
|
|
276
276
|
if (!isBuild) {
|
|
277
277
|
// Dev mode: empty map - use fallback path parsing in loader registry
|
|
278
278
|
// IDs in dev mode are "filePath#exportName" format for easier debugging
|
|
279
|
-
return `import { setLoaderImports } from "rsc-router/server";
|
|
279
|
+
return `import { setLoaderImports } from "@ivogt/rsc-router/server";
|
|
280
280
|
|
|
281
281
|
// Dev mode: empty map, loaders are resolved dynamically via path parsing
|
|
282
282
|
setLoaderImports({});
|
|
@@ -297,14 +297,14 @@ setLoaderImports({});
|
|
|
297
297
|
|
|
298
298
|
// If no loaders discovered, set empty map
|
|
299
299
|
if (lazyImports.length === 0) {
|
|
300
|
-
return `import { setLoaderImports } from "rsc-router/server";
|
|
300
|
+
return `import { setLoaderImports } from "@ivogt/rsc-router/server";
|
|
301
301
|
|
|
302
302
|
// No fetchable loaders discovered during build
|
|
303
303
|
setLoaderImports({});
|
|
304
304
|
`;
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
const code = `import { setLoaderImports } from "rsc-router/server";
|
|
307
|
+
const code = `import { setLoaderImports } from "@ivogt/rsc-router/server";
|
|
308
308
|
|
|
309
309
|
// Lazy import map - loaders are loaded on-demand when first requested
|
|
310
310
|
setLoaderImports({
|
|
@@ -25,9 +25,9 @@ function hashLocationStateKey(filePath: string, exportName: string): string {
|
|
|
25
25
|
* Check if file imports createLocationState from rsc-router
|
|
26
26
|
*/
|
|
27
27
|
function hasCreateLocationStateImport(code: string): boolean {
|
|
28
|
-
// Match: import { createLocationState } from "rsc-router" or "rsc-router/client"
|
|
28
|
+
// Match: import { createLocationState } from "@ivogt/rsc-router" or "@ivogt/rsc-router/client"
|
|
29
29
|
const pattern =
|
|
30
|
-
/import\s*\{[^}]*\bcreateLocationState\b[^}]*\}\s*from\s*["']rsc-router(?:\/[^"']+)?["']/;
|
|
30
|
+
/import\s*\{[^}]*\bcreateLocationState\b[^}]*\}\s*from\s*["']@ivogt\/rsc-router(?:\/[^"']+)?["']/;
|
|
31
31
|
return pattern.test(code);
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -135,7 +135,7 @@ function transformLocationStateExports(
|
|
|
135
135
|
* The key is auto-generated from file path + export name.
|
|
136
136
|
*
|
|
137
137
|
* Requirements:
|
|
138
|
-
* - Must use direct import: import { createLocationState } from "rsc-router"
|
|
138
|
+
* - Must use direct import: import { createLocationState } from "@ivogt/rsc-router"
|
|
139
139
|
* - Must use named export: export const MyState = createLocationState(...)
|
|
140
140
|
*/
|
|
141
141
|
export function exposeLocationStateId(): Plugin {
|
|
@@ -143,7 +143,7 @@ export function exposeLocationStateId(): Plugin {
|
|
|
143
143
|
let isBuild = false;
|
|
144
144
|
|
|
145
145
|
return {
|
|
146
|
-
name: "rsc-router:expose-location-state-id",
|
|
146
|
+
name: "@ivogt/rsc-router:expose-location-state-id",
|
|
147
147
|
enforce: "post",
|
|
148
148
|
|
|
149
149
|
configResolved(resolvedConfig) {
|
package/src/vite/index.ts
CHANGED
|
@@ -33,13 +33,13 @@ export { exposeLocationStateId } from "./expose-location-state-id.ts";
|
|
|
33
33
|
* before Vite's plugin system can handle virtual modules.
|
|
34
34
|
*/
|
|
35
35
|
const versionEsbuildPlugin = {
|
|
36
|
-
name: "rsc-router-version",
|
|
36
|
+
name: "@ivogt/rsc-router-version",
|
|
37
37
|
setup(build: any) {
|
|
38
38
|
build.onResolve({ filter: /^rsc-router:version$/ }, (args: any) => ({
|
|
39
39
|
path: args.path,
|
|
40
|
-
namespace: "rsc-router-virtual",
|
|
40
|
+
namespace: "@ivogt/rsc-router-virtual",
|
|
41
41
|
}));
|
|
42
|
-
build.onLoad({ filter: /.*/, namespace: "rsc-router-virtual" }, () => ({
|
|
42
|
+
build.onLoad({ filter: /.*/, namespace: "@ivogt/rsc-router-virtual" }, () => ({
|
|
43
43
|
contents: `export const VERSION = "dev";`,
|
|
44
44
|
loader: "js",
|
|
45
45
|
}));
|
|
@@ -184,7 +184,7 @@ function createVirtualEntriesPlugin(
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
return {
|
|
187
|
-
name: "rsc-router:virtual-entries",
|
|
187
|
+
name: "@ivogt/rsc-router:virtual-entries",
|
|
188
188
|
enforce: "pre",
|
|
189
189
|
|
|
190
190
|
resolveId(id) {
|
|
@@ -254,7 +254,7 @@ function createVersionPlugin(): Plugin {
|
|
|
254
254
|
let server: any = null;
|
|
255
255
|
|
|
256
256
|
return {
|
|
257
|
-
name: "rsc-router:version",
|
|
257
|
+
name: "@ivogt/rsc-router:version",
|
|
258
258
|
enforce: "pre",
|
|
259
259
|
|
|
260
260
|
configResolved(config) {
|
|
@@ -323,7 +323,7 @@ function createVersionInjectorPlugin(rscEntryPath: string): Plugin {
|
|
|
323
323
|
let resolvedEntryPath = "";
|
|
324
324
|
|
|
325
325
|
return {
|
|
326
|
-
name: "rsc-router:version-injector",
|
|
326
|
+
name: "@ivogt/rsc-router:version-injector",
|
|
327
327
|
enforce: "pre",
|
|
328
328
|
|
|
329
329
|
configResolved(config) {
|
|
@@ -346,7 +346,7 @@ function createVersionInjectorPlugin(rscEntryPath: string): Plugin {
|
|
|
346
346
|
}
|
|
347
347
|
|
|
348
348
|
// Check if VERSION is already imported
|
|
349
|
-
if (code.includes("rsc-router:version")) {
|
|
349
|
+
if (code.includes("@ivogt/rsc-router:version")) {
|
|
350
350
|
return null;
|
|
351
351
|
}
|
|
352
352
|
|
|
@@ -382,7 +382,7 @@ function createVersionInjectorPlugin(rscEntryPath: string): Plugin {
|
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
// Insert VERSION import
|
|
385
|
-
const versionImport = `import { VERSION } from "rsc-router:version";\n`;
|
|
385
|
+
const versionImport = `import { VERSION } from "@ivogt/rsc-router:version";\n`;
|
|
386
386
|
let newCode = code.slice(0, insertIndex) + versionImport + code.slice(insertIndex);
|
|
387
387
|
|
|
388
388
|
// Add version: VERSION to createRSCHandler call
|
|
@@ -454,7 +454,7 @@ export async function rscRouter(
|
|
|
454
454
|
};
|
|
455
455
|
|
|
456
456
|
plugins.push({
|
|
457
|
-
name: "rsc-router:cloudflare-integration",
|
|
457
|
+
name: "@ivogt/rsc-router:cloudflare-integration",
|
|
458
458
|
enforce: "pre",
|
|
459
459
|
config() {
|
|
460
460
|
// Configure environments for cloudflare deployment
|
|
@@ -561,7 +561,7 @@ export async function rscRouter(
|
|
|
561
561
|
let hasWarnedDuplicate = false;
|
|
562
562
|
|
|
563
563
|
plugins.push({
|
|
564
|
-
name: "rsc-router:rsc-integration",
|
|
564
|
+
name: "@ivogt/rsc-router:rsc-integration",
|
|
565
565
|
enforce: "pre",
|
|
566
566
|
|
|
567
567
|
config() {
|
|
@@ -691,7 +691,7 @@ export async function rscRouter(
|
|
|
691
691
|
*/
|
|
692
692
|
function createCjsToEsmPlugin(): Plugin {
|
|
693
693
|
return {
|
|
694
|
-
name: "rsc-router:cjs-to-esm",
|
|
694
|
+
name: "@ivogt/rsc-router:cjs-to-esm",
|
|
695
695
|
enforce: "pre",
|
|
696
696
|
transform(code, id) {
|
|
697
697
|
const cleanId = id.split("?")[0];
|
|
@@ -727,21 +727,30 @@ function createCjsToEsmPlugin(): Plugin {
|
|
|
727
727
|
transformed = transformed.slice(license.length);
|
|
728
728
|
}
|
|
729
729
|
|
|
730
|
-
// Remove "use strict" and
|
|
730
|
+
// Remove "use strict" (both dev and prod have this)
|
|
731
|
+
transformed = transformed.replace(/^\s*["']use strict["'];\s*/, "");
|
|
732
|
+
|
|
733
|
+
// Remove the conditional IIFE wrapper (development only)
|
|
731
734
|
transformed = transformed.replace(
|
|
732
|
-
/^\s*["']
|
|
735
|
+
/^\s*["']production["']\s*!==\s*process\.env\.NODE_ENV\s*&&\s*\(function\s*\(\)\s*\{/,
|
|
733
736
|
""
|
|
734
737
|
);
|
|
735
738
|
|
|
736
|
-
// Remove the closing of the conditional IIFE at the end
|
|
739
|
+
// Remove the closing of the conditional IIFE at the end (development only)
|
|
737
740
|
transformed = transformed.replace(/\}\)\(\);?\s*$/, "");
|
|
738
741
|
|
|
739
|
-
// Replace require('react') and require('react-dom') with imports
|
|
742
|
+
// Replace require('react') and require('react-dom') with imports (development)
|
|
740
743
|
transformed = transformed.replace(
|
|
741
744
|
/var\s+React\s*=\s*require\s*\(\s*["']react["']\s*\)\s*,[\s\n]+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
|
|
742
745
|
'import React from "react";\nimport ReactDOM from "react-dom";\nvar '
|
|
743
746
|
);
|
|
744
747
|
|
|
748
|
+
// Replace require('react-dom') only (production - doesn't import React)
|
|
749
|
+
transformed = transformed.replace(
|
|
750
|
+
/var\s+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
|
|
751
|
+
'import ReactDOM from "react-dom";\nvar '
|
|
752
|
+
);
|
|
753
|
+
|
|
745
754
|
// Transform exports.xyz = function() to export function xyz()
|
|
746
755
|
transformed = transformed.replace(
|
|
747
756
|
/exports\.(\w+)\s*=\s*function\s*\(/g,
|
|
@@ -12,7 +12,7 @@ import packageJson from "../../package.json" with { type: "json" };
|
|
|
12
12
|
/**
|
|
13
13
|
* The canonical name used in virtual entries (without scope)
|
|
14
14
|
*/
|
|
15
|
-
const VIRTUAL_PACKAGE_NAME = "rsc-router";
|
|
15
|
+
const VIRTUAL_PACKAGE_NAME = "@ivogt/rsc-router";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Get the published package name (e.g., "@ivogt/rsc-router")
|
|
@@ -25,12 +25,12 @@ export function getPublishedPackageName(): string {
|
|
|
25
25
|
* Check if the package is installed from npm (scoped) vs workspace (unscoped)
|
|
26
26
|
*
|
|
27
27
|
* In workspace development:
|
|
28
|
-
* - Package is installed as "rsc-router" via pnpm workspace alias
|
|
28
|
+
* - Package is installed as "@ivogt/rsc-router" via pnpm workspace alias
|
|
29
29
|
* - The scoped name (@ivogt/rsc-router) doesn't exist in node_modules
|
|
30
30
|
*
|
|
31
31
|
* When installed from npm:
|
|
32
32
|
* - Package is installed as "@ivogt/rsc-router"
|
|
33
|
-
* - We need aliases to map "rsc-router/*" to "@ivogt/rsc-router/*"
|
|
33
|
+
* - We need aliases to map "@ivogt/rsc-router/*" to "@ivogt/rsc-router/*"
|
|
34
34
|
*/
|
|
35
35
|
export function isInstalledFromNpm(): boolean {
|
|
36
36
|
const packageName = getPublishedPackageName();
|
package/src/vite/version.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* This module is provided by the Vite plugin at build/dev time.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
declare module "rsc-router:version" {
|
|
6
|
+
declare module "@ivogt/rsc-router:version" {
|
|
7
7
|
/**
|
|
8
8
|
* Auto-generated version string for cache invalidation.
|
|
9
9
|
* Changes on server restart (dev) or build (prod).
|
|
@@ -10,11 +10,11 @@ import {
|
|
|
10
10
|
setServerCallback,
|
|
11
11
|
encodeReply,
|
|
12
12
|
createTemporaryReferenceSet,
|
|
13
|
-
} from "rsc-router/internal/deps/browser";
|
|
13
|
+
} from "@ivogt/rsc-router/internal/deps/browser";
|
|
14
14
|
import { createElement, StrictMode } from "react";
|
|
15
15
|
import { hydrateRoot } from "react-dom/client";
|
|
16
|
-
import { rscStream } from "rsc-router/internal/deps/html-stream-client";
|
|
17
|
-
import { initBrowserApp, RSCRouter } from "rsc-router/browser";
|
|
16
|
+
import { rscStream } from "@ivogt/rsc-router/internal/deps/html-stream-client";
|
|
17
|
+
import { initBrowserApp, RSCRouter } from "@ivogt/rsc-router/browser";
|
|
18
18
|
|
|
19
19
|
async function initializeApp() {
|
|
20
20
|
const deps = {
|
|
@@ -37,10 +37,10 @@ initializeApp().catch(console.error);
|
|
|
37
37
|
`.trim();
|
|
38
38
|
|
|
39
39
|
export const VIRTUAL_ENTRY_SSR: string = `
|
|
40
|
-
import { createFromReadableStream } from "rsc-router/internal/deps/ssr";
|
|
40
|
+
import { createFromReadableStream } from "@ivogt/rsc-router/internal/deps/ssr";
|
|
41
41
|
import { renderToReadableStream } from "react-dom/server.edge";
|
|
42
|
-
import { injectRSCPayload } from "rsc-router/internal/deps/html-stream-server";
|
|
43
|
-
import { createSSRHandler } from "rsc-router/ssr";
|
|
42
|
+
import { injectRSCPayload } from "@ivogt/rsc-router/internal/deps/html-stream-server";
|
|
43
|
+
import { createSSRHandler } from "@ivogt/rsc-router/ssr";
|
|
44
44
|
|
|
45
45
|
export const renderHTML = createSSRHandler({
|
|
46
46
|
createFromReadableStream,
|
|
@@ -63,10 +63,10 @@ import {
|
|
|
63
63
|
loadServerAction,
|
|
64
64
|
decodeAction,
|
|
65
65
|
decodeFormState,
|
|
66
|
-
} from "rsc-router/internal/deps/rsc";
|
|
66
|
+
} from "@ivogt/rsc-router/internal/deps/rsc";
|
|
67
67
|
import { router } from "${routerPath}";
|
|
68
|
-
import { createRSCHandler } from "rsc-router/rsc";
|
|
69
|
-
import { VERSION } from "rsc-router:version";
|
|
68
|
+
import { createRSCHandler } from "@ivogt/rsc-router/rsc";
|
|
69
|
+
import { VERSION } from "@ivogt/rsc-router:version";
|
|
70
70
|
|
|
71
71
|
// Import loader manifest to ensure all fetchable loaders are registered at startup
|
|
72
72
|
// This is critical for serverless/multi-process deployments where the loader module
|
|
@@ -97,7 +97,7 @@ export const VIRTUAL_IDS = {
|
|
|
97
97
|
browser: "virtual:rsc-router/entry.browser.js",
|
|
98
98
|
ssr: "virtual:rsc-router/entry.ssr.js",
|
|
99
99
|
rsc: "virtual:rsc-router/entry.rsc.js",
|
|
100
|
-
version: "rsc-router:version",
|
|
100
|
+
version: "@ivogt/rsc-router:version",
|
|
101
101
|
} as const;
|
|
102
102
|
|
|
103
103
|
/**
|