Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2aeeea0
fix(sdk): make isBrowserEnv tree-shakable
cursoragent Feb 16, 2026
fea3d21
chore: add changeset for isBrowserEnv
cursoragent Feb 17, 2026
577e6ff
chore: expand bundle size metrics
cursoragent Feb 17, 2026
82b5e2e
fix(sdk): keep isBrowserEnv API
cursoragent Feb 17, 2026
106578d
chore: tolerate bundle size assets
cursoragent Feb 17, 2026
1367002
chore: align bundle size ENV_TARGET
cursoragent Feb 17, 2026
3c9770c
chore: use rslib for bundle sizes
cursoragent Feb 17, 2026
680acb4
Merge branch 'main' into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Feb 19, 2026
2dc915f
Merge branch 'main' into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Feb 19, 2026
a850bd1
fix(sdk): recompute browser env at call time
ScriptedAlchemy Feb 24, 2026
d871a21
Merge remote-tracking branch 'origin/main' into cursor/tree-shaking-c…
ScriptedAlchemy Feb 24, 2026
193f7fe
fix(dts-plugin): align workspace entrypoints and RawSource typing
ScriptedAlchemy Feb 24, 2026
4d8ed5c
Merge branch 'main' into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Feb 25, 2026
e03ea52
fix(sdk): align package entrypoints with emitted artifacts
ScriptedAlchemy Feb 25, 2026
bc68caa
Merge branch 'main' into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Feb 25, 2026
d548490
Merge branch 'main' into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Feb 26, 2026
c59c563
Merge branch 'main' into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Feb 26, 2026
038bea0
Merge branch 'main' into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Feb 27, 2026
c957914
Merge branch 'main' into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Feb 27, 2026
e89d77c
Merge main into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Feb 28, 2026
de8aa29
Merge branch 'main' into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Feb 28, 2026
8e685af
Merge branch 'main' into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Mar 2, 2026
66a47c6
Merge branch 'main' into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Mar 3, 2026
5fc3639
Merge branch 'main' into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Mar 5, 2026
db769ee
Merge branch 'main' into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Mar 9, 2026
564a259
Merge branch 'main' into cursor/tree-shaking-coverage-5fd5
ScriptedAlchemy Mar 10, 2026
e953ab1
fix(metro,sdk): stabilize type import and env test mocks
ScriptedAlchemy Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/breezy-walls-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@module-federation/sdk": minor
"@module-federation/runtime-core": minor
---

