rwsdk 0.2.0-alpha.8 → 0.2.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 (48) hide show
  1. package/dist/runtime/client/client.d.ts +10 -0
  2. package/dist/runtime/{client.js → client/client.js} +13 -10
  3. package/dist/runtime/client/navigation.d.ts +9 -0
  4. package/dist/runtime/client/navigation.js +88 -0
  5. package/dist/runtime/{clientNavigation.test.js → client/navigation.test.js} +1 -1
  6. package/dist/runtime/client/setWebpackRequire.d.ts +1 -0
  7. package/dist/runtime/client/setWebpackRequire.js +2 -0
  8. package/dist/runtime/{client.d.ts → client/types.d.ts} +4 -10
  9. package/dist/runtime/client/types.js +1 -0
  10. package/dist/runtime/entries/client.d.ts +2 -2
  11. package/dist/runtime/entries/client.js +2 -2
  12. package/dist/runtime/imports/client.d.ts +3 -3
  13. package/dist/runtime/imports/client.js +7 -6
  14. package/dist/runtime/imports/ssr.d.ts +3 -3
  15. package/dist/runtime/imports/ssr.js +3 -3
  16. package/dist/runtime/imports/worker.d.ts +3 -3
  17. package/dist/runtime/imports/worker.js +3 -3
  18. package/dist/runtime/lib/manifest.d.ts +11 -2
  19. package/dist/runtime/lib/manifest.js +1 -1
  20. package/dist/runtime/lib/memoizeOnId.d.ts +1 -0
  21. package/dist/runtime/lib/memoizeOnId.js +11 -0
  22. package/dist/runtime/lib/realtime/client.d.ts +1 -1
  23. package/dist/runtime/lib/realtime/client.js +1 -1
  24. package/dist/runtime/lib/router.d.ts +3 -3
  25. package/dist/runtime/lib/router.js +77 -33
  26. package/dist/runtime/register/ssr.d.ts +1 -1
  27. package/dist/runtime/register/ssr.js +2 -2
  28. package/dist/runtime/render/preloads.d.ts +6 -0
  29. package/dist/runtime/render/preloads.js +40 -0
  30. package/dist/runtime/render/renderRscThenableToHtmlStream.js +2 -1
  31. package/dist/runtime/render/stylesheets.js +1 -1
  32. package/dist/runtime/requestInfo/types.d.ts +3 -1
  33. package/dist/runtime/worker.js +1 -1
  34. package/dist/scripts/debug-sync.mjs +159 -33
  35. package/dist/scripts/worker-run.mjs +8 -3
  36. package/dist/vite/hasOwnReactVitePlugin.d.mts +3 -0
  37. package/dist/vite/hasOwnReactVitePlugin.mjs +14 -0
  38. package/dist/vite/miniflareHMRPlugin.mjs +17 -2
  39. package/dist/vite/reactConditionsResolverPlugin.d.mts +3 -4
  40. package/dist/vite/reactConditionsResolverPlugin.mjs +71 -48
  41. package/dist/vite/redwoodPlugin.d.mts +1 -0
  42. package/dist/vite/redwoodPlugin.mjs +9 -3
  43. package/dist/vite/transformJsxScriptTagsPlugin.mjs +106 -91
  44. package/dist/vite/transformJsxScriptTagsPlugin.test.mjs +341 -110
  45. package/package.json +22 -4
  46. /package/dist/runtime/{imports → client}/ClientOnly.d.ts +0 -0
  47. /package/dist/runtime/{imports → client}/ClientOnly.js +0 -0
  48. /package/dist/runtime/{clientNavigation.test.d.ts → client/navigation.test.d.ts} +0 -0
@@ -169,7 +169,7 @@ export const defineApp = (routes) => {
169
169
  if (e instanceof Response) {
170
170
  return e;
171
171
  }
172
- console.error("Unhandled error", e);
172
+ console.error("rwsdk: Received an unhandled error:\n\n%s", e);
173
173
  throw e;
174
174
  }
175
175
  },
