rwsdk 0.2.0-alpha.9 → 0.3.0

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.
Files changed (93) hide show
  1. package/dist/lib/constants.d.mts +6 -1
  2. package/dist/lib/constants.mjs +6 -1
  3. package/dist/lib/smokeTests/browser.mjs +5 -21
  4. package/dist/lib/smokeTests/codeUpdates.d.mts +1 -1
  5. package/dist/lib/smokeTests/codeUpdates.mjs +41 -5
  6. package/dist/lib/smokeTests/development.d.mts +1 -1
  7. package/dist/lib/smokeTests/development.mjs +4 -10
  8. package/dist/lib/smokeTests/release.d.mts +1 -1
  9. package/dist/lib/smokeTests/release.mjs +4 -9
  10. package/dist/lib/smokeTests/runSmokeTests.mjs +2 -2
  11. package/dist/lib/smokeTests/templates/SmokeTest.template.js +3 -2
  12. package/dist/lib/testUtils/stubEnvVars.d.mts +2 -0
  13. package/dist/lib/testUtils/stubEnvVars.mjs +11 -0
  14. package/dist/runtime/client/client.d.ts +10 -0
  15. package/dist/runtime/{client.js → client/client.js} +13 -10
  16. package/dist/runtime/{clientNavigation.test.js → client/navigation.test.js} +1 -1
  17. package/dist/runtime/client/setWebpackRequire.d.ts +1 -0
  18. package/dist/runtime/client/setWebpackRequire.js +2 -0
  19. package/dist/runtime/{client.d.ts → client/types.d.ts} +4 -10
  20. package/dist/runtime/client/types.js +1 -0
  21. package/dist/runtime/entries/client.d.ts +2 -2
  22. package/dist/runtime/entries/client.js +2 -2
  23. package/dist/runtime/imports/client.d.ts +3 -3
  24. package/dist/runtime/imports/client.js +11 -15
  25. package/dist/runtime/imports/ssr.d.ts +3 -3
  26. package/dist/runtime/imports/ssr.js +3 -3
  27. package/dist/runtime/imports/worker.d.ts +3 -3
  28. package/dist/runtime/imports/worker.js +5 -4
  29. package/dist/runtime/lib/manifest.d.ts +11 -2
  30. package/dist/runtime/lib/manifest.js +1 -1
  31. package/dist/runtime/lib/memoizeOnId.d.ts +1 -0
  32. package/dist/runtime/lib/memoizeOnId.js +11 -0
  33. package/dist/runtime/lib/realtime/client.d.ts +1 -1
  34. package/dist/runtime/lib/realtime/client.js +1 -1
  35. package/dist/runtime/lib/router.d.ts +3 -3
  36. package/dist/runtime/lib/router.js +77 -33
  37. package/dist/runtime/register/ssr.d.ts +1 -1
  38. package/dist/runtime/register/ssr.js +4 -3
  39. package/dist/runtime/render/preloads.d.ts +6 -0
  40. package/dist/runtime/render/preloads.js +40 -0
  41. package/dist/runtime/render/renderRscThenableToHtmlStream.js +2 -1
  42. package/dist/runtime/render/stylesheets.js +1 -1
  43. package/dist/runtime/requestInfo/types.d.ts +3 -1
  44. package/dist/runtime/requestInfo/worker.js +9 -1
  45. package/dist/runtime/worker.d.ts +0 -3
  46. package/dist/runtime/worker.js +2 -11
  47. package/dist/scripts/debug-sync.mjs +142 -39
  48. package/dist/scripts/smoke-test.mjs +0 -10
  49. package/dist/scripts/worker-run.mjs +8 -3
  50. package/dist/vite/buildApp.d.mts +15 -0
  51. package/dist/vite/buildApp.mjs +53 -0
  52. package/dist/vite/configPlugin.d.mts +4 -2
  53. package/dist/vite/configPlugin.mjs +69 -62
  54. package/dist/vite/createDirectiveLookupPlugin.d.mts +0 -6
  55. package/dist/vite/createDirectiveLookupPlugin.mjs +61 -145
  56. package/dist/vite/directiveModulesDevPlugin.d.mts +8 -0
  57. package/dist/vite/directiveModulesDevPlugin.mjs +62 -0
  58. package/dist/vite/directivesFilteringPlugin.d.mts +6 -0
  59. package/dist/vite/directivesFilteringPlugin.mjs +31 -0
  60. package/dist/vite/directivesPlugin.mjs +28 -42
  61. package/dist/vite/getViteEsbuild.d.mts +1 -0
  62. package/dist/vite/getViteEsbuild.mjs +12 -0
  63. package/dist/vite/hasOwnReactVitePlugin.d.mts +3 -0
  64. package/dist/vite/hasOwnReactVitePlugin.mjs +14 -0
  65. package/dist/vite/injectVitePreamblePlugin.d.mts +3 -2
  66. package/dist/vite/injectVitePreamblePlugin.mjs +4 -2
  67. package/dist/vite/linkerPlugin.d.mts +4 -0
  68. package/dist/vite/linkerPlugin.mjs +41 -0
  69. package/dist/vite/manifestPlugin.d.mts +2 -2
  70. package/dist/vite/manifestPlugin.mjs +12 -37
  71. package/dist/vite/miniflareHMRPlugin.mjs +17 -2
  72. package/dist/vite/moveStaticAssetsPlugin.mjs +2 -1
  73. package/dist/vite/prismaPlugin.mjs +1 -1
  74. package/dist/vite/reactConditionsResolverPlugin.d.mts +3 -4
  75. package/dist/vite/reactConditionsResolverPlugin.mjs +74 -56
  76. package/dist/vite/redwoodPlugin.d.mts +1 -1
  77. package/dist/vite/redwoodPlugin.mjs +36 -12
  78. package/dist/vite/runDirectivesScan.d.mts +7 -0
  79. package/dist/vite/runDirectivesScan.mjs +152 -0
  80. package/dist/vite/ssrBridgePlugin.mjs +13 -14
  81. package/dist/vite/transformClientComponents.d.mts +0 -1
  82. package/dist/vite/transformClientComponents.mjs +1 -9
  83. package/dist/vite/transformJsxScriptTagsPlugin.d.mts +4 -3
  84. package/dist/vite/transformJsxScriptTagsPlugin.mjs +151 -158
  85. package/dist/vite/transformJsxScriptTagsPlugin.test.mjs +393 -136
  86. package/dist/vite/transformServerFunctions.d.mts +1 -1
  87. package/dist/vite/transformServerFunctions.mjs +11 -12
  88. package/package.json +28 -4
  89. /package/dist/runtime/{imports → client}/ClientOnly.d.ts +0 -0
  90. /package/dist/runtime/{imports → client}/ClientOnly.js +0 -0
  91. /package/dist/runtime/{clientNavigation.d.ts → client/navigation.d.ts} +0 -0
  92. /package/dist/runtime/{clientNavigation.js → client/navigation.js} +0 -0
  93. /package/dist/runtime/{clientNavigation.test.d.ts → client/navigation.test.d.ts} +0 -0