Add `isBrowserEnvValue` as a tree-shakable ENV_TARGET-aware constant while
preserving the `isBrowserEnv()` function. Internal callers use the constant to
enable bundler dead-code elimination without breaking the public API.
7 changes: 5 additions & 2 deletions packages/bridge/bridge-react/src/lazy/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { isBrowserEnv, composeKeyWithSeparator } from '@module-federation/sdk';
import {
isBrowserEnvValue,
composeKeyWithSeparator,
} from '@module-federation/sdk';
import logger from './logger';
import {
DOWNGRADE_KEY,
Expand Down Expand Up @@ -153,7 +156,7 @@ export async function fetchData(
_id: id,
});
};
if (isBrowserEnv()) {
if (isBrowserEnvValue) {
const dataFetchItem = getDataFetchItem(id);
if (!dataFetchItem) {
throw new Error(`dataFetchItem not found, id: ${id}`);
Expand Down
4 changes: 1 addition & 3 deletions packages/metro-core/src/types/runtime.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import type runtimeCore from '@module-federation/runtime/core';

type RemoteEntryExports = runtimeCore.types.RemoteEntryExports;
import type { RemoteEntryExports } from '@module-federation/runtime-core/types';

declare module '@module-federation/runtime' {
interface Federation {
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime-core/src/core.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isBrowserEnv } from '@module-federation/sdk';
import { isBrowserEnvValue } from '@module-federation/sdk';
import type {
CreateScriptHookReturn,
GlobalModuleInfo,
Expand Down Expand Up @@ -190,7 +190,7 @@ export class ModuleFederation {
plugins,
remotes: [],
shared: {},
inBrowser: isBrowserEnv(),
inBrowser: isBrowserEnvValue,
};

this.name = userOptions.name;
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime-core/src/plugins/generate-preload-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
ProviderModuleInfo,
isManifestProvider,
getResourceUrl,
isBrowserEnv,
isBrowserEnvValue,
} from '@module-federation/sdk';
import {
EntryAssets,
Expand Down Expand Up @@ -324,7 +324,7 @@ export const generatePreloadAssetsPlugin: () => ModuleFederationRuntimePlugin =
globalSnapshot,
remoteSnapshot,
} = args;
if (!isBrowserEnv()) {
if (!isBrowserEnvValue) {
return {
cssAssets: [],
jsAssetsWithoutEntry: [],
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime-core/src/plugins/snapshot/SnapshotHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
ModuleInfo,
generateSnapshotFromManifest,
isManifestProvider,
isBrowserEnv,
isBrowserEnvValue,
} from '@module-federation/sdk';
import {
RUNTIME_003,
Expand Down Expand Up @@ -186,7 +186,7 @@ export class SnapshotHandler {
// global snapshot includes manifest or module info includes manifest
if (globalRemoteSnapshot) {
if (isManifestProvider(globalRemoteSnapshot)) {
const remoteEntry = isBrowserEnv()
const remoteEntry = isBrowserEnvValue
? globalRemoteSnapshot.remoteEntry
: globalRemoteSnapshot.ssrRemoteEntry ||
globalRemoteSnapshot.remoteEntry ||
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime-core/src/plugins/snapshot/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
ModuleInfo,
getResourceUrl,
isBrowserEnv,
isBrowserEnvValue,
} from '@module-federation/sdk';
import { ModuleFederationRuntimePlugin } from '../../type/plugin';
import { RUNTIME_011, runtimeDescMap } from '@module-federation/error-codes';
Expand All @@ -25,7 +25,7 @@ export function assignRemoteInfo(

let entryUrl = getResourceUrl(remoteSnapshot, remoteEntryInfo.url);

if (!isBrowserEnv() && !entryUrl.startsWith('http')) {
if (!isBrowserEnvValue && !entryUrl.startsWith('http')) {
entryUrl = `https:${entryUrl}`;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/runtime-core/src/remote/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
isBrowserEnv,
isBrowserEnvValue,
warn,
composeKeyWithSeparator,
ModuleInfo,
Expand Down Expand Up @@ -429,7 +429,7 @@ export class RemoteHandler {
}
// Set the remote entry to a complete path
if ('entry' in remote) {
if (isBrowserEnv() && !remote.entry.startsWith('http')) {
if (isBrowserEnvValue && !remote.entry.startsWith('http')) {
remote.entry = new URL(remote.entry, window.location.origin).href;
}
}
Expand Down
6 changes: 5 additions & 1 deletion packages/runtime-core/src/utils/env.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export { isBrowserEnv, isDebugMode } from '@module-federation/sdk';
export {
isBrowserEnv,
isBrowserEnvValue,
isDebugMode,
} from '@module-federation/sdk';

export function isDevelopmentMode(): boolean {
return true;
Expand Down
6 changes: 3 additions & 3 deletions packages/runtime-core/src/utils/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
loadScript,
loadScriptNode,
composeKeyWithSeparator,
isBrowserEnv,
isBrowserEnvValue,
} from '@module-federation/sdk';
import { DEFAULT_REMOTE_TYPE, DEFAULT_SCOPE } from '../constant';
import { ModuleFederation } from '../core';
Expand Down Expand Up @@ -258,11 +258,11 @@ export async function getRemoteEntry(params: {
if (res) {
return res;
}
// Use ENV_TARGET if defined, otherwise fallback to isBrowserEnv, must keep this
// Use ENV_TARGET if defined, otherwise fallback to isBrowserEnvValue
const isWebEnvironment =
typeof ENV_TARGET !== 'undefined'
? ENV_TARGET === 'web'
: isBrowserEnv();
: isBrowserEnvValue;

return isWebEnvironment
? loadEntryDom({
Expand Down
8 changes: 6 additions & 2 deletions packages/runtime-core/src/utils/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
RemoteWithEntry,
ModuleInfo,
RemoteEntryType,
isBrowserEnv,
isBrowserEnvValue,
isReactNativeEnv,
} from '@module-federation/sdk';
import { Remote, RemoteInfoOptionalVersion } from '../type';
Expand Down Expand Up @@ -88,7 +88,11 @@ export function getRemoteEntryInfoFromSnapshot(snapshot: ModuleInfo): {
type: 'global',
globalName: '',
};
if (isBrowserEnv() || isReactNativeEnv() || !('ssrRemoteEntry' in snapshot)) {
if (
isBrowserEnvValue ||
isReactNativeEnv() ||
!('ssrRemoteEntry' in snapshot)
) {
return 'remoteEntry' in snapshot
? {
url: snapshot.remoteEntry,
Expand Down
12 changes: 9 additions & 3 deletions packages/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// The SDK can be used to parse entry strings, encode and decode module names, and generate filenames for exposed modules and shared packages.
// It also includes a logger for debugging and environment detection utilities.
// Additionally, it provides a function to generate a snapshot from a manifest and environment detection utilities.
import { parseEntry, encodeName, decodeName, generateExposeFilename, generateShareFilename, createLogger, isBrowserEnv, isDebugMode, getProcessEnv, generateSnapshotFromManifest } from '@module-federation/sdk';
import { parseEntry, encodeName, decodeName, generateExposeFilename, generateShareFilename, createLogger, isBrowserEnv, isBrowserEnvValue, isDebugMode, getProcessEnv, generateSnapshotFromManifest } from '@module-federation/sdk';

// Parse an entry string into a RemoteEntryInfo object
parseEntry('entryString');
Expand All @@ -32,7 +32,8 @@ generateShareFilename('packageName', true);
const logger = createLogger('identifier');

// Check if the current environment is a browser
isBrowserEnv();
const inBrowser = isBrowserEnv();
const inBrowserStatic = isBrowserEnvValue;

// Check if the current environment is in debug mode
isDebugMode();
Expand Down Expand Up @@ -76,9 +77,14 @@ generateSnapshotFromManifest(manifest, options);

### isBrowserEnv

- Type: `isBrowserEnv()`
- Type: `isBrowserEnv(): boolean`
- Checks if the current environment is a browser.

### isBrowserEnvValue

- Type: `isBrowserEnvValue: boolean`
- Static browser environment flag (tree-shakable when ENV_TARGET is defined).

### isDebugMode

- Type: `isDebugMode()`
Expand Down
35 changes: 23 additions & 12 deletions packages/sdk/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import { getResourceUrl } from '../src/utils';
import { ModuleInfo } from '../src/types';
import { isBrowserEnv, isReactNativeEnv } from '../src/env';
import * as env from '../src/env';

jest.mock('../src/env', () => ({
isBrowserEnv: jest.fn(),
isReactNativeEnv: jest.fn(),
}));
jest.mock('../src/env', () => {
const mock = {
isBrowserEnvValue: false,
isBrowserEnv: jest.fn(() => mock.isBrowserEnvValue),
isReactNativeEnv: jest.fn(),
};
return mock;
});

const mockedEnv = env as unknown as {
isBrowserEnvValue: boolean;
isBrowserEnv: jest.Mock;
isReactNativeEnv: jest.Mock;
};

describe('getResourceUrl', () => {
let module: ModuleInfo;
let sourceUrl: string;

beforeEach(() => {
sourceUrl = 'test.js';
(isBrowserEnv as jest.Mock).mockReset();
(isReactNativeEnv as jest.Mock).mockReset();
mockedEnv.isBrowserEnvValue = false;
mockedEnv.isBrowserEnv.mockClear();
mockedEnv.isReactNativeEnv.mockReset();
});

test('should return url with getPublicPath', () => {
Expand All @@ -34,7 +45,7 @@ describe('getResourceUrl', () => {
test('should return url with publicPath in browser or RN env', () => {
const publicPath = 'https://public.com/';
module = { publicPath } as ModuleInfo;
(isBrowserEnv as jest.Mock).mockReturnValue(true);
mockedEnv.isBrowserEnvValue = true;
const result = getResourceUrl(module, sourceUrl);
expect(result).toBe('https://public.com/test.js');
});
Expand All @@ -43,17 +54,17 @@ describe('getResourceUrl', () => {
const publicPath = 'https://public.com/';
const ssrPublicPath = 'https://ssr.com/';
module = { publicPath, ssrPublicPath } as ModuleInfo;
(isBrowserEnv as jest.Mock).mockReturnValue(false);
(isReactNativeEnv as jest.Mock).mockReturnValue(false);
mockedEnv.isBrowserEnvValue = false;
mockedEnv.isReactNativeEnv.mockReturnValue(false);
const result = getResourceUrl(module, sourceUrl);
expect(result).toBe('https://ssr.com/test.js');
});

test('should fallback to publicPath when ssrPublicPath is undefined', () => {
const publicPath = 'https://public.com/';
module = { publicPath, ssrPublicPath: undefined } as ModuleInfo;
(isBrowserEnv as jest.Mock).mockReturnValue(false);
(isReactNativeEnv as jest.Mock).mockReturnValue(false);
mockedEnv.isBrowserEnv.mockReturnValue(false);
mockedEnv.isReactNativeEnv.mockReturnValue(false);
const result = getResourceUrl(module, sourceUrl);
expect(result).toBe('https://public.com/test.js');
});
Expand Down
22 changes: 18 additions & 4 deletions packages/sdk/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@ declare global {
var FEDERATION_DEBUG: string | undefined;
}

// Declare the ENV_TARGET constant that will be defined by DefinePlugin
declare const ENV_TARGET: 'web' | 'node';

const detectBrowserEnv = () =>
typeof ENV_TARGET !== 'undefined'
? ENV_TARGET === 'web'
: typeof window !== 'undefined' && typeof window.document !== 'undefined';

const isBrowserEnvValue = detectBrowserEnv();

function isBrowserEnv(): boolean {
return (
typeof window !== 'undefined' && typeof window.document !== 'undefined'
);
return detectBrowserEnv();
}

function isReactNativeEnv(): boolean {
Expand Down Expand Up @@ -48,4 +56,10 @@ const getProcessEnv = function (): Record<string, string | undefined> {
return typeof process !== 'undefined' && process.env ? process.env : {};
};

export { isBrowserEnv, isReactNativeEnv, isDebugMode, getProcessEnv };
export {
isBrowserEnv,
isBrowserEnvValue,
isReactNativeEnv,
isDebugMode,
getProcessEnv,
};
Loading
Loading