@@ -7,6 +7,9 @@ import chokidar from "chokidar";
7
7
  import { lock } from "proper-lockfile";
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
9
  const getPackageManagerInfo = (targetDir) => {
10
+ if (existsSync(path.join(targetDir, "bun.lock"))) {
11
+ return { name: "bun", lockFile: "bun.lock", command: "add" };
12
+ }
10
13
  const pnpmResult = {
11
14
  name: "pnpm",
12
15
  lockFile: "pnpm-lock.yaml",
@@ -24,15 +27,67 @@ const getPackageManagerInfo = (targetDir) => {
24
27
  }
25
28
  return pnpmResult;
26
29
  };
30
+ const cleanupViteEntries = async (targetDir) => {
31
+ const nodeModulesDir = path.join(targetDir, "node_modules");
32
+ if (!existsSync(nodeModulesDir)) {
33
+ return;
34
+ }
35
+ try {
36
+ const entries = await fs.readdir(nodeModulesDir);
37
+ const viteEntries = entries.filter((entry) => entry.startsWith(".vite"));
38
+ for (const entry of viteEntries) {
39
+ const entryPath = path.join(nodeModulesDir, entry);
40
+ try {
41
+ const stat = await fs.lstat(entryPath);
42
+ if (!stat.isSymbolicLink()) {
43
+ console.log(`Removing vite cache entry: ${entry}`);
44
+ await fs.rm(entryPath, { recursive: true, force: true });
45
+ }
46
+ else {
47
+ console.log(`Skipping symlinked vite cache entry: ${entry}`);
48
+ }
49
+ }
50
+ catch {
51
+ // If we can't stat it, try to remove it
52
+ console.log(`Removing vite cache entry: ${entry}`);
53
+ await fs.rm(entryPath, { recursive: true, force: true });
54
+ }
55
+ }
56
+ }
57
+ catch (error) {
58
+ console.log(`Failed to cleanup vite cache entries: ${error}`);
59
+ }
60
+ };
27
61
  const performFullSync = async (sdkDir, targetDir) => {
28
62
  const sdkPackageJsonPath = path.join(sdkDir, "package.json");
29
63
  let originalSdkPackageJson = null;
30
64
  let tarballPath = "";
31
65
  let tarballName = "";
66
+ // Clean up vite cache
67
+ await cleanupViteEntries(targetDir);
32
68
  try {
69
+ try {
70
+ originalSdkPackageJson = await fs.readFile(sdkPackageJsonPath, "utf-8");
71
+ const packageJson = JSON.parse(originalSdkPackageJson);
72
+ const originalVersion = packageJson.version;
73
+ const timestamp = new Date()
74
+ .toISOString()
75
+ .replace(/[-:T.]/g, "")
76
+ .slice(0, 14);
77
+ const newVersion = `${originalVersion}+build.${timestamp}`;
78
+ console.log(`Temporarily setting version to ${newVersion}`);
79
+ packageJson.version = newVersion;
80
+ await fs.writeFile(sdkPackageJsonPath, JSON.stringify(packageJson, null, 2));
81
+ }
82
+ catch (e) {
83
+ console.warn("Could not modify package.json version, proceeding without it.");
84
+ originalSdkPackageJson = null; // don't restore if we failed to modify
85
+ }
33
86
  console.log("📦 Packing SDK...");
34
- const packResult = await $({ cwd: sdkDir }) `npm pack`;
35
- tarballName = packResult.stdout?.trim() ?? "";
87
+ const packResult = await $({ cwd: sdkDir }) `npm pack --json`;
88
+ const json = JSON.parse(packResult.stdout || "[]");
89
+ const packInfo = Array.isArray(json) ? json[0] : undefined;
90
+ tarballName = (packInfo && (packInfo.filename || packInfo.name)) || "";
36
91
  if (!tarballName) {
37
92
  console.error("❌ Failed to get tarball name from npm pack.");
38
93
  return;
@@ -49,6 +104,29 @@ const performFullSync = async (sdkDir, targetDir) => {
49
104
  .readFile(lockfilePath, "utf-8")
50
105
  .catch(() => null);
51
106
  try {
107
+ // For bun, we need to remove the existing dependency from package.json
108
+ // before adding the tarball to avoid a dependency loop error.
109
+ if (pm.name === "bun" && originalPackageJson) {
110
+ try {
111
+ const targetPackageJson = JSON.parse(originalPackageJson);
112
+ let modified = false;
113
+ if (targetPackageJson.dependencies?.rwsdk) {
114
+ delete targetPackageJson.dependencies.rwsdk;
115
+ modified = true;
116
+ }
117
+ if (targetPackageJson.devDependencies?.rwsdk) {
118
+ delete targetPackageJson.devDependencies.rwsdk;
119
+ modified = true;
120
+ }
121
+ if (modified) {
122
+ console.log("Temporarily removing rwsdk from target package.json to prevent dependency loop with bun.");
123
+ await fs.writeFile(packageJsonPath, JSON.stringify(targetPackageJson, null, 2));
124
+ }
125
+ }
126
+ catch (e) {
127
+ console.warn("Could not modify target package.json, proceeding anyway.");
128
+ }
129
+ }
52
130
  const cmd = pm.name;
53
131
  const args = [pm.command];
54
132
  if (pm.name === "yarn") {
@@ -86,19 +164,63 @@ const performFullSync = async (sdkDir, targetDir) => {
86
164
  }
87
165
  }
88
166
  };
89
- const performFastSync = async (sdkDir, targetDir) => {
90
- console.log("⚡️ No dependency changes, performing fast sync...");
91
- const sdkPackageJson = JSON.parse(await fs.readFile(path.join(sdkDir, "package.json"), "utf-8"));
92
- const filesToSync = sdkPackageJson.files || [];
93
- for (const file of filesToSync) {
94
- const source = path.join(sdkDir, file);
95
- const destination = path.join(targetDir, "node_modules/rwsdk", file);
96
- if (existsSync(source)) {
97
- await fs.cp(source, destination, { recursive: true, force: true });
167
+ const syncFilesWithRsyncOrFs = async (sdkDir, destDir, filesEntries) => {
168
+ const sources = filesEntries.map((p) => path.join(sdkDir, p));
169
+ // Always include package.json in sync
170
+ const pkgJsonPath = path.join(sdkDir, "package.json");
171
+ sources.push(pkgJsonPath);
172
+ await fs.mkdir(destDir, { recursive: true });
173
+ // Try rsync across all sources in one shot
174
+ try {
175
+ if (sources.length > 0) {
176
+ const rsyncArgs = [
177
+ "-a",
178
+ "--delete",
179
+ "--omit-dir-times",
180
+ "--no-perms",
181
+ "--no-owner",
182
+ "--no-group",
183
+ ...sources,
184
+ destDir + path.sep,
185
+ ];
186
+ await $({ stdio: "inherit" })("rsync", rsyncArgs);
187
+ return;
188
+ }
189
+ }
190
+ catch {
191
+ // fall through to fs fallback
192
+ }
193
+ console.log("Rsync failed, falling back to fs");
194
+ // Fallback: destructive copy using Node fs to mirror content
195
+ await fs.rm(destDir, { recursive: true, force: true });
196
+ await fs.mkdir(destDir, { recursive: true });
197
+ for (const src of sources) {
198
+ const rel = path.relative(sdkDir, src);
199
+ const dst = path.join(destDir, rel);
200
+ await fs.mkdir(path.dirname(dst), { recursive: true });
201
+ try {
202
+ const stat = await fs.lstat(src);
203
+ if (stat.isDirectory()) {
204
+ await fs.cp(src, dst, { recursive: true, force: true });
205
+ }
206
+ else {
207
+ await fs.copyFile(src, dst);
208
+ }
209
+ }
210
+ catch {
211
+ await fs.cp(src, dst, { recursive: true, force: true }).catch(() => { });
98
212
  }
99
213
  }
100
- // Always copy package.json
101
- await fs.copyFile(path.join(sdkDir, "package.json"), path.join(targetDir, "node_modules/rwsdk/package.json"));
214
+ };
215
+ const performFastSync = async (sdkDir, targetDir) => {
216
+ console.log("⚡️ No dependency changes, performing fast sync...");
217
+ // Clean up vite cache
218
+ await cleanupViteEntries(targetDir);
219
+ const nodeModulesPkgDir = path.join(targetDir, "node_modules", "rwsdk");
220
+ // Copy directories/files declared in package.json#files (plus package.json)
221
+ const filesToSync = JSON.parse(await fs.readFile(path.join(sdkDir, "package.json"), "utf-8"))
222
+ .files || [];
223
+ await syncFilesWithRsyncOrFs(sdkDir, nodeModulesPkgDir, filesToSync);
102
224
  };
103
225
  const areDependenciesEqual = (deps1, deps2) => {
104
226
  // Simple string comparison for this use case is sufficient
@@ -120,13 +242,8 @@ const performSync = async (sdkDir, targetDir) => {
120
242
  if (existsSync(installedSdkPackageJsonPath)) {
121
243
  const sdkPackageJsonContent = await fs.readFile(sdkPackageJsonPath, "utf-8");
122
244
  const installedSdkPackageJsonContent = await fs.readFile(installedSdkPackageJsonPath, "utf-8");
123
- const sdkPkg = JSON.parse(sdkPackageJsonContent);
124
- const installedPkg = JSON.parse(installedSdkPackageJsonContent);
125
- if (areDependenciesEqual(sdkPkg.dependencies, installedPkg.dependencies) &&
126
- areDependenciesEqual(sdkPkg.devDependencies, installedPkg.devDependencies) &&
127
- areDependenciesEqual(sdkPkg.peerDependencies, installedPkg.peerDependencies)) {
128
- packageJsonChanged = false;
129
- }
245
+ packageJsonChanged =
246
+ sdkPackageJsonContent !== installedSdkPackageJsonContent;
130
247
  }
131
248
  if (packageJsonChanged) {
132
249
  console.log("📦 package.json changed, performing full sync...");
@@ -198,20 +315,19 @@ export const debugSync = async (opts) => {
198
315
  cwd: sdkDir,
199
316
  });
200
317
  let syncing = false;
201
- // todo(justinvdm, 2025-07-22): Figure out wtf makes the full sync
202
- // cause chokidar to find out about package.json after sync has resolved
203
- let expectingFileChanges = Boolean(process.env.RWSDK_FORCE_FULL_SYNC);
204
- watcher.on("all", async (_event, filePath) => {
205
- if (syncing || filePath.endsWith(".tgz")) {
206
- return;
207
- }
208
- if (expectingFileChanges && process.env.RWSDK_FORCE_FULL_SYNC) {
209
- expectingFileChanges = false;
318
+ let pendingResync = false;
319
+ const triggerResync = async (reason) => {
320
+ if (syncing) {
321
+ pendingResync = true;
210
322
  return;
211
323
  }
212
324
  syncing = true;
213
- expectingFileChanges = true;
214
- console.log(`\nDetected change, re-syncing... (file: ${filePath})`);
325
+ if (reason) {
326
+ console.log(`\nDetected change, re-syncing... (file: ${reason})`);
327
+ }
328
+ else {
329
+ console.log(`\nDetected change, re-syncing...`);
330
+ }
215
331
  if (childProc && !childProc.killed) {
216
332
  console.log("Stopping running process...");
217
333
  childProc.kill();
@@ -220,7 +336,6 @@ export const debugSync = async (opts) => {
220
336
  });
221
337
  }
222
338
  try {
223
- watcher.unwatch(filesToWatch);
224
339
  await performSync(sdkDir, targetDir);
225
340
  runWatchedCommand();
226
341
  }
@@ -230,8 +345,19 @@ export const debugSync = async (opts) => {
230
345
  }
231
346
  finally {
232
347
  syncing = false;
233
- watcher.add(filesToWatch);
234
348
  }
349
+ if (pendingResync) {
350
+ pendingResync = false;
351
+ // Coalesce any rapid additional events into a single follow-up sync
352
+ await new Promise((r) => setTimeout(r, 50));
353
+ return triggerResync();
354
+ }
355
+ };
356
+ watcher.on("all", async (_event, filePath) => {
357
+ if (filePath.endsWith(".tgz")) {
358
+ return;
359
+ }
360
+ await triggerResync(filePath);
235
361
  });
236
362
  const cleanup = async () => {
237
363
  console.log("\nCleaning up...");
@@ -16,13 +16,18 @@ export const runWorkerScript = async (relativeScriptPath) => {
16
16
  console.error("Error: Script path is required");
17
17
  console.log("\nUsage:");
18
18
  console.log(" npm run worker:run <script-path>");
19
- console.log("\nExample:");
20
- console.log(" npm run worker:run src/scripts/seed.ts\n");
19
+ console.log("\nOptions:");
20
+ console.log(" RWSDK_WRANGLER_CONFIG Environment variable for config path");
21
+ console.log("\nExamples:");
22
+ console.log(" npm run worker:run src/scripts/seed.ts");
23
+ console.log(" RWSDK_WRANGLER_CONFIG=custom.toml npm run worker:run src/scripts/seed.ts\n");
21
24
  process.exit(1);
22
25
  }
23
26
  const scriptPath = resolve(process.cwd(), relativeScriptPath);
24
27
  debug("Running worker script: %s", scriptPath);
25
- const workerConfigPath = await findWranglerConfig(process.cwd());
28
+ const workerConfigPath = process.env.RWSDK_WRANGLER_CONFIG
29
+ ? resolve(process.cwd(), process.env.RWSDK_WRANGLER_CONFIG)
30
+ : await findWranglerConfig(process.cwd());
26
31
  debug("Using wrangler config: %s", workerConfigPath);
27
32
  const workerConfig = unstable_readConfig({
28
33
  config: workerConfigPath,
@@ -0,0 +1,3 @@
1
+ export declare function hasOwnReactVitePlugin({ rootProjectDir, }: {
2
+ rootProjectDir: string;
3
+ }): Promise<boolean>;
@@ -0,0 +1,14 @@
1
+ import { readFile } from "fs/promises";
2
+ import path from "path";
3
+ export async function hasOwnReactVitePlugin({ rootProjectDir, }) {
4
+ const packageJsonPath = path.join(rootProjectDir, "package.json");
5
+ try {
6
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
7
+ return !!(packageJson.dependencies?.["@vitejs/plugin-react"] ||
8
+ packageJson.devDependencies?.["@vitejs/plugin-react"]);
9
+ }
10
+ catch (error) {
11
+ console.error("Error reading package.json:", error);
12
+ return false;
13
+ }
14
+ }
@@ -66,6 +66,9 @@ export const miniflareHMRPlugin = (givenOptions) => [
66
66
  };
67
67
  },
68
68
  async hotUpdate(ctx) {
69
+ if (ctx.file.includes(".wrangler")) {
70
+ return;
71
+ }
69
72
  if (hasErrored) {
70
73
  const shortName = getShortName(ctx.file, ctx.server.config.root);
71
74
  this.environment.logger.info(`${colors.cyan(`attempting to recover from error`)}: update to ${colors.dim(shortName)}`, {
@@ -77,6 +80,7 @@ export const miniflareHMRPlugin = (givenOptions) => [
77
80
  type: "full-reload",
78
81
  path: "*",
79
82
  });
83
+ log("hmr: Full reload after error");
80
84
  return [];
81
85
  }
82
86
  const { clientFiles, serverFiles, viteEnvironment: { name: environment }, workerEntryPathname: entry, } = givenOptions;
@@ -84,6 +88,7 @@ export const miniflareHMRPlugin = (givenOptions) => [
84
88
  log(`Hot update: (env=${this.environment.name}) ${ctx.file}\nModule graph:\n\n${dumpFullModuleGraph(ctx.server, this.environment.name)}`);
85
89
  }
86
90
  if (!isJsFile(ctx.file) && !ctx.file.endsWith(".css")) {
91
+ log(`hmr: not a js file, skipping`);
87
92
  return;
88
93
  }
89
94
  if (this.environment.name === "ssr") {
@@ -94,14 +99,17 @@ export const miniflareHMRPlugin = (givenOptions) => [
94
99
  server: ctx.server,
95
100
  });
96
101
  if (!isUseClientUpdate) {
102
+ log("hmr: not a use client update, short circuiting");
97
103
  return [];
98
104
  }
99
105
  invalidateModule(ctx.server, "ssr", ctx.file);
100
106
  invalidateModule(ctx.server, environment, VIRTUAL_SSR_PREFIX +
101
107
  normalizeModulePath(ctx.file, givenOptions.rootDir));
108
+ log("hmr: invalidated ssr module");
102
109
  return [];
103
110
  }
104
111
  if (!["client", environment].includes(this.environment.name)) {
112
+ log(`hmr: incorrect env, skipping (env=${this.environment.name}, worker env=${environment})`);
105
113
  return [];
106
114
  }
107
115
  const hasClientDirective = await hasDirective(ctx.file, "use client");
@@ -165,8 +173,10 @@ export const miniflareHMRPlugin = (givenOptions) => [
165
173
  server: ctx.server,
166
174
  });
167
175
  if (!isUseClientUpdate && !ctx.file.endsWith(".css")) {
176
+ log("hmr: not a use client update and not css, short circuiting");
168
177
  return [];
169
178
  }
179
+ log("hmr: returning client modules for hmr");
170
180
  return ctx.modules;
171
181
  }
172
182
  // The worker needs an update, and the hot check is for the worker environment
@@ -184,8 +194,12 @@ export const miniflareHMRPlugin = (givenOptions) => [
184
194
  if (m) {
185
195
  invalidateModule(ctx.server, environment, m);
186
196
  }
187
- const virtualSSRModule = ctx.server.environments[environment].moduleGraph.idToModuleMap.get(VIRTUAL_SSR_PREFIX +
188
- normalizeModulePath(ctx.file, givenOptions.rootDir));
197
+ let virtualSSRModuleId = VIRTUAL_SSR_PREFIX +
198
+ normalizeModulePath(ctx.file, givenOptions.rootDir);
199
+ if (ctx.file.endsWith(".css")) {
200
+ virtualSSRModuleId += ".js";
201
+ }
202
+ const virtualSSRModule = ctx.server.environments[environment].moduleGraph.idToModuleMap.get(virtualSSRModuleId);
189
203
  if (virtualSSRModule) {
190
204
  invalidateModule(ctx.server, environment, virtualSSRModule);
191
205
  }
@@ -196,6 +210,7 @@ export const miniflareHMRPlugin = (givenOptions) => [
196
210
  file: ctx.file,
197
211
  },
198
212
  });
213
+ log("hmr: sent rsc update");
199
214
  return [];
200
215
  }
201
216
  },
@@ -10,7 +10,6 @@ export declare const ENV_RESOLVERS: {
10
10
  worker: enhancedResolve.ResolveFunction;
11
11
  client: enhancedResolve.ResolveFunction;
12
12
  };
13
- export declare const ENV_IMPORT_MAPPINGS: {
14
- [k: string]: Map<string, string>;
15
- };
16
- export declare const reactConditionsResolverPlugin: () => Plugin[];
13
+ export declare const reactConditionsResolverPlugin: ({ projectRootDir, }: {
14
+ projectRootDir: string;
15
+ }) => Plugin[];
@@ -3,6 +3,7 @@ import { ROOT_DIR } from "../lib/constants.mjs";
3
3
  import enhancedResolve from "enhanced-resolve";
4
4
  import { ensureAliasArray } from "./ensureAliasArray.mjs";
5
5
  const log = debug("rwsdk:vite:react-conditions-resolver-plugin");
6
+ const REACT_PREFIXES = ["react", "react-dom", "react-server-dom-webpack"];
6
7
  export const ENV_REACT_IMPORTS = {
7
8
  worker: [
8
9
  "react",
@@ -41,28 +42,41 @@ export const ENV_RESOLVERS = {
41
42
  conditionNames: ["browser", "default"],
42
43
  }),
43
44
  };
44
- export const ENV_IMPORT_MAPPINGS = Object.fromEntries(Object.keys(ENV_RESOLVERS).map((env) => [
45
- env,
46
- resolveEnvImportMappings(env),
47
- ]));
48
- function resolveEnvImportMappings(env) {
49
- process.env.VERBOSE &&
50
- log("Resolving environment import mappings for env=%s", env);
51
- const mappings = new Map();
52
- const reactImports = ENV_REACT_IMPORTS[env];
53
- for (const importRequest of reactImports) {
45
+ function resolveReactImport(id, envName, projectRootDir, isReactImportKnown = false) {
46
+ if (!isReactImportKnown) {
47
+ const isReactImport = REACT_PREFIXES.some((prefix) => id === prefix || id.startsWith(`${prefix}/`));
48
+ if (!isReactImport) {
49
+ return undefined;
50
+ }
51
+ }
52
+ let resolved;
53
+ try {
54
+ resolved = ENV_RESOLVERS[envName](projectRootDir, id) || undefined;
55
+ process.env.VERBOSE &&
56
+ log("Successfully resolved %s to %s for env=%s from project root", id, resolved, envName);
57
+ }
58
+ catch {
54
59
  process.env.VERBOSE &&
55
- log("Resolving import request=%s for env=%s", importRequest, env);
56
- let resolved = false;
60
+ log("Failed to resolve %s for env=%s from project root, trying ROOT_DIR", id, envName);
57
61
  try {
58
- resolved = ENV_RESOLVERS[env](ROOT_DIR, importRequest);
62
+ resolved = ENV_RESOLVERS[envName](ROOT_DIR, id) || undefined;
59
63
  process.env.VERBOSE &&
60
- log("Successfully resolved %s to %s for env=%s", importRequest, resolved, env);
64
+ log("Successfully resolved %s to %s for env=%s from rwsdk root", id, resolved, envName);
61
65
  }
62
66
  catch {
63
67
  process.env.VERBOSE &&
64
- log("Failed to resolve %s for env=%s", importRequest, env);
68
+ log("Failed to resolve %s for env=%s", id, envName);
65
69
  }
70
+ }
71
+ return resolved;
72
+ }
73
+ function resolveEnvImportMappings(env, projectRootDir) {
74
+ process.env.VERBOSE &&
75
+ log("Resolving environment import mappings for env=%s", env);
76
+ const mappings = new Map();
77
+ const reactImports = ENV_REACT_IMPORTS[env];
78
+ for (const importRequest of reactImports) {
79
+ const resolved = resolveReactImport(importRequest, env, projectRootDir, true);
66
80
  if (resolved) {
67
81
  mappings.set(importRequest, resolved);
68
82
  log("Added mapping for %s -> %s in env=%s", importRequest, resolved, env);
@@ -71,39 +85,45 @@ function resolveEnvImportMappings(env) {
71
85
  log("Environment import mappings complete for env=%s: %d mappings", env, mappings.size);
72
86
  return mappings;
73
87
  }
74
- function createEsbuildResolverPlugin(envName) {
75
- const mappings = ENV_IMPORT_MAPPINGS[envName];
76
- if (!mappings) {
77
- return null;
78
- }
79
- return {
80
- name: `rwsdk:react-conditions-resolver-esbuild-${envName}`,
81
- setup(build) {
82
- build.onResolve({ filter: /.*/ }, (args) => {
83
- process.env.VERBOSE &&
84
- log("ESBuild resolving %s for env=%s, args=%O", args.path, envName, args);
85
- const resolved = mappings.get(args.path);
86
- if (resolved && args.importer !== "") {
87
- process.env.VERBOSE &&
88
- log("ESBuild resolving %s -> %s for env=%s", args.path, resolved, envName);
89
- if (args.path === "react-server-dom-webpack/client.edge") {
90
- return;
91
- }
92
- return {
93
- path: resolved,
94
- };
95
- }
96
- else {
97
- process.env.VERBOSE &&
98
- log("ESBuild no resolution found for %s for env=%s", args.path, envName);
99
- }
100
- });
101
- },
102
- };
103
- }
104
- export const reactConditionsResolverPlugin = () => {
88
+ export const reactConditionsResolverPlugin = ({ projectRootDir, }) => {
105
89
  log("Initializing react conditions resolver plugin");
106
90
  let isBuild = false;
91
+ const ENV_IMPORT_MAPPINGS = Object.fromEntries(Object.keys(ENV_RESOLVERS).map((env) => [
92
+ env,
93
+ resolveEnvImportMappings(env, projectRootDir),
94
+ ]));
95
+ function createEsbuildResolverPlugin(envName, mappings) {
96
+ if (!mappings) {
97
+ return null;
98
+ }
99
+ return {
100
+ name: `rwsdk:react-conditions-resolver-esbuild-${envName}`,
101
+ setup(build) {
102
+ build.onResolve({ filter: /.*/ }, (args) => {
103
+ process.env.VERBOSE &&
104
+ log("ESBuild resolving %s for env=%s, args=%O", args.path, envName, args);
105
+ let resolved = mappings.get(args.path);
106
+ if (!resolved) {
107
+ resolved = resolveReactImport(args.path, envName, projectRootDir);
108
+ }
109
+ if (resolved && args.importer !== "") {
110
+ process.env.VERBOSE &&
111
+ log("ESBuild resolving %s -> %s for env=%s", args.path, resolved, envName);
112
+ if (args.path === "react-server-dom-webpack/client.edge") {
113
+ return;
114
+ }
115
+ return {
116
+ path: resolved,
117
+ };
118
+ }
119
+ else {
120
+ process.env.VERBOSE &&
121
+ log("ESBuild no resolution found for %s for env=%s", args.path, envName);
122
+ }
123
+ });
124
+ },
125
+ };
126
+ }
107
127
  return [
108
128
  {
109
129
  name: "rwsdk:react-conditions-resolver:config",
@@ -125,7 +145,7 @@ export const reactConditionsResolverPlugin = () => {
125
145
  config.environments[envName] = {};
126
146
  }
127
147
  const envConfig = config.environments[envName];
128
- const esbuildPlugin = createEsbuildResolverPlugin(envName);
148
+ const esbuildPlugin = createEsbuildResolverPlugin(envName, mappings);
129
149
  if (esbuildPlugin && mappings) {
130
150
  envConfig.optimizeDeps ??= {};
131
151
  envConfig.optimizeDeps.esbuildOptions ??= {};
@@ -166,7 +186,10 @@ export const reactConditionsResolverPlugin = () => {
166
186
  log("No mappings found for environment: %s", envName);
167
187
  return;
168
188
  }
169
- const resolved = mappings.get(id);
189
+ let resolved = mappings.get(id);
190
+ if (!resolved) {
191
+ resolved = resolveReactImport(id, envName, projectRootDir);
192
+ }
170
193
  if (resolved) {
171
194
  log("Resolved %s -> %s for env=%s", id, resolved, envName);
172
195
  return resolved;
@@ -3,6 +3,7 @@ export type RedwoodPluginOptions = {
3
3
  silent?: boolean;
4
4
  rootDir?: string;
5
5
  includeCloudflarePlugin?: boolean;
6
+ includeReactPlugin?: boolean;
6
7
  configPath?: string;
7
8
  entry?: {
8
9
  client?: string | string[];
@@ -3,6 +3,7 @@ import { unstable_readConfig } from "wrangler";
3
3
  import { cloudflare } from "@cloudflare/vite-plugin";
4
4
  import { devServerConstantPlugin } from "./devServerConstant.mjs";
5
5
  import { hasOwnCloudflareVitePlugin } from "./hasOwnCloudflareVitePlugin.mjs";
6
+ import { hasOwnReactVitePlugin } from "./hasOwnReactVitePlugin.mjs";
6
7
  import reactPlugin from "@vitejs/plugin-react";
7
8
  import tsconfigPaths from "vite-tsconfig-paths";
8
9
  import { transformJsxScriptTagsPlugin } from "./transformJsxScriptTagsPlugin.mjs";
@@ -32,7 +33,10 @@ const determineWorkerEntryPathname = async (projectRootDir, workerConfigPath, op
32
33
  };
33
34
  export const redwoodPlugin = async (options = {}) => {
34
35
  const projectRootDir = process.cwd();
35
- const workerConfigPath = options.configPath ?? (await findWranglerConfig(projectRootDir));
36
+ const workerConfigPath = options.configPath ??
37
+ (process.env.RWSDK_WRANGLER_CONFIG
38
+ ? resolve(projectRootDir, process.env.RWSDK_WRANGLER_CONFIG)
39
+ : await findWranglerConfig(projectRootDir));
36
40
  const workerEntryPathname = await determineWorkerEntryPathname(projectRootDir, workerConfigPath, options);
37
41
  const clientEntryPathnames = (Array.isArray(options.entry?.client)
38
42
  ? options.entry.client
@@ -41,6 +45,8 @@ export const redwoodPlugin = async (options = {}) => {
41
45
  const serverFiles = new Set();
42
46
  const shouldIncludeCloudflarePlugin = options.includeCloudflarePlugin ??
43
47
  !(await hasOwnCloudflareVitePlugin({ rootProjectDir: projectRootDir }));
48
+ const shouldIncludeReactPlugin = options.includeReactPlugin ??
49
+ !(await hasOwnReactVitePlugin({ rootProjectDir: projectRootDir }));
44
50
  // context(justinvdm, 31 Mar 2025): We assume that if there is no .wrangler directory,
45
51
  // then this is fresh install, and we run `npm run dev:init` here.
46
52
  if (process.env.RWSDK_WORKER_RUN !== "1" &&
@@ -68,7 +74,7 @@ export const redwoodPlugin = async (options = {}) => {
68
74
  serverFiles,
69
75
  projectRootDir,
70
76
  }),
71
- reactConditionsResolverPlugin(),
77
+ reactConditionsResolverPlugin({ projectRootDir }),
72
78
  tsconfigPaths({ root: projectRootDir }),
73
79
  shouldIncludeCloudflarePlugin
74
80
  ? cloudflare({
@@ -83,7 +89,7 @@ export const redwoodPlugin = async (options = {}) => {
83
89
  viteEnvironment: { name: "worker" },
84
90
  workerEntryPathname,
85
91
  }),
86
- reactPlugin(),
92
+ shouldIncludeReactPlugin ? reactPlugin() : [],
87
93
  directivesPlugin({
88
94
  projectRootDir,
89
95
  clientFiles,