Skip to content

Commit 0b1486b

Browse files
committed
progress
1 parent 676ed65 commit 0b1486b

File tree

4 files changed

+92
-4
lines changed

4 files changed

+92
-4
lines changed

.worklogs/justin/2025-09-01-dev-server-dependency-optimization.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ To solve the scanning problem, we inject a custom `esbuild` plugin directly into
6565
To ensure the virtual barrel can be resolved by the browser during development, the plugin also configures Vite's dev server:
6666

6767
- A Vite-level **alias** is created, mapping the clean virtual ID to a null-byte prefixed ID (`\0virtual:...`). This signals to Vite that it's a virtual module to be handled by a plugin.
68-
- The plugin implements Vite's standard **`resolveId` and `load` hooks**. At runtime, when the browser requests the barrel (via the `createDirectiveLookupPlugin`), the alias triggers these hooks, which serve the barrel's content.
68+
- The plugin implements Vite's standard **`resolveId` and `load` hooks`. At runtime, when the browser requests the barrel (via the `createDirectiveLookupPlugin`), the alias triggers these hooks, which serve the barrel's content.
6969

7070
This dual-mechanism approach is the complete solution. It correctly feeds the barrel to the `esbuild`-based optimizer *before* the server starts, while also making the same barrel available to the Vite dev server *at runtime*, finally eliminating the dependency waterfall.
7171

@@ -194,3 +194,35 @@ The corrected flow inside our `configureServer` hook is:
194194

195195
This ensures that we register our barrels for re-optimization at the earliest possible moment, but only *after* the necessary file discovery is complete, finally resolving the race condition.
196196

197+
### 9.9. The Final Piece: Importing the Optimized Barrel
198+
199+
The last remaining issue was identified in the `createDirectiveLookupPlugin`. This plugin was correctly generating the `virtual:use-client-lookup` and `virtual:use-server-lookup` modules, but the dynamic `import()` statements inside them were pointing to the wrong place.
200+
201+
The generated code was importing the *source* dummy file (e.g., `/node_modules/.vite/rwsdk-client-barrel.js`). However, the browser needs to import the final, *processed* file that Vite's dependency optimizer generates in its cache directory (e.g., `/node_modules/.vite/deps/_Users_..._barrel.js`).
202+
203+
The definitive solution is to look up this final path at runtime. The implementation is as follows:
204+
205+
1. **Access the Dev Server:** The `createDirectiveLookupPlugin` will use the `configureServer` hook to get and store a reference to the `ViteDevServer` instance.
206+
2. **Runtime Path Lookup:** The plugin's `load` hook, which generates the virtual module's code, will now perform a lookup:
207+
a. It accesses the appropriate environment's dependency optimizer via the stored server instance (e.g., `server.environments.client.depsOptimizer`).
208+
b. It constructs the absolute path to the source dummy barrel file.
209+
c. It uses this absolute path as a key to look into the `depsOptimizer.metadata.optimized` record. This returns an `OptimizedDepInfo` object containing the final processed `file` name.
210+
d. It constructs the correct, final browser-loadable path using this filename (e.g., `/node_modules/.vite/deps/PROCESSED_FILENAME.js`).
211+
3. **Generate Correct Import:** This final, correct path is used in the generated `import()` statement.
212+
213+
This completes the entire feature, ensuring that from discovery to optimization to runtime, the correct modules are being generated and loaded.
214+
215+
### 9.10. Definitive Solution: Awaiting the Re-Optimization Promise
216+
217+
The `Vite Error, ... optimized info should be defined` error provided the final clue. It originates from Vite's core `importAnalysis` plugin, and it confirms that our virtual module's `load` hook is executing *after* the re-optimization has been triggered but *before* the results of that re-optimization have been committed to the optimizer's metadata.
218+
219+
This is a classic race condition. The `registerMissingImport` API is intentionally debounced and asynchronous. We need to wait for its work to be fully completed before we can safely access the results.
220+
221+
The definitive solution is to hook into the optimizer's internal processing promise.
222+
223+
1. **Expose Processing Promises:** In the `directiveModulesDevPlugin`'s `configureServer` hook, after wrapping `server.listen` and awaiting the initial scans, we will call `registerMissingImport`. We will then immediately grab the *new* `processing` promise from the `depsOptimizer.metadata.discovered` record for our barrel files. These promises are the key; they will only resolve when the re-optimization for that specific dependency is complete. We will store these promises in a shared object.
224+
2. **Await the Correct Promise in `load`:** The `createDirectiveLookupPlugin`'s `load` hook will be modified. When it needs to look up an optimized barrel path, it will first retrieve the corresponding promise from the shared object and `await` it. This will pause the `load` hook until the re-optimization is finished and the metadata is guaranteed to be available.
225+
3. **Perform the Metadata Lookup:** Once the promise resolves, the `load` hook can safely access `depsOptimizer.metadata.optimized` to get the correct, final path to the processed barrel file.
226+
227+
This creates the correct, final synchronization chain, resolving the race condition and completing the feature.
228+