@@ -1,4 +1,9 @@
1
1
  export declare const ROOT_DIR: string;
2
2
  export declare const SRC_DIR: string;
3
3
  export declare const DIST_DIR: string;
4
- export declare const SSR_BRIDGE_PATH: string;
4
+ export declare const VITE_DIR: string;
5
+ export declare const INTERMEDIATES_OUTPUT_DIR: string;
6
+ export declare const CLIENT_BARREL_PATH: string;
7
+ export declare const SERVER_BARREL_PATH: string;
8
+ export declare const INTERMEDIATE_SSR_BRIDGE_PATH: string;
9
+ export declare const CLIENT_MANIFEST_RELATIVE_PATH: string;
@@ -3,4 +3,9 @@ const __dirname = new URL(".", import.meta.url).pathname;
3
3
  export const ROOT_DIR = resolve(__dirname, "..", "..");
4
4
  export const SRC_DIR = resolve(ROOT_DIR, "src");
5
5
  export const DIST_DIR = resolve(ROOT_DIR, "dist");
6
- export const SSR_BRIDGE_PATH = resolve(DIST_DIR, "worker", "__ssr_bridge.js");
6
+ export const VITE_DIR = resolve(ROOT_DIR, "src", "vite");
7
+ export const INTERMEDIATES_OUTPUT_DIR = resolve(DIST_DIR, "__intermediate_builds");
8
+ export const CLIENT_BARREL_PATH = resolve(INTERMEDIATES_OUTPUT_DIR, "rwsdk-client-barrel.js");
9
+ export const SERVER_BARREL_PATH = resolve(INTERMEDIATES_OUTPUT_DIR, "rwsdk-server-barrel.js");
10
+ export const INTERMEDIATE_SSR_BRIDGE_PATH = resolve(INTERMEDIATES_OUTPUT_DIR, "ssr", "ssr_bridge.js");
11
+ export const CLIENT_MANIFEST_RELATIVE_PATH = resolve("dist", "client", ".vite", "manifest.json");
@@ -316,25 +316,9 @@ export async function checkUrl(url, artifactDir, browserPath, headless = true, b
316
316
  export async function checkUrlSmoke(page, url, isRealtime, bail = false, skipClient = false, environment = "Development", timestampState, targetDir, skipHmr = false, skipStyleTests = false) {
317
317
  const phase = isRealtime ? "Post-upgrade" : "Initial";
318
318
  console.log(`🔍 Testing ${phase} smoke tests at ${url}`);
319
- // Parse the base URL and path to properly handle smoke test queries
320
- const parsedUrl = new URL(url);
321
- log("Parsed URL: %O", {
322
- origin: parsedUrl.origin,
323
- pathname: parsedUrl.pathname,
324
- search: parsedUrl.search,
325
- });
326
- // Add __smoke_test query parameter, preserving any existing query parameters
327
- if (parsedUrl.searchParams.has("__smoke_test")) {
328
- console.log(`URL already has __smoke_test parameter: ${url}`);
329
- }
330
- else {
331
- parsedUrl.searchParams.append("__smoke_test", "1");
332
- log("Added __smoke_test parameter to URL");
333
- }
334
319
  // Navigate to smoke test page
335
- const smokeUrl = parsedUrl.toString();
336
- console.log(`🔍 Accessing smoke test page: ${smokeUrl}`);
337
- await page.goto(smokeUrl, { waitUntil: "networkidle0" });
320
+ console.log(`🔍 Accessing smoke test page: ${url}`);
321
+ await page.goto(url, { waitUntil: "networkidle0" });
338
322
  log("Page loaded successfully");
339
323
  // Track failures to report at the end
340
324
  let hasFailures = false;
@@ -692,9 +676,9 @@ export async function checkClientSmoke(page, phase = "", environment = "Developm
692
676
  // Check if we're on a smoke test page - in which case missing the refresh button is a failure
693
677
  const currentUrl = page.url();
694
678
  log("Current URL: %s", currentUrl);
695
- if (currentUrl.includes("__smoke_test")) {
696
- log("ERROR: Smoke test page is missing the refresh-health button");
697
- throw new Error("Smoke test page is missing the refresh-health button - this is a test failure");
679
+ if (!currentUrl.includes("/__smoke_test")) {
680
+ log("ERROR: Smoke test page is not the current URL");
681
+ throw new Error("Smoke test page is not the current URL - this is a test failure");
698
682
  }
699
683
  console.log("ℹ️ Basic page structure verified, continuing without client-side smoke test");
700
684
  return null;
@@ -6,4 +6,4 @@ export declare function createSmokeTestStylesheets(targetDir: string): Promise<v
6
6
  /**
7
7
  * Modifies the worker.tsx and wrangler.jsonc files to add realtime support
8
8
  */
9
- export declare function modifyAppForRealtime(targetDir: string): Promise<void>;
9
+ export declare function modifyAppForRealtime(targetDir: string, skipClient?: boolean): Promise<void>;
@@ -46,7 +46,7 @@ export async function createSmokeTestComponents(targetDir, skipClient = false) {
46
46
  log("Skipping client-side smoke test component creation");
47
47
  }
48
48
  // Modify worker.tsx and wrangler.jsonc for realtime support
49
- await modifyAppForRealtime(targetDir);
49
+ await modifyAppForRealtime(targetDir, skipClient);
50
50
  log("Smoke test components created successfully");
51
51
  console.log("Created smoke test components:");
52
52
  console.log(`- ${smokeTestFunctionsPath}`);
@@ -104,7 +104,7 @@ export async function createSmokeTestStylesheets(targetDir) {
104
104
  /**
105
105
  * Modifies the worker.tsx and wrangler.jsonc files to add realtime support
106
106
  */
107
- export async function modifyAppForRealtime(targetDir) {
107
+ export async function modifyAppForRealtime(targetDir, skipClient = false) {
108
108
  log("Modifying worker.tsx and wrangler.jsonc for realtime support");
109
109
  // Modify worker.tsx
110
110
  const workerPath = join(targetDir, "src", "worker.tsx");
@@ -118,8 +118,14 @@ export async function modifyAppForRealtime(targetDir) {
118
118
  const hasRealtimeExport = workerContent.includes('export { RealtimeDurableObject } from "rwsdk/realtime/durableObject"');
119
119
  const hasRealtimeRoute = workerContent.includes("realtimeRoute(");
120
120
  const hasEnvImport = workerContent.includes('import { env } from "cloudflare:workers"');
121
- if (!hasRealtimeExport || !hasRealtimeRoute || !hasEnvImport) {
122
- log("Need to modify worker.tsx for realtime support");
121
+ const hasSmokeTestImport = workerContent.includes('import { SmokeTest } from "@/app/components/__SmokeTest.tsx"');
122
+ const hasSmokeTestRoute = workerContent.includes('route("/__smoke_test", SmokeTest)');
123
+ if (!hasRealtimeExport ||
124
+ !hasRealtimeRoute ||
125
+ !hasEnvImport ||
126
+ !hasSmokeTestImport ||
127
+ !hasSmokeTestRoute) {
128
+ log("Need to modify worker.tsx for realtime and smoke test support");
123
129
  const s = new MagicString(workerContent);
124
130
  // Add the export line if it doesn't exist
125
131
  if (!hasRealtimeExport) {
@@ -145,6 +151,26 @@ export async function modifyAppForRealtime(targetDir) {
145
151
  log("Added env import from cloudflare:workers");
146
152
  }
147
153
  }
154
+ // Add SmokeTest import if it doesn't exist
155
+ if (!hasSmokeTestImport) {
156
+ const importRegex = /import.*?from.*?;\n/g;
157
+ let lastImportMatch;
158
+ let lastImportPosition = 0;
159
+ // Find the position after the last import statement
160
+ while ((lastImportMatch = importRegex.exec(workerContent)) !== null) {
161
+ lastImportPosition =
162
+ lastImportMatch.index + lastImportMatch[0].length;
163
+ }
164
+ if (lastImportPosition > 0) {
165
+ s.appendRight(lastImportPosition, 'import { SmokeTest } from "@/app/components/__SmokeTest";\n');
166
+ log("Added SmokeTest import");
167
+ }
168
+ else {
169
+ // if no imports found, just prepend to the file
170
+ s.prepend('import { SmokeTest } from "@/app/components/__SmokeTest";\n');
171
+ log("Added SmokeTest import to the beginning of the file");
172
+ }
173
+ }
148
174
  // Add the realtimeRoute line if it doesn't exist
149
175
  if (!hasRealtimeRoute) {
150
176
  const defineAppMatch = workerContent.match(/export default defineApp\(\[/);
@@ -154,6 +180,16 @@ export async function modifyAppForRealtime(targetDir) {
154
180
  log("Added realtimeRoute to defineApp");
155
181
  }
156
182
  }
183
+ // Add the smoke test route if it doesn't exist
184
+ if (!hasSmokeTestRoute) {
185
+ const defineAppRegex = /(export default defineApp\(\[)([\s\S]*)(\]\);)/m;
186
+ const match = workerContent.match(defineAppRegex);
187
+ if (match) {
188
+ const insertionPoint = match.index + match[1].length + match[2].length;
189
+ s.appendLeft(insertionPoint, ' render(Document, [route("/__smoke_test", SmokeTest)]),\n');
190
+ log("Added smoke test route to defineApp");
191
+ }
192
+ }
157
193
  // Import realtimeRoute if it's not already imported
158
194
  if (!workerContent.includes("realtimeRoute")) {
159
195
  // First check if we already have the import from rwsdk/realtime/worker
@@ -196,7 +232,7 @@ export async function modifyAppForRealtime(targetDir) {
196
232
  log("Successfully modified worker.tsx");
197
233
  }
198
234
  else {
199
- log("worker.tsx already has realtime support, no changes needed");
235
+ log("worker.tsx already has realtime and smoke test support, no changes needed");
200
236
  }
201
237
  }
202
238
  else {
@@ -8,4 +8,4 @@ export declare function runDevServer(cwd?: string): Promise<{
8
8
  /**
9
9
  * Runs tests against the development server
10
10
  */
11
- export declare function runDevTest(url: string, artifactDir: string, customPath?: string, browserPath?: string, headless?: boolean, bail?: boolean, skipClient?: boolean, realtime?: boolean, skipHmr?: boolean, skipStyleTests?: boolean): Promise<void>;
11
+ export declare function runDevTest(url: string, artifactDir: string, browserPath?: string, headless?: boolean, bail?: boolean, skipClient?: boolean, realtime?: boolean, skipHmr?: boolean, skipStyleTests?: boolean): Promise<void>;
@@ -172,21 +172,15 @@ export async function runDevServer(cwd) {
172
172
  /**
173
173
  * Runs tests against the development server
174
174
  */
175
- export async function runDevTest(url, artifactDir, customPath = "/", browserPath, headless = true, bail = false, skipClient = false, realtime = false, skipHmr = false, skipStyleTests = false) {
176
- log("Starting dev server test with path: %s", customPath || "/");
175
+ export async function runDevTest(url, artifactDir, browserPath, headless = true, bail = false, skipClient = false, realtime = false, skipHmr = false, skipStyleTests = false) {
176
+ log("Starting dev server test");
177
177
  console.log("🚀 Testing local development server");
178
178
  const browser = await launchBrowser(browserPath, headless);
179
179
  const page = await browser.newPage();
180
180
  try {
181
+ const testUrl = new URL("/__smoke_test", url).toString();
181
182
  // DRY: check both root and custom path
182
- await checkServerUp(url, customPath, RETRIES, bail);
183
- // Now run the tests with the custom path
184
- const testUrl = url +
185
- (customPath === "/"
186
- ? ""
187
- : customPath.startsWith("/")
188
- ? customPath
189
- : "/" + customPath);
183
+ await checkServerUp(url, "/", RETRIES, bail);
190
184
  // Pass the target directory to checkUrl for HMR testing
191
185
  const targetDir = state.resources.targetDir;
192
186
  await page.goto(testUrl, { waitUntil: "networkidle0" });
@@ -40,7 +40,7 @@ export declare function runRelease(cwd: string, projectDir: string, resourceUniq
40
40
  /**
41
41
  * Runs tests against the production deployment
42
42
  */
43
- export declare function runReleaseTest(customPath: string | undefined, artifactDir: string, resources: TestResources, browserPath?: string, headless?: boolean, bail?: boolean, skipClient?: boolean, projectDir?: string, realtime?: boolean, skipHmr?: boolean, skipStyleTests?: boolean): Promise<void>;
43
+ export declare function runReleaseTest(artifactDir: string, resources: TestResources, browserPath?: string, headless?: boolean, bail?: boolean, skipClient?: boolean, projectDir?: string, realtime?: boolean, skipHmr?: boolean, skipStyleTests?: boolean): Promise<void>;
44
44
  /**
45
45
  * Check if a resource name includes a specific resource unique key
46
46
  * This is used to identify resources created during our tests
@@ -379,8 +379,8 @@ export async function runRelease(cwd, projectDir, resourceUniqueKey) {
379
379
  /**
380
380
  * Runs tests against the production deployment
381
381
  */
382
- export async function runReleaseTest(customPath = "/", artifactDir, resources, browserPath, headless = true, bail = false, skipClient = false, projectDir, realtime = false, skipHmr = false, skipStyleTests = false) {
383
- log("Starting release test with path: %s", customPath || "/");
382
+ export async function runReleaseTest(artifactDir, resources, browserPath, headless = true, bail = false, skipClient = false, projectDir, realtime = false, skipHmr = false, skipStyleTests = false) {
383
+ log("Starting release test");
384
384
  console.log("\n🚀 Testing production deployment");
385
385
  try {
386
386
  log("Running release process");
@@ -389,14 +389,9 @@ export async function runReleaseTest(customPath = "/", artifactDir, resources, b
389
389
  log("Waiting 1s before checking server...");
390
390
  await setTimeout(1000);
391
391
  // DRY: check both root and custom path
392
- await checkServerUp(url, customPath);
392
+ await checkServerUp(url, "/");
393
393
  // Now run the tests with the custom path
394
- const testUrl = url +
395
- (customPath === "/"
396
- ? ""
397
- : customPath.startsWith("/")
398
- ? customPath
399
- : "/" + customPath);
394
+ const testUrl = new URL("/__smoke_test", url).toString();
400
395
  await checkUrl(testUrl, artifactDir, browserPath, headless, bail, skipClient, "Production", realtime, resources.targetDir, // Add target directory parameter
401
396
  true, // Always skip HMR in production
402
397
  skipStyleTests);
@@ -57,7 +57,7 @@ export async function runSmokeTests(options = {}) {
57
57
  resources.stopDev = stopDev;
58
58
  state.resources.stopDev = stopDev;
59
59
  log("Running development server tests");
60
- await runDevTest(url, options.artifactDir, options.customPath, browserPath, options.headless !== false, options.bail, options.skipClient, options.realtime, options.skipHmr, options.skipStyleTests);
60
+ await runDevTest(url, options.artifactDir, browserPath, options.headless !== false, options.bail, options.skipClient, options.realtime, options.skipHmr, options.skipStyleTests);
61
61
  // Mark that dev tests have run successfully
62
62
  state.devTestsRan = true;
63
63
  // Update the overall dev test status to PASSED
@@ -95,7 +95,7 @@ export async function runSmokeTests(options = {}) {
95
95
  // Update status when release command runs
96
96
  try {
97
97
  console.log("\n🚀 Running release command smoke test");
98
- await runReleaseTest(options.customPath, options.artifactDir, resources, browserPath, options.headless !== false, options.bail, options.skipClient, options.projectDir, options.realtime, options.skipHmr, options.skipStyleTests);
98
+ await runReleaseTest(options.artifactDir, resources, browserPath, options.headless !== false, options.bail, options.skipClient, options.projectDir, options.realtime, options.skipHmr, options.skipStyleTests);
99
99
  // Update release command status to PASSED
100
100
  updateTestStatus("production", "releaseCommand", "PASSED");
101
101
  // Mark that release tests have run successfully
@@ -5,7 +5,7 @@ import { RequestInfo } from "rwsdk/worker";
5
5
  ${skipClient ? "" : 'import { SmokeTestClient } from "./__SmokeTestClient";'}
6
6
  import { getSmokeTestTimestamp } from "./__smokeTestFunctions";
7
7
 
8
- export const SmokeTestInfo: React.FC = async () => {
8
+ export const SmokeTest = async () => {
9
9
  const currentTime = Date.now();
10
10
  let status = "error";
11
11
  let timestamp = 0;
@@ -77,5 +77,6 @@ export const SmokeTestInfo: React.FC = async () => {
77
77
  ${!skipClient ? "<SmokeTestClient/>" : ""}
78
78
  </div>
79
79
  );
80
- };`;
80
+ };
81
+ `;
81
82
  }
@@ -0,0 +1,2 @@
1
+ declare const stubEnvVars: () => void;
2
+ export default stubEnvVars;
@@ -0,0 +1,11 @@
1
+ import { beforeEach, afterEach } from "vitest";
2
+ const stubEnvVars = () => {
3
+ let originals = {};
4
+ beforeEach(() => {
5
+ originals = { ...process.env };
6
+ });
7
+ afterEach(() => {
8
+ process.env = { ...originals };
9
+ });
10
+ };
11
+ export default stubEnvVars;
@@ -0,0 +1,10 @@
1
+ import "./setWebpackRequire";
2
+ export { ClientOnly } from "./ClientOnly.js";
3
+ export { default as React } from "react";
4
+ import type { Transport, HydrationOptions } from "./types";
5
+ export declare const fetchTransport: Transport;
6
+ export declare const initClient: ({ transport, hydrateRootOptions, handleResponse, }?: {
7
+ transport?: Transport;
8
+ hydrateRootOptions?: HydrationOptions;
9
+ handleResponse?: (response: Response) => boolean;
10
+ }) => Promise<void>;
@@ -1,11 +1,18 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
- import { clientWebpackRequire } from "./imports/client";
3
- // NOTE: `react-server-dom-webpack` uses this global to load modules,
4
- // so we need to define it here before importing "react-server-dom-webpack."
5
- globalThis.__webpack_require__ = clientWebpackRequire;
2
+ // note(justinvdm, 14 Aug 2025): Rendering related imports and logic go here.
3
+ // See client.tsx for the actual client entrypoint.
4
+ // context(justinvdm, 14 Aug 2025): `react-server-dom-webpack` uses this global
5
+ // to load modules, so we need to define it here before importing
6
+ // "react-server-dom-webpack."
7
+ import "./setWebpackRequire";
8
+ import React from "react";
9
+ import { hydrateRoot } from "react-dom/client";
10
+ import { createFromReadableStream, createFromFetch, encodeReply, } from "react-server-dom-webpack/client.browser";
11
+ import { rscStream } from "rsc-html-stream/client";
12
+ export { ClientOnly } from "./ClientOnly.js";
13
+ export { default as React } from "react";
6
14
  export const fetchTransport = (transportContext) => {
7
15
  const fetchCallServer = async (id, args) => {
8
- const { createFromFetch, encodeReply } = await import("react-server-dom-webpack/client.browser");
9
16
  const url = new URL(window.location.href);
10
17
  url.searchParams.set("__rsc", "");
11
18
  if (id != null) {
@@ -41,8 +48,6 @@ export const fetchTransport = (transportContext) => {
41
48
  return fetchCallServer;
42
49
  };
43
50
  export const initClient = async ({ transport = fetchTransport, hydrateRootOptions, handleResponse, } = {}) => {
44
- const React = await import("react");
45
- const { hydrateRoot } = await import("react-dom/client");
46
51
  const transportContext = {
47
52
  setRscPayload: () => { },
48
53
  handleResponse,
@@ -50,7 +55,7 @@ export const initClient = async ({ transport = fetchTransport, hydrateRootOption
50
55
  let transportCallServer = transport(transportContext);
51
56
  const callServer = (id, args) => transportCallServer(id, args);
52
57
  const upgradeToRealtime = async ({ key } = {}) => {
53
- const { realtimeTransport } = await import("./lib/realtime/client");
58
+ const { realtimeTransport } = await import("../lib/realtime/client");
54
59
  const createRealtimeTransport = realtimeTransport({ key });
55
60
  transportCallServer = createRealtimeTransport(transportContext);
56
61
  };
@@ -67,8 +72,6 @@ export const initClient = async ({ transport = fetchTransport, hydrateRootOption
67
72
  // context(justinvdm, 18 Jun 2025): We inject the RSC payload
68
73
  // unless render(Document, [...], { rscPayload: false }) was used.
69
74
  if (globalThis.__FLIGHT_DATA) {
70
- const { createFromReadableStream } = await import("react-server-dom-webpack/client.browser");
71
- const { rscStream } = await import("rsc-html-stream/client");
72
75
  rscPayload = createFromReadableStream(rscStream, {
73
76
  callServer,
74
77
  });
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { validateClickEvent } from "./clientNavigation";
2
+ import { validateClickEvent } from "./navigation";
3
3
  describe("clientNavigation", () => {
4
4
  let mockEvent = {
5
5
  button: 0, // right click
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { clientWebpackRequire } from "../imports/client";
2
+ globalThis.__webpack_require__ = clientWebpackRequire;
@@ -1,19 +1,13 @@
1
- import { type CallServerCallback } from "react-server-dom-webpack/client.browser";
2
- import { type HydrationOptions } from "react-dom/client";
1
+ import type { CallServerCallback } from "react-server-dom-webpack/client.browser";
2
+ export type { CallServerCallback } from "react-server-dom-webpack/client.browser";
3
+ export type { HydrationOptions } from "react-dom/client";
3
4
  export type ActionResponse<Result> = {
4
5
  node: React.ReactNode;
5
6
  actionResult: Result;
6
7
  };
7
- type TransportContext = {
8
+ export type TransportContext = {
8
9
  setRscPayload: <Result>(v: Promise<ActionResponse<Result>>) => void;
9
10
  handleResponse?: (response: Response) => boolean;
10
11
  };
11
12
  export type Transport = (context: TransportContext) => CallServerCallback;
12
13
  export type CreateCallServer = (context: TransportContext) => <Result>(id: null | string, args: null | unknown[]) => Promise<Result>;
13
- export declare const fetchTransport: Transport;
14
- export declare const initClient: ({ transport, hydrateRootOptions, handleResponse, }?: {
15
- transport?: Transport;
16
- hydrateRootOptions?: HydrationOptions;
17
- handleResponse?: (response: Response) => boolean;
18
- }) => Promise<void>;
19
- export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,5 @@
1
1
  import "./types/client";
2
- export * from "../client";
2
+ export * from "../client/client";
3
3
  export * from "../register/client";
4
4
  export * from "../lib/streams/consumeEventStream";
5
- export * from "../clientNavigation";
5
+ export * from "../client/navigation";
@@ -1,5 +1,5 @@
1
1
  import "./types/client";
2
- export * from "../client";
2
+ export * from "../client/client";
3
3
  export * from "../register/client";
4
4
  export * from "../lib/streams/consumeEventStream";
5
- export * from "../clientNavigation";
5
+ export * from "../client/navigation";
@@ -1,4 +1,4 @@
1
- export declare const loadModule: ((id: string) => Promise<any>) & import("lodash").MemoizedFunction;
2
- export declare const clientWebpackRequire: ((id: string) => Promise<{
1
+ export declare const loadModule: (id: string) => Promise<any>;
2
+ export declare const clientWebpackRequire: (id: string) => Promise<{
3
3
  [x: string]: any;
4
- }>) & import("lodash").MemoizedFunction;
4
+ }>;
@@ -1,20 +1,17 @@
1
1
  import React from "react";
2
- import memoize from "lodash/memoize";
3
- export const loadModule = memoize(async (id) => {
4
- if (import.meta.env.VITE_IS_DEV_SERVER) {
5
- return await import(/* @vite-ignore */ id);
6
- }
7
- else {
8
- const { useClientLookup } = await import("virtual:use-client-lookup.js");
9
- const moduleFn = useClientLookup[id];
10
- if (!moduleFn) {
11
- throw new Error(`(client) No module found for '${id}' in module lookup for "use client" directive`);
12
- }
13
- return await moduleFn();
2
+ import { ClientOnly } from "../client/client";
3
+ import { memoizeOnId } from "../lib/memoizeOnId";
4
+ // @ts-ignore
5
+ import { useClientLookup } from "virtual:use-client-lookup.js";
6
+ export const loadModule = memoizeOnId(async (id) => {
7
+ const moduleFn = useClientLookup[id];
8
+ if (!moduleFn) {
9
+ throw new Error(`(client) No module found for '${id}' in module lookup for "use client" directive`);
14
10
  }
11
+ return await moduleFn();
15
12
  });
16
13
  // context(justinvdm, 2 Dec 2024): re memoize(): React relies on the same promise instance being returned for the same id
17
- export const clientWebpackRequire = memoize(async (id) => {
14
+ export const clientWebpackRequire = memoizeOnId(async (id) => {
18
15
  const [file, name] = id.split("#");
19
16
  const promisedModule = loadModule(file);
20
17
  const promisedComponent = promisedModule.then((module) => module[name]);
@@ -23,11 +20,10 @@ export const clientWebpackRequire = memoize(async (id) => {
23
20
  const awaitedComponent = await promisedComponent;
24
21
  return { [id]: awaitedComponent };
25
22
  }
26
- const { ClientOnly } = await import("./ClientOnly");
27
23
  const promisedDefault = promisedComponent.then((Component) => ({
28
24
  default: Component,
29
25
  }));
30
26
  const Lazy = React.lazy(() => promisedDefault);
31
- const Wrapped = () => React.createElement(ClientOnly, null, React.createElement(Lazy));
27
+ const Wrapped = (props) => React.createElement(ClientOnly, null, React.createElement(Lazy, props));
32
28
  return { [id]: Wrapped };
33
29
  });
@@ -1,5 +1,5 @@
1
- export declare const ssrLoadModule: ((id: string) => Promise<any>) & import("lodash").MemoizedFunction;
1
+ export declare const ssrLoadModule: (id: string) => Promise<any>;
2
2
  export declare const ssrGetModuleExport: (id: string) => Promise<any>;
3
- export declare const ssrWebpackRequire: ((id: string) => Promise<{
3
+ export declare const ssrWebpackRequire: (id: string) => Promise<{
4
4
  [x: string]: any;
5
- }>) & import("lodash").MemoizedFunction;
5
+ }>;
@@ -1,5 +1,5 @@
1
- import memoize from "lodash/memoize";
2
- export const ssrLoadModule = memoize(async (id) => {
1
+ import { memoizeOnId } from "../lib/memoizeOnId";
2
+ export const ssrLoadModule = memoizeOnId(async (id) => {
3
3
  const { useClientLookup } = await import("virtual:use-client-lookup.js");
4
4
  const moduleFn = useClientLookup[id];
5
5
  if (!moduleFn) {
@@ -13,7 +13,7 @@ export const ssrGetModuleExport = async (id) => {
13
13
  return module[name];
14
14
  };
15
15
  // context(justinvdm, 2 Dec 2024): re memoize(): React relies on the same promise instance being returned for the same id
16
- export const ssrWebpackRequire = memoize(async (id) => {
16
+ export const ssrWebpackRequire = memoizeOnId(async (id) => {
17
17
  const [file, name] = id.split("#");
18
18
  const module = await ssrLoadModule(file);
19
19
  return { [id]: module[name] };
@@ -1,5 +1,5 @@
1
- export declare const loadServerModule: ((id: string) => Promise<any>) & import("lodash").MemoizedFunction;
1
+ export declare const loadServerModule: (id: string) => Promise<any>;
2
2
  export declare const getServerModuleExport: (id: string) => Promise<any>;
3
- export declare const ssrWebpackRequire: ((id: string) => Promise<{
3
+ export declare const ssrWebpackRequire: (id: string) => Promise<{
4
4
  [x: string]: any;
5
- }>) & import("lodash").MemoizedFunction;
5
+ }>;
@@ -1,8 +1,9 @@
1
- import memoize from "lodash/memoize";
2
1
  import { requestInfo } from "../requestInfo/worker";
3
2
  import { ssrWebpackRequire as baseSsrWebpackRequire } from "rwsdk/__ssr_bridge";
4
- export const loadServerModule = memoize(async (id) => {
5
- const { useServerLookup } = await import("virtual:use-server-lookup.js");
3
+ import { memoizeOnId } from "../lib/memoizeOnId";
4
+ // @ts-ignore
5
+ import { useServerLookup } from "virtual:use-server-lookup.js";
6
+ export const loadServerModule = memoizeOnId(async (id) => {
6
7
  const moduleFn = useServerLookup[id];
7
8
  if (!moduleFn) {
8
9
  throw new Error(`(worker) No module found for '${id}' in module lookup for "use server" directive`);
@@ -14,7 +15,7 @@ export const getServerModuleExport = async (id) => {
14
15
  const module = await loadServerModule(file);
15
16
  return module[name];
16
17
  };
17
- export const ssrWebpackRequire = memoize(async (id) => {
18
+ export const ssrWebpackRequire = memoizeOnId(async (id) => {
18
19
  if (!requestInfo.rw.ssr) {
19
20
  return { [id]: () => null };
20
21
  }
@@ -1,2 +1,11 @@
1
- import { type RequestInfo } from "../requestInfo/types";
2
- export declare const getManifest: (requestInfo: RequestInfo) => Promise<Record<string, any>>;
1
+ export type Manifest = Record<string, ManifestChunk>;
2
+ export interface ManifestChunk {
3
+ file: string;
4
+ src?: string;
5
+ isEntry?: boolean;
6
+ isDynamicEntry?: boolean;
7
+ imports?: string[];
8
+ css?: string[];
9
+ assets?: string[];
10
+ }
11
+ export declare const getManifest: () => Promise<Manifest>;
@@ -1,5 +1,5 @@
1
1
  let manifest;
2
- export const getManifest = async (requestInfo) => {
2
+ export const getManifest = async () => {
3
3
  if (manifest) {
4
4
  return manifest;
5
5
  }
@@ -0,0 +1 @@
1
+ export declare const memoizeOnId: <Result>(fn: (id: string) => Result) => (id: string) => Result;
@@ -0,0 +1,11 @@
1
+ export const memoizeOnId = (fn) => {
2
+ const hasOwnProperty = Object.prototype.hasOwnProperty;
3
+ const results = {};
4
+ const memoizedFn = (id) => {
5
+ if (hasOwnProperty.call(results, id)) {
6
+ return results[id];
7
+ }
8
+ return (results[id] = fn(id));
9
+ };
10
+ return memoizedFn;
11
+ };
@@ -1,4 +1,4 @@
1
- import { type Transport } from "../../client";
1
+ import { type Transport } from "../../client/types";
2
2
  export declare const initRealtimeClient: ({ key, handleResponse, }?: {
3
3
  key?: string;
4
4
  handleResponse?: (response: Response) => boolean;
@@ -1,4 +1,4 @@
1
- import { initClient } from "../../client";
1
+ import { initClient } from "../../client/client";
2
2
  import { createFromReadableStream } from "react-server-dom-webpack/client.browser";
3
3
  import { MESSAGE_TYPE } from "./shared";
4
4
  import { packMessage, unpackMessage, } from "./protocol";
@@ -18,10 +18,10 @@ export type RwContext = {
18
18
  scriptsToBeLoaded: Set<string>;
19
19
  pageRouteResolved: PromiseWithResolvers<void> | undefined;
20
20
  };
21
- export type RouteMiddleware<T extends RequestInfo = RequestInfo> = (requestInfo: T) => Response | Promise<Response> | void | Promise<void> | Promise<Response | void>;
22
- type RouteFunction<T extends RequestInfo = RequestInfo> = (requestInfo: T) => Response | Promise<Response>;
21
+ export type RouteMiddleware<T extends RequestInfo = RequestInfo> = (requestInfo: T) => MaybePromise<React.JSX.Element | Response | void>;
22
+ type RouteFunction<T extends RequestInfo = RequestInfo> = (requestInfo: T) => MaybePromise<Response>;
23
23
  type MaybePromise<T> = T | Promise<T>;
24
- type RouteComponent<T extends RequestInfo = RequestInfo> = (requestInfo: T) => MaybePromise<React.JSX.Element | Response>;
24
+ type RouteComponent<T extends RequestInfo = RequestInfo> = (requestInfo: T) => MaybePromise<React.JSX.Element | Response | void>;
25
25
  type RouteHandler<T extends RequestInfo = RequestInfo> = RouteFunction<T> | RouteComponent<T> | [...RouteMiddleware<T>[], RouteFunction<T> | RouteComponent<T>];
26
26
  export type Route<T extends RequestInfo = RequestInfo> = RouteMiddleware<T> | RouteDefinition<T> | Array<Route<T>>;
27
27
  export type RouteDefinition<T extends RequestInfo = RequestInfo> = {