Skip to content

Commit

Permalink
WIP: chore: split runtime as core and entry (#3154)
Browse files Browse the repository at this point in the history
Co-authored-by: ScriptedAlchemy <[email protected]>
Co-authored-by: Zack Jackson <[email protected]>
  • Loading branch information
3 people authored Dec 31, 2024
1 parent de80bb5 commit f573ad0
Show file tree
Hide file tree
Showing 146 changed files with 5,036 additions and 2,545 deletions.
3 changes: 2 additions & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"@module-federation/retry-plugin",
"@module-federation/data-prefetch",
"@module-federation/rsbuild-plugin",
"@module-federation/error-codes"
"@module-federation/error-codes",
"@module-federation/inject-external-runtime-core-plugin"
]
],
"ignorePatterns": ["^alpha|^beta"],
Expand Down
8 changes: 8 additions & 0 deletions .changeset/nervous-starfishes-whisper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@module-federation/inject-external-runtime-core-plugin': patch
'@module-federation/runtime-core': patch
'@module-federation/enhanced': patch
'@module-federation/runtime': patch
---

feat: add externalRuntime and provideExternalRuntime fields to help optimize assets size
3 changes: 3 additions & 0 deletions apps/manifest-demo/3009-webpack-provider/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ module.exports = composePlugins(
requiredVersion: '^18.3.1',
},
},
experiments: {
externalRuntime: true,
},
}),
);

Expand Down
3 changes: 3 additions & 0 deletions apps/manifest-demo/3010-rspack-provider/rspack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ module.exports = composePlugins(
},
},
dataPrefetch: true,
experiments: {
externalRuntime: true,
},
}),
);
(config.devServer = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ module.exports = composePlugins(
requiredVersion: '^18.3.1',
},
},
experiments: {
externalRuntime: true,
},
}),
);
(config.devServer = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ module.exports = composePlugins(
},
},
manifest: false,
experiments: {
externalRuntime: true,
},
}),
);
(config.devServer = {
Expand Down
50 changes: 0 additions & 50 deletions apps/manifest-demo/webpack-host/src/bootstrap.tsx

This file was deleted.

9 changes: 5 additions & 4 deletions apps/manifest-demo/webpack-host/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => {
'modern-js-provider': 'app1@http://127.0.0.1:4001/mf-manifest.json',
},
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button.tsx',
},
shared: {
lodash: {},
antd: {},
Expand All @@ -47,8 +44,12 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => {
},
},
dataPrefetch: true,
experiments: { federationRuntime: 'hoisted' },
// experiments: { federationRuntime: 'hoisted' },
runtimePlugins: [path.join(__dirname, './runtimePlugin.ts')],
experiments: {
provideExternalRuntime: true,
federationRuntime: 'hoisted',
},
}),
);

Expand Down
19 changes: 19 additions & 0 deletions apps/website-new/docs/en/configure/experiments.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,22 @@ There should be no "eager consumption" errors possible, as the initialization of
Instead of federation runtime initilizing in the entrypoint code, it is initialized in a RuntimeModule, within the webpack runtime.
This allows module federation to be avaliable ahead of time, thus enabling "Async Startup" capabilities etc.

## externalRuntime

- Type: `boolean`
- Required: No
- Default: `false`

After setting `true`, the external MF runtime will be used and the runtime provided by the consumer will be used. (Please make sure your consumer has `provideExternalRuntime: true` set, otherwise it will not run properly!)

## provideExternalRuntime

- Type: `boolean`
- Required: No
- Default: `false`

::: warning note
Make sure to only configure it on the topmost consumer! If multiple consumers inject runtime at the same time, the ones executed later will not overwrite the existing runtime.
:::

Setting `true` will inject the MF runtime at the consumer.
5 changes: 5 additions & 0 deletions apps/website-new/docs/zh/configure/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,10 @@
"type": "file",
"name": "shareStrategy",
"label": "shareStrategy"
},
{
"type": "file",
"name": "experiments",
"label": "experiments"
}
]
88 changes: 88 additions & 0 deletions apps/website-new/docs/zh/configure/experiments.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Experiments

