Skip to content

Commit c856ec1

Browse files
fix(enhanced,sdk,runtime): tree-shake env-target runtime paths (#4518)
1 parent d3b8500 commit c856ec1

File tree

21 files changed

+447
-42
lines changed

21 files changed

+447
-42
lines changed

.changeset/breezy-walls-burn.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@module-federation/bridge-react": patch
3+
"@module-federation/sdk": minor
4+
"@module-federation/runtime-core": minor
5+
---
6+
7+
Add `isBrowserEnvValue` as a tree-shakable ENV_TARGET-aware constant while
8+
preserving the `isBrowserEnv()` function. Internal runtime and bridge callers
9+
use the constant to enable bundler dead-code elimination without breaking the
10+
public API.

.codex/skills/local-ci/SKILL.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
---
2+
name: local-ci
3+
description: Run this repository's local CI parity commands and `pnpm run ci:local` jobs. Use when validating a change against the same job shapes used in GitHub Actions, choosing the smallest local CI job for a package, workflow, E2E, Metro, devtools, bundle-size, or build-and-test change.
4+
---
5+
6+
# Local CI
7+
8+
Use the local CI runner and Turbo/package scripts as the source of truth for validation in this repo.
9+
10+
## Quick Start
11+
12+
1. Start with the smallest deterministic check that matches the change.
13+
2. Prefer `pnpm run ci:local --only=<job>` for CI-parity validation.
14+
3. Prefer direct Turbo or package scripts only when they are the smaller equivalent of the CI job.
15+
4. Report exactly which command ran, what failed first, and which expected checks were skipped.
16+
17+
## Workflow
18+
19+
### 1. Inspect available local CI jobs
20+
21+
Run:
22+
23+
```bash
24+
pnpm run ci:local --list
25+
```
26+
27+
Use this when the right job is unclear or when the repo may have added or renamed jobs.
28+
29+
For the current job map, read [references/jobs.md](./references/jobs.md).
30+
31+
### 2. Pick the smallest matching job
32+
33+
Use these defaults:
34+
35+
- Package code in `packages/*`: start with `build-and-test` only if the change is broad; otherwise prefer package build/test commands.
36+
- Metro packages or Metro workflow changes: use `build-metro`, then Metro E2E jobs only if relevant.
37+
- E2E app changes: run the matching `e2e-*` job instead of `build-and-test`.
38+
- Devtools workflow or Playwright/devtools changes: run `devtools`.
39+
- Bundle-size workflow/reporting changes: run `bundle-size`.
40+
- GitHub workflow syntax only: use `actionlint` locally only to see skip behavior; the actual action is CI-only.
41+
- Broad CI, packaging, publint, Turbo, or shared runtime changes: run `build-and-test`.
42+
43+
### 3. Run commands in CI order
44+
45+
When reproducing CI, use the same command shape as the repo:
46+
47+
```bash
48+
pnpm run ci:local --only=build-and-test
49+
pnpm run ci:local --only=devtools
50+
pnpm run ci:local --only=e2e-runtime
51+
```
52+
53+
To run more than one job:
54+
55+
```bash
56+
pnpm run ci:local --only=build-and-test,bundle-size
57+
```
58+
59+
### 4. Interpret failures correctly
60+
61+
- Treat the first failing step as the actionable failure.
62+
- Ignore known noisy warnings unless they exit non-zero.
63+
- Distinguish repo-wide drift from changed-file gates.
64+
- If `ci:local` fails in `Check code format`, inspect the changed-file formatter result before running broader builds.
65+
- If a dependency build outside the touched scope fails, note it separately as an unrelated blocker.
66+
67+
## Common Cases
68+
69+
### Broad package or runtime/plugin change
70+
71+
Run:
72+
73+
```bash
74+
pnpm run ci:local --only=build-and-test
75+
```
76+
77+
If that fails on format first, fix the changed files before rerunning the whole job.
78+
79+
### One package only
80+
81+
Prefer the smaller direct command first:
82+
83+
```bash
84+
pnpm exec turbo run build --filter=@module-federation/<pkg>
85+
pnpm exec turbo run test --filter=@module-federation/<pkg> --force
86+
```
87+
88+
Then run `build-and-test` only if the user asks for CI parity or the package change affects shared build/runtime behavior.
89+
90+
### E2E change
91+
92+
Run the matching job from [references/jobs.md](./references/jobs.md), not the whole matrix.
93+
94+
### Workflow change
95+
96+
Use the local CI job if one exists for the workflow's behavior. For workflow syntax, pair local validation with the repo's actionlint path when relevant.
97+
98+
## Notes
99+
100+
- `ci:local` sets `CI=true` and mirrors the repo workflow order.
101+
- Some jobs intentionally skip GitHub-only actions such as `actionlint` and `bundle-size-comment`.
102+
- The repo uses `pnpm`, Node 20, and Turbo as the standard execution path.
103+
- In worktrees, still use Turbo/package scripts directly as documented by the repo instructions.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
interface:
2+
display_name: 'Local CI'
3+
short_description: 'Run repo CI parity jobs locally'
4+
default_prompt: 'Use $local-ci to choose and run the smallest local CI parity command for this repo change.'
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Local CI job map
2+
3+
Use `pnpm run ci:local --list` if you need to refresh this list from the repo.
4+
5+
## Core jobs
6+
7+
- `build-and-test`
8+
- Full package-oriented CI parity job.
9+
- Includes install, Cypress install, changed-file format check, Turbo/publint verification, package build, and affected package tests.
10+
- Use for shared runtime, plugin, CI, packaging, publint, or broad package changes.
11+
12+
- `build-metro`
13+
- Metro-specific parity job.
14+
- Use for `packages/metro*` and related Metro workflow changes.
15+
16+
- `bundle-size`
17+
- Bundle-size measurement workflow parity.
18+
- Use for bundle-size collection or reporting logic.
19+
20+
- `devtools`
21+
- Devtools workflow parity job.
22+
- Use for `packages/chrome-devtools`, Playwright/devtools setup, and devtools workflow changes.
23+
24+
## E2E jobs
25+
26+
- `e2e-modern`
27+
- `e2e-runtime`
28+
- `e2e-manifest`
29+
- `e2e-node`
30+
- `e2e-next-dev`
31+
- `e2e-next-prod`
32+
- `e2e-treeshake`
33+
- `e2e-modern-ssr`
34+
- `e2e-router`
35+
- `metro-affected-check`
36+
- `metro-android-e2e`
37+
- `metro-ios-e2e`
38+
- `e2e-shared-tree-shaking`
39+
40+
Use the smallest job that matches the app/runtime surface you changed.
41+
42+
## CI-only local skips
43+
44+
- `actionlint`
45+
- Listed for parity, but skipped because the GitHub-only action does not fully execute locally.
46+
47+
- `bundle-size-comment`
48+
- Listed for parity, but skipped locally because it depends on GitHub workflow-run artifacts.
49+
50+
## Helpful commands
51+
52+
List jobs:
53+
54+
```bash
55+
pnpm run ci:local --list
56+
```
57+
58+
Run one job:
59+
60+
```bash
61+
pnpm run ci:local --only=build-and-test
62+
```
63+
64+
Run multiple jobs:
65+
66+
```bash
67+
pnpm run ci:local --only=build-and-test,bundle-size
68+
```
69+
70+
Run a smaller non-`ci:local` package check:
71+
72+
```bash
73+
pnpm exec turbo run build --filter=@module-federation/<pkg>
74+
pnpm exec turbo run test --filter=@module-federation/<pkg> --force
75+
```

packages/bridge/bridge-react/src/lazy/utils.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { isBrowserEnv, composeKeyWithSeparator } from '@module-federation/sdk';
1+
import {
2+
isBrowserEnvValue,
3+
composeKeyWithSeparator,
4+
} from '@module-federation/sdk';
25
import logger from './logger';
36
import {
47
DOWNGRADE_KEY,
@@ -153,7 +156,7 @@ export async function fetchData(
153156
_id: id,
154157
});
155158
};
156-
if (isBrowserEnv()) {
159+
if (isBrowserEnvValue) {
157160
const dataFetchItem = getDataFetchItem(id);
158161
if (!dataFetchItem) {
159162
throw new Error(`dataFetchItem not found, id: ${id}`);

packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ function resolveRuntimeEntryWithFallback(
7979

8080
return resolveRuntimeEntry(spec, undefined, resolve);
8181
}
82-
8382
export function resolveRuntimePaths(
8483
implementation?: string,
8584
resolve: ResolveFn = require.resolve,
@@ -292,7 +291,7 @@ class FederationRuntimePlugin {
292291
: fs;
293292
try {
294293
fsLike.readFileSync(filePath);
295-
} catch (err) {
294+
} catch {
296295
mkdirpSync(fsLike as any, TEMP_DIR);
297296
fsLike.writeFileSync(
298297
filePath,
@@ -344,7 +343,7 @@ class FederationRuntimePlugin {
344343
compiler.context,
345344
federationRuntimeDependency,
346345
{ name: undefined },
347-
(err, module) => {
346+
(err) => {
348347
if (err) {
349348
return callback(err);
350349
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* global __non_webpack_require__, __dirname, globalThis, it, expect */
2+
const fs = __non_webpack_require__('fs');
3+
const path = __non_webpack_require__('path');
4+
5+
if (!globalThis.__EXPERIMENTS_OPTIMIZATION_CASE__) {
6+
globalThis.__EXPERIMENTS_OPTIMIZATION_CASE__ = true;
7+
8+
const readOutput = (filename) =>
9+
fs.readFileSync(path.join(__dirname, filename), 'utf-8');
10+
11+
const webRemoteEntry = readOutput('remoteEntry-web.js');
12+
const nodeRemoteEntry = readOutput('remoteEntry-node.js');
13+
const webRemoteEntryEsm = readOutput('module/remoteEntry-web.mjs');
14+
const nodeRemoteEntryEsm = readOutput('module/remoteEntry-node.mjs');
15+
16+
it('should replace optimization define flags with static values', () => {
17+
expect(webRemoteEntry).not.toContain('ENV_TARGET');
18+
expect(webRemoteEntry).not.toContain(
19+
'FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN',
20+
);
21+
22+
expect(nodeRemoteEntry).not.toContain('ENV_TARGET');
23+
expect(nodeRemoteEntry).not.toContain(
24+
'FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN',
25+
);
26+
27+
expect(webRemoteEntryEsm).not.toContain('ENV_TARGET');
28+
expect(webRemoteEntryEsm).not.toContain(
29+
'FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN',
30+
);
31+
32+
expect(nodeRemoteEntryEsm).not.toContain('ENV_TARGET');
33+
expect(nodeRemoteEntryEsm).not.toContain(
34+
'FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN',
35+
);
36+
});
37+
38+
it('should eliminate snapshot plugins from the web optimized CJS bundle', () => {
39+
expect(webRemoteEntry).not.toContain('snapshot-plugin');
40+
expect(webRemoteEntry).not.toContain('generate-preload-assets-plugin');
41+
expect(webRemoteEntry).not.toContain('attrs:{name');
42+
});
43+
44+
it('should preserve node loading and snapshot plugins in the node CJS bundle', () => {
45+
expect(nodeRemoteEntry).toContain('snapshot-plugin');
46+
expect(nodeRemoteEntry).toContain('generate-preload-assets-plugin');
47+
expect(nodeRemoteEntry).toContain('attrs:{name');
48+
});
49+
50+
it('should eliminate snapshot plugins from the web optimized ESM bundle', () => {
51+
expect(webRemoteEntryEsm).not.toContain('snapshot-plugin');
52+
expect(webRemoteEntryEsm).not.toContain('generate-preload-assets-plugin');
53+
expect(webRemoteEntryEsm).not.toContain('attrs:{name');
54+
});
55+
56+
it('should preserve node loading and snapshot plugins in the node ESM bundle', () => {
57+
expect(nodeRemoteEntryEsm).toContain('snapshot-plugin');
58+
expect(nodeRemoteEntryEsm).toContain('generate-preload-assets-plugin');
59+
expect(nodeRemoteEntryEsm).toContain('attrs:{name');
60+
});
61+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/* eslint-env node */
2+
module.exports = 'noop';

0 commit comments

Comments
 (0)