Skip to content

Commit a92584c

Browse files
committed
progress
1 parent e81b22c commit a92584c

File tree

4 files changed

+94
-35
lines changed

4 files changed

+94
-35
lines changed

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

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -376,12 +376,32 @@ This is the definitive proof of the problem. Vite is processing the application
376376

377377
The root cause appears to be a failure in Vite's dependency scanner to detect the implicit `jsx-runtime` dependency for the modules imported within our barrel file.
378378

379-
### 9.17. The Real Root Cause: A Re-Optimization Trigger
379+
### 9.17. The Root Cause Identified: Importing the Wrong Barrel
380380

381-
Further investigation has revealed that the "duplicate dependency" issue is not a result of a dependency graph schism, but of a secondary re-optimization pass being triggered by Vite.
381+
We have confirmed that our standalone scanner and "Dummy File" + `onLoad` hook strategy are working correctly. Vite's optimizer is successfully processing our barrel file and generating a single, large, pre-bundled chunk in the `.vite/deps` directory.
382382

383-
After implementing the standalone `esbuild` scan based on the `worker` entry point, we observed the following behavior:
384-
1. The initial page load is successful.
385-
2. Shortly after, the browser re-fetches the pre-bundled dependency chunks, but with a new `?v=<hash>` query string.
383+
The request waterfall persists for a simple reason: our `createDirectiveLookupPlugin` is generating `import()` statements that point to the wrong file.
386384

387-
This confirms that Vite is performing a corrective re-optimization pass after the initial server startup has completed. The trigger for this re-optimization is the subject of our current investigation.
385+
- **Currently Importing:** The `load` hook is generating an `import()` that points to the *source* dummy barrel file (e.g., `/node_modules/.vite/rwsdk-client-barrel.js`).
386+
- **Correct Import Path:** It should be pointing to the final, *optimized* chunk that Vite generates (e.g., `/node_modules/.vite/deps/PROCESSED_FILENAME.js`).
387+
388+
The browser is therefore bypassing the pre-bundled chunk entirely, requesting the source file, and then triggering the waterfall as it resolves the imports inside.
389+
390+
The solution, as previously discovered, is to modify the `createDirectiveLookupPlugin`'s `load` hook to look up the correct, final file path from the `depsOptimizer.metadata` at runtime and use that path in the generated `import()` statement.
391+
392+
The trigger for this re-optimization is the subject of our current investigation.
393+
394+
### 9.18. The Final Solution: Bypassing the `.vite` Directory
395+
396+
The final piece of the puzzle was discovered by identifying a subtle but critical behavior in Vite's dev server. The root cause of the persistent waterfall was the location of our dummy barrel files.
397+
398+
**The Hypothesis:** We hypothesized that Vite's dev server applies special, undocumented resolution rules to any path inside the `node_modules/.vite` directory. Our `createDirectiveLookupPlugin` was generating an `import()` for the *source* barrel file (e.g., `.../.vite/rwsdk-client-barrel.js`), and because of this special handling, Vite was not resolving it to the final, *optimized* chunk path (e.g., `.../.vite/deps/PROCESSED_FILENAME.js`). This bypassed the optimizer's work and triggered the waterfall.
399+
400+
**The Solution:** The successful, definitive solution involved two key changes:
401+
402+
1. **Relocate the Barrel Files:** We moved the dummy barrel files from the problematic `node_modules/.vite` directory to a neutral location within our own package's distribution directory (`node_modules/rwsdk/dist/__intermediate_builds/`).
403+
2. **Resolve the Optimized Path:** With the barrel files now in a standard location, we updated the `createDirectiveLookupPlugin`'s `load` hook to perform a runtime lookup. It now uses the path of the new source barrel file as a key into the `depsOptimizer.metadata.optimized` object to find the correct, final path to the processed chunk that `esbuild` generated.
404+
405+
**The Result:** This combination works perfectly. The standalone `esbuild` scan discovers all client components, the barrel files are generated in a location that Vite does not treat specially, and the runtime `import()` statements correctly resolve to the single, large, pre-bundled chunk.
406+
407+
This finally solves the in-browser request waterfall, achieving the original goal of the optimization.

sdk/src/lib/constants.mts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@ export const INTERMEDIATES_OUTPUT_DIR = resolve(
1212
DIST_DIR,
1313
"__intermediate_builds",
1414
);
15+
16+
export const CLIENT_BARREL_PATH = resolve(
17+
INTERMEDIATES_OUTPUT_DIR,
18+
"client",
19+
"rwsdk-client-barrel.js",
20+
);
21+
22+
export const SERVER_BARREL_PATH = resolve(
23+
INTERMEDIATES_OUTPUT_DIR,
24+
"ssr",
25+
"rwsdk-server-barrel.js",
26+
);
27+
1528
export const INTERMEDIATE_SSR_BRIDGE_PATH = resolve(
1629
INTERMEDIATES_OUTPUT_DIR,
1730
"ssr",

sdk/src/vite/createDirectiveLookupPlugin.mts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
77
import { stat } from "fs/promises";
88
import { getSrcPaths } from "../lib/getSrcPaths.js";
99
import { hasDirective } from "./hasDirective.mjs";
10+
import { CLIENT_BARREL_PATH, SERVER_BARREL_PATH } from "../lib/constants.mjs";
1011

1112
interface DirectiveLookupConfig {
1213
kind: "client" | "server";
@@ -208,20 +209,15 @@ export const ${config.exportName} = {
208209
${Array.from(files)
209210
.map((file: string) => {
210211
if (file.includes("node_modules") && isDev) {
211-
const barrelFileName =
212-
config.kind === "client"
213-
? "rwsdk-client-barrel.js"
214-
: "rwsdk-server-barrel.js";
215-
216-
const dummyPath = path.join(
217-
projectRootDir,
218-
"node_modules",
219-
".vite",
220-
barrelFileName,
221-
);
212+
const barrelPath =
213+
config.kind === "client" ? CLIENT_BARREL_PATH : SERVER_BARREL_PATH;
214+
215+
const outputBarrelPath =
216+
devServer.environments[environment]?.depsOptimizer?.metadata
217+
?.optimized[barrelPath].file;
222218
223219
return `
224-
"${file}": () => import("${dummyPath}").then(m => m.default["${file}"]),
220+
"${file}": () => import("${outputBarrelPath}").then(m => m.default["${file}"]),
225221
`;
226222
} else {
227223
return `

sdk/src/vite/directiveModulesDevPlugin.mts

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { Plugin } from "vite";
21
import path from "node:path";
2+
import { Plugin } from "vite";
33
import { writeFileSync, mkdirSync } from "node:fs";
44
import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
55
import { runEsbuildScan } from "./runEsbuildScan.mjs";
6+
import { CLIENT_BARREL_PATH } from "../lib/constants.mjs";
7+
import { SERVER_BARREL_PATH } from "../lib/constants.mjs";
68

79
export const VIRTUAL_CLIENT_BARREL_ID = "virtual:rwsdk:client-module-barrel";
810
export const VIRTUAL_SERVER_BARREL_ID = "virtual:rwsdk:server-module-barrel";
@@ -61,18 +63,8 @@ export const directiveModulesDevPlugin = ({
6163

6264
// Phase 2: Barrel Generation for Vite's optimizer
6365
const dummyFilePaths = {
64-
client: path.join(
65-
projectRootDir,
66-
"node_modules",
67-
".vite",
68-
"rwsdk-client-barrel.js",
69-
),
70-
server: path.join(
71-
projectRootDir,
72-
"node_modules",
73-
".vite",
74-
"rwsdk-server-barrel.js",
75-
),
66+
client: CLIENT_BARREL_PATH,
67+
server: SERVER_BARREL_PATH,
7668
};
7769

7870
// Generate the barrel content and write it to the dummy files.
@@ -86,11 +78,42 @@ export const directiveModulesDevPlugin = ({
8678
projectRootDir,
8779
);
8880

89-
mkdirSync(path.dirname(dummyFilePaths.client), { recursive: true });
90-
writeFileSync(dummyFilePaths.client, clientBarrelContent);
81+
mkdirSync(path.dirname(CLIENT_BARREL_PATH), { recursive: true });
82+
writeFileSync(CLIENT_BARREL_PATH, "");
83+
84+
mkdirSync(path.dirname(SERVER_BARREL_PATH), { recursive: true });
85+
writeFileSync(SERVER_BARREL_PATH, "");
9186

92-
mkdirSync(path.dirname(dummyFilePaths.server), { recursive: true });
93-
writeFileSync(dummyFilePaths.server, serverBarrelContent);
87+
const esbuildPlugin = {
88+
name: "rwsdk:esbuild:barrel-handler",
89+
setup(build: any) {
90+
const barrelPaths = Object.values([
91+
CLIENT_BARREL_PATH,
92+
SERVER_BARREL_PATH,
93+
]);
94+
const filter = new RegExp(
95+
`^(${barrelPaths
96+
.map((p) => p.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"))
97+
.join("|")})$`,
98+
);
99+
100+
build.onLoad({ filter }, (args: any) => {
101+
if (args.path === CLIENT_BARREL_PATH) {
102+
return {
103+
contents: clientBarrelContent,
104+
loader: "js",
105+
};
106+
}
107+
if (args.path === SERVER_BARREL_PATH) {
108+
return {
109+
contents: serverBarrelContent,
110+
loader: "js",
111+
};
112+
}
113+
return null;
114+
});
115+
},
116+
};
94117

95118
for (const [envName, env] of Object.entries(config.environments)) {
96119
if (envName === "client" || envName === "ssr") {
@@ -99,6 +122,13 @@ export const directiveModulesDevPlugin = ({
99122
dummyFilePaths.client,
100123
dummyFilePaths.server,
101124
];
125+
env.optimizeDeps.esbuildOptions = {
126+
...env.optimizeDeps.esbuildOptions,
127+
plugins: [
128+
...(env.optimizeDeps.esbuildOptions?.plugins || []),
129+
esbuildPlugin,
130+
],
131+
};
102132
}
103133
}
104134
},

0 commit comments

Comments
 (0)