`experiments` 配置用于启用插件中的实验功能。

- Example

```ts
new ModuleFederationPlugin({
name: '@demo/host',
experiments: {
federationRuntime: 'hoisted'
},
shared: {
react: {
singleton: true,
},
'react-dom': {
singleton: true,
},
},
//...
});
```

## federationRuntime

- Type: `enum`
- Required: No
- Default: `false`
- Options: `false | "hoisted"`

### `Hoisted` Runtime

当设置 `federationRuntime: 'hoisted'` 时,会发生以下情况:

这些配置可用于下列场景:

- 设置`runtimeChunk: 'single'`
- 避免在应用程序顶部或用户代码入口点使用“import()”,以防止急切消费错误。过去,这通常是强制性的,并且在示例应用程序中通常被视为“import('./bootstrap.js')”。
- 将模块联合运行时包移动到运行时块中,使它们远离入口点 - 减少代码重复

下面会详细解释对应的场景。

1)优化

原先 `module-federation/runtime` 会被加入到编译入口,这意味着在多 entry 的情况下会打包多次 mf runtime,会增大产物体积。
当设置了某些特定的分包策略,还会导致共享依赖 `eager consumption` 的问题。

并且 `federationRuntime: 'hoisted'` 还允许设置 `runtimeChunk: "single"`

2)异步启动

:::警告
此模式仍然允许设置异步入口。导出 UMD 库时,它会返回 Promise resolve 导出。
如果您手动 require() Node 中的入口点,它将 module.exports 设置为 Promise.resolve(exports)。
:::

入口启动将切换到“主动”初始化并使用异步依赖项启动。

你将不再需要强制的设置异步入口:`“import('./bootsrtap')”`

不再出现共享依赖 `eager consumption` 错误,因为文件本身的初始化表现为异步块。

3) 提升 MF 运行时访问优先级

与原先在入口挂载 MF runtime 不同,现在将会提升到 bundler runtime 初始化阶段挂载 MF runtime 。

这允许 MF runtime 提前可用,从而支持 “异步启动”(不再需要异步入口) 功能等。

## externalRuntime

- Type: `boolean`
- Required: No
- Default: `false`

设置 `true` 后 会 external MF runtime,并使用消费者提供的 runtime 。(请确保你的消费者有设置 `provideExternalRuntime: true`,否则无法正常运行!)

## provideExternalRuntime

- Type: `boolean`
- Required: No
- Default: `false`

::: warning 注意
请确保仅在最顶层消费者配置!若同时有多个消费者注入 runtime,后执行的不会覆盖已有的 runtime。
:::