sdk/src/vite/createDirectiveLookupPlugin.mts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,14 @@ export const createDirectiveLookupPlugin = async ({
206206
const environment = this.environment?.name || "client";
207207
log("Current environment: %s, isDev: %s", environment, isDev);
208208

209+
if (isDev) {
210+
if (config.kind === "client") {
211+
await devServer.rwsdk?.barrelProcessingPromises?.client;
212+
} else {
213+
await devServer.rwsdk?.barrelProcessingPromises?.ssr;
214+
}
215+
}
216+
209217
const s = new MagicString(`
210218
export const ${config.exportName} = {
211219
${Array.from(files)
@@ -215,7 +223,28 @@ export const ${config.exportName} = {
215223
config.kind === "client"
216224
? "rwsdk-client-barrel.js"
217225
: "rwsdk-server-barrel.js";
218-
const barrelPath = path.join("/node_modules", ".vite", barrelFileName);
226+
227+
const dummyPath = path.join(
228+
projectRootDir,
229+
"node_modules",
230+
".vite",
231+
barrelFileName,
232+
);
233+
234+
const optimizer =
235+
config.kind === "client"
236+
? devServer.environments.client.depsOptimizer
237+
: devServer.environments.ssr.depsOptimizer;
238+
239+
const barrelPath = optimizer?.metadata.optimized[dummyPath]?.file;
240+
if (!barrelPath) {
241+
// This can happen if the barrel is empty (no client/server deps).
242+
// Return an empty object to avoid breaking the import.
243+
return `
244+
"${file}": () => Promise.resolve({ default: {} }),
245+
`;
246+
}
247+
219248
return `
220249
"${file}": () => import("${barrelPath}").then(m => m.default["${file}"]),
221250
`;

sdk/src/vite/directiveModulesDevPlugin.mts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
1-
import { Plugin, ResolvedConfig } from "vite";
1+
import { Plugin, ResolvedConfig, ViteDevServer } from "vite";
22
import debug from "debug";
33
import path from "node:path";
44
import { ensureFileSync } from "fs-extra";
55
import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
66

77
const log = debug("rwsdk:vite:directive-modules-dev");
88

9+
// Augment the ViteDevServer type to include our custom property
10+
declare module "vite" {
11+
interface ViteDevServer {
12+
rwsdk?: {
13+
barrelProcessingPromises?: {
14+
client?: Promise<void>;
15+
ssr?: Promise<void>;
16+
};
17+
};
18+
}
19+
}
20+
921
export const VIRTUAL_CLIENT_BARREL_ID = "virtual:rwsdk:client-module-barrel";
1022
export const VIRTUAL_SERVER_BARREL_ID = "virtual:rwsdk:server-module-barrel";
1123

@@ -49,12 +61,25 @@ export const directiveModulesDevPlugin = ({
4961
name: "rwsdk:directive-modules-dev",
5062
enforce: "pre",
5163
configureServer(server) {
64+
const barrelProcessingPromises = {
65+
client: Promise.withResolvers<void>(),
66+
ssr: Promise.withResolvers<void>(),
67+
};
68+
69+
server.rwsdk = {
70+
barrelProcessingPromises: {
71+
client: barrelProcessingPromises.client.promise,
72+
ssr: barrelProcessingPromises.ssr.promise,
73+
},
74+
};
75+
5276
const originalListen = server.listen;
5377
server.listen = async function (...args) {
5478
const result = await originalListen.apply(this, args);
5579

5680
// Wait for the worker's dependency scan to complete before proceeding.
5781
await server.environments.worker.depsOptimizer?.scanProcessing;
82+
server.environments.worker.depsOptimizer?.getOptimizedDepId;
5883

5984
const dummyFilePaths = {
6085
client: path.join(
@@ -76,13 +101,15 @@ export const directiveModulesDevPlugin = ({
76101
dummyFilePaths.client,
77102
dummyFilePaths.client,
78103
);
104+
barrelProcessingPromises.client.resolve();
79105
}
80106

81107
if (serverFiles.size > 0) {
82108
server.environments.ssr.depsOptimizer?.registerMissingImport(
83109
dummyFilePaths.server,
84110
dummyFilePaths.server,
85111
);
112+
barrelProcessingPromises.ssr.resolve();
86113
}
87114

88115
return result;

sdk/src/vite/redwoodPlugin.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { resolve } from "node:path";
2-
import { InlineConfig, Plugin } from "vite";
2+
import { InlineConfig } from "vite";
33
import { unstable_readConfig } from "wrangler";
44
import { cloudflare } from "@cloudflare/vite-plugin";
55

0 commit comments

Comments
 (0)