Skip to content

Commit 29eb8ce

Browse files
miknclaude
andauthored
fix(dev_server): keep workspace members out of the SSR dep optimizer (#370)
Vite applies the top-level optimizeDeps to the client environment only, so the ssr environment and worker environments still pre-bundled workspace members and plugin transforms never ran on them. The member-sources plugin now adds the member exclude list to every non-client environment through configEnvironment, which merges with a consumer's own list. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 7c7e8fd commit 29eb8ce

4 files changed

Lines changed: 50 additions & 0 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
### Fixed
2+
3+
- **`ts_dev_server` keeps workspace members out of every environment's dep
4+
optimizer.** The generated config listed members only in the top-level
5+
`optimizeDeps.exclude`, which Vite applies to the client environment alone.
6+
When a framework or worker plugin turned on dep discovery for the `ssr` or a
7+
worker environment, that environment pre-bundled a member, and a pre-bundle
8+
never reaches plugin transforms such as StyleX. A `configEnvironment` hook
9+
now adds the same exclude list to every non-client environment; a
10+
consumer's own exclude list still merges in. A member a plugin lists in an
11+
environment's `optimizeDeps.include` is still pre-bundled there.

‎tests/dev_server/dev_server_behaviour_test.go‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,27 @@ func TestDevServerBehaviour(t *testing.T) {
385385
t.Errorf("no module id the plugin transformed lies under the member's store %s: %v", store, seen.Transformed)
386386
}
387387
})
388+
t.Run("server_environment_optimizer_cannot_pre_bundle_a_workspace_member", func(t *testing.T) {
389+
const pkg = "tests/npm/dev_inherited_types"
390+
t.Setenv("MEMBER_SOURCES_SERVER_OPTIMIZERS", "1")
391+
ws := t.TempDir()
392+
app := filepath.Join(ws, filepath.FromSlash(pkg))
393+
mkdir(t, app)
394+
mkdir(t, filepath.Join(ws, "bazel-bin"))
395+
source := filepath.Join(ws, "packages", "shared", "src")
396+
mkdir(t, source)
397+
write(t, filepath.Join(source, "index.ts"), `export const tagline: string = "MEMBER_SOURCE";`)
398+
write(t, filepath.Join(app, "index.html"), "SERVER_OPTIMIZERS")
399+
write(t, filepath.Join(app, "entry.ts"), `export { tagline } from "shared";`)
400+
srv := start(t, tree.File(pkg+"/member_sources_vite_dev_launcher").Abs(), ws, t.TempDir())
401+
base := srv.awaitHTTP(t, "/index.html")
402+
for _, environment := range []string{"ssr", "worker"} {
403+
r := get(t, base, "/environment-transform?"+url.Values{"environment": {environment}, "url": {"/entry.ts"}}.Encode())
404+
if r.status != 200 || !strings.Contains(r.body, "/packages/shared/src/index.ts") {
405+
t.Errorf("the %s environment imports the member from somewhere other than its source: HTTP %d\n%s\n%s", environment, r.status, r.body, srv.log(t))
406+
}
407+
}
408+
})
388409
t.Run("workspace_member_is_served_from_its_source", func(t *testing.T) {
389410
const pkg = "tests/npm/dev_inherited_types"
390411
for _, launcher := range []string{"member_sources_vite_dev_launcher", "member_sources_dev_launcher"} {

‎tests/npm/dev_inherited_types/member_sources.vite.mjs‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ const transformed = [];
77
export default {
88
plugins: [{
99
name: 'member-sources',
10+
// Server environments that discover deps to pre-bundle, as worker and SSR framework plugins configure them.
11+
config: () => process.env.MEMBER_SOURCES_SERVER_OPTIMIZERS && {
12+
environments: {
13+
ssr: { optimizeDeps: { noDiscovery: false } },
14+
worker: { consumer: 'server', resolve: { noExternal: true }, optimizeDeps: { noDiscovery: false } },
15+
},
16+
},
1017
async transform(code, id) {
1118
transformed.push(id);
1219
// Like most transforms, it leaves node_modules alone; one that resolves for
@@ -22,6 +29,15 @@ export default {
2229
sources: JSON.parse(process.env.BAZEL_MEMBER_SOURCES ?? 'null'),
2330
transformed,
2431
})));
32+
server.middlewares.use('/environment-transform', async (req, res) => {
33+
const query = new URL(req.url, 'http://localhost').searchParams;
34+
try {
35+
res.end((await server.environments[query.get('environment')].transformRequest(query.get('url')))?.code ?? '');
36+
} catch (error) {
37+
res.statusCode = 500;
38+
res.end(String(error?.stack ?? error));
39+
}
40+
});
2541
},
2642
}, {
2743
name: 'pre-claims',

‎ts/private/ts_dev_server.bzl‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,8 @@ def _generate_dev_config(
471471
"const bazelMemberSources = {\n" +
472472
" name: 'bazel:member-sources',\n" +
473473
" enforce: 'pre',\n" +
474+
" // Vite gives the top-level optimizeDeps to the client environment only; a pre-bundle skips plugin transforms.\n" +
475+
" configEnvironment: name => (name === 'client' ? null : { optimizeDeps: { exclude: [...memberNames] } }),\n" +
474476
" async resolveId(id, importer, options) {\n" +
475477
" // Only a member by name, or an import from a member's files, can reach a member's store.\n" +
476478
" const pkg = id.split('?')[0].split('/').slice(0, id.startsWith('@') ? 2 : 1).join('/');\n" +

0 commit comments

Comments
 (0)