设置 `true` 后会在消费者处注入 MF runtime。
2 changes: 1 addition & 1 deletion packages/chrome-devtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"release": "npm publish --tag canary",
"test": "vitest run",
"test:e2e": "E2ETEST=true pnpm build && playwright test",
"test:e2e:ui": "E2ETEST=true pnpm build && playwright test --ui",
"test:e2e:ui": "E2ETEST=true pnpm build && playwright test --ui",
"start": "modern start",
"serve": "modern serve",
"new": "modern new",
Expand Down
5 changes: 4 additions & 1 deletion packages/chrome-devtools/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
timeout: 2 * 60 * 1000,
timeout: 90000,
testDir: './e2e',
fullyParallel: true,
forbidOnly: Boolean(process.env.CI),
Expand All @@ -10,6 +10,9 @@ export default defineConfig({
use: {
trace: 'on-first-retry',
},
expect: {
timeout: 10000,
},
projects: [
{
name: 'chromium',
Expand Down
10 changes: 9 additions & 1 deletion packages/chrome-devtools/src/component/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
GlobalModuleInfo,
MODULE_DEVTOOL_IDENTIFIER,
} from '@module-federation/sdk';
import type { Federation } from '@module-federation/runtime';

import FormComponent from '../Form';
import Dependency from '../Graph';
Expand Down Expand Up @@ -33,12 +34,19 @@ import {
BROWSER_ENV_KEY,
__FEDERATION_DEVTOOLS__,
} from '../../template/constant';

interface FormItemType {
key: string;
value: string;
checked: boolean;
}
declare global {
interface Window {
__FEDERATION__: Federation & {
originModuleInfo: GlobalModuleInfo;
moduleInfo: GlobalModuleInfo;
};
}
}

const Layout = (
props: { moduleInfo: GlobalModuleInfo } & RootComponentProps,
Expand Down
37 changes: 21 additions & 16 deletions packages/data-prefetch/src/prefetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,16 @@ import {
ProviderModuleInfo,
} from '@module-federation/sdk';
import { Remote } from '@module-federation/runtime/types';

import type { Federation } from '@module-federation/runtime';
import { getPrefetchId, compatGetPrefetchId } from './common/runtime-utils';

declare module '@module-federation/runtime' {
export interface Federation {
__PREFETCH__: {
entryLoading: Record<string, undefined | Promise<void>>;
instance: Map<string, MFDataPrefetch>;
__PREFETCH_EXPORTS__: Record<string, () => Promise<Record<string, any>>>;
};
}
// Define an interface that extends Federation to include __PREFETCH__
interface FederationWithPrefetch extends Federation {
__PREFETCH__: {
entryLoading: Record<string, undefined | Promise<void>>;
instance: Map<string, MFDataPrefetch>;
__PREFETCH_EXPORTS__: Record<string, () => Promise<Record<string, any>>>;
};
}

type PrefetchExports = Record<string, any>;
Expand All @@ -40,11 +39,13 @@ export interface prefetchOptions {

// @ts-ignore init global variable for test
globalThis.__FEDERATION__ ??= {};
globalThis.__FEDERATION__.__PREFETCH__ ??= {
(
globalThis.__FEDERATION__ as unknown as FederationWithPrefetch
).__PREFETCH__ ??= {
entryLoading: {},
instance: new Map(),
__PREFETCH_EXPORTS__: {},
};
} as FederationWithPrefetch['__PREFETCH__'];
export class MFDataPrefetch {
public prefetchMemory: Map<string, Promise<any>>;
public recordOutdate: Record<string, Record<string, boolean>>;
Expand All @@ -59,12 +60,15 @@ export class MFDataPrefetch {
this.global.instance.set(options.name, this);
}

get global(): Record<string, any> {
return globalThis.__FEDERATION__.__PREFETCH__;
get global(): FederationWithPrefetch['__PREFETCH__'] {
return (globalThis.__FEDERATION__ as unknown as FederationWithPrefetch)
.__PREFETCH__;
}

static getInstance(id: string): MFDataPrefetch | undefined {
return globalThis.__FEDERATION__.__PREFETCH__.instance.get(id);
return (
globalThis.__FEDERATION__ as unknown as FederationWithPrefetch
).__PREFETCH__.instance.get(id);
}

async loadEntry(entry: string | undefined): Promise<any> {
Expand Down Expand Up @@ -94,8 +98,9 @@ export class MFDataPrefetch {
return this._exports;
}
const { name } = this._options;
const exportsPromiseFn =
globalThis.__FEDERATION__.__PREFETCH__.__PREFETCH_EXPORTS__?.[name];
const exportsPromiseFn = (
globalThis.__FEDERATION__ as unknown as FederationWithPrefetch
).__PREFETCH__.__PREFETCH_EXPORTS__?.[name];
const exportsPromise =
typeof exportsPromiseFn === 'function'
? exportsPromiseFn()
Expand Down
1 change: 1 addition & 0 deletions packages/enhanced/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"@module-federation/rspack": "workspace:*",
"@module-federation/bridge-react-webpack-plugin": "workspace:*",
"@module-federation/data-prefetch": "workspace:*",
"@module-federation/inject-external-runtime-core-plugin": "workspace:*",
"upath": "2.0.1",
"btoa": "^1.2.1"
}
Expand Down
Loading

0 comments on commit f573ad0

Please sign in to comment.