diff --git a/.changeset/config.json b/.changeset/config.json index 6a62d9a7350..ae4f134f72c 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -27,7 +27,8 @@ "@module-federation/inject-external-runtime-core-plugin", "@module-federation/runtime-core", "create-module-federation", - "@module-federation/cli" + "@module-federation/cli", + "@module-federation/rspress-plugin" ] ], "ignorePatterns": ["^alpha|^beta"], diff --git a/.changeset/shaggy-pans-teach.md b/.changeset/shaggy-pans-teach.md new file mode 100644 index 00000000000..93e85681dfa --- /dev/null +++ b/.changeset/shaggy-pans-teach.md @@ -0,0 +1,5 @@ +--- +'@module-federation/rspress-plugin': patch +--- + +feat(rspress-plugin): support rspress diff --git a/.gitignore b/.gitignore index 31cf4e18cf6..33cea2a448b 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,7 @@ vitest.config.*.timestamp* .cursor/rules/nx-rules.mdc .github/instructions/nx.instructions.md .temp-commit-msg + +# website-new +.rsbuild +ssg diff --git a/apps/website-new/docs/en/configure/shared.mdx b/apps/website-new/docs/en/configure/shared.mdx index c1266b297e8..70bc231ec94 100644 --- a/apps/website-new/docs/en/configure/shared.mdx +++ b/apps/website-new/docs/en/configure/shared.mdx @@ -111,34 +111,22 @@ At this point, you can add the corresponding dependencies to the `shared` config ### How to use shared dependencies -Depending on the use case, `Module Federation` supports two forms of shared dependency configuration: array and object. The former is suitable for most scenarios, while the latter is suitable for complex customization needs. +Depending on the use case, {props.name || 'Module Federation'} supports two forms of shared dependency configuration: array and object. The former is suitable for most scenarios, while the latter is suitable for complex customization needs. **Array Format (General Scenario)** -Simply add the corresponding dependencies to the `shared` configuration in the `Module Federation` build configuration, for example: +Simply add the corresponding dependencies to the `shared` configuration in the {props.name || 'Module Federation'} build configuration, for example: -```ts -new ModuleFederationPlugin({ - name: '@demo/button', - shared: ['react', 'react-dom'], - //... -}); -``` +import ArrayShared from '@components/common/configure/array-shared'; +import React from 'react'; + +{props.arrayShared || React.createElement(ArrayShared)} **Object Format (Customized Configuration)** -Add the shared dependencies in the `shared` configuration of the `Module Federation Plugin`, with the `key` being the dependency name and the `value` being the provided configuration. +Add the shared dependencies in the `shared` configuration of the {props.name || 'Module Federation'}, with the `key` being the dependency name and the `value` being the provided configuration. + +import ObjectShared from '@components/common/configure/object-shared' + +{props.objectShared || React.createElement(ObjectShared)} -```ts -new ModuleFederationPlugin({ - name: '@demo/button', - shared: { - react: { - singleton: true, - requiredVersion: '~18.2.0', - fixedDependencies: true, - }, - }, - //... -}); -``` diff --git a/apps/website-new/docs/en/guide/basic/_meta.json b/apps/website-new/docs/en/guide/basic/_meta.json index 38a41abff49..78addf012d9 100644 --- a/apps/website-new/docs/en/guide/basic/_meta.json +++ b/apps/website-new/docs/en/guide/basic/_meta.json @@ -1 +1,8 @@ -["runtime", "rsbuild", "rspack", "webpack", "vite", "type-prompt","cli"] +[ + { + "type": "dir", + "name": "runtime", + "label": "Runtime" + }, +"rsbuild", "rspack", "webpack", "rspress","vite","type-prompt","cli", "css-isolate" +] diff --git a/apps/website-new/docs/en/guide/basic/cli.mdx b/apps/website-new/docs/en/guide/basic/cli.mdx index fd9ce752a95..0368f97ea99 100644 --- a/apps/website-new/docs/en/guide/basic/cli.mdx +++ b/apps/website-new/docs/en/guide/basic/cli.mdx @@ -1,64 +1,41 @@ -# CLI +# Command Line Tool -`@module-federation/enhanced` and `@module-federation/modern-js` provides a lightweight CLI. +

+{props.name || 'Module Federation'} provides lightweight command-line tools: {(props.cmdTools || ['@module-federation/enhanced', '@module-federation/modern-js']).map((cmdTool,index,arr)=>(<>{cmdTool}{index+1===arr.length ? '':' and '}))} . +

-## All commands +## View All Commands -To view all available CLI commands, run the following command in the project directory: +import React from 'react'; +import ViewAllCmdsMdx from '@components/common/cli/view-all-cmds'; -```bash -npx mf -h -``` +{/* rspress flattenMdxContent issue */} +{props.show || React.createElement(ViewAllCmdsMdx)} -The output is shown below: +## Common Options -```text -Usage: mf [options] +{props.name || 'Module Federation'} CLI provides some common options that can be used for all commands: -Options: - -V, --version output the version number - -h, --help display help for command +| Option | Description | +| -------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `-c, --config ` | Specify the configuration file path, which can be a relative or absolute path. The default value is {props.configName ? props.configName : 'module-federation.config.ts' } | +| `-m, --mode ` | Specify the running environment, you can choose "dev" or "prod". The default value is "dev". After setting, "development" or "production" will be automatically injected into the `process.env.NODE_ENV` environment variable according to the value. | +| `-h, --help` | Show command help | -Commands: - dts [options] generate or fetch the mf types - help [command] display help for command -``` +## {props.cmd || 'mf'} dts -## Common flags +

+ The {props.cmd || 'mf'} dts command is used to pull or generate TypeScript type declaration files. +

-Module Federation CLI provides several common flags that can be used with all commands: +import CommandInfo from '@components/common/cli/cmd-info'; -| Flag | Description | -| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| `-c, --config ` | Specify the configuration file, can be a relative or absolute path, default value is `module-federation.ts` | -| `-m, --mode ` | Specify the runtime environment. You can choose "dev" or "prod". The default value is "dev". After setting, the `process.env.NODE_ENV` environment variable will be automatically injected with "development" or "production" according to the value. | -| `-h, --help` | Display help for command | +{props.commandInfo || } -## mf dts +:::info 注意 -The `mf dts` command is used to generate or fetch remote types. +

The {props.cmd || 'mf'} dts command will automatically generate or pull type declaration files based on the configuration in {props.configName || 'module-federation.config.ts'}. This means you must provide a valid configuration file, otherwise the command will not run correctly.

-```bash -Usage: mf dts [options] +If you are only using the runtime API, you need to create a temporary {props.configName || 'module-federation.config.ts'} file, configure [dts.consumeTypes.remoteTypeUrls](../../configure/dts#remotetypeurls), and then run the `mf dts` command. -generate or fetch the mf types - -Options: - --root specify the project root directory - --output specify the generated dts output directory - --fetch fetch types from remote, default is true (default: true) - --generate generate types, default is true (default: true) - -c --config specify the configuration file, can be a relative or absolute path - -m --mode Specify the runtime environment. You can choose "dev" or "prod". The default value is "dev". After setting, the process.env.NODE_ENV environment variable will be - automatically injected with "development" or "production" according to the value. (default: "dev") - -h, --help display help for command -``` - -:::info Note - -The `mf dts` command will automatically generate or pull type declaration files based on the configuration in `module-federation.config.ts`. This means you must provide a valid configuration file, otherwise the command will not work properly. - -If you only use the runtime API, you need to create a temporary `module-federation.config.ts` file, write the remote configuration you need to get the type, and then run the `mf dts` command. -If you are only using the runtime API, you need to create a temporary `module-federation.config.ts` file, configure [dts.consumeTypes.remoteTypeUrls](../../configure/dts#remotetypeurls), and then run the `mf dts` command. - -::: \ No newline at end of file +::: diff --git a/apps/website-new/docs/en/guide/basic/css-isolate.mdx b/apps/website-new/docs/en/guide/basic/css-isolate.mdx new file mode 100644 index 00000000000..8f991d8d1cd --- /dev/null +++ b/apps/website-new/docs/en/guide/basic/css-isolate.mdx @@ -0,0 +1,175 @@ +# Style Isolation + +To achieve CSS style isolation, the following methods can be used. Here are the detailed usage instructions for each method: + +## 1. BEM (Block Element Modifier) + +BEM is a naming convention that achieves style isolation by adding prefixes and suffixes to class names. The structure of BEM is as follows: + +- **Block**: Represents an independent functional block, e.g., `.button` +- **Element**: Represents a part of the block, e.g., `.button__text` +- **Modifier**: Represents different states or versions of the block or element, e.g., `.button--primary` + +### Usage Example +```html +
+ Button +
+``` + +```css +.button { + background-color: blue; + color: white; +} + +.button__text { + font-size: 16px; +} + +.button--primary { + background-color: green; +} +``` + +## 2. CSS Modules + +CSS Modules achieve style isolation by treating each CSS file as a module, where each class name is transformed into a unique identifier during compilation. + +### Usage Example + +1. Create a CSS file named `styles.module.css`: + +```css +/* styles.module.css */ +.button { + background-color: blue; + color: white; +} +``` + +2. Import and use it in a React component: + +```javascript +import React from 'react'; +import styles from './styles.module.css'; + +function App() { + return ; +} + +export default App; +``` + +## 3. CSS-in-JS + +CSS-in-JS writes styles directly in JavaScript files. Common libraries include [styled-components](https://styled-components.com/) and [emotion](https://github.com/emotion-js/emotion). + +### Usage Example + +1. Install styled-components: + +```bash +npm install styled-components +``` + +2. Use it in a React component: + +```javascript +import React from 'react'; +import styled from 'styled-components'; + +const Button = styled.button` + background: blue; + color: white; +`; + +function App() { + return ; +} + +export default App; +``` + +## 4. Shadow DOM + +Shadow DOM is part of the Web Components technology, creating an isolated DOM tree to achieve style isolation. React can use Shadow DOM via [react-shadow](https://www.npmjs.com/package/react-shadow). + +### Usage Example + +1. Create an HTML template: + +```html + +``` + +2. Define a custom element and attach Shadow DOM: + +```javascript +class MyComponent extends HTMLElement { + constructor() { + super(); + const shadow = this.attachShadow({ mode: 'open' }); + const template = document.getElementById('my-component').content; + shadow.appendChild(template.cloneNode(true)); + } +} + +customElements.define('my-component', MyComponent); +``` + +3. Use the custom element in HTML: + +```html + +``` + +## 5. Vue Scoped Styles + +In Vue, the `scoped` attribute can be used to achieve component-level style isolation. + +### Usage Example + +1. Define styles in a Vue component: + +```vue + + + +``` + +Each method has its own applicable scenarios and limitations. Developers can choose the appropriate style isolation solution based on project requirements. + +## FAQ + +### Why doesn't Module Federation directly handle CSS style isolation? + +The main reasons for not directly incorporating CSS isolation into Module Federation are: + +* CSS isolation can conflict significantly with shared dependencies. Shared dependencies aim to reuse common dependencies as much as possible, which can lead to some shared dependencies escaping the sandbox, making isolation uncontrollable and potentially affected by load order. + +* Runtime handling of CSS isolation can have many edge cases and troubleshooting issues can be very difficult, leading to decreased business stability. Common issues include: + * Shadow DOM: This method can cause compatibility issues with various component libraries, and troubleshooting online issues can be long and complex, with no guarantee of resolution. + * Collecting and clearing CSS: Due to the isolation and shared reuse issues mentioned above, CSS might be unintentionally cleared. + * Upgrading the sandbox in the consumer: The impact on business is uncontrollable. + +> Suggested handling methods: + +* Process CSS at the module or sub-application producer level to ensure that the module or application runs as expected in any environment. +* Use CSS modules, component library prefixes, and unified component library versions to solve the problem. +* Directly export Shadow DOM components for use by other businesses. diff --git a/apps/website-new/docs/en/guide/basic/rsbuild.mdx b/apps/website-new/docs/en/guide/basic/rsbuild.mdx index 7b95cc1d410..3289b899af2 100644 --- a/apps/website-new/docs/en/guide/basic/rsbuild.mdx +++ b/apps/website-new/docs/en/guide/basic/rsbuild.mdx @@ -82,7 +82,7 @@ export default defineConfig({ ``` ### Note -If you need to use the Module Federation runtime capabilities, please install [@module-federation/enhanced](/en/guide/basic/runtime.html) +If you need to use the Module Federation runtime capabilities, please install [@module-federation/enhanced](/en/guide/basic/runtime/runtime.html) ## Configuration diff --git a/apps/website-new/docs/en/guide/basic/rspack.mdx b/apps/website-new/docs/en/guide/basic/rspack.mdx index 746fe13811f..127c2fd1e20 100644 --- a/apps/website-new/docs/en/guide/basic/rspack.mdx +++ b/apps/website-new/docs/en/guide/basic/rspack.mdx @@ -10,6 +10,8 @@ Requires Rspack version 0.5.0 or above. - When a module has remote types, it will automatically download and consume the types of the remote modules. - Consuming remote modules will have hot update capabilities. +{props.tip} + ## Quick Start ### Installation @@ -20,65 +22,29 @@ import { PackageManagerTabs } from '@theme'; -### Register the Plugin - -#### Rspack - -In [Rspack](https://rspack.dev/), you can add the plugin in the `rspack.config.js` file: - -```ts title="rspack.config.js" -const { ModuleFederationPlugin } = require('@module-federation/enhanced/rspack'); - -module.exports = { - devServer: { - port: 2000, - }, - output: { - // You need to set a unique value that is not equal to other applications - uniqueName: 'federation_provider', - // publicPath must be configured if using manifest - publicPath: 'http://localhost:2000/', - }, - plugins: [ - new ModuleFederationPlugin({ - name: 'federation_provider', - exposes: { - './button': './src/button.tsx', - }, - shared: ['react', 'react-dom'], - }), - ], -}; -``` - -## Configure the Build Plugin - -- Type: `ModuleFederationPlugin(options: ModuleFederationOptions)` - -- The configuration structure for the Module Federation plugin is as follows: - -```ts -type ModuleFederationOptions = { - name: string; - filename?: string; - remotes?: Array; - shared?: ShareInfos; -}; -``` - -You can find detailed explanations of all configuration items on the [Configuration Overview](../../configure/index) page. +### Create {props.configName || 'module-federation.config.ts'} + +Create the {props.configName || 'module-federation.config.ts'} file with the following content: + +import CreateConfig from '@components/common/rspack/create-config'; + +{props.createConfig || } + +### Register Plugin + +In `Rspack`, you can add plugins through the `plugins` configuration item: + +import RegisterPlugin from '@components/common/rspack/register-plugin'; + +{props.registerPlugin || } + +## Configuration + +You can find detailed descriptions of all configuration items on the [Config Overview](../../configure/index) page. diff --git a/apps/website-new/docs/en/guide/basic/rspress.mdx b/apps/website-new/docs/en/guide/basic/rspress.mdx new file mode 100644 index 00000000000..02766150434 --- /dev/null +++ b/apps/website-new/docs/en/guide/basic/rspress.mdx @@ -0,0 +1,118 @@ +# Rspress Plugin + +:::info Note +Requires [Rspress version 2.0.0-beta.16](https://v2.rspress.rs/plugin/system/introduction) or higher. +::: + +Helps users build and consume {props.name || 'Module Federation'} products in **Rspress**. + +## Quick Start + +### Installation + +You can install the plugin with the following command: + +import InstallKit from '@components/common/install-kit'; + + + +### Create {props.configName || 'module-federation.config.ts'} + +Create the {props.configName || 'module-federation.config.ts'} file with the following content: + +import CreateConfig from '@components/common/rspress/create-config'; + +{props.createConfig || } + +### Register Plugin + +import RegisterPlugin from '@components/common/rspress/register-plugin'; +import React from 'react'; + +{props.registerPlugin || React.createElement(RegisterPlugin)} + +### Loading Document Fragments + +You can directly load exported document fragments in your `mdx` files. + +```mdx title='docs/en/guide/intro.mdx' +import Intro from 'mf-doc/intro-zh'; + +{/* Document fragments support passing parameters, which are consumed as props. */} + +``` + +Document fragments support passing parameters, which are consumed as props. + +If you need to use the `cmdTools` variable in a document fragment, you can refer to the following: + +```mdx title='docs/zh/guide/intro.mdx' +{(props.cmdTools || ['pkg-a', 'pkg-b']).map(cmdTool=>(

{cmdTool}

))} +``` + +## Configuration + +* Type: + +import ConfigType from '@components/common/rspress/config-type'; + +{props.configType || } + +### {props.pluginOptionName || 'moduleFederationOptions'} + +[{props.name || 'Module Federation'} Configuration](../../configure/index) + +### rspressOptions + +Additional configuration for the Rspress plugin. + +#### autoShared + +* Type: `boolean` +* Default: `true` + +Rspress uses `react`, `react-dom`, and `@mdx-js/react` as third-party dependencies. These three dependencies need to be singletons, so the `shared` configuration is automatically injected during the build. + +You can also set `autoShared: false` to disable this behavior. + +#### rebuildSearchIndex + +* Type: `boolean` +* Default: `true` + +Rspress automatically generates a search index during the build, but the generation process only supports `.mdx` or `.md` files. Therefore, when a Module Federation document fragment is loaded, it will not be searchable. + +To avoid this, the MF Rspress Plugin will regenerate the search index based on the rendered `html` after SSG is complete to support the search function. + +If you are using remoteSearch or other search functions, you can set `rebuildSearchIndex: false` to disable this behavior. + +> Note: This feature is only effective in ssg mode. + +## FAQ + +### Does it support local search? + +Only `ssg` mode is supported. For details, refer to [rebuildSearchIndex](#rebuildsearchindex). + +### Could not parse expression with swc: Expression expected" + +When referencing an MDX component, you may encounter the following error: + +```bash +File: "/root/docs/zh/guide/basic/mf.mdx" +Error: "23:8: Could not parse expression with swc: Expression expected" +``` + +This is an issue where Rspress fails to parse the expression correctly when parsing the MDX component. It can be resolved as follows: + +```diff +import RemoteIntroDoc from 'mf-doc/intro'; +import Head from '@components/Head'; ++ import React from 'react'; + +- } /> ++ + +``` diff --git a/apps/website-new/docs/en/guide/basic/runtime.mdx b/apps/website-new/docs/en/guide/basic/runtime.mdx deleted file mode 100644 index 291e150343d..00000000000 --- a/apps/website-new/docs/en/guide/basic/runtime.mdx +++ /dev/null @@ -1,486 +0,0 @@ -# Federation Runtime - -:::tip -Before reading this section, it is assumed that you have already understood: - -- The [features and capabilities](../start/index) of Module Federation -- The [glossary of terms](../start/glossary) for Module Federation -- How to [consume and expose modules](../start/quick-start) - -::: - -`Federation Runtime` is one of the main features of the new version of `Module Federation`. It supports registering shared dependencies at runtime, dynamically registering and loading remote modules. To understand the design principles of Runtime, you can refer to: [Why Runtime](#why-runtime). - -## Install Dependencies - -import { PackageManagerTabs } from '@theme'; - - - -## API - -```javascript -// You can use the runtime to load modules without depending on the build plugin -// When not using the build plugin, shared dependencies cannot be automatically configured -import { init, loadRemote } from '@module-federation/enhanced/runtime'; - -init({ - name: '@demo/app-main', - remotes: [ - { - name: "@demo/app1", - // mf-manifest.json is a file type generated in the new version of Module Federation build tools, providing richer functionality compared to remoteEntry - // Preloading depends on the use of the mf-manifest.json file type - entry: "http://localhost:3005/mf-manifest.json", - alias: "app1" - }, - { - name: "@demo/app2", - entry: "http://localhost:3006/remoteEntry.js", - alias: "app2" - }, - { - name: "@demo/app4", - entry: "http://localhost:3006/remoteEntry.mjs", - alias: "app2", - type: 'module' // tell federation its a certain format, like ESM module - }, - ], -}); - -// Load using alias -loadRemote<{add: (...args: Array)=> number }>("app2/util").then((md)=>{ - md.add(1,2,3); -}); -``` - -### init - -- Type: `init(options: InitOptions): void` -- Create a runtime instance, which can be called repeatedly, but only one instance exists. If you want to dynamically register remotes or plugins, use [registerPlugins](#registerplugins) or [registerRemotes](#registerremotes) -- InitOptions: - -```typescript -type InitOptions = { - // The name of the current consumer - name: string; - // The list of remote modules to depend on - // When using the version content, it needs to be used with snapshot, which is still under construction - remotes: Array; - // The list of dependencies that the current consumer needs to share - // When using the build plugin, users can configure the dependencies to be shared in the build plugin, and the build plugin will inject the shared dependencies into the runtime sharing configuration - // Shared must be manually passed in the version instance when passed at runtime because it cannot be directly passed at runtime. - shared?: { - [pkgName: string]: ShareArgs | ShareArgs[]; - }; - // Sharing strategy, which strategy will be used to decide whether to reuse the dependency - shareStrategy?: 'version-first' | 'loaded-first'; -}; - -type ShareArgs = - | (SharedBaseArgs & { get: SharedGetter }) - | (SharedBaseArgs & { lib: () => Module }); - -type SharedBaseArgs = { - version?: string; - shareConfig?: SharedConfig; - scope?: string | Array; - deps?: Array; - loaded?: boolean; -}; - -type SharedGetter = (() => () => Module) | (() => Promise<() => Module>); - -type Remote = (RemotesWithEntry | RemotesWithVersion) & RemoteInfoCommon; - -interface RemotesWithVersion { - name: string; - version: string; -}; - -interface RemotesWithEntry { - name: string; - entry: string; -}; - -interface RemoteInfoCommon { - alias?: string; - shareScope?: string; - type?: RemoteEntryType; - entryGlobalName?: string; -} - -type RemoteEntryType =|'var'|'module'|'assign'|'assign-properties'|'this'|'window'|'self'|'global'|'commonjs'|'commonjs2'|'commonjs-module'| 'commonjs-static'|'amd'|'amd-require'|'umd'|'umd2'|'jsonp'|'system'| string; -``` - -### loadRemote - -- Type: `loadRemote(id: string)` -- Used to load initialized remote modules. When used with the build plugin, it can be directly loaded through the native `import("remote name/expose")` syntax, and the build plugin will automatically convert it to `loadRemote("remote name/expose")` usage. - -- Example - -```javascript -import { init, loadRemote } from '@module-federation/enhanced/runtime'; - -init({ - name: '@demo/main-app', - remotes: [ - { - name: '@demo/app2', - alias: 'app2', - entry: 'http://localhost:3006/remoteEntry.js', - }, - ], -}); - -// remoteName + expose -loadRemote('@demo/app2/util').then((m) => m.add(1, 2, 3)); - -// alias + expose -loadRemote('app2/util').then((m) => m.add(1, 2, 3)); -``` - -### loadShare - -- Type: `loadShare(pkgName: string, extraOptions?: { customShareInfo?: Partial;resolver?: (sharedOptions: ShareInfos[string]) => Shared;})` -- Obtains the `share` dependency. When a "shared" dependency matching the current consumer exists in the global environment, the existing and eligible dependency will be reused first. Otherwise, it loads its own dependency and stores it in the global cache. -- This `API` is usually not called directly by users but is used by the build plugin to convert its own dependencies. - -```typescript -type ShareInfos = { - // The name of the dependency, basic information about the dependency, and sharing strategy - [pkgName: string]: Shared[]; -}; - -type Shared = { - // The version of the shared dependency - version: string; - // Which modules are currently consuming this dependency - useIn: Array; - // From which module does the shared dependency come? - from: string; - // Factory function to get the shared dependency instance. When no other existing dependencies, it will load its own shared dependencies. - lib?: () => Module; - // Sharing strategy, which strategy will be used to decide whether to reuse the dependency - shareConfig: SharedConfig; - // The scope where the shared dependency is located, the default value is default - scope: Array; - // Function to retrieve the shared dependency instance. - get: SharedGetter; - // List of dependencies that this shared module depends on - deps: Array; - // Indicates whether the shared dependency has been loaded - loaded?: boolean; - // Represents the loading state of the shared dependency - loading?: null | Promise; - // Determines if the shared dependency should be loaded eagerly - eager?: boolean; -}; -``` - -- Example - -```javascript -import { init, loadRemote, loadShare } from '@module-federation/enhanced/runtime'; -import React from 'react'; -import ReactDOM from 'react-dom'; - -init({ - name: '@demo/main-app', - remotes: [], - shared: { - react: { - version: '17.0.0', - scope: 'default', - lib: () => React, - shareConfig: { - singleton: true, - requiredVersion: '^17.0.0', - }, - }, - 'react-dom': { - version: '17.0.0', - scope: 'default', - lib: () => ReactDOM, - shareConfig: { - singleton: true, - requiredVersion: '^17.0.0', - }, - }, - }, -}); - -loadShare('react').then((reactFactory) => { - console.log(reactFactory()); -}); -``` - -If has set multiple version shared, `loadShare` will return the loaded and has max version shared. The behavior can be controlled by set `extraOptions.resolver`: - -```js -import { init, loadRemote, loadShare } from '@module-federation/runtime'; - -init({ - name: '@demo/main-app', - remotes: [], - shared: { - react: [ - { - version: '17.0.0', - scope: 'default', - get: async ()=>() => ({ version: '17.0.0' }), - shareConfig: { - singleton: true, - requiredVersion: '^17.0.0', - }, - }, - { - version: '18.0.0', - scope: 'default', - // pass lib means the shared has loaded - lib: () => ({ version: '18.0.0' }), - shareConfig: { - singleton: true, - requiredVersion: '^18.0.0', - }, - }, - ], - }, -}); - -loadShare('react', { - resolver: (sharedOptions) => { - return ( - sharedOptions.find((i) => i.version === '17.0.0') ?? sharedOptions[0] - ); - }, - }).then((reactFactory) => { - console.log(reactFactory()); // { version: '17.0.0' } -}); -``` - -### preloadRemote - -:::warning -The preloadRemote interface is only valid when the entry is a manifest file protocol -::: - -- Type: `preloadRemote(preloadOptions: Array)` -- Used to preload remote resources of modules, such as `remote-entry.js` and other js chunks, css files, using the preloadRemote API. - -- Type - -```typescript -async function preloadRemote(preloadOptions: Array) {} - -type depsPreloadArg = Omit; -type PreloadRemoteArgs = { - // The name and alias of the preloaded remote module - nameOrAlias: string; - // The specific expose of the preloaded remote module - // By default, all exposes are preloaded - // When provides exposes, only specific exposes are preloaded - exposes?: Array; // Default request - // Default is sync, only preloads synchronous chunks referenced in expose - // Set to all to load both synchronous and asynchronous referenced chunks - resourceCategory?: 'all' | 'sync'; - // By default, true, loads all sub-module dependencies of the current module - // After configuring dependencies, only the required resources are loaded - depsRemote?: boolean | Array; - // No filtering by default - // After configuration, unnecessary resources are filtered out - filter?: (assetUrl: string) => boolean; -}; -``` - -- Details - -Through `preloadRemote`, module resources can be preloaded at an earlier stage to avoid waterfall requests. `preloadRemote` can preload the following: - -- The `remote entry` of `remote` -- The `expose` resources in `remote` -- Synchronous and asynchronous resources in `remote` -- Dependencies of `remote` in `remote` - -* Example - -```typescript -import { init, preloadRemote } from '@module-federation/enhanced/runtime'; - -init({ - name: '@demo/preload-remote', - remotes: [ - { - name: '@demo/sub1', - entry: 'http://localhost:2001/mf-manifest.json', - }, - { - name: '@demo/sub2', - entry: 'http://localhost:2002/mf-manifest.json', - }, - { - name: '@demo/sub3', - entry: 'http://localhost:2003/mf-manifest.json', - }, - ], -}); - -// Preload the @demo/sub1 module -// Filter out resource information with 'ignore' in the resource name -// Only preload the sub-dependency @demo/sub1-button module -preloadRemote([ - { - nameOrAlias: '@demo/sub1', - filter(assetUrl) { - return assetUrl.indexOf('ignore') === -1; - }, - depsRemote: [{ nameOrAlias: '@demo/sub1-button' }], - }, -]); - -// Preload the @demo/sub2 module -// Preload all exposes of @demo/sub2 -// Preload synchronous and asynchronous resources of @demo/sub2 -preloadRemote([ - { - nameOrAlias: '@demo/sub2', - resourceCategory: 'all', - }, -]); - -// Preload the 'add' expose of the @demo/sub3 module -preloadRemote([ - { - nameOrAlias: '@demo/sub3', - resourceCategory: 'all', - exposes: ['add'], - }, -]); -``` - -### registerRemotes - -- Type: `registerRemotes(remotes: Remote[], options?: { force?: boolean }): void` -- Used to register remote modules after initialization. - -- Type - -```typescript -function registerRemotes(remotes: Remote[], options?: { force?: boolean }) {} - -type Remote = (RemoteWithEntry | RemoteWithVersion) & RemoteInfoCommon; - -interface RemoteInfoCommon { - alias?: string; - shareScope?: string; - type?: RemoteEntryType; - entryGlobalName?: string; -} - -interface RemoteWithEntry { - name: string; - entry: string; -} - -interface RemoteWithVersion { - name: string; - version: string; -} -``` - -- Details - **info**: Be cautious when setting `force: true`! - -If `force: true` is set, it will merge remotes (including those already loaded), remove the loaded remote cache, and use `console.warn` to warn that this operation may be risky. - -- Example - -```typescript -import { init, registerRemotes } from '@module-federation/enhanced/runtime'; - -init({ - name: '@demo/register-new-remotes', - remotes: [ - { - name: '@demo/sub1', - entry: 'http://localhost:2001/mf-manifest.json', - }, - ], -}); - -// Add new remote @demo/sub2 -registerRemotes([ - { - name: '@demo/sub2', - entry: 'http://localhost:2002/mf-manifest.json', - }, -]); - -// Override the previous remote @demo/sub1 -registerRemotes([ - { - name: '@demo/sub1', - entry: 'http://localhost:2003/mf-manifest.json', - }, -], { force: true }); -``` - -### registerPlugins - -- Type: `registerPlugins(plugins: FederationRuntimePlugin[]): void` -- Used to register plugins after initialization - -- Example - -```ts -import { registerPlugins } from '@module-federation/enhanced/runtime' -import runtimePlugin from 'custom-runtime-plugin.ts'; - -registerPlugins([runtimePlugin()]); -``` - -If you want to develop Module Federation plugin, you can read [Module Federation Plugin System](../../plugin/dev/index) for more info. - - -## FAQ - -### Differences Between Build Plugins and Runtime - -`Federation Runtime` is one of the main features of the new version of `Module Federation`. It supports registering shared dependencies at runtime, dynamically registering and loading remote modules, and can expand the capabilities of `Module Federation` at runtime through `Plugin`. Build plugins are based on the basic implementation of Runtime. - -There are the following differences between `Federation Runtime` and `Build Plugin`: - -| Federation Runtime | Build Plugin | -| ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -| Can be used independently of build plugins, and can directly use pure runtime for module loading in projects like `webpack4` | Build plugins require Webpack5, Rspack, Vite, or above | -| Supports dynamic registration of modules | Does not support dynamic registration of modules | -| Does not support loading modules with `import` syntax | Supports loading modules synchronously with `import` syntax | -| Supports `loadRemote` for loading modules | Supports `loadRemote` for loading modules | -| `shared` must provide specific version and instance information | `shared` only requires configuration rules, no specific version or instance information needed | -| `shared` dependencies can only be used externally, cannot use external `shared` dependencies | `shared` dependencies can be shared bidirectionally according to specific rules | -| Can affect the loading process through `runtime`'s `plugin` mechanism | Can affect the loading process through `runtimePlugin` configuration | -| Supports remote module type hints | Supports remote module type hints | - -### Why Runtime - -`Runtime` may be a completely new concept for users who previously used the built-in `Module Federation` build plugin in `Webpack`. In the previous Webpack's Module Federation, both exporting and consuming modules were purely build behaviors, with all module loading processes encapsulated by the build tool. -Compared to module loaders like Systemjs and esmodule, this brings the following two benefits: - -- The cost of exporting modules in existing projects is very low. No need to install excessive additional dependencies and build configurations, just declare the module name and the path of the exported module to complete the module export. -- Consuming remote modules only requires declaring the name and address of the remote module, which can be used like `NPM` dependencies with `import`. - -However, this model also brings the following impacts on the flexibility of the project and the maintenance cost of the build plugin: - -- Different build tools such as Webpack, Rspack, and Vite all need to implement `Module Federation`'s build tools and runtime separately, which impacts maintenance costs and feature consistency. -- Unable to consume remote modules in build plugins that do not support Module Federation, such as Webpack 4. -- Lack of flexibility, unable to dynamically add modules, change module behavior, and add more framework capabilities. - -Therefore, in the new version of `Module Federation` design, `Runtime` has been separated, and different build tools implement the export build of modules, collection of shared module information, and handling of remote module references based on `Runtime`. Other specific behaviors such as shared dependency reuse and remote module loading are all built into `Runtime`. diff --git a/apps/website-new/docs/en/guide/basic/runtime/_meta.json b/apps/website-new/docs/en/guide/basic/runtime/_meta.json new file mode 100644 index 00000000000..6cb45df1af5 --- /dev/null +++ b/apps/website-new/docs/en/guide/basic/runtime/_meta.json @@ -0,0 +1 @@ +["runtime", "runtime-api", "runtime-hooks"] diff --git a/apps/website-new/docs/en/guide/basic/runtime/runtime-api.mdx b/apps/website-new/docs/en/guide/basic/runtime/runtime-api.mdx new file mode 100644 index 00000000000..4beaf9e5b3a --- /dev/null +++ b/apps/website-new/docs/en/guide/basic/runtime/runtime-api.mdx @@ -0,0 +1,419 @@ +# Runtime API + +## init + +- Type: `init(options: InitOptions): void` +- Used for dynamically registering `Vmok` modules at runtime. +- InitOptions: + +```ts +type InitOptions { + // Name of the current host + name: string; + // List of dependent remote modules + // tip: The remotes configured at runtime are not completely consistent in type and data with those passed in by the build plugin. + remotes: Array; + // List of dependencies that the current host needs to share + // When using the build plugin, users can configure the dependencies to be shared in the build plugin, and the build plugin will inject the dependencies to be shared into the shared configuration at runtime. + // When shared is passed in at runtime, the version instance reference must be passed in manually, because it cannot be directly obtained at runtime. + shared?: { + [pkgName: string]: ShareArgs | ShareArgs[]; + }; +}; + +type ShareArgs = + | (SharedBaseArgs & { get: SharedGetter }) + | (SharedBaseArgs & { lib: () => Module }); + +type SharedBaseArgs = { + version: string; + shareConfig?: SharedConfig; + scope?: string | Array; + deps?: Array; + strategy?: 'version-first' | 'loaded-first'; +}; + +type SharedGetter = (() => () => Module) | (() => Promise<() => Module>); + +type RemoteInfo = { + alias?: string; +}; + +interface RemotesWithEntry { + name: string; + entry: string; +} + +type ShareInfos = { + // Package name and basic information of the dependency, sharing strategy + [pkgName: string]: Share; +}; + +type Share = { + // Version of the shared dependency + version: string; + // Which modules consume the current shared dependency + useIn?: Array; + // Which module the shared dependency comes from + from?: string; + // Factory function to get the instance of the shared dependency. When the cached shared instance cannot be loaded, it will load its own shared dependency. + lib: () => Module; + // Sharing strategy, which strategy will be used to determine the reuse of shared dependencies + shareConfig?: SharedConfig; + // Dependencies between shares + deps?: Array; + // Under which scope the current shared dependency is placed, the default is default + scope?: string | Array; +}; +``` + +- Example + +```js +import { init, loadRemote } from '@module-federation/enhanced/runtime'; + +init({ + name: "mf_host", + remotes: [ + { + name: "remote", + // After configuring an alias, it can be loaded directly through the alias + alias: "app1", + // Decide which module to load by specifying the address of the module's manifest.json file + entry: "http://localhost:2001/mf-manifest.json" + } + ], +}); +``` + +## loadRemote + +- Type: `loadRemote(id: string)` +- Loads a remote module. +- Example + +```javascript +import { init, loadRemote } from '@module-federation/enhanced/runtime'; + +init({ + name: "mf_host", + remotes: [ + { + name: "remote", + // Remote module alias. After configuring an alias, the module can be loaded through the alias. Please note: the prefixes of alias and name cannot be the same. + alias: "app1", + // Specify the remote module address ending with `mf-manifest.json`, which is generated by the mf plugin. + entry: "http://localhost:2001/vmok-manifest.json" + } + ] +}); + +// remoteName + expose +loadRemote("remote/util").then((m)=> m.add(1,2,3)); + +// alias + expose +loadRemote("app1/util").then((m)=> m.add(1,2,3)); +``` + +## loadShare + +- Type: `loadShare(pkgName: string, extraOptions?: { customShareInfo?: Partial;resolver?: (sharedOptions: ShareInfos[string]) => Shared;})` +- Gets a `share` dependency. When there is a `share` dependency in the global environment that meets the requirements of the current `host`, the existing dependency that meets the `share` conditions will be reused first. Otherwise, its own dependency will be loaded and stored in the global cache. +- This `API` is **generally not called directly by the user, but is used by the build plugin to transform its own dependencies**. + +- Example + +```js +import { init, loadRemote, loadShare } from '@module-federation/enhanced/runtime'; +import React from 'react'; +import ReactDom from 'react-dom'; + +init({ + name: "mf_host", + remotes: [], + shared: { + react: { + version: "17.0.0", + scope: "default", + lib: ()=> React, + shareConfig: { + singleton: true, + requiredVersion: "^17.0.0" + } + }, + "react-dom": { + version: "17.0.0", + scope: "default", + lib: ()=> ReactDom, + shareConfig: { + singleton: true, + requiredVersion: "^17.0.0" + } + } + } +}); + + +loadShare("react").then((reactFactory)=>{ + console.log(reactFactory()) +}); +``` + +If multiple versions of shared are set, the loaded shared with the highest version will be returned by default. This behavior can be changed by setting `extraOptions.resolver`: + +```js +import { init, loadRemote, loadShare } from '@module-federation/enhanced/runtime'; + +init({ + name: 'mf_host', + remotes: [], + shared: { + react: [ + { + version: '17.0.0', + scope: 'default', + get: async ()=>() => ({ version: '17.0.0' }), + shareConfig: { + singleton: true, + requiredVersion: '^17.0.0', + }, + }, + { + version: '18.0.0', + scope: 'default', + // pass lib means the shared has loaded + lib: () => ({ version: '18.0.0' }), + shareConfig: { + singleton: true, + requiredVersion: '^18.0.0', + }, + }, + ], + }, +}); + +loadShare('react', { + resolver: (sharedOptions) => { + return ( + sharedOptions.find((i) => i.version === '17.0.0') ?? sharedOptions[0] + ); + }, + }).then((reactFactory) => { + console.log(reactFactory()); // { version: '17.0.0)' } +}); +``` + +## preloadRemote + +- type + +```typescript +async function preloadRemote(preloadOptions: Array){} + +type depsPreloadArg = Omit; +type PreloadRemoteArgs = { + // Name or alias of the remote to be preloaded + nameOrAlias: string; + // Whether to preload the module's interface, the default value is false. For details, please refer to the chapter in . The @vmok/kit version needs to be greater than 1.7.6. + prefetchInterface?: boolean; + // The exposes to be preloaded + // By default, all exposes are preloaded + // When exposes are provided, only the required exposes will be loaded + exposes?: Array; // Default request + // The default is sync, which only loads the synchronous code referenced in expose + // When set to all, both synchronous and asynchronous references will be loaded + resourceCategory?: 'all' | 'sync'; + // When no value is configured, all dependencies are loaded by default + // After configuring dependencies, only the configuration options will be loaded + depsRemote?: boolean | Array; + // When not configured, resources are not filtered + // After configuration, unnecessary resources will be filtered + filter?: (assetUrl: string) => boolean; +}; +``` + +- Details + +Through `preloadRemote`, you can start preloading module resources at an earlier stage to avoid waterfall requests. What can `preloadRemote` preload: + +* `remote`'s `remoteEntry` +* `remote`'s `expose` +* `remote`'s synchronous or asynchronous resources +* `remote`'s dependent `remote` resources + +- Example + +```ts +import { init, preloadRemote } from '@module-federation/enhanced/runtime'; +init({ + name: 'mf_host', + remotes: [ + { + name: 'sub1', + entry: "http://localhost:2001/mf-manifest.json" + }, + { + name: 'sub2', + entry: "http://localhost:2002/mf-manifest.json" + }, + { + name: 'sub3', + entry: "http://localhost:2003/mf-manifest.json" + }, + ], +}); + +// Preload the @demo/sub1 module +// Filter resource information that contains 'ignore' in the resource name +// Only preload the sub-dependency @demo/sub1-button module +preloadRemote([ + { + nameOrAlias: 'sub1', + filter(assetUrl) { + return assetUrl.indexOf('ignore') === -1; + }, + depsRemote: [{ nameOrAlias: 'sub1-button' }], + }, +]); + + +// Preload the @demo/sub2 module +// Preload all exposes under @demo/sub2 +// Preload the synchronous and asynchronous resources of @demo/sub2 +preloadRemote([ + { + nameOrAlias: 'sub2', + resourceCategory: 'all', + }, +]); + +// Preload the add expose of the @demo/sub3 module +preloadRemote([ + { + nameOrAlias: 'sub3', + resourceCategory: 'all', + exposes: ['add'], + }, +]); +``` + +## registerRemotes + +- type + +```typescript +function registerRemotes(remotes: Remote[], options?: { force?: boolean }) {} + +type Remote = (RemoteWithEntry | RemoteWithVersion) & RemoteInfoCommon; + +interface RemoteInfoCommon { + alias?: string; + shareScope?: string; + type?: RemoteEntryType; + entryGlobalName?: string; +} + +interface RemoteWithEntry { + name: string; + entry: string; +} + +interface RemoteWithVersion { + name: string; + version: string; +} +``` + +- Details + +**info**: Please set `force:true` with caution! + +If `force: true` is set, it will overwrite the registered (and loaded) modules, and automatically delete the cache of the loaded modules (if they have been loaded), and output a warning in the console to inform that this operation is risky. + +* Example + +```ts +import { init, registerRemotes } from '@module-federation/enhanced/runtime'; + +init({ + name: 'mf_host', + remotes: [ + { + name: 'sub1', + entry: 'http://localhost:2001/mf-manifest.json', + } + ], +}); + +// Add a new remote @demo/sub2 +registerRemotes([ + { + name: 'sub2', + entry: 'http://localhost:2002/mf-manifest.json', + } +]); + +// Overwrite the previous remote @demo/sub1 +registerRemotes([ + { + name: 'sub1', + entry: 'http://localhost:2003/mf-manifest.json', + } +],{ force:true }); +``` + +## registerPlugins + +- type + +```typescript +function registerPlugins(plugins: FederationRuntimePlugin[]) {} +``` + +* Example + +```ts +import { init, registerPlugins } from '@module-federation/enhanced/runtime'; +import runtimePlugin from './custom-runtime-plugin'; + +init({ + name: 'mf_host', + remotes: [ + { + name: 'sub1', + entry: 'http://localhost:2001/mf-manifest.json', + } + ], +}); + +// Add a new runtime plugin +registerPlugins([runtimePlugin()]); + +registerPlugins([ + { + name: 'custom-plugin-runtime', + beforeInit(args) { + const { userOptions, origin } = args; + if (origin.options.name && origin.options.name !== userOptions.name) { + userOptions.name = origin.options.name; + } + console.log('[build time inject] beforeInit: ', args); + return args; + }, + beforeLoadShare(args) { + console.log('[build time inject] beforeLoadShare: ', args); + return args; + }, + createLink({ url }) { + const link = document.createElement('link'); + link.setAttribute('href', url); + link.setAttribute('rel', 'preload'); + link.setAttribute('as', 'script'); + link.setAttribute('crossorigin', 'anonymous'); + return link; + }, + } +]); +``` + + diff --git a/apps/website-new/docs/en/guide/basic/runtime/runtime-hooks.mdx b/apps/website-new/docs/en/guide/basic/runtime/runtime-hooks.mdx new file mode 100644 index 00000000000..cd6b8f436a0 --- /dev/null +++ b/apps/website-new/docs/en/guide/basic/runtime/runtime-hooks.mdx @@ -0,0 +1,379 @@ +# Runtime Hooks + +## beforeInit + +`SyncWaterfallHook` + +Updates the corresponding init configuration before the MF instance is initialized. + +* type + +```ts +function beforeInit(args: BeforeInitOptions): BeforeInitOptions + +type BeforeInitOptions ={ + userOptions: UserOptions; + options: FederationRuntimeOptions; + origin: FederationHost; + shareInfo: ShareInfos; +} + +interface FederationRuntimeOptions { + id?: string; + name: string; + version?: string; + remotes: Array; + shared: ShareInfos; + plugins: Array; + inBrowser: boolean; +} +``` + +## init + +`SyncHook` + +Called after the MF instance is initialized. + +* type + +```ts +function init(args: InitOptions): void + +type InitOptions ={ + options: FederationRuntimeOptions; + origin: FederationHost; +} +``` + +## beforeRequest + +`AsyncWaterfallHook` + +Called before resolving the remote path, useful for updating something before lookup. + +* type + +```ts +async function beforeRequest(args: BeforeRequestOptions): Promise + +type BeforeRequestOptions ={ + id: string; + options: FederationRuntimeOptions; + origin: FederationHost; +} +``` + +## afterResolve + +`AsyncWaterfallHook` + +Called after resolving the remote path, allowing modification of the resolved content. + +* type + +```ts +async function afterResolve(args: AfterResolveOptions): Promise + +type AfterResolveOptions ={ + id: string; + pkgNameOrAlias: string; + expose: string; + remote: Remote; + options: FederationRuntimeOptions; + origin: FederationHost; + remoteInfo: RemoteInfo; + remoteSnapshot?: ModuleInfo; +} +``` + +## onLoad + +`AsyncHook` + +Triggered once a federated module is loaded, allowing access and modification to the exports of the loaded file. + +* type + +```ts +async function onLoad(args: OnLoadOptions): Promise + +type OnLoadOptions ={ + id: string; + expose: string; + pkgNameOrAlias: string; + remote: Remote; + options: ModuleOptions; + origin: FederationHost; + exposeModule: any; + exposeModuleFactory: any; + moduleInstance: Module; +} + +type ModuleOptions = { + remoteInfo: RemoteInfo; + host: FederationHost; +} + +interface RemoteInfo { + name: string; + version?: string; + buildVersion?: string; + entry: string; + type: RemoteEntryType; + entryGlobalName: string; + shareScope: string; +} +``` + +## handlePreloadModule + +`SyncHook` + +Handles the preloading logic for remotes. + +* type + +```ts +function handlePreloadModule(args: HandlePreloadModuleOptions): void + +type HandlePreloadModuleOptions ={ + id: string; + name: string; + remoteSnapshot: ModuleInfo; + preloadConfig: PreloadRemoteArgs; +} +``` + +## errorLoadRemote + +`AsyncHook` + +Called if loading remotes fails, enabling custom error handling. Can return a custom fallback logic. + +* type + +```ts +async function errorLoadRemote(args: ErrorLoadRemoteOptions): Promise + +type ErrorLoadRemoteOptions ={ + id: string; + error: unknown; + from: 'build' | 'runtime'; + origin: FederationHost; +} +``` +* example + +```ts +import { init, loadRemote } from '@module-federation/enhanced/runtime' + +import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; + +const fallbackPlugin: () => FederationRuntimePlugin = + function () { + return { + name: 'fallback-plugin', + errorLoadRemote(args) { + const fallback = 'fallback' + return fallback; + }, + }; + }; + + +init({ + name: 'mf_host', + remotes: [ + { + name: "remote", + alias: "app1", + entry: "http://localhost:2001/mf-manifest.json" + } + ], + plugins: [fallbackPlugin()] +}); + +loadRemote('app1/un-existed-module').then(mod=>{ + expect(mod).toEqual('fallback'); +}) +``` + +## beforeLoadShare + +`AsyncWaterfallHook` + +Called before loading shared, can be used to modify the corresponding shared configuration. + +* type + +```ts +async function beforeLoadShare(args: BeforeLoadShareOptions): Promise + +type BeforeLoadShareOptions ={ + pkgName: string; + shareInfo?: Shared; + shared: Options['shared']; + origin: FederationHost; +} +``` + +## resolveShare + +`SyncWaterfallHook` + +Allows manual setting of the actual shared module to be used. + +* type + +```ts +function resolveShare(args: ResolveShareOptions): ResolveShareOptions + +type ResolveShareOptions ={ + shareScopeMap: ShareScopeMap; + scope: string; + pkgName: string; + version: string; + GlobalFederation: Federation; + resolver: () => Shared | undefined; +} +``` + +* example + +```ts +import { init, loadRemote } from '@module-federation/enhanced/runtime' + +import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; + +const customSharedPlugin: () => FederationRuntimePlugin = + function () { + return { + name: 'custom-shared-plugin', + resolveShare(args) { + const { shareScopeMap, scope, pkgName, version, GlobalFederation } = args; + + if ( + pkgName !== 'react' + ) { + return args; + } + + args.resolver = function () { + shareScopeMap[scope][pkgName][version] = window.React; // replace local share scope manually with desired module + return shareScopeMap[scope][pkgName][version]; + }; + return args; + }, + }; + }; + + +init({ + name: 'mf_host', + shared: { + react: { + version: '17.0.0', + scope: 'default', + lib: () => React, + shareConfig: { + singleton: true, + requiredVersion: '^17.0.0', + }, + }, + }, + plugins: [customSharedPlugin()] +}); + +window.React = ()=> 'Desired Shared'; + +loadShare("react").then((reactFactory)=>{ + expect(reactFactory()).toEqual(window.React()); +}); +``` + +## beforePreloadRemote + +`AsyncHook` + +Called before the preload handler executes any preload logic. + +* type + +```ts +async function beforePreloadRemote(args: BeforePreloadRemoteOptions): BeforePreloadRemoteOptions + +type BeforePreloadRemoteOptions ={ + preloadOps: Array; + options: Options; + origin: FederationHost; +} +``` + +## generatePreloadAssets + +`AsyncHook` + +Used to control the generation of resources that need to be preloaded. + +* type + +```ts +async function generatePreloadAssets(args: GeneratePreloadAssetsOptions): Promise + +type GeneratePreloadAssetsOptions ={ + origin: FederationHost; + preloadOptions: PreloadOptions[number]; + remote: Remote; + remoteInfo: RemoteInfo; + remoteSnapshot: ModuleInfo; + globalSnapshot: GlobalModuleInfo; +} + +interface PreloadAssets { + cssAssets: Array; + jsAssetsWithoutEntry: Array; + entryAssets: Array; +} +``` + +## loaderHook + +## createScript + +`SyncHook` + +Used to modify the script when loading resources. + +* type + +```ts +function createScript(args: CreateScriptOptions): HTMLScriptElement | void + +type CreateScriptOptions ={ + url: string; +} +``` + +* example + +```ts +import { init } from '@module-federation/enhanced/runtime' +import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; + +const changeScriptAttributePlugin: () => FederationRuntimePlugin = + function () { + return { + name: 'change-script-attribute', + createScript({ url }) { + if (url === testRemoteEntry) { + let script = document.createElement('script'); + script.src = testRemoteEntry; + script.setAttribute('loader-hooks', 'isTrue'); + script.setAttribute('crossorigin', 'anonymous'); + return script; + } + } + }; + }; +``` diff --git a/apps/website-new/docs/en/guide/basic/runtime/runtime.mdx b/apps/website-new/docs/en/guide/basic/runtime/runtime.mdx new file mode 100644 index 00000000000..b6572241113 --- /dev/null +++ b/apps/website-new/docs/en/guide/basic/runtime/runtime.mdx @@ -0,0 +1,156 @@ +# Runtime Access + +:::tip + +Before reading this chapter, it is assumed that you already understand: + +- The [features and capabilities](../../start/index) of Module Federation +- The [glossary](../../start/glossary) of Module Federation +- How to [consume and export modules](../../start/quick-start) + +::: + + +Currently, `Module Federation` provides two ways to register and load modules: + +- One is to declare it in the build plugin (usually in the `module-federation.config.ts` file) + +- The other way is to directly register and load modules through the `runtime` API. + +The two modes are not conflicting and can be used together. You can flexibly choose the module registration method and timing according to your actual scenario. + +
+**The differences between registering modules at runtime and registering modules in the build configuration are as follows:** + +|Registering modules at runtime|Registering modules in the plugin| +|------------------------------------------------------------------------------------------------------------------|---------------------- | +|Can be used without the build plugin, and pure runtime can be used directly for module registration and loading in projects like `webpack4`|The build plugin needs to be webpack5 or above| +|Supports dynamic module registration|Does not support dynamic module registration| +|Does not support loading modules with `import` syntax|Supports loading modules with `import` synchronous syntax| +|Supports loading modules with `loadRemote`|Supports loading modules with `loadRemote`| +|Setting `shared` must provide specific version and instance information|Setting `shared` only requires configuring rules, without providing specific version and instance information| +|`shared` dependencies can only be used externally, and external `shared` dependencies cannot be used|`shared` dependencies are shared bidirectionally according to specific rules| +|The loading process can be affected through the `runtime`'s `plugin` mechanism|Currently does not support providing `plugin` to affect the loading process| +|Does not support remote type hints|Supports remote type hints| + +## Installation + +import { PackageManagerTabs } from '@theme'; + + + +::: tip Note: + +- In the following `Federation Runtime` examples, we all show cases that are detached from specific frameworks such as Modern.js, so the API will be exported from the initial `@module-federation/enhanced/runtime` package. + +- If your project is a Modern.js project and uses `@module-federation/modern-js`, the runtime should export the runtime API from `@module-federation/modern-js/runtime`. This ensures that the plugin and the runtime use the same runtime instance, ensuring that modules are loaded normally. + +- If your project is a Modern.js project but does not use `@module-federation/modern-js`, you should export the runtime API from `@module-federation/enhanced/runtime`. However, we recommend that you use `@module-federation/modern-js` for module registration and loading, which will allow you to enjoy more capabilities combined with the framework. + +::: + + +## Module Registration + +```ts +import { init, loadRemote } from '@module-federation/enhanced/runtime'; + +init({ + // The name of the consumer module, required. If the ModuleFederationPlugin plugin is also registered, the `name` should be consistent with the `name`[/configure/name.html] configured in the plugin, + name: 'mf_host', + // Remote module registration information, including module name, alias, version, etc. + remotes: [ + { + name: "remote1", + alias: "remotes-1", + entry: "http://localhost:3001/mf-manifest.json" + } + ], +}); +``` + +## Module Loading + +```tsx +import { loadRemote } from '@module-federation/enhanced/runtime'; + +export default () => { + const MyButton = React.lazy(() => + loadRemote('remote1').then(({ MyButton }) => { + return { + default: MyButton + }; + }), + ); + + return ( + + + + ); +} +``` + +### Loading Anonymous Modules + +```tsx +import React from 'react'; +import { loadRemote } from '@module-federation/enhanced/runtime'; + +const RemoteButton = React.lazy(() => loadRemote('provider/button')); +// Can also be loaded by module alias: +// const RemoteButton = React.lazy(() => loadRemote('remotes-1/button')); + +export default () => { + return ( + + + + ); +} +``` + +### Loading Named Modules + +```tsx +import React from 'react'; +import { loadRemote } from '@module-federation/enhanced/runtime'; + +export default () => { + const RemoteButton = React.lazy(() => + loadRemote('remote1/button').then(({ RemoteButton }) => { + return { + default: RemoteButton + }; + }), + ); + return ( + + + + ); +} +``` + +### Loading Utility Functions + +```ts +import { loadRemote } from '@module-federation/enhanced/runtime'; + +// Load the util exposed by app2 +loadRemote<{add: (...args: Array)=> number }>("@demo/app2/util").then((md)=>{ + md.add(1,2); +}); + +// Load by alias: +// loadRemote<{add: (...args: Array)=> number }>("app3/util").then((md)=>{ +// md.add(1,2); +// }); +``` diff --git a/apps/website-new/docs/en/guide/basic/type-prompt.mdx b/apps/website-new/docs/en/guide/basic/type-prompt.mdx index a9897f7fb5e..76a4027ad2c 100644 --- a/apps/website-new/docs/en/guide/basic/type-prompt.mdx +++ b/apps/website-new/docs/en/guide/basic/type-prompt.mdx @@ -1,20 +1,20 @@ -# Remote Type Hints +# Type Hinting -Just like NPM Packages, `Module Federation` products also generate types and enjoy type hot reloading, although the products are hosted on a remote CDN. +Just like an NPM Package, {props.name || 'Module Federation'} artifacts also generate types and enjoy hot-reloading of types, even though the artifacts are hosted on a remote CDN. -`@module-federation/enhanced` enables the type prompt function by default. This article will introduce several common usage scenarios and specific configurations. +

{props.pkgName || '@module-federation/enhanced'} has type hinting enabled by default. This article will introduce several common usage scenarios and their specific configurations.

-## Use Cases +## Usage Scenarios -### Generate type +### Generating Types -Building using the build plugin provided by `@module-federation/enhanced` will automatically generate type files. +

When building with the build plugin provided by {props.pkgName || '@module-federation/enhanced'}, type files are automatically generated.

-### Consume type +### Loading Types 1. Consumer modifies `tsconfig.json` -add `paths` to `tsconfig.json`: +Add `paths` in `tsconfig.json`: ```json { @@ -22,29 +22,29 @@ add `paths` to `tsconfig.json`: "paths": { "*": ["./@mf-types/*"] } - }, + } } ``` -2. Start consumer and provider +2. Start the consumer and producer -> info: No need to care the startup sequence of provider and consumer +> info: You don't need to worry about the startup order of the producer and consumer. -After startup, the consumer will automatically pull the provider's type file. +After starting, the consumer will automatically fetch the producer's type files. -### Type hot reload +### Type Hot Reloading -After modifying the producer code, the consumer will automatically pull the producer's type. +After modifying the producer's code, the consumer will automatically fetch the producer's types. ![hot-types-reload-static](@public/guide/type-prompt/hot-types-reload-static.gif) -### Federation Runtime API type prompt +### Runtime API Type Hinting :::info -If the builder is `webpack`, you also need to add `**/@mf-types/**` to [watchOptions.ignored](https://webpack.js.org/configuration/watch/#watchoptionsignored) to avoid Circular compilation issues caused by type updates +If the builder is `webpack`, you also need to add `**/@mf-types/**` to [watchOptions.ignored](https://webpack.js.org/configuration/watch/#watchoptionsignored) to avoid circular compilation issues caused by type updates. ::: -It needs to add `./@mf-types/*` in the `include` field to enjoy `Federation Runtime` `loadRemote` type hints and type hot reloading +You need to add `./@mf-types/*` to the `include` field to enjoy `Runtime` `loadRemote` type hinting and type hot reloading. ```json title="tsconfig.json" { @@ -54,39 +54,48 @@ It needs to add `./@mf-types/*` in the `include` field to enjoy `Federation Runt ![hot-types-reload](@public/guide/type-prompt/hot-types-reload.gif) -### Third-party package type extraction +### Third-Party Package Type Extraction -`Module Federation` will automatically extract third-party package types to ensure that `exposes` file types can be accessed normally. +{props.name || 'Module Federation'} automatically extracts third-party package types to ensure that `exposes` file types can be accessed normally. ![third-party](@public/guide/type-prompt/third-party.jpg) -### Nested type re-export +### Nested Type Re-export -`Module Federation` supports nested remotes and will automatically extract the nested remotes type. +{props.name || 'Module Federation'} supports nested remotes and will automatically extract the types of nested remotes. ![nested-remote](@public/guide/type-prompt/nested-remote.jpg) -### dynamic type hints +### Dynamic Type Hinting -`Module Federation` supports loading dynamic types and generally supports hot updates. +{props.name || 'Module Federation'} supports loading dynamic types and also supports hot updates. ![dynamic-remote-hot-types-reload](@public/guide/type-prompt/dynamic-remote-hot-types-reload-static.gif) ## Configuration -- [dts](../../configure/dts):generate/load type -- [dev](../../configure/dev):type hot reload +- [dts](../../configure/dts): Type generation/loading +- [dev](../../configure/dev): Type hot reloading ## FAQ -1. The project compiles in a loop and cannot be terminated +### Project has circular compilation and cannot be terminated -A: It is most likely caused by the compiler monitoring changes in the type folder or `dist`. Just add `@mf-types` to the ignore file. +A: It is likely that the compiler is watching for changes in the type folder or the `dist` directory. Add `@mf-types` to the ignored files. -Take webpack/rspack as an example: +For example, with webpack/rspack: ```ts config.watchOptions = { ignored: ['**/node_modules/**', '**/@mf-types/**'], }; ``` + +### 2. No type files are generated, how to find the reason? + +**Solution** + +1. Add the `FEDERATION_DEBUG=true` environment variable before the project startup command. +2. Set [dts.displayErrorInTerminal](../../configure/dts#displayerrorinterminal) to `true`. +3. Start the project and check the console output. +4. (Optional) If there is no error in the console, check the `.mf/typesGenerate.log` log file. diff --git a/apps/website-new/docs/en/guide/basic/webpack.mdx b/apps/website-new/docs/en/guide/basic/webpack.mdx index 50c79e3e7c8..97c8b8198a2 100644 --- a/apps/website-new/docs/en/guide/basic/webpack.mdx +++ b/apps/website-new/docs/en/guide/basic/webpack.mdx @@ -1,79 +1,43 @@ # Webpack Plugin -- Can build modules that meet the `Module Federation` loading specifications. -- Can consume modules that adhere to the `Module Federation` specifications using aliases. -- Can configure shared dependencies for modules, so that when the host environment of the loaded module already has the corresponding dependency, it will not be loaded again. -- When a module has remote types, it will automatically download and consume the types of the remote modules. -- Consuming remote modules will have hot update capabilities. +- Capable of building modules that meet the {props.name || 'Module Federation'} loading specification. +- Capable of consuming modules of the {props.name || 'Module Federation'} specification using aliases. +- Capable of setting the shared dependency configuration for modules. When the host environment where the module is loaded already has the corresponding dependency, it will not be loaded repeatedly. +- When a module has a remote type, the type of the remote module will be automatically downloaded for consumption. +- Hot-reloading capability when consuming remote modules. + +{props.tip} ## Quick Start +{props.demo} + ### Installation -You can install the plugin with the following commands: +You can install the plugin with the following command: -import { PackageManagerTabs } from '@theme'; +import InstallKit from '@components/common/install-kit'; - -### Register the Plugin - -In `Webpack`, you can add the plugin through the `plugins` configuration item in the `webpack.config.js` file: - -:::warning Public Path Auto & Manifest -When using mf-manifest.json remotes `publicPath: 'auto'` support is experimental -::: - -```ts title='webpack.config.js' -import { ModuleFederationPlugin } from '@module-federation/enhanced/webpack'; -module.exports = { - devServer: { - port: 2000, - }, - output: { - publicPath: 'http://localhost:2000/', // or auto - }, - plugins: [ - new ModuleFederationPlugin({ - name: 'webpack_provider', - filename: 'remoteEntry.js', - exposes: { - // Set the modules to be exported, default export as '.' - './button': './src/components/button', - }, - shared: { - react: { - singleton: true, - }, - 'react-dom': { - singleton: true, - }, - }, - }), - ], -}; -``` - -## Configure the Build Plugin - -- Type: `ModuleFederationPlugin(options: ModuleFederationOptions)` - -- The configuration structure for the Module Federation plugin is as follows: - -```ts -type ModuleFederationOptions = { - name: string; - filename?: string; - remotes?: Array; - shared?: ShareInfos; -}; -``` - -You can find detailed explanations of all configuration items on the [Configuration Overview](../../configure/index) page. +### Create {props.configName || 'module-federation.config.js'} + +Create a {props.configName || 'module-federation.config.js'} file with the following content: + +import CreateConfig from '@components/common/webpack/create-config'; + +{props.createConfig || } + +### Register Plugin + +In `Webpack`, you can add the plugin through the `plugins` configuration item in the `webpack.config.js` configuration file: + +import RegisterPlugin from '@components/common/webpack/register-plugin'; + +{props.registerPlugin || } + +## Configuration + +You can find detailed descriptions of all configuration items on the [Config Overview](../../configure/index) page. diff --git a/apps/website-new/docs/en/guide/debug/mode.mdx b/apps/website-new/docs/en/guide/debug/mode.mdx index e1a2a80f11c..3839c551776 100644 --- a/apps/website-new/docs/en/guide/debug/mode.mdx +++ b/apps/website-new/docs/en/guide/debug/mode.mdx @@ -1,6 +1,6 @@ # Enable debug mode -To facilitate troubleshooting, Module Federation provides a debug mode. You can add the FEDERATION_DEBUG=true environment variable when executing a build or execute `localStorage.setItem('FEDERATION_DEBUG','true')` in the browser to enable the debug mode of Module Federation. +To facilitate troubleshooting, {props.name || 'Module Federation'} provides a debug mode. You can add the FEDERATION_DEBUG=true environment variable when executing a build or execute `localStorage.setItem('FEDERATION_DEBUG','true')` in the browser to enable the debug mode of Module Federation. Build and add environment variables: @@ -22,4 +22,4 @@ localStorage.setItem('FEDERATION_DEBUG','true') After enabling debug mode, you can see the following information in the console: -In debug mode, you will see some logs starting with `[ Module Federation ]` output in the terminal. +

In debug mode, you will see some logs starting with [ {props.name || 'Module Federation'} ] output in the terminal.

diff --git a/apps/website-new/docs/en/guide/framework/modernjs.mdx b/apps/website-new/docs/en/guide/framework/modernjs.mdx index ded63d08eec..5286aeb6ad3 100644 --- a/apps/website-new/docs/en/guide/framework/modernjs.mdx +++ b/apps/website-new/docs/en/guide/framework/modernjs.mdx @@ -299,7 +299,7 @@ Not supported yet. ## API -In addition to exporting [MF Runtime](../basic/runtime), `@module-federation/modern-js/runtime` also provides a series of APIs to help developers better use Module Federation. +In addition to exporting [MF Runtime](../basic/runtime/runtime-api), `@module-federation/modern-js/runtime` also provides a series of APIs to help developers better use Module Federation. To prevent conflicts with Shared modules, you need to import them as follows: diff --git a/apps/website-new/docs/en/guide/framework/nextjs.mdx b/apps/website-new/docs/en/guide/framework/nextjs.mdx index 352ec9f27da..574c2031534 100644 --- a/apps/website-new/docs/en/guide/framework/nextjs.mdx +++ b/apps/website-new/docs/en/guide/framework/nextjs.mdx @@ -177,7 +177,7 @@ new NextFederationPlugin({ ## Utilities -`loadRemote` has been removed - you can take advantage of the new runtime apis: https://module-federation.io/guide/basic/runtime.html#loadremote +`loadRemote` has been removed - you can take advantage of the new runtime apis: https://module-federation.io/guide/basic/runtime/runtime-api.html#loadremote **revalidate** diff --git a/apps/website-new/docs/en/guide/start/features.mdx b/apps/website-new/docs/en/guide/start/features.mdx index dfb85c12b06..59f0b54d430 100644 --- a/apps/website-new/docs/en/guide/start/features.mdx +++ b/apps/website-new/docs/en/guide/start/features.mdx @@ -6,7 +6,7 @@ Here, you will discover the key functionalities offered through Module Federatio | Feature | Description | | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -| [Federation Runtime](../basic/runtime) | Capable of operating independently of build plugins: registering remote modules, consuming remote modules, registering shared dependencies | +| [Federation Runtime](../basic/runtime/runtime) | Capable of operating independently of build plugins: registering remote modules, consuming remote modules, registering shared dependencies | | [Webpack Plugin](../basic/webpack) | Supports consuming and generating remote modules with Webpack | | [Rspack Plugin](../basic/rspack) | Supports consuming and generating remote modules with Rspack | | [TypeScript Hints](../basic/type-prompt) | Supports dynamic module type hints and remote types | diff --git a/apps/website-new/docs/en/guide/start/index.mdx b/apps/website-new/docs/en/guide/start/index.mdx index bb5c6d2a102..a92d9ca5bcd 100644 --- a/apps/website-new/docs/en/guide/start/index.mdx +++ b/apps/website-new/docs/en/guide/start/index.mdx @@ -19,7 +19,7 @@ Module Federation has the following features: - ⚡ Code sharing, Dependency reuse - 📝 Manifest -- 🎨 [Module Federation Runtime](../basic/runtime.mdx) +- 🎨 [Module Federation Runtime](../basic/runtime/runtime.mdx) - 🧩 [Runtime Plugins System](../../plugin/dev/index.mdx) - 🚀 [Dynamic type prompt](../basic/type-prompt.mdx) - 🛠️ [Chrome Devtool](../debug/chrome-devtool) diff --git a/apps/website-new/docs/en/guide/start/npm-packages.mdx b/apps/website-new/docs/en/guide/start/npm-packages.mdx index 1bf58738993..ebba8bd33fa 100644 --- a/apps/website-new/docs/en/guide/start/npm-packages.mdx +++ b/apps/website-new/docs/en/guide/start/npm-packages.mdx @@ -10,7 +10,7 @@ The core package of Module Federation, serving as a Webpack build plugin, Rspack - [npm](https://npmjs.com/package/@module-federation/enhanced) - [Source Code](https://github.com/module-federation/core/tree/main/packages/enhanced) -- [Runtime Documentation](/guide/basic/runtime) +- [Runtime Documentation](/guide/basic/runtime/runtime) - [Rspack Build Plugin](/guide/basic/rspack) - [Webpack Build Plugin](/guide/basic/webpack) @@ -22,7 +22,7 @@ The Runtime package of Module Federation, typically used with @module-federation - [npm](https://npmjs.com/package/@module-federation/runtime) - [Source Code](https://github.com/module-federation/core/tree/main/packages/runtime) -- [Documentation](/guide/basic/runtime) +- [Documentation](/guide/basic/runtime/runtime) ## @module-federation/rspack diff --git a/apps/website-new/docs/en/guide/troubleshooting/build/BUILD-002.mdx b/apps/website-new/docs/en/guide/troubleshooting/build/BUILD-002.mdx new file mode 100644 index 00000000000..50ffd1670a9 --- /dev/null +++ b/apps/website-new/docs/en/guide/troubleshooting/build/BUILD-002.mdx @@ -0,0 +1,37 @@ +import ErrorCodeTitle from '@components/ErrorCodeTitle'; + + + +## Cause + +When building an Rspress producer, the project needs to set `publicPath`, otherwise it cannot be loaded normally by other consumers. + +## Solution + +Set `publicPath`. You can set it in the following ways: + +* Set [builderConfig.output.assetPrefix](https://rsbuild.rs/config/output/asset-prefix) in `rspress.config.ts` + +```ts title="rspress.config.ts" +export default { + builderConfig: { + output: { + assetPrefix: 'https://module-federation.io/', + } + } +} +``` + +* Set [builderConfig.tools.rspack](https://rsbuild.rs/config/tools/rspack) in `rspress.config.js` + +```ts title="rspress.config.ts" +export default { + builderConfig: { + tools: { + rspack: (config)=>{ + config.output.publicPath = 'https://module-federation.io/'; + }, + } + } +} +``` diff --git a/apps/website-new/docs/en/guide/troubleshooting/runtime/RUNTIME-006.mdx b/apps/website-new/docs/en/guide/troubleshooting/runtime/RUNTIME-006.mdx index ef162504f2d..423e814ca06 100644 --- a/apps/website-new/docs/en/guide/troubleshooting/runtime/RUNTIME-006.mdx +++ b/apps/website-new/docs/en/guide/troubleshooting/runtime/RUNTIME-006.mdx @@ -11,4 +11,4 @@ The current shared dependency has not been loaded, so `loadShareSync` cannot be Just choose one of the two: 1. Use `loadShare` instead of `loadShareSync` -2. Provide the [lib](../../basic/runtime#loadshare) function to the current shared dependency +2. Provide the [lib](../../basic/runtime/runtime-api#loadshare) function to the current shared dependency diff --git a/apps/website-new/docs/en/practice/bridge/index.mdx b/apps/website-new/docs/en/practice/bridge/index.mdx index 03fd2d0c0fc..f54930b89f1 100644 --- a/apps/website-new/docs/en/practice/bridge/index.mdx +++ b/apps/website-new/docs/en/practice/bridge/index.mdx @@ -11,7 +11,7 @@ Before reading this chapter, it's assumed you're familiar with: - [How to consume and export modules](../../guide/start/quick-start.mdx) - [Module Federation Builder plugin](../../guide/basic/rspack.mdx) -- [Characteristics and capabilities of Module Federation Runtime](../../guide/basic/runtime.mdx) +- [Characteristics and capabilities of Module Federation Runtime](../../guide/basic/runtime/runtime.mdx) ::: diff --git a/apps/website-new/docs/en/practice/performance/prefetch.mdx b/apps/website-new/docs/en/practice/performance/prefetch.mdx index e930a62866f..0227b5e6aef 100644 --- a/apps/website-new/docs/en/practice/performance/prefetch.mdx +++ b/apps/website-new/docs/en/practice/performance/prefetch.mdx @@ -210,7 +210,7 @@ return ( ### loadRemote #### Function -If the user manually calls [loadRemote](/zh/guide/basic/runtime.html#loadremote) in the hosts project API, then it will be considered that the hosts not only wants to load the producer's static resources, but also wants to send the interface request in advance, which can make the project render faster. This is especially effective in the scenario where **the first screen has a pre-request** or **you want the second screen to be directly displayed**. It is suitable for the scenario where the second screen module is loaded in advance on the current page. +If the user manually calls [loadRemote](/zh/guide/basic/runtime/runtime-api.html#loadremote) in the hosts project API, then it will be considered that the hosts not only wants to load the producer's static resources, but also wants to send the interface request in advance, which can make the project render faster. This is especially effective in the scenario where **the first screen has a pre-request** or **you want the second screen to be directly displayed**. It is suitable for the scenario where the second screen module is loaded in advance on the current page. #### How to use ``` ts import { loadRemote } from '@module-federation/enhanced/runtime'; diff --git a/apps/website-new/docs/zh/configure/dev.mdx b/apps/website-new/docs/zh/configure/dev.mdx index 9375c4f9c40..55ac80aad8d 100644 --- a/apps/website-new/docs/zh/configure/dev.mdx +++ b/apps/website-new/docs/zh/configure/dev.mdx @@ -3,7 +3,7 @@ - 类型:`boolean | PluginDevOptions` - 是否必填:否 - 默认值:`true` -- 使用场景:用于控制 `Module Federation` dev 行为 +- 使用场景:用于控制 dev 环境下资源热更新或类型热更新的行为 `PluginDevOptions` 类型如下: diff --git a/apps/website-new/docs/zh/configure/dts.mdx b/apps/website-new/docs/zh/configure/dts.mdx index d93e4a6bcec..1091db0365f 100644 --- a/apps/website-new/docs/zh/configure/dts.mdx +++ b/apps/website-new/docs/zh/configure/dts.mdx @@ -3,7 +3,7 @@ - 类型:`boolean | PluginDtsOptions` - 是否必填:否 - 默认值:`true` -- 使用场景:用于控制 `Module Federation` 生成/消费类型行为 +- 使用场景:用于控制生成/消费类型行为 配置之后,生产者会在构建时自动生成一个压缩的类型文件 `@mf-types.zip`(默认名称),消费者会自动拉取 `remotes` 的类型文件并解压至 `@mf-types`(默认名称)。 @@ -22,7 +22,7 @@ interface PluginDtsOptions { - 类型:`boolean | DtsRemoteOptions` - 是否必填:否 - 默认值:`true` -- 使用场景:用于控制 `Module Federation` 生成类型行为 +- 使用场景:用于控制生成类型行为 `DtsRemoteOptions` 类型如下: diff --git a/apps/website-new/docs/zh/configure/experiments.mdx b/apps/website-new/docs/zh/configure/experiments.mdx index d582c5e08da..bccab45771c 100644 --- a/apps/website-new/docs/zh/configure/experiments.mdx +++ b/apps/website-new/docs/zh/configure/experiments.mdx @@ -30,7 +30,7 @@ new ModuleFederationPlugin({ - Required: No - Default: `false` -启用 `asyncStartup` 后,所有 Module Federation 入口将默认以异步方式初始化。这意味着: +启用 `asyncStartup` 后,所有 {props.name || 'Module Federation'} 入口将默认以异步方式初始化。这意味着: - 无需在应用顶部手动添加 `import()` 语句(例如 `import('./bootstrap')`) - 所有入口都会自动处理异步初始化 diff --git a/apps/website-new/docs/zh/configure/exposes.mdx b/apps/website-new/docs/zh/configure/exposes.mdx index 885f684f03b..17f572a8ffc 100644 --- a/apps/website-new/docs/zh/configure/exposes.mdx +++ b/apps/website-new/docs/zh/configure/exposes.mdx @@ -3,9 +3,10 @@ - 类型:`PluginExposesOptions` - 是否必填:否 - 默认值:`undefined` -- 使用场景:决定 `Module Federation` 对外暴露的模块以及文件入口 +- 使用场景:决定对外暴露的模块以及文件入口 :::tip + 生产者特有参数,设置了 `exposes` 则可认为这是一个生产者 ::: diff --git a/apps/website-new/docs/zh/configure/name.mdx b/apps/website-new/docs/zh/configure/name.mdx index 85124bedf6f..019702ca62a 100644 --- a/apps/website-new/docs/zh/configure/name.mdx +++ b/apps/website-new/docs/zh/configure/name.mdx @@ -3,6 +3,6 @@ - 类型:`string` - 是否必填:是 -`Module Federation` 模块名称,name 必须保证唯一。 +{props.name || 'Module Federation'} 模块名称,name 必须保证唯一。 -`Module Federation` 通过 name 进行关联。name 将用于运行时数据获取以及 chunk 全局存储变量 +{props.name || 'Module Federation'} 通过 name 进行关联。name 将用于运行时数据获取以及 chunk 全局存储变量 diff --git a/apps/website-new/docs/zh/configure/shared.mdx b/apps/website-new/docs/zh/configure/shared.mdx index 597419f691e..4f3597453b4 100644 --- a/apps/website-new/docs/zh/configure/shared.mdx +++ b/apps/website-new/docs/zh/configure/shared.mdx @@ -111,34 +111,23 @@ new ModuleFederationPlugin({ ### 如何使用共享依赖 -根据使用场景,`Module Federation` 支持两种形式配置共享依赖,分别是 数组、对象。前者适用于大部分场景,后者适用于复杂的定制需求。 +根据使用场景,{props.name || 'Module Federation'} 支持两种形式配置共享依赖,分别是 数组、对象。前者适用于大部分场景,后者适用于复杂的定制需求。 **数组格式(通用场景)** -仅需在 `Module Federation` 构建配置中的 `shared` 配置添加对应的依赖即可,例如: +仅需在 {props.name || 'Module Federation'} 构建配置中的 `shared` 配置添加对应的依赖即可,例如: + +import ArrayShared from '@components/common/configure/array-shared'; +import React from 'react'; + +{props.arrayShared || React.createElement(ArrayShared)} -```ts -new ModuleFederationPlugin({ - name: '@demo/button', - shared: ['react', 'react-dom'], - //... -}); -``` **对象格式(定制化配置)** -在 `Module Federation Plugin` 中的 `shared` 配置添加需要共享的依赖, `key` 为依赖名称,`value` 为提供的配置。 +在 {props.name || 'Module Federation'} 中的 `shared` 配置添加需要共享的依赖, `key` 为依赖名称,`value` 为提供的配置。 + +import ObjectShared from '@components/common/configure/object-shared' + +{props.objectShared || React.createElement(ObjectShared)} -```ts -new ModuleFederationPlugin({ - name: '@demo/button', - shared: { - react: { - singleton: true, - requiredVersion: '~18.2.0', - fixedDependencies: true - } - }, - //... -}); -``` diff --git a/apps/website-new/docs/zh/guide/basic/_meta.json b/apps/website-new/docs/zh/guide/basic/_meta.json index f1c2562ce49..0e46909341a 100644 --- a/apps/website-new/docs/zh/guide/basic/_meta.json +++ b/apps/website-new/docs/zh/guide/basic/_meta.json @@ -1 +1,8 @@ -["runtime", "rsbuild", "rspack", "webpack", "vite","type-prompt","cli"] +[ + { + "type": "dir", + "name": "runtime", + "label": "运行时" + }, + "rsbuild", "rspack", "webpack", "rspress","vite","type-prompt","cli","css-isolate" +] diff --git a/apps/website-new/docs/zh/guide/basic/cli.mdx b/apps/website-new/docs/zh/guide/basic/cli.mdx index 0889b09abbc..a6a1680408d 100644 --- a/apps/website-new/docs/zh/guide/basic/cli.mdx +++ b/apps/website-new/docs/zh/guide/basic/cli.mdx @@ -1,63 +1,42 @@ # 命令行工具 -`@module-federation/enhanced` 和 `@module-federation/modern-js` 提供了轻量的命令行工具。 +

+{props.name || 'Module Federation'} 提供了轻量的命令行工具:{(props.cmdTools || ['@module-federation/enhanced', '@module-federation/modern-js']).map((cmdTool,index,arr)=>(<>{cmdTool}{index+1===arr.length ? '':' 和 '}))} 。 +

## 查看所有命令 -如果你需要查看所有可用的 CLI 命令,请在项目目录中运行以下命令: +import React from 'react'; +import ViewAllCmdsMdx from '@components/common/cli/view-all-cmds'; -```bash -npx mf -h -``` - -输出如下: - -```text -Usage: mf [options] - -Options: - -V, --version output the version number - -h, --help display help for command - -Commands: - dts [options] generate or fetch the mf types - help [command] display help for command -``` +{/* rspress flattenMdxContent issue */} +{props.show || React.createElement(ViewAllCmdsMdx)} ## 公共选项 -Module Federation CLI 提供了一些公共选项,可以用于所有命令: +{props.name || 'Module Federation'} CLI 提供了一些公共选项,可以用于所有命令: | 选项 | 描述 | | -------------------------- | ----------------------------------------------------------------------------------------------------------------- | -| `-c, --config ` | 指定配置文件路径,可以为相对路径或绝对路径,默认值为 `module-federation.config.ts` | +| `-c, --config ` | 指定配置文件路径,可以为相对路径或绝对路径,默认值为 {props.configName ? props.configName : 'module-federation.config.ts'} | | `-m, --mode ` | 指定运行环境,可以选择 "dev" 或 "prod" ,默认值为 "dev" ,设置后会按照值自动往 `process.env.NODE_ENV` 环境变量注入 "development" 或 "production" | | `-h, --help` | 显示命令帮助 | -## mf dts - -`mf dts` 命令用于拉取或生成 TypeScript 类型声明文件。 +## {props.cmd || 'mf'} dts -```bash -Usage: mf dts [options] +

+ {props.cmd || 'mf'} dts + 命令用于拉取或生成 TypeScript 类型声明文件。 +

-generate or fetch the mf types +import CommandInfo from '@components/common/cli/cmd-info'; -Options: - --root specify the project root directory - --output specify the generated dts output directory - --fetch fetch types from remote, default is true (default: true) - --generate generate types, default is true (default: true) - -c --config specify the configuration file, can be a relative or absolute path - -m --mode Specify the runtime environment. You can choose "dev" or "prod". The default value is "dev". After setting, the process.env.NODE_ENV environment variable will be - automatically injected with "development" or "production" according to the value. (default: "dev") - -h, --help display help for command -``` +{props.commandInfo || } :::info 注意 -`mf dts` 命令会自动根据 `module-federation.config.ts` 中的配置生成或拉取类型声明文件。 这意味着你必须提供一个有效的配置文件,否则命令将无法正常运行。 +

{props.cmd || 'mf'} dts 命令会自动根据 {props.configName || 'module-federation.config.ts'} 中的配置生成或拉取类型声明文件。 这意味着你必须提供一个有效的配置文件,否则命令将无法正常运行。

-如果你是只使用了 runtime API,那么你需要创建一份临时的 `module-federation.config.ts` 文件,配置 [dts.consumeTypes.remoteTypeUrls](../../configure/dts#remotetypeurls),然后运行 `mf dts` 命令。 +

如果你是只使用了 runtime API,那么你需要创建一份临时的 {props.configName || 'module-federation.config.ts'} 文件,配置 [dts.consumeTypes.remoteTypeUrls](../../configure/dts#remotetypeurls),然后运行 {props.cmd || 'mf'} dts 命令。

-::: \ No newline at end of file +::: diff --git a/apps/website-new/docs/zh/guide/basic/css-isolate.mdx b/apps/website-new/docs/zh/guide/basic/css-isolate.mdx new file mode 100644 index 00000000000..9d59e021305 --- /dev/null +++ b/apps/website-new/docs/zh/guide/basic/css-isolate.mdx @@ -0,0 +1,176 @@ +# 样式隔离 + +要实现CSS样式隔离,可以采用以下几种方法。下面是每种方法的详细使用方式: + +## 1. BEM(Block Element Modifier) + +BEM是一种命名规范,通过给类名添加前缀和后缀来实现样式隔离。BEM的结构如下: + +- **Block**:代表独立的功能块,例如`.button` +- **Element**:代表块的组成部分,例如`.button__text` +- **Modifier**:代表块或元素的不同状态或版本,例如`.button--primary` + +### 使用示例 +```html +
+ 按钮 +
+``` + +```css +.button { + background-color: blue; + color: white; +} + +.button__text { + font-size: 16px; +} + +.button--primary { + background-color: green; +} +``` + +## 2. CSS Modules + +CSS Modules通过将每个CSS文件作为一个模块来实现样式隔离,每个类名在编译时会被转换成唯一的标识符。[Rsbuild 中使用 css module](https://rsbuild.dev/zh/guide/basic/css-modules) + +### 使用示例 + +1. 创建CSS文件并命名为`styles.module.css`: + +```css +/* styles.module.css */ +.button { + background-color: blue; + color: white; +} +``` + +2. 在React组件中引入并使用: + +```javascript +import React from 'react'; +import styles from './styles.module.css'; + +function App() { + return ; +} + +export default App; +``` + +## 3. CSS-in-JS + +CSS-in-JS将样式直接写在JavaScript文件中,常用的库有 [styled-components](https://styled-components.com/) 、[emotion](https://github.com/emotion-js/emotion) 等。[Rsbuild 中使用 css-in-js](https://rsbuild.dev/zh/guide/basic/css-usage#css-in-js) + +### 使用示例 + +1. 安装styled-components: + +```bash +npm install styled-components +``` + +2. 在React组件中使用: + +```javascript +import React from 'react'; +import styled from 'styled-components'; + +const Button = styled.button` + background: blue; + color: white; +`; + +function App() { + return ; +} + +export default App; +``` + +## 4. Shadow DOM + +Shadow DOM是 Web 组件技术的一部分,通过创建独立的DOM树来实现样式隔离。React 可以通过 [react-shadow](https://www.npmjs.com/package/react-shadow) 使用 ShadowDOM。 + +### 使用示例 + +1. 创建一个HTML模板: + +```html + +``` + +2. 定义自定义元素并附加Shadow DOM: + +```javascript +class MyComponent extends HTMLElement { + constructor() { + super(); + const shadow = this.attachShadow({ mode: 'open' }); + const template = document.getElementById('my-component').content; + shadow.appendChild(template.cloneNode(true)); + } +} + +customElements.define('my-component', MyComponent); +``` + +3. 在HTML中使用自定义元素: + +```html + +``` + +## 5. Vue Scoped Styles + +在Vue中,可以使用`scoped`属性来实现组件级别的样式隔离。 + +### 使用示例 + +1. 在Vue组件中定义样式: + +```vue + + + +``` + +每种方法都有其适用场景和限制,开发者可以根据项目需求选择合适的样式隔离方案。 + + +## FAQ + +### 为什么 Module Federation 不直接处理 css 样式隔离? + +之所以目前未选择直接将 CSS 隔离放到 Module Federation 中主要有以下几个原因: + +* CSS 隔离,这个能力和 shared 会有极大的冲突,比较典型的是因为 shared 会尽可能的复用公共依赖,这会导致部分共享依赖会逃逸沙箱,从而导致隔离不可控,可能会受到加载时序的影响 + +* 运行时处理 CSS 隔离会有比较多的边界问题,并且遇到了问题排查非常困难,从而导致对业务稳定性下降,比较典型的有下面几种: + * shadowDOM ,通过这种方式后可能会遇到各类组件库不兼容的情况,如果业务遇到线上问题时可能排查链路和周期都比较长,最终问题不一定能解决 + * 收集 CSS 进行清除,因为上面的隔离和共享复用问题,可能会意外的 CSS 被清除 + * 在消费者进行 sandbox 升级的时候,业务影响面不可控 + +> 建议处理方式: + +* 在模块或者子应用的生产者就将 CSS 进行处理,这样可以保证模块或者应用在任意环境运行执行结果都不会不符合预期 +* 通过 CSS module、组件库前缀、统一组件库版本,来解决该问题 +* 直接导出 shadowDOM 的组件给到其他业务使用 diff --git a/apps/website-new/docs/zh/guide/basic/rsbuild.mdx b/apps/website-new/docs/zh/guide/basic/rsbuild.mdx index fe3fa60b293..b67184920d9 100644 --- a/apps/website-new/docs/zh/guide/basic/rsbuild.mdx +++ b/apps/website-new/docs/zh/guide/basic/rsbuild.mdx @@ -82,7 +82,7 @@ export default defineConfig({ ``` ### 注意 -如果需要使用 Module Federation 运行时能力,请安装 [@module-federation/enhanced](/zh/guide/basic/runtime.html) +如果需要使用 Module Federation 运行时能力,请安装 [@module-federation/enhanced](/zh/guide/basic/runtime/runtime.html) ## 配置 diff --git a/apps/website-new/docs/zh/guide/basic/rspack.mdx b/apps/website-new/docs/zh/guide/basic/rspack.mdx index 76376ddfcbc..c6cdb9698fb 100644 --- a/apps/website-new/docs/zh/guide/basic/rspack.mdx +++ b/apps/website-new/docs/zh/guide/basic/rspack.mdx @@ -1,14 +1,18 @@ -# Rspack Plugin +# Rspack 插件 :::info 注意 -需要 Rspack 0.5.0 及以上版本。 +需要 Rspack 0.5.0 及以上版本 ::: -- 能够构建出满足 `Module Federation` 加载规范的模块 -- 能够使用别名消费 `Module Federation` 规范的模块 -- 能够设置模块的共享依赖配置,当加载模块的宿主环境已经存在对应依赖时将不会重复加载 -- 当模块具备远程类型时将会自动把远程模块的类型下载下来消费 -- 消费远程模块时将具备热更新能力 +* 使用 `Rspack` 构建工具的应用构建出满足 {props.name || 'Module Federation'} 加载规范的微模块 +* 能够在构建插件里面使用别名消费微模块 +* 能够设置微模块的共享依赖配置,当加载微模块的宿主环境已经存在对应依赖时将不会重复加载 +* 当微模块具备远程类型时将会自动把远程模块的类型下载下来消费 +* 消费远程模块时将具备热更新能力 + +{props.tip} + +import { Tab, Tabs } from '@theme'; ## 快速开始 @@ -16,69 +20,28 @@ 你可以通过如下的命令安装插件: -import { PackageManagerTabs } from '@theme'; - - +### 创建 {props.configName || 'module-federation.config.ts'} + +创建 {props.configName || 'module-federation.config.ts'} 文件,内容如下: + +import CreateConfig from '@components/common/rspack/create-config'; + +{props.createConfig || } + ### 注册插件 -#### Rspack - -在 [Rspack](https://rspack.dev/) 中,你可以通过 `rspack.config.js` 配置文件来添加插件: - -```ts title="rspack.config.js" -const { ModuleFederationPlugin } = require('@module-federation/enhanced/rspack'); - -module.exports = { - devServer: { - port: 2000, - }, - output: { - // 需要设置一个唯一值,不能和其他应用相等 - uniqueName: 'federation_provider', - // 使用 manifest 必须要配置 publicPath - publicPath: 'http://localhost:2000/', - }, - plugins: [ - new ModuleFederationPlugin({ - name: 'federation_provider', - exposes: { - './button': './src/button.tsx', - }, - shared: ['react', 'react-dom'], - }), - ], -}; -``` - -## 配置构建插件 - -- Type: `ModuleFederationPlugin(options: ModuleFederationOptions)` - -- Module federation 插件的配置结构如下所示: - -```ts -type ModuleFederationOptions { - name: string; - filename?: string, - remotes?: Array; - shared?: ShareInfos; -}; -``` +在 `Rspack` 中,你可以通过 `plugins` 配置项来添加插件: + +import RegisterPlugin from '@components/common/rspack/register-plugin'; + +{props.registerPlugin || } + +## 配置 你可以在 [Config 总览](../../configure/index) 页面找到所有配置项的详细说明。 diff --git a/apps/website-new/docs/zh/guide/basic/rspress.mdx b/apps/website-new/docs/zh/guide/basic/rspress.mdx new file mode 100644 index 00000000000..7b0ca83f3ab --- /dev/null +++ b/apps/website-new/docs/zh/guide/basic/rspress.mdx @@ -0,0 +1,118 @@ +# Rspress Plugin + +:::info 注意 +需要 [Rspress 2.0.0-beta.16](https://v2.rspress.rs/zh/plugin/system/introduction) 及以上版本。 +::: + +帮助用户在 **Rspress** 中构建、消费 {props.name || 'Module Federation'} 产物 + +## 快速开始 + +### 安装 + +你可以通过如下的命令安装插件: + +import InstallKit from '@components/common/install-kit'; + + + +### 创建 {props.configName || 'module-federation.config.ts'} + +创建 {props.configName || 'module-federation.config.ts'} 文件,内容如下: + +import CreateConfig from '@components/common/rspress/create-config'; + +{props.createConfig || } + +### 注册插件 + +import RegisterPlugin from '@components/common/rspress/register-plugin'; +import React from 'react'; + +{props.registerPlugin || React.createElement(RegisterPlugin)} + +### 加载文档片段 + +你可以直接在 `mdx` 文件中加载导出的文档片段。 + +```mdx title='docs/en/guide/intro.mdx' +import Intro from 'mf-doc/intro-zh'; + +{/* 文档片段支持传参,以 props 方式去消费 */} + +``` + +文档片段支持传参,以 props 方式去消费。 + +假设你需要在文档片段中使用 `cmdTools` 变量,可以参考下方内容: + +```mdx title='docs/zh/guide/intro.mdx' +{(props.cmdTools || ['pkg-a', 'pkg-b']).map(cmdTool=>(

{cmdTool}

))} +``` + +## 配置 + +* 类型: + +import ConfigType from '@components/common/rspress/config-type'; + +{props.configType || } + +### {props.pluginOptionName || 'moduleFederationOptions'} + +[{props.name || 'Module Federation'} 配置项](../../configure/index) + +### rspressOptions + +Rspress 插件额外配置。 + +#### autoShared + +* 类型:`boolean` +* 默认值:`true` + +Rspress 使用了 `react`、`react-dom`、`@mdx-js/react` 第三方依赖,并且上述三个依赖需要保证单利,因此在构建时会自动注入 `shared` 配置。 + +你也可以设置 `autoShared: false` 来禁用此行为。 + +#### rebuildSearchIndex + +* 类型:`boolean` +* 默认值:`true` + +Rspress 构建时会自动生成搜索索引,但是生成过程仅支持 `.mdx` 或 `.md` 文件,因此当加载了模块联邦的文档片段时,该文档片段不会被搜索到。 + +为了避免此行为,MF Rspress Plugin 会在 SSG 完成后根据渲染完成的 `html` 重新生成搜索索引以支持搜索功能。 + +如果你采用了 remoteSearch 或其他搜索功能,可以设置 `rebuildSearchIndex: false` 来禁用此行为。 + +> 注意:该功能仅在 ssg 模式下生效。 + +## FAQ + +### 是否支持 local search ? + +仅支持 `ssg` 模式,详情参考 [rebuildSearchIndex](#rebuildsearchindex)。 + +### Could not parse expression with swc: Expression expected" + +当引用 MDX 组件时,可能会遇到如下错误: + +```bash +File: "/root/docs/zh/guide/basic/mf.mdx" +Error: "23:8: Could not parse expression with swc: Expression expected" +``` + +这是 Rspress 在解析 MDX 组件时未能正确解析表达式的问题,可以通过以下方式解决: + +```diff +import RemoteIntroDoc from 'mf-doc/intro'; +import Head from '@components/Head'; ++ import React from 'react'; + +- } /> ++ + +``` diff --git a/apps/website-new/docs/zh/guide/basic/runtime.mdx b/apps/website-new/docs/zh/guide/basic/runtime.mdx deleted file mode 100644 index 9c3e747067a..00000000000 --- a/apps/website-new/docs/zh/guide/basic/runtime.mdx +++ /dev/null @@ -1,467 +0,0 @@ -# Federation Runtime - -:::tip -在阅读本章前,预设你已经了解: - -- Module Federation 的[特点和能力](../start/index) -- Module Federation 的[名词解释](../start/glossary) -- 了解如何[消费和导出模块](../start/quick-start) - -::: - -`Federation Runtime` 是新版本 `Module Federation` 的主要功能之一,它能够支持通过运行时 API 注册共享依赖、动态注册和加载远程模块,了解 Runtime 的设计原理可以参考:[Why Runtime](#why-runtime)。 - -## 安装依赖 - -import { PackageManagerTabs } from '@theme'; - - - -## API - -```javascript -// 可以只使用运行时加载模块,而不依赖于构建插件 -// 当不使用构建插件时,共享的依赖项不能自动设置细节 -import { init, loadRemote } from '@module-federation/enhanced/runtime'; - -init({ - name: '@demo/app-main', - remotes: [ - { - name: "@demo/app1", - // mf-manifest.json 是在 Module federation 新版构建工具中生成的文件类型,对比 remoteEntry 提供了更丰富的功能 - // 预加载功能依赖于使用 mf-manifest.json 文件类型 - entry: "http://localhost:3005/mf-manifest.json", - alias: "app1" - }, - { - name: "@demo/app2", - entry: "http://localhost:3006/remoteEntry.js", - alias: "app2" - }, - ], -}); - -// 使用别名加载 -loadRemote<{add: (...args: Array)=> number }>("app2/util").then((md)=>{ - md.add(1,2,3); -}); -``` - -### init - -- Type: `init(options: InitOptions): void` -- 创建运行时实例,它可以重复调用,但只存在一个实例。若想动态注册远程模块或插件,请使用 [registerPlugins](#registerplugins) 或 [registerRemotes](#registerremotes) -- InitOptions: - -```ts -type InitOptions { - //当前消费者的名称 - name: string; - // 依赖的远程模块列表 - // 使用 version 内容的时候需要配合 snapshot 使用,该内容还在施工中 - remotes: Array; - // 当前消费者需要共享的依赖项列表 - // 当使用构建插件时,用户可以在构建插件中配置需要共享的依赖项,而构建插件会将需要共享的依赖项注入到运行时共享配置中 - // Shared 在运行时传入时必须在版本实例引用中手动传入,因为它不能在运行时直接传入。 - shared?: { - [pkgName: string]: ShareArgs | ShareArgs[]; - }; - }; - -type ShareArgs = - | (SharedBaseArgs & { get: SharedGetter }) - | (SharedBaseArgs & { lib: () => Module }); - -type SharedBaseArgs = { - version: string; - shareConfig?: SharedConfig; - scope?: string | Array; - deps?: Array; - strategy?: 'version-first' | 'loaded-first'; -}; - -type SharedGetter = (() => () => Module) | (() => Promise<() => Module>); - -type Remote = (RemotesWithEntry | RemotesWithVersion) & RemoteInfoCommon - -interface RemotesWithVersion { - name: string; - version: string; -} - -interface RemotesWithEntry { - name: string; - entry: string; -} - -interface RemoteInfoCommon { - alias?: string; - shareScope?: string; - type?: RemoteEntryType; - entryGlobalName?: string; -} - -type RemoteEntryType =|'var'|'module'|'assign'|'assign-properties'|'this'|'window'|'self'|'global'|'commonjs'|'commonjs2'|'commonjs-module'| 'commonjs-static'|'amd'|'amd-require'|'umd'|'umd2'|'jsonp'|'system'| string; - -type ShareInfos = { - // 依赖的包名、依赖的基本信息和共享策略 - [pkgName: string]: Share; -}; - -type Share = { - // 共享依赖的版本 - version: string; - // 当前依赖再被哪些模块消费 - useIn?: Array; - // 共享依赖来自哪个模块? - from?: string; - // 获取共享依赖实例的工厂函数。当没有其他已经存在的依赖,将加载它自己的共享依赖项。 - lib: () => Module; - // 共享策略,将使用什么策略来决定依赖项是否复用 - shareConfig?: SharedConfig; - // 共享依赖项所在的作用域下,默认值为 default - scope?: string | Array; -}; -``` - -### loadRemote - -- Type: `loadRemote(id: string)` -- 用于加载初始化的远程模块,当与构建插件一起使用时,它可以通过原生的 `import("remote name/expose")`语法直接加载,并且构建插件会自动将其转换为`loadRemote("remote name/expose")`用法。 - -- Example - -```javascript -import { init, loadRemote } from '@module-federation/enhanced/runtime'; - -init({ - name: '@demo/main-app', - remotes: [ - { - name: '@demo/app2', - alias: 'app2', - entry: 'http://localhost:3006/remoteEntry.js', - }, - ], -}); - -// remoteName + expose -loadRemote('@demo/app2/util').then((m) => m.add(1, 2, 3)); - -// alias + expose -loadRemote('app2/util').then((m) => m.add(1, 2, 3)); -``` - -### loadShare - -- Type: `loadShare(pkgName: string, extraOptions?: { customShareInfo?: Partial;resolver?: (sharedOptions: ShareInfos[string]) => Shared;})` -- 获取 `share` 依赖项。当全局环境中存在与当前消费者匹配的“共享”依赖时,现有的和符合共享条件的依赖将首先被复用。否则,加载它自己的依赖项并将它们存储在全局缓存中。 -- 这个 `API` 通常不是由用户直接调用,而是由构建插件使用来转换它们自己的依赖项。 - -- Example - -```js -import { init, loadRemote, loadShare } from '@module-federation/enhanced/runtime'; -import React from 'react'; -import ReactDOM from 'react-dom'; - -init({ - name: '@demo/main-app', - remotes: [], - shared: { - react: { - version: '17.0.0', - scope: 'default', - lib: () => React, - shareConfig: { - singleton: true, - requiredVersion: '^17.0.0', - }, - }, - 'react-dom': { - version: '17.0.0', - scope: 'default', - lib: () => ReactDOM, - shareConfig: { - singleton: true, - requiredVersion: '^17.0.0', - }, - }, - }, -}); - -loadShare('react').then((reactFactory) => { - console.log(reactFactory()); -}); -``` - -如果设置了多个版本 shared,默认会返回已加载且最高版本的 shared 。可以通过设置 `extraOptions.resolver` 来改变这个行为: - -```js -import { init, loadRemote, loadShare } from '@module-federation/runtime'; - -init({ - name: '@demo/main-app', - remotes: [], - shared: { - react: [ - { - version: '17.0.0', - scope: 'default', - get: async ()=>() => ({ version: '17.0.0)' }), - shareConfig: { - singleton: true, - requiredVersion: '^17.0.0', - }, - }, - { - version: '18.0.0', - scope: 'default', - // pass lib means the shared has loaded - lib: () => ({ version: '18.0.0)' }), - shareConfig: { - singleton: true, - requiredVersion: '^18.0.0', - }, - }, - ], - }, -}); - -loadShare('react', { - resolver: (sharedOptions) => { - return ( - sharedOptions.find((i) => i.version === '17.0.0') ?? sharedOptions[0] - ); - }, - }).then((reactFactory) => { - console.log(reactFactory()); // { version: '17.0.0' } -}); -``` - -### preloadRemote - -:::warning -只有当 entry 是 manifest 文件协议时,preloadRemote 接口才有效 -::: - -- Type: `preloadRemote(preloadOptions: Array)` -- 用于预加载模块的远程资源,比如 `remote-entry.js` 和其他的 js chunk、css 文件,使用 preloadRemote API. - -- Type - -```typescript -async function preloadRemote(preloadOptions: Array) {} - -type depsPreloadArg = Omit; -type PreloadRemoteArgs = { - // 预加载远程模块的名称和别名 - nameOrAlias: string; - // 预加载远程模块的特定 expose - // 默认预加载所有 expose - // 当提供 exposes 时,只会预加载特定的 expose - exposes?: Array; // Default request - // 默认为 sync,只加载 expose 中引用的同步 chunk - // 设置为 all 以加载同步和异步引用 chunk - resourceCategory?: 'all' | 'sync'; - // 当没有配置任何值时,默认值为 true,加载当前模块的所有子模块依赖 - // 在配置依赖项之后,只会加载所需的资源 - depsRemote?: boolean | Array; - // 未配置时不过滤资源 - // 配置后会过滤掉不需要的资源 - filter?: (assetUrl: string) => boolean; -}; -``` - -- 细节 - -通过 `preloadRemote`,模块资源可以在较早的阶段预加载,以避免瀑布式请求。`preloadRemote` 可以预加载以下内容: - -- `remote` 的 `remote entry` -- `remote` 中的 `expose` 资源 -- `remote` 中的同步资源和异步资源 -- `remote` 中 `remote` 的依赖 - -* Example - -```ts -import { init, preloadRemote } from '@module-federation/enhanced/runtime'; - -init({ - name: '@demo/preload-remote', - remotes: [ - { - name: '@demo/sub1', - entry: 'http://localhost:2001/mf-manifest.json', - }, - { - name: '@demo/sub2', - entry: 'http://localhost:2002/mf-manifest.json', - }, - { - name: '@demo/sub3', - entry: 'http://localhost:2003/mf-manifest.json', - }, - ], -}); - -// Preload @demo/sub1 模块 -// 过滤资源名称中包含 ignore 的资源信息 -// 只预加载子依赖的 @demo/sub1-button 模块 -preloadRemote([ - { - nameOrAlias: '@demo/sub1', - filter(assetUrl) { - return assetUrl.indexOf('ignore') === -1; - }, - depsRemote: [{ nameOrAlias: '@demo/sub1-button' }], - }, -]); - -// Preload @demo/sub2 模块 -// 预加载 @demo/sub2 下的所有 expose -// 预加载 @demo/sub2 的同步和异步资源 -preloadRemote([ - { - nameOrAlias: '@demo/sub2', - resourceCategory: 'all', - }, -]); - -// 预加载 @demo/sub3 模块的 add expose -preloadRemote([ - { - nameOrAlias: '@demo/sub3', - resourceCategory: 'all', - exposes: ['add'], - }, -]); -``` - -### registerRemotes - -- Type: `registerRemotes(remotes: Remote[], options?: { force?: boolean }): void` -- 用于在初始化后注册远程模块. - -- Type - -```typescript -function registerRemotes(remotes: Remote[], options?: { force?: boolean }) {} - -type Remote = (RemoteWithEntry | RemoteWithVersion) & RemoteInfoCommon; - -interface RemoteInfoCommon { - alias?: string; - shareScope?: string; - type?: RemoteEntryType; - entryGlobalName?: string; -} - -interface RemoteWithEntry { - name: string; - entry: string; -} - -interface RemoteWithVersion { - name: string; - version: string; -} -``` - -:::warning -请谨慎设置 `force:true`! - -在你设置 `force: true` 后我们将合并远程(包括已加载的远程),并移除已加载的远程缓存,同时我们将使用 `console.warn` 来警告此操作可能存在风险。 -::: - - -- Example - -```ts -import { init, registerRemotes } from '@module-federation/enhanced/runtime'; - -init({ - name: '@demo/register-new-remotes', - remotes: [ - { - name: '@demo/sub1', - entry: 'http://localhost:2001/mf-manifest.json', - }, - ], -}); - -// 添加新的远程 @demo/sub2 -registerRemotes([ - { - name: '@demo/sub2', - entry: 'http://localhost:2002/mf-manifest.json', - }, -]); - -// 覆盖以前的远程 @demo/sub1 -registerRemotes([ - { - name: '@demo/sub1', - entry: 'http://localhost:2003/mf-manifest.json', - }, -], { force: true }); -``` - - -### registerPlugins - -- Type: `registerPlugins(plugins: FederationRuntimePlugin[]): void` -- 用于在初始化后注册远程插件. - -- Example - -```ts -import { registerPlugins } from '@module-federation/enhanced/runtime' -import runtimePlugin from 'custom-runtime-plugin.ts'; - -registerPlugins([runtimePlugin()]); -``` - -如果你需要开发 Module Federation 插件,可以阅读 [Module Federation 插件系统](../../plugin/dev/index) 获取更多信息。 - -## FAQ - -### 构建插件和 Runtime 差异 - -`Federation Runtime` 是新版本 `Module Federation` 的主要功能之一,它能够支持在运行时注册共享依赖、动态注册和加载远程模块,还可以通过 `Plugin` 来扩展 `Module Federation` 在运行时的能力,构建插件是基于 Runtime 的基础实现的。 - -`Federation Runtime` 和 `Builder Plugin` 存在以下差异: - -| Federation Runtime | Builder Plugin | -| ------------------------------------------------------------------------ | ------------------------------------------------------------ | -| 可脱离构建插件使用,在 `webpack4` 等项目中可直接使用纯运行时进行模块加载 | 构建插件需要是 webpack5、Rspack、Vite 以上 | -| 支持动态注册模块 | 不支持动态注册模块 | -| 不支持 `import` 语法加载模块 | 支持 `import` 同步语法加载模块 | -| 支持 `loadRemote` 加载模块 | 支持 `loadRemote` 加载模块 | -| 设置 `shared` 必须提供具体版本和实例信息 | 设置 `shared` 只需要配置规则即可,无须提供具体版本及实例信息 | -| `shared` 依赖只能供外部使用,无法使用外部 `shared` 依赖 | `shared` 依赖按照特定规则双向共享 | -| 可以通过 `runtime` 的 `plugin` 机制影响加载流程 | 可以通过 `runtimePlugin` 配置影响加载流程 | -| 支持远程模块类型提示 | 支持远程模块类型提示 | - -### Why Runtime - -`Runtime` 对于之前使用 `Webpack` 内置的 `Module Federation` 构建插件的用户而言可能是一个全新的概念,在之前 Webpack 中的 Module Federation 无论是导出模块、还是消费模块都是纯构建的行为,所有模块的加载过程都被构建工具给封装了起来,对比模块加载器 Systemjs、esmodule 对比带来以下两点收益: - -- 在现有项目中导出模块的成本非常低,无需安装过多额外的依赖和构建配置,只需要声明模块名称和导出的模块路径就可以完成模块导出 -- 消费远程模块的只需要声明远程模块的名称和地址就可以和 `NPM` 依赖一样 `import` 使用即可 - -但是这种模式同时也对于项目的灵活性和构建插件的维护成本带来了以下影响: - -- 不同的构建工具 Webpack、Rspack、Vite 都需要针对 Module Federation 分别实现:Builder 构建工具和运行时,导致维护成本和功能一致性受到影响 -- 无法在 Webpack 4 等不支持 Module Federation 的构建插件中消费远程模块 -- 缺乏灵活性,无法动态增加模块、更改模块行为,增加更多框架上的能力 - -因此在新版本 `Module Federation` 设计中,将 `Runtime` 单独抽离了出来,不同的构建工具基于 `Runtime` 去实现对于模块的导出的构建、共享模块的信息收集、远程模块引用的处理,其他具体的共享依赖复用、远程模块加载等行为全部内置到 Runtime 中。 diff --git a/apps/website-new/docs/zh/guide/basic/runtime/_meta.json b/apps/website-new/docs/zh/guide/basic/runtime/_meta.json new file mode 100644 index 00000000000..6cb45df1af5 --- /dev/null +++ b/apps/website-new/docs/zh/guide/basic/runtime/_meta.json @@ -0,0 +1 @@ +["runtime", "runtime-api", "runtime-hooks"] diff --git a/apps/website-new/docs/zh/guide/basic/runtime/runtime-api.mdx b/apps/website-new/docs/zh/guide/basic/runtime/runtime-api.mdx new file mode 100644 index 00000000000..0fba9c823d3 --- /dev/null +++ b/apps/website-new/docs/zh/guide/basic/runtime/runtime-api.mdx @@ -0,0 +1,419 @@ +# Runtime API + +## init + +- Type: `init(options: InitOptions): void` +- 用于运行时动态注册 `Vmok` 模块 +- InitOptions: + +```ts +type InitOptions { + // 当前 host 的名称 + name: string; + // 依赖远程模块的列表 + // tip: 在运行时配置的 remotes 和构建插件传入的类型和数据并不完全一致 + remotes: Array; + // 当前 host 需要共享出去的依赖列表 + // 在使用构建插件时,用户可以在构建插件中配置需要共享的依赖,构建插件将会将需要共享的依赖注入到运行时的 shared 配置中 + // shared 在运行时传入时必须要要手动传入版本实例引用,因为在运行时无法直接 + shared?: { + [pkgName: string]: ShareArgs | ShareArgs[]; + }; +}; + +type ShareArgs = + | (SharedBaseArgs & { get: SharedGetter }) + | (SharedBaseArgs & { lib: () => Module }); + +type SharedBaseArgs = { + version: string; + shareConfig?: SharedConfig; + scope?: string | Array; + deps?: Array; + strategy?: 'version-first' | 'loaded-first'; +}; + +type SharedGetter = (() => () => Module) | (() => Promise<() => Module>); + +type RemoteInfo = { + alias?: string; +}; + +interface RemotesWithEntry { + name: string; + entry: string; +} + +type ShareInfos = { + // 依赖的包名和依赖的基础信息、共享策略 + [pkgName: string]: Share; +}; + +type Share = { + // 共享依赖的版本 + version: string; + // 当前的共享依赖被哪些模块消费了 + useIn?: Array; + // 共享的依赖来自哪个模块 + from?: string; + // 获取共享依赖的实例的工厂函数,当无法加载缓存的共享实例时将加载自身的共享依赖 + lib: () => Module; + // 共享策略,将以一个什么策略来决定共享依赖的复用 + shareConfig?: SharedConfig; + // share 间的依赖 + deps?: Array; + // 当前共享依赖放在什么 scope 下面,默认为 default + scope?: string | Array; +}; +``` + +- 示例 + +```js +import { init, loadRemote } from '@module-federation/enhanced/runtime'; + +init({ + name: "mf_host", + remotes: [ + { + name: "remote", + // 配置别名后可直接通过别名加载 + alias: "app1", + // 通过指定模块的 manifest.json 文件地址来决定加载的模块 + entry: "http://localhost:2001/mf-manifest.json" + } + ], +}); +``` + +## loadRemote + +- Type: `loadRemote(id: string)` +- 加载远程模块 +- 示例 + +```javascript +import { init, loadRemote } from '@module-federation/enhanced/runtime'; + +init({ + name: "mf_host", + remotes: [ + { + name: "remote", + // 远程模块别名,配置别名后可通过别名加载模块。请注意:alias 和 name 的前缀不能相等 + alias: "app1", + // 指定以 `mf-manifest.json` 结尾的远程模块地址,该文件由 mf 插件生成 + entry: "http://localhost:2001/vmok-manifest.json" + } + ] +}); + +// remoteName + expose +loadRemote("remote/util").then((m)=> m.add(1,2,3)); + +// alias + expose +loadRemote("app1/util").then((m)=> m.add(1,2,3)); +``` + +## loadShare + +- Type: `loadShare(pkgName: string, extraOptions?: { customShareInfo?: Partial;resolver?: (sharedOptions: ShareInfos[string]) => Shared;})` +- 获取 `share` 依赖,当全局环境有符合当前 `host` 的 `share` 依赖时,将优先复用当前已存在且满足 `share` 条件的依赖,否则将加载自身的依赖并存入全局缓存 +- 该 `API` **一般不由用户直接调用,用于构建插件转换自身依赖时使用** + +- 示例 + +```js +import { init, loadRemote, loadShare } from '@module-federation/enhanced/runtime'; +import React from 'react'; +import ReactDom from 'react-dom'; + +init({ + name: "mf_host", + remotes: [], + shared: { + react: { + version: "17.0.0", + scope: "default", + lib: ()=> React, + shareConfig: { + singleton: true, + requiredVersion: "^17.0.0" + } + }, + "react-dom": { + version: "17.0.0", + scope: "default", + lib: ()=> ReactDom, + shareConfig: { + singleton: true, + requiredVersion: "^17.0.0" + } + } + } +}); + + +loadShare("react").then((reactFactory)=>{ + console.log(reactFactory()) +}); +``` + +如果设置了多个版本 shared,默认会返回已加载且最高版本的 shared 。可以通过设置 `extraOptions.resolver` 来改变这个行为: + +```js +import { init, loadRemote, loadShare } from '@module-federation/enhanced/runtime'; + +init({ + name: 'mf_host', + remotes: [], + shared: { + react: [ + { + version: '17.0.0', + scope: 'default', + get: async ()=>() => ({ version: '17.0.0' }), + shareConfig: { + singleton: true, + requiredVersion: '^17.0.0', + }, + }, + { + version: '18.0.0', + scope: 'default', + // pass lib means the shared has loaded + lib: () => ({ version: '18.0.0' }), + shareConfig: { + singleton: true, + requiredVersion: '^18.0.0', + }, + }, + ], + }, +}); + +loadShare('react', { + resolver: (sharedOptions) => { + return ( + sharedOptions.find((i) => i.version === '17.0.0') ?? sharedOptions[0] + ); + }, + }).then((reactFactory) => { + console.log(reactFactory()); // { version: '17.0.0)' } +}); +``` + +## preloadRemote + +- type + +```typescript +async function preloadRemote(preloadOptions: Array){} + +type depsPreloadArg = Omit; +type PreloadRemoteArgs = { + // 预加载 remote 的名称和别名 + nameOrAlias: string; + // 是否需要预加载模块的接口,默认值为 false,具体参考<性能优化>中章节,@vmok/kit 版本需要大于 1.7.6 + prefetchInterface?: boolean; + // 需要预加载的 expose + // 默认预加载所有的 expose + // 提供了 expose 时只会加载所需要的 expose + exposes?: Array; // 默认请求 + // 默认为 sync,只会加载 expose 中引用的同步代码 + // 设置为 all 时将加载同步引用和异步引用 + resourceCategory?: 'all' | 'sync'; + // 未配置值时默认加载所有的依赖 + // 配置了依赖后仅会加载配置选项 + depsRemote?: boolean | Array; + // 未配置时不过滤资源 + // 配置了后将会过滤不需要的资源 + filter?: (assetUrl: string) => boolean; +}; +``` + +- Details + +通过 `preloadRemote` 可以在更早的阶段开始预加载模块资源,避免出现瀑布请求,`preloadRemote` 可以预加载哪些内容: + +* `remote` 的 `remoteEntry` +* `remote` 的 `expose` +* `remote` 的同步资源还是异步资源 +* `remote` 依赖的 `remote` 资源 + +- Example + +```ts +import { init, preloadRemote } from '@module-federation/enhanced/runtime'; +init({ + name: 'mf_host', + remotes: [ + { + name: 'sub1', + entry: "http://localhost:2001/mf-manifest.json" + }, + { + name: 'sub2', + entry: "http://localhost:2002/mf-manifest.json" + }, + { + name: 'sub3', + entry: "http://localhost:2003/mf-manifest.json" + }, + ], +}); + +// 预加载 @demo/sub1 模块 +// 过滤资源名称中携带 ignore 的资源信息 +// 只预加载子依赖的 @demo/sub1-button 模块 +preloadRemote([ + { + nameOrAlias: 'sub1', + filter(assetUrl) { + return assetUrl.indexOf('ignore') === -1; + }, + depsRemote: [{ nameOrAlias: 'sub1-button' }], + }, +]); + + +// 预加载 @demo/sub2 模块 +// 预加载 @demo/sub2 下的所有 expose +// 预加载 @demo/sub2 的同步资源和异步资源 +preloadRemote([ + { + nameOrAlias: 'sub2', + resourceCategory: 'all', + }, +]); + +// 预加载 @demo/sub3 模块的 add expose +preloadRemote([ + { + nameOrAlias: 'sub3', + resourceCategory: 'all', + exposes: ['add'], + }, +]); +``` + +## registerRemotes + +- type + +```typescript +function registerRemotes(remotes: Remote[], options?: { force?: boolean }) {} + +type Remote = (RemoteWithEntry | RemoteWithVersion) & RemoteInfoCommon; + +interface RemoteInfoCommon { + alias?: string; + shareScope?: string; + type?: RemoteEntryType; + entryGlobalName?: string; +} + +interface RemoteWithEntry { + name: string; + entry: string; +} + +interface RemoteWithVersion { + name: string; + version: string; +} +``` + +- Details + +**info**: 请谨慎设置 `force:true` ! + +如果设置 `force: true`, 这会覆盖已经注册(且加载的模块, 并且自动删除已经加载过的模块缓存(如果已经加载过),同时在控制台输出警告,告知这操作存在风险性。 + +* Example + +```ts +import { init, registerRemotes } from '@module-federation/enhanced/runtime'; + +init({ + name: 'mf_host', + remotes: [ + { + name: 'sub1', + entry: 'http://localhost:2001/mf-manifest.json', + } + ], +}); + +// 增加新的 remote @demo/sub2 +registerRemotes([ + { + name: 'sub2', + entry: 'http://localhost:2002/mf-manifest.json', + } +]); + +// 覆盖之前的 remote @demo/sub1 +registerRemotes([ + { + name: 'sub1', + entry: 'http://localhost:2003/mf-manifest.json', + } +],{ force:true }); +``` + +## registerPlugins + +- type + +```typescript +function registerPlugins(plugins: FederationRuntimePlugin[]) {} +``` + +* Example + +```ts +import { init, registerPlugins } from '@module-federation/enhanced/runtime'; +import runtimePlugin from './custom-runtime-plugin'; + +init({ + name: 'mf_host', + remotes: [ + { + name: 'sub1', + entry: 'http://localhost:2001/mf-manifest.json', + } + ], +}); + +// 增加新的运行时插件 +registerPlugins([runtimePlugin()]); + +registerPlugins([ + { + name: 'custom-plugin-runtime', + beforeInit(args) { + const { userOptions, origin } = args; + if (origin.options.name && origin.options.name !== userOptions.name) { + userOptions.name = origin.options.name; + } + console.log('[build time inject] beforeInit: ', args); + return args; + }, + beforeLoadShare(args) { + console.log('[build time inject] beforeLoadShare: ', args); + return args; + }, + createLink({ url }) { + const link = document.createElement('link'); + link.setAttribute('href', url); + link.setAttribute('rel', 'preload'); + link.setAttribute('as', 'script'); + link.setAttribute('crossorigin', 'anonymous'); + return link; + }, + } +]); +``` + + diff --git a/apps/website-new/docs/zh/guide/basic/runtime/runtime-hooks.mdx b/apps/website-new/docs/zh/guide/basic/runtime/runtime-hooks.mdx new file mode 100644 index 00000000000..7cb43306ede --- /dev/null +++ b/apps/website-new/docs/zh/guide/basic/runtime/runtime-hooks.mdx @@ -0,0 +1,381 @@ +# Runtime Hooks + +## beforeInit + +`SyncWaterfallHook` + +在 MF 实例初始化之前更新对应 init 配置 + +* type + +```ts +function beforeInit(args: BeforeInitOptions): BeforeInitOptions + +type BeforeInitOptions ={ + userOptions: UserOptions; + options: FederationRuntimeOptions; + origin: FederationHost; + shareInfo: ShareInfos; +} + +interface FederationRuntimeOptions { + id?: string; + name: string; + version?: string; + remotes: Array; + shared: ShareInfos; + plugins: Array; + inBrowser: boolean; +} +``` + +## init + +`SyncHook` + +在 MF 实例初始化后调用 + +* type + +```ts +function init(args: InitOptions): void + +type InitOptions ={ + options: FederationRuntimeOptions; + origin: FederationHost; +} +``` + +## beforeRequest + +`AsyncWaterfallHook` + +在解析 remote 路径前调用,对于在查找之前更新某些内容很有用。 + +* type + +```ts +async function beforeRequest(args: BeforeRequestOptions): Promise + +type BeforeRequestOptions ={ + id: string; + options: FederationRuntimeOptions; + origin: FederationHost; +} +``` + +## afterResolve + +`AsyncWaterfallHook` + +在解析 remote 路径后调用,允许修改解析后的内容。 + +* type + +```ts +async function afterResolve(args: AfterResolveOptions): Promise + +type AfterResolveOptions ={ + id: string; + pkgNameOrAlias: string; + expose: string; + remote: Remote; + options: FederationRuntimeOptions; + origin: FederationHost; + remoteInfo: RemoteInfo; + remoteSnapshot?: ModuleInfo; +} +``` + +## onLoad + +`AsyncHook` + +Triggered once a federated module is loaded, allowing access and modification to the exports of the loaded file. + +加载 remote 后触发,允许访问和修改已加载文件的导出(exposes)。 + +* type + +```ts +async function onLoad(args: OnLoadOptions): Promise + +type OnLoadOptions ={ + id: string; + expose: string; + pkgNameOrAlias: string; + remote: Remote; + options: ModuleOptions; + origin: FederationHost; + exposeModule: any; + exposeModuleFactory: any; + moduleInstance: Module; +} + +type ModuleOptions = { + remoteInfo: RemoteInfo; + host: FederationHost; +} + +interface RemoteInfo { + name: string; + version?: string; + buildVersion?: string; + entry: string; + type: RemoteEntryType; + entryGlobalName: string; + shareScope: string; +} +``` + +## handlePreloadModule + +`SyncHook` + +处理 remotes 的预加载逻辑。 + +* type + +```ts +function handlePreloadModule(args: HandlePreloadModuleOptions): void + +type HandlePreloadModuleOptions ={ + id: string; + name: string; + remoteSnapshot: ModuleInfo; + preloadConfig: PreloadRemoteArgs; +} +``` + +## errorLoadRemote + +`AsyncHook` + +如果加载 remotes 失败,则调用,从而启用自定义错误处理。可返回自定义的兜底逻辑。 + +* type + +```ts +async function errorLoadRemote(args: ErrorLoadRemoteOptions): Promise + +type ErrorLoadRemoteOptions ={ + id: string; + error: unknown; + from: 'build' | 'runtime'; + origin: FederationHost; +} +``` +* example + +```ts +import { init, loadRemote } from '@module-federation/enhanced/runtime' + +import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; + +const fallbackPlugin: () => FederationRuntimePlugin = + function () { + return { + name: 'fallback-plugin', + errorLoadRemote(args) { + const fallback = 'fallback' + return fallback; + }, + }; + }; + + +init({ + name: 'mf_host', + remotes: [ + { + name: "remote", + alias: "app1", + entry: "http://localhost:2001/mf-manifest.json" + } + ], + plugins: [fallbackPlugin()] +}); + +loadRemote('app1/un-existed-module').then(mod=>{ + expect(mod).toEqual('fallback'); +}) +``` + +## beforeLoadShare + +`AsyncWaterfallHook` + +在加载 shared 之前调用,可用于修改对应的 shared 配置 + +* type + +```ts +async function beforeLoadShare(args: BeforeLoadShareOptions): Promise + +type BeforeLoadShareOptions ={ + pkgName: string; + shareInfo?: Shared; + shared: Options['shared']; + origin: FederationHost; +} +``` + +## resolveShare + +`SyncWaterfallHook` + +允许手动设置实际使用的共享模块。 + +* type + +```ts +function resolveShare(args: ResolveShareOptions): ResolveShareOptions + +type ResolveShareOptions ={ + shareScopeMap: ShareScopeMap; + scope: string; + pkgName: string; + version: string; + GlobalFederation: Federation; + resolver: () => Shared | undefined; +} +``` + +* example + +```ts +import { init, loadRemote } from '@module-federation/enhanced/runtime' + +import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; + +const customSharedPlugin: () => FederationRuntimePlugin = + function () { + return { + name: 'custom-shared-plugin', + resolveShare(args) { + const { shareScopeMap, scope, pkgName, version, GlobalFederation } = args; + + if ( + pkgName !== 'react' + ) { + return args; + } + + args.resolver = function () { + shareScopeMap[scope][pkgName][version] = window.React; // replace local share scope manually with desired module + return shareScopeMap[scope][pkgName][version]; + }; + return args; + }, + }; + }; + + +init({ + name: 'mf_host', + shared: { + react: { + version: '17.0.0', + scope: 'default', + lib: () => React, + shareConfig: { + singleton: true, + requiredVersion: '^17.0.0', + }, + }, + }, + plugins: [customSharedPlugin()] +}); + +window.React = ()=> 'Desired Shared'; + +loadShare("react").then((reactFactory)=>{ + expect(reactFactory()).toEqual(window.React()); +}); +``` + +## beforePreloadRemote + +`AsyncHook` + +在预加载处理程序执行任何预加载逻辑之前调用 + +* type + +```ts +async function beforePreloadRemote(args: BeforePreloadRemoteOptions): BeforePreloadRemoteOptions + +type BeforePreloadRemoteOptions ={ + preloadOps: Array; + options: Options; + origin: FederationHost; +} +``` + +## generatePreloadAssets + +`AsyncHook` + +用于控制生成需要预加载的资源 + +* type + +```ts +async function generatePreloadAssets(args: GeneratePreloadAssetsOptions): Promise + +type GeneratePreloadAssetsOptions ={ + origin: FederationHost; + preloadOptions: PreloadOptions[number]; + remote: Remote; + remoteInfo: RemoteInfo; + remoteSnapshot: ModuleInfo; + globalSnapshot: GlobalModuleInfo; +} + +interface PreloadAssets { + cssAssets: Array; + jsAssetsWithoutEntry: Array; + entryAssets: Array; +} +``` + +## loaderHook + +## createScript + +`SyncHook` + +用于修改加载资源时的 script + +* type + +```ts +function createScript(args: CreateScriptOptions): HTMLScriptElement | void + +type CreateScriptOptions ={ + url: string; +} +``` + +* example + +```ts +import { init } from '@module-federation/enhanced/runtime' +import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime'; + +const changeScriptAttributePlugin: () => FederationRuntimePlugin = + function () { + return { + name: 'change-script-attribute', + createScript({ url }) { + if (url === testRemoteEntry) { + let script = document.createElement('script'); + script.src = testRemoteEntry; + script.setAttribute('loader-hooks', 'isTrue'); + script.setAttribute('crossorigin', 'anonymous'); + return script; + } + } + }; + }; +``` diff --git a/apps/website-new/docs/zh/guide/basic/runtime/runtime.mdx b/apps/website-new/docs/zh/guide/basic/runtime/runtime.mdx new file mode 100644 index 00000000000..47b3ee7ab1c --- /dev/null +++ b/apps/website-new/docs/zh/guide/basic/runtime/runtime.mdx @@ -0,0 +1,155 @@ +# Runtime 接入 + +:::tip +在阅读本章前,预设你已经了解: + +- Module Federation 的[特点和能力](../../start/index) +- Module Federation 的[名词解释](../../start/glossary) +- 了解如何[消费和导出模块](../../start/quick-start) + +::: + + +目前 `Module Federation` 提供了两种注册模块和加载模块的方式: + +- 一种是在构建插件中声明(一般是在 `module-federation.config.ts` 文件中声明) + +- 另一种方式是直接通过 `runtime` 的 api 进行模块注册和加载。 + +两种模式并不冲突可结合使用。你可以根据你的实际场景灵活选取模块注册方式和时机 + +
+**运行时注册模块和构建配置注册模块的区别如下:** + +|运行时注册模块|插件中注册模块| +|------------------------------------------------------------------------------------------------------------------|---------------------- | +|可脱离构建插件使用,在 `webpack4` 等项目中可直接使用纯运行时进行模块注册和加载|构建插件需要是 webpack5 或以上| +|支持动态注册模块|不支持动态注册模块| +|不支持 `import` 语法加载模块|支持 `import` 同步语法加载模块| +|支持 `loadRemote` 加载模块|支持 `loadRemote` 加载模块| +|设置 `shared` 必须提供具体版本和实例信息|设置 `shared` 只需要配置规则即可,无须提供具体版本及实例信息| +|`shared` 依赖只能供外部使用,无法使用外部 `shared` 依赖|`shared` 依赖按照特定规则双向共享| +|可以通过 `runtime` 的 `plugin` 机制影响加载流程|目前不支持提供 `plugin` 影响加载流程| +|不支持远程类型提示|支持远程类型提示| + +## 安装 + +import { PackageManagerTabs } from '@theme'; + + + +::: tip 注意: + +- 以下 `Federation Runtime` 示例我们均展示脱离特定框架如 Modern.js 的 case, 所以 API 将均从初始 `@module-federation/enhanced/runtime` 包中导出。 + +- 如果你的项目是 Modern.js 项目且使用 `@module-federation/modern-js`,运行时应该从 `@module-federation/modern-js/runtime` 中导出运行时 API。这样能保证插件和运行时使用的是同一个运行时实例,保证模块加载正常 + +- 如果你的项目是 Modern.js 项目但是没有使用 `@module-federation/modern-js`,则应当从 `@module-federation/enhanced/runtime` 导出 runtime API。但是我们推荐你使用 `@module-federation/modern-js` 进行模块注册和加载,这将使你享受到更多和框架结合的能力。 + +::: + + +## 模块注册 + +```ts +import { init, loadRemote } from '@module-federation/enhanced/runtime'; + +init({ + // 消费者模块名称,必填。如同时注册了 ModuleFederationPlugin 插件,则 `name` 应与插件中配置的 `name`[/configure/name.html] 保持一致, + name: 'mf_host', + // 远程模块注册信息,包含模块名称、别名、版本等信息。 + remotes: [ + { + name: "remote1", + alias: "remotes-1", + entry: "http://localhost:3001/mf-manifest.json" + } + ], +}); +``` + +## 模块加载 + +```tsx +import { loadRemote } from '@module-federation/enhanced/runtime'; + +export default () => { + const MyButton = React.lazy(() => + loadRemote('remote1').then(({ MyButton }) => { + return { + default: MyButton + }; + }), + ); + + return ( + + + + ); +} +``` + +### 加载匿名模块 + +```tsx +import React from 'react'; +import { loadRemote } from '@module-federation/enhanced/runtime'; + +const RemoteButton = React.lazy(() => loadRemote('provider/button')); +// 也可通过模块别名加载: +// const RemoteButton = React.lazy(() => loadRemote('remotes-1/button')); + +export default () => { + return ( + + + + ); +} +``` + +### 加载具名模块 + +```tsx +import React from 'react'; +import { loadRemote } from '@module-federation/enhanced/runtime'; + +export default () => { + const RemoteButton = React.lazy(() => + loadRemote('remote1/button').then(({ RemoteButton }) => { + return { + default: RemoteButton + }; + }), + ); + return ( + + + + ); +} +``` + +### 加载工具函数 + +```ts +import { loadRemote } from '@module-federation/enhanced/runtime'; + +// 加载 app2 expose 的 util +loadRemote<{add: (...args: Array)=> number }>("@demo/app2/util").then((md)=>{ + md.add(1,2); +}); + +// 通过别名加载: +// loadRemote<{add: (...args: Array)=> number }>("app3/util").then((md)=>{ +// md.add(1,2); +// }); +``` diff --git a/apps/website-new/docs/zh/guide/basic/type-prompt.mdx b/apps/website-new/docs/zh/guide/basic/type-prompt.mdx index 0a332dc2e14..a1587dc3b7b 100644 --- a/apps/website-new/docs/zh/guide/basic/type-prompt.mdx +++ b/apps/website-new/docs/zh/guide/basic/type-prompt.mdx @@ -1,14 +1,14 @@ # 类型提示 -就像 NPM Package 一样,`Module Federation` 产物同样会生成类型,并且享有类型热加载,尽管产物托管在远程 CDN 上。 +就像 NPM Package 一样,{props.name || 'Module Federation'} 产物同样会生成类型,并且享有类型热加载,尽管产物托管在远程 CDN 上。 -`@module-federation/enhanced` 默认开启类型提示功能,本文将介绍几种常见的使用场景以及具体配置。 +

{props.pkgName || '@module-federation/enhanced'} 默认开启类型提示功能,本文将介绍几种常见的使用场景以及具体配置。

## 使用场景 ### 生成类型 -使用 `@module-federation/enhanced` 提供的构建插件进行构建,会自动生成类型文件。 +

使用{props.pkgName || '@module-federation/enhanced'} 提供的构建插件进行构建,会自动生成类型文件。

### 加载类型 @@ -38,13 +38,13 @@ ![hot-types-reload-static](@public/guide/type-prompt/hot-types-reload-static.gif) -### Federation Runtime API 类型提示 +### Runtime API 类型提示 :::info 如果构建器为 `webpack` ,还需要再 [watchOptions.ignored](https://webpack.js.org/configuration/watch/#watchoptionsignored) 增加 `**/@mf-types/**`,以避免类型更新导致的循环编译问题 ::: -需要在 `include` 字段增加 `./@mf-types/*` 以享有 `Federation Runtime` `loadRemote` 类型提示以及类型热重载 +需要在 `include` 字段增加 `./@mf-types/*` 以享有 `Runtime` `loadRemote` 类型提示以及类型热重载 ```json title="tsconfig.json" { @@ -56,22 +56,24 @@ ### 第三方包类型抽取 -`Module Federation` 会自动抽取第三方包类型,确保 `exposes` 文件类型能正常访问。 +{props.name || 'Module Federation'} 会自动抽取第三方包类型,确保 `exposes` 文件类型能正常访问。 ![third-party](@public/guide/type-prompt/third-party.jpg) ### 嵌套类型重导出 -`Module Federation` 支持嵌套 remotes ,并会自动抽取嵌套的 remotes 类型。 +{props.name || 'Module Federation'} 支持嵌套 remotes ,并会自动抽取嵌套的 remotes 类型。 ![nested-remote](@public/guide/type-prompt/nested-remote.jpg) ### 动态类型提示 -`Module Federation` 支持加载动态类型,并且通用支持热更新。 +{props.name || 'Module Federation'} 支持加载动态类型,并且通用支持热更新。 ![dynamic-remote-hot-types-reload](@public/guide/type-prompt/dynamic-remote-hot-types-reload-static.gif) +{props.dynamic} + ## 配置 - [dts](../../configure/dts):类型生成/加载 @@ -79,7 +81,7 @@ ## FAQ -1. 项目循环编译,无法终止 +### 项目循环编译,无法终止 A: 大概率是编译器监听了类型文件夹或者 `dist` 里的变动导致,将 `@mf-types` 添加至忽略文件即可。 @@ -89,4 +91,13 @@ A: 大概率是编译器监听了类型文件夹或者 `dist` 里的变动导致 config.watchOptions = { ignored: ['**/node_modules/**', '**/@mf-types/**'], }; -``` \ No newline at end of file +``` + +### 2. 没有生成类型文件,如何查看原因 + +**解决方案** + +1. 在项目启动命令前加上 `FEDERATION_DEBUG=true` 环境变量 +2. 设置 [dts.displayErrorInTerminal](../../configure/dts#displayerrorinterminal) 为 `true` +3. 启动项目,查看控制台输出 +4. (可选)若控制台没输出错误,查看 `.mf/typesGenerate.log` 日志文件 diff --git a/apps/website-new/docs/zh/guide/basic/webpack.mdx b/apps/website-new/docs/zh/guide/basic/webpack.mdx index 8f3cdf9e37a..554c1bfebe1 100644 --- a/apps/website-new/docs/zh/guide/basic/webpack.mdx +++ b/apps/website-new/docs/zh/guide/basic/webpack.mdx @@ -1,80 +1,43 @@ # Webpack Plugin -- 能够构建出满足 `Module Federation` 加载规范的模块 -- 能够使用别名消费 `Module Federation` 规范的模块 +- 能够构建出满足 {props.name || 'Module Federation'} 加载规范的模块 +- 能够使用别名消费 {props.name || 'Module Federation'} 规范的模块 - 能够设置模块的共享依赖配置,当加载模块的宿主环境已经存在对应依赖时将不会重复加载 - 当模块具备远程类型时将会自动把远程模块的类型下载下来消费 - 消费远程模块时将具备热更新能力 +{props.tip} + ## 快速开始 +{props.demo} + ### 安装 你可以通过如下的命令安装插件: -import { PackageManagerTabs } from '@theme'; +import InstallKit from '@components/common/install-kit'; - +### 创建 {props.configName || 'module-federation.config.js'} + +创建 {props.configName || 'module-federation.config.js'} 文件,内容如下: + +import CreateConfig from '@components/common/webpack/create-config'; + +{props.createConfig || } + ### 注册插件 在 `Webpack` 中,你可以通过 `webpack.config.js` 配置文件中的 `plugins` 配置项来添加插件: -:::warning 公共路径自动配置与 Manifest -当使用 mf-manifest.json 远程模块时,`publicPath: 'auto'` 的支持还处于实验阶段 -::: - -```ts title='webpack.config.js' -import { ModuleFederationPlugin } from '@module-federation/enhanced/webpack'; -module.exports = { - devServer: { - port: 2000, - }, - output: { - // 使用 manifest 必须要配置 publicPath - publicPath: 'http://localhost:2000/', //or 'auto' - }, - plugins: [ - new ModuleFederationPlugin({ - name: 'rspack_provider', - filename: 'remoteEntry.js', - exposes: { - // 设置需要导出的模块,default 导出为 . - './button': './src/components/button', - }, - shared: { - react: { - singleton: true, - }, - 'react-dom': { - singleton: true, - }, - }, - }), - ], -}; -``` - -## 配置构建插件 - -- Type: `ModuleFederationPlugin(options: ModuleFederationOptions)` - -- Module federation 插件的配置结构如下所示: - -```ts -type ModuleFederationOptions { - name: string; - filename?: string, - remotes?: Array; - shared?: ShareInfos; -}; -``` +import RegisterPlugin from '@components/common/webpack/register-plugin'; + +{props.registerPlugin || } + +## 配置 你可以在 [Config 总览](../../configure/index) 页面找到所有配置项的详细说明。 diff --git a/apps/website-new/docs/zh/guide/debug/mode.mdx b/apps/website-new/docs/zh/guide/debug/mode.mdx index 062b3f2adeb..1cd232d9800 100644 --- a/apps/website-new/docs/zh/guide/debug/mode.mdx +++ b/apps/website-new/docs/zh/guide/debug/mode.mdx @@ -1,6 +1,6 @@ # 开启调试模式 -为了便于排查问题,Module Federation 提供了调试模式,你可以在执行构建时添加 FEDERATION_DEBUG=true 环境变量或者在浏览器执行 `localStorage.setItem('FEDERATION_DEBUG','true')` 来开启 Module Federation 的调试模式。 +为了便于排查问题,{props.name || 'Module Federation'} 提供了调试模式,你可以在执行构建时添加 FEDERATION_DEBUG=true 环境变量或者在浏览器执行 `localStorage.setItem('FEDERATION_DEBUG','true')` 来开启 {props.name || 'Module Federation'} 的调试模式。 构建添加环境变量: @@ -22,4 +22,4 @@ localStorage.setItem('FEDERATION_DEBUG','true') 开启调试模式后,你可以在控制台看到如下信息: -在调试模式下,你会看到 terminal 中输出了一些以 `[ Module Federation ]` 开头的日志。 +

在调试模式下,你会看到 terminal 中输出了一些以 [ {props.name || 'Module Federation'} ] 开头的日志。

diff --git a/apps/website-new/docs/zh/guide/framework/modernjs.mdx b/apps/website-new/docs/zh/guide/framework/modernjs.mdx index ec25577541e..55305dfbe1b 100644 --- a/apps/website-new/docs/zh/guide/framework/modernjs.mdx +++ b/apps/website-new/docs/zh/guide/framework/modernjs.mdx @@ -301,7 +301,7 @@ export async function loader({ params }) { ## API -`@module-federation/modern-js/runtime` 除了导出了 [MF Runtime](../basic/runtime),还提供供了一系列的 API 来帮助开发者更好的使用 Module Federation。 +`@module-federation/modern-js/runtime` 除了导出了 [MF Runtime](../basic/runtime/runtime),还提供供了一系列的 API 来帮助开发者更好的使用 Module Federation。 为防止与 Shared 冲突,需要通过下列方式引用。 diff --git a/apps/website-new/docs/zh/guide/start/features.mdx b/apps/website-new/docs/zh/guide/start/features.mdx index f29d9f48c85..009fece98e9 100644 --- a/apps/website-new/docs/zh/guide/start/features.mdx +++ b/apps/website-new/docs/zh/guide/start/features.mdx @@ -6,7 +6,7 @@ | 功能 | 描述 | | ----------------------------------------------------------------- | ---------------------------------------------------------- | -| [Federation Runtime](../basic/runtime) | 可以脱离构建插件:注册远程模块、消费远程模块、注册共享依赖 | +| [Federation Runtime](../basic/runtime/runtime) | 可以脱离构建插件:注册远程模块、消费远程模块、注册共享依赖 | | [Webpack Plugin](../basic/webpack) | 支持通过 Webpack 构建工具消费和生成远程模块 | | [Rspack Plugin](../basic/rspack) | 支持通过 Rspack 构建工具消费和生成远程模块 | | [类型提示](../basic/type-prompt) | 支持动态模块类型提示能力 | diff --git a/apps/website-new/docs/zh/guide/start/index.mdx b/apps/website-new/docs/zh/guide/start/index.mdx index 4ec98dec4b8..52cc0c848e2 100644 --- a/apps/website-new/docs/zh/guide/start/index.mdx +++ b/apps/website-new/docs/zh/guide/start/index.mdx @@ -26,7 +26,7 @@ Module Federation 2.0 具有以下特性: - ⚡ 代码共享、依赖复用 - 📝 Manifest -- 🎨 [Module Federation 运行时](../basic/runtime.mdx) +- 🎨 [Module Federation 运行时](../basic/runtime/runtime.mdx) - 🧩 [运行时插件系统](../../plugin/dev/index.mdx) - 🚀 [动态类型提示](../basic/type-prompt.mdx) - 🛠️ [Chrome Devtool](../debug/chrome-devtool) diff --git a/apps/website-new/docs/zh/guide/start/npm-packages.mdx b/apps/website-new/docs/zh/guide/start/npm-packages.mdx index e18968c1879..d0a1dd65a2b 100644 --- a/apps/website-new/docs/zh/guide/start/npm-packages.mdx +++ b/apps/website-new/docs/zh/guide/start/npm-packages.mdx @@ -11,7 +11,7 @@ Module Federation 核心包,作为 Webpack 构建插件、 Rspack 构建插件 - [npm](https://npmjs.com/package/@module-federation/enhanced) - [源代码](https://github.com/module-federation/core/tree/main/packages/enhanced) -- [Runtime 文档](/guide/basic/runtime) +- [Runtime 文档](/guide/basic/runtime/runtime) - [Rspack 构建插件](/guide/basic/rspack) - [Webpack 构建插件](/guide/basic/webpack) @@ -24,7 +24,7 @@ Module Federation 的 Runtime 包,通常使用 @module-federation/enhanced 来 - [npm](https://npmjs.com/package/@module-federation/runtime) - [源代码](https://github.com/module-federation/core/tree/main/packages/runtime) -- [文档](/guide/basic/runtime) +- [文档](/guide/basic/runtime/runtime) ## @module-federation/rspack diff --git a/apps/website-new/docs/zh/guide/troubleshooting/build/BUILD-002.mdx b/apps/website-new/docs/zh/guide/troubleshooting/build/BUILD-002.mdx new file mode 100644 index 00000000000..f581f0ed508 --- /dev/null +++ b/apps/website-new/docs/zh/guide/troubleshooting/build/BUILD-002.mdx @@ -0,0 +1,37 @@ +import ErrorCodeTitle from '@components/ErrorCodeTitle'; + + + +## 原因 + +Rspress 生产者在构建时,需要项目设置 `publicPath`,否则无法正常被其他消费者加载。 + +## 解决方法 + +设置 `publicPath`,可以通过以下方式设置: + +* 在 `rspress.config.ts` 中设置 [builderConfig.output.assetPrefix](https://rsbuild.rs/config/output/asset-prefix) + +```ts title="rspress.config.ts" +export default { + builderConfig: { + output: { + assetPrefix: 'https://module-federation.io/', + } + } +} +``` + +* 在 `rspress.config.js` 中设置 [builderConfig.tools.rspack](https://rsbuild.rs/config/tools/rspack) + +```ts title="rspress.config.ts" +export default { + builderConfig: { + tools: { + rspack: (config)=>{ + config.output.publicPath = 'https://module-federation.io/'; + }, + } + } +} +``` diff --git a/apps/website-new/docs/zh/guide/troubleshooting/runtime/RUNTIME-006.mdx b/apps/website-new/docs/zh/guide/troubleshooting/runtime/RUNTIME-006.mdx index 7b6a0a25970..cd8a7a383a3 100644 --- a/apps/website-new/docs/zh/guide/troubleshooting/runtime/RUNTIME-006.mdx +++ b/apps/website-new/docs/zh/guide/troubleshooting/runtime/RUNTIME-006.mdx @@ -11,4 +11,4 @@ import ErrorCodeTitle from '@components/ErrorCodeTitle'; 两者选一即可: 1. 使用 `loadShare` 替代 `loadShareSync` -2. 给当前共享依赖提供 [lib](../../basic/runtime#loadshare) 函数 +2. 给当前共享依赖提供 [lib](../../basic/runtime/runtime-api#loadshare) 函数 diff --git a/apps/website-new/docs/zh/practice/bridge/index.mdx b/apps/website-new/docs/zh/practice/bridge/index.mdx index cec9757060c..e009a9d8d04 100644 --- a/apps/website-new/docs/zh/practice/bridge/index.mdx +++ b/apps/website-new/docs/zh/practice/bridge/index.mdx @@ -11,7 +11,7 @@ - [如何消费和导出模块](../../guide/start/quick-start.mdx) - [Module Federation Builder plugin](../../guide/basic/rspack.mdx) -- [Module Federation Runtime 的特点和能力](../../guide/basic/runtime.mdx) +- [Module Federation Runtime 的特点和能力](../../guide/basic/runtime/runtime.mdx) ::: diff --git a/apps/website-new/docs/zh/practice/performance/prefetch.mdx b/apps/website-new/docs/zh/practice/performance/prefetch.mdx index ec2b4a66e9f..4746340ebeb 100644 --- a/apps/website-new/docs/zh/practice/performance/prefetch.mdx +++ b/apps/website-new/docs/zh/practice/performance/prefetch.mdx @@ -209,7 +209,7 @@ export const Button = () => { ### loadRemote #### 功能 -用户如果在消费者项目中手动调用 [loadRemote](/zh/guide/basic/runtime.html#loadremote) API,那么会认为消费者不仅希望加载生产者的静态资源,同时希望将接口请求也提前发出,这可以让项目获得更快的渲染速度, +用户如果在消费者项目中手动调用 [loadRemote](/zh/guide/basic/runtime/runtime-api.html#loadremote) API,那么会认为消费者不仅希望加载生产者的静态资源,同时希望将接口请求也提前发出,这可以让项目获得更快的渲染速度, 这在**首屏有前置请求**场景或者**希望次屏直出**这些场景中尤其有效,适用于在当前页面提前加载次屏模块的场景 #### 使用方法 ``` ts diff --git a/apps/website-new/module-federation.config.ts b/apps/website-new/module-federation.config.ts new file mode 100644 index 00000000000..8087eb8d66c --- /dev/null +++ b/apps/website-new/module-federation.config.ts @@ -0,0 +1,59 @@ +import { createModuleFederationConfig } from '@module-federation/rspress-plugin'; +import * as path from 'path'; + +const LANGUAGE = 'LANGUAGE'; +const LANGUAGES = ['zh', 'en']; + +const exposes = { + // basic + [`./rspack-${LANGUAGE}`]: `./docs/${LANGUAGE}/guide/basic/rspack.mdx`, + [`./webpack-${LANGUAGE}`]: `./docs/${LANGUAGE}/guide/basic/webpack.mdx`, + [`./rspress-${LANGUAGE}`]: `./docs/${LANGUAGE}/guide/basic/rspress.mdx`, + [`./cli-${LANGUAGE}`]: `./docs/${LANGUAGE}/guide/basic/cli.mdx`, + [`./type-prompt-${LANGUAGE}`]: `./docs/${LANGUAGE}/guide/basic/type-prompt.mdx`, + [`./css-isolate-${LANGUAGE}`]: `./docs/${LANGUAGE}/guide/basic/css-isolate.mdx`, + // runtime + // [`./runtime-overview-${LANGUAGE}`]: `./docs/${LANGUAGE}/guide/basic/runtime/runtime.mdx`, + // [`./runtime-api-${LANGUAGE}`]: `./docs/${LANGUAGE}/guide/basic/runtime/runtime-api.mdx`, + // [`./runtime-hooks-${LANGUAGE}`]: `./docs/${LANGUAGE}/guide/basic/runtime/runtime-hooks.mdx`, + + // debug + [`./mode-${LANGUAGE}`]: `./docs/${LANGUAGE}/guide/debug/mode.mdx`, + [`./variables-${LANGUAGE}`]: `./docs/${LANGUAGE}/guide/debug/variables.mdx`, + + // framework + // [`./modernjs-${LANGUAGE}`]: `./docs/${LANGUAGE}/guide/framework/modernjs.mdx`, + + // configure + [`./configure-overview-${LANGUAGE}`]: `./docs/${LANGUAGE}/configure/index.mdx`, + [`./configure-name-${LANGUAGE}`]: `./docs/${LANGUAGE}/configure/name.mdx`, + [`./configure-filename-${LANGUAGE}`]: `./docs/${LANGUAGE}/configure/filename.mdx`, + [`./configure-remotes-${LANGUAGE}`]: `./docs/${LANGUAGE}/configure/remotes.mdx`, + [`./configure-exposes-${LANGUAGE}`]: `./docs/${LANGUAGE}/configure/exposes.mdx`, + [`./configure-shared-${LANGUAGE}`]: `./docs/${LANGUAGE}/configure/shared.mdx`, + // [`./configure-runtimePlugins-${LANGUAGE}`]: `./docs/${LANGUAGE}/configure/runtimePlugins.mdx`, + [`./configure-getpublicpath-${LANGUAGE}`]: `./docs/${LANGUAGE}/configure/getpublicpath.mdx`, + [`./configure-implementation-${LANGUAGE}`]: `./docs/${LANGUAGE}/configure/implementation.mdx`, + [`./configure-dts-${LANGUAGE}`]: `./docs/${LANGUAGE}/configure/dts.mdx`, + [`./configure-dev-${LANGUAGE}`]: `./docs/${LANGUAGE}/configure/dev.mdx`, + [`./configure-manifest-${LANGUAGE}`]: `./docs/${LANGUAGE}/configure/manifest.mdx`, + // [`./configure-shareStrategy-${LANGUAGE}`]: `./docs/${LANGUAGE}/configure/shareStrategy.mdx`, + // [`./configure-experiments-${LANGUAGE}`]: `./docs/${LANGUAGE}/configure/experiments.mdx`, + + // blog + [`./error-load-remote-${LANGUAGE}`]: `./docs/${LANGUAGE}/blog/error-load-remote.mdx`, +}; + +export default createModuleFederationConfig({ + filename: 'remoteEntry.js', + name: 'mf_doc', + exposes: Object.entries(exposes).reduce((acc, [key, value]) => { + LANGUAGES.forEach((lang) => { + acc[key.replace(LANGUAGE, lang)] = path.join( + __dirname, + value.replace(LANGUAGE, lang), + ); + }); + return acc; + }, {}), +}); diff --git a/apps/website-new/netlify.toml b/apps/website-new/netlify.toml index 19024220ae6..80870ea1717 100644 --- a/apps/website-new/netlify.toml +++ b/apps/website-new/netlify.toml @@ -11,4 +11,9 @@ from = "/*" to = "/" status = 200 +[[headers]] + for = "/*" + [headers.values] + Access-Control-Allow-Origin = "*" + diff --git a/apps/website-new/package.json b/apps/website-new/package.json index 2807290ae52..42b48cf690e 100644 --- a/apps/website-new/package.json +++ b/apps/website-new/package.json @@ -9,14 +9,17 @@ }, "dependencies": { "framer-motion": "^10.0.0", - "rspress": "2.0.0-beta.14", + "rspress": "2.0.0-beta.16", "tailwindcss": "^3.2.7", "video-react": "^0.16.0", "xgplayer": "^3.0.16", "rspress-plugin-annotation-words": "0.0.1", "@rsbuild/plugin-sass": "^1.3.2", "@module-federation/error-codes": "workspace:*", - "@rspress/plugin-llms": "2.0.0-beta.14" + "@rspress/plugin-llms": "2.0.0-beta.16", + "@module-federation/rspress-plugin": "workspace:*", + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "devDependencies": { "@types/node": "^20" diff --git a/apps/website-new/rspress.config.ts b/apps/website-new/rspress.config.ts index e40aa7389e7..cbbc7f749e9 100644 --- a/apps/website-new/rspress.config.ts +++ b/apps/website-new/rspress.config.ts @@ -4,6 +4,8 @@ import { moduleFederationPluginOverview } from './src/moduleFederationPluginOver import { pluginAnnotationWords } from 'rspress-plugin-annotation-words'; import { pluginSass } from '@rsbuild/plugin-sass'; import { pluginLlms } from '@rspress/plugin-llms'; +import { pluginModuleFederation } from '@module-federation/rspress-plugin'; +import mfConfig from './module-federation.config'; const getNavbar = (lang: string) => { const cn = lang === 'zh'; @@ -84,9 +86,20 @@ export default defineConfig({ wordsMapPath: 'words-map.json', }), pluginLlms(), + pluginModuleFederation(mfConfig), ], builderConfig: { plugins: [moduleFederationPluginOverview, pluginSass()], + output: { + assetPrefix: + process.env.CONTEXT === 'deploy-preview' + ? process.env.DEPLOY_PRIME_URL + : 'https://module-federation.io/', + }, + dev: { + assetPrefix: true, + writeToDisk: true, + }, tools: { postcss: (config, { addPlugins }) => { addPlugins([require('tailwindcss/nesting'), require('tailwindcss')]); diff --git a/apps/website-new/src/components/common/cli/cmd-info.mdx b/apps/website-new/src/components/common/cli/cmd-info.mdx new file mode 100644 index 00000000000..97ba9931e6f --- /dev/null +++ b/apps/website-new/src/components/common/cli/cmd-info.mdx @@ -0,0 +1,17 @@ +```bash + +Usage: mf dts [options] + +generate or fetch the mf types + +Options: + --root specify the project root directory + --output specify the generated dts output directory + --fetch fetch types from remote, default is true (default: true) + --generate generate types, default is true (default: true) + -c --config specify the configuration file, can be a relative or absolute path + -m --mode Specify the runtime environment. You can choose "dev" or "prod". The default value is "dev". After setting, the process.env.NODE_ENV environment variable will be + automatically injected with "development" or "production" according to the value. (default: "dev") + -h, --help display help for command + +``` diff --git a/apps/website-new/src/components/common/cli/view-all-cmds.mdx b/apps/website-new/src/components/common/cli/view-all-cmds.mdx new file mode 100644 index 00000000000..070801fa290 --- /dev/null +++ b/apps/website-new/src/components/common/cli/view-all-cmds.mdx @@ -0,0 +1,21 @@ +如果你需要查看所有可用的 CLI 命令,请在项目目录中运行以下命令: + +```bash +npx mf -h +``` + +输出如下: + +```bash + +Usage: mf [options] + +Options: + -V, --version output the version number + -h, --help display help for command + +Commands: + dts [options] generate or fetch the mf types + help [command] display help for command + +``` diff --git a/apps/website-new/src/components/common/configure/array-shared.mdx b/apps/website-new/src/components/common/configure/array-shared.mdx new file mode 100644 index 00000000000..af47a4e2c44 --- /dev/null +++ b/apps/website-new/src/components/common/configure/array-shared.mdx @@ -0,0 +1,7 @@ +```ts +new ModuleFederationPlugin({ + name: 'mf_host', + shared: ['react', 'react-dom'], + //... +}); +``` diff --git a/apps/website-new/src/components/common/configure/object-shared.mdx b/apps/website-new/src/components/common/configure/object-shared.mdx new file mode 100644 index 00000000000..e64fd7897e8 --- /dev/null +++ b/apps/website-new/src/components/common/configure/object-shared.mdx @@ -0,0 +1,13 @@ +```ts +new ModuleFederationPlugin({ + name: 'mf_host', + shared: { + react: { + singleton: true, + requiredVersion: '~18.2.0', + fixedDependencies: true, + }, + }, + //... +}); +``` diff --git a/apps/website-new/src/components/common/install-kit.mdx b/apps/website-new/src/components/common/install-kit.mdx new file mode 100644 index 00000000000..ca4b94e4a8c --- /dev/null +++ b/apps/website-new/src/components/common/install-kit.mdx @@ -0,0 +1,10 @@ +import { PackageManagerTabs } from '@theme'; + + diff --git a/apps/website-new/src/components/common/rspack/create-config.mdx b/apps/website-new/src/components/common/rspack/create-config.mdx new file mode 100644 index 00000000000..3f8346916d8 --- /dev/null +++ b/apps/website-new/src/components/common/rspack/create-config.mdx @@ -0,0 +1,21 @@ +```ts title='module-federation.config.ts' +import { createModuleFederationConfig } from '@module-federation/enhanced/rspack'; + +export default createModuleFederationConfig({ + name: 'host', + remotes: { + provider: 'provider@http://localhost:2004/mf-manifest.json', + }, + exposes: { + './Button': './src/components/Button.tsx', + }, + shared: { + react: { + singleton: true, + }, + 'react-dom': { + singleton: true, + }, + }, +}); +``` diff --git a/apps/website-new/src/components/common/rspack/register-plugin.mdx b/apps/website-new/src/components/common/rspack/register-plugin.mdx new file mode 100644 index 00000000000..3543df89665 --- /dev/null +++ b/apps/website-new/src/components/common/rspack/register-plugin.mdx @@ -0,0 +1,8 @@ +```ts title='rspack.config.ts' +import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack'; +import mfConfig from './module-federation.config'; + +export default defineConfig({ + plugins: [new ModuleFederationPlugin(mfConfig)], +}); +``` diff --git a/apps/website-new/src/components/common/rspress/config-type.mdx b/apps/website-new/src/components/common/rspress/config-type.mdx new file mode 100644 index 00000000000..2db8bc651c2 --- /dev/null +++ b/apps/website-new/src/components/common/rspress/config-type.mdx @@ -0,0 +1,11 @@ +```ts +export declare const pluginModuleFederation: ( + moduleFederationOptions: ModuleFederationOptions, + rspressOptions?: RspressPluginOptions, +) => RspressPlugin; + +type RspressPluginOptions = { + autoShared?: boolean; + rebuildSearchIndex?: boolean; +}; +``` diff --git a/apps/website-new/src/components/common/rspress/create-config.mdx b/apps/website-new/src/components/common/rspress/create-config.mdx new file mode 100644 index 00000000000..4514c869b77 --- /dev/null +++ b/apps/website-new/src/components/common/rspress/create-config.mdx @@ -0,0 +1,12 @@ +```ts title='module-federation.config.ts' +import { createModuleFederationConfig } from '@module-federation/rspress-plugin'; + +export default createModuleFederationConfig({ + filename: 'remoteEntry.js', + name: 'mf_doc', + exposes: { + './intro-en': './docs/en/guide/intro.mdx', + './intro-zh': './docs/zh/guide/intro.mdx', + }, +}); +``` diff --git a/apps/website-new/src/components/common/rspress/register-plugin.mdx b/apps/website-new/src/components/common/rspress/register-plugin.mdx new file mode 100644 index 00000000000..6b3e3b6b340 --- /dev/null +++ b/apps/website-new/src/components/common/rspress/register-plugin.mdx @@ -0,0 +1,15 @@ +```ts title='rspress.config.ts' +import { defineConfig } from 'rspress/config'; +import { pluginModuleFederation } from '@module-federation/rspress-plugin'; +import mfConfig from './module-federation.config'; + +export default defineConfig({ + // ... + plugins: [pluginModuleFederation(mfConfig)], + builderConfig: { + output: { + assetPrefix: 'https://module-federation.io/', + }, + }, +}); +``` diff --git a/apps/website-new/src/components/common/webpack/create-config.mdx b/apps/website-new/src/components/common/webpack/create-config.mdx new file mode 100644 index 00000000000..b37c83ce37f --- /dev/null +++ b/apps/website-new/src/components/common/webpack/create-config.mdx @@ -0,0 +1,19 @@ +```js title='module-federation.config.js' +module.export = { + name: 'host', + remotes: { + provider: 'provider@http://localhost:2004/mf-manifest.json', + }, + exposes: { + './Button': './src/components/Button.tsx', + }, + shared: { + react: { + singleton: true, + }, + 'react-dom': { + singleton: true, + }, + }, +}; +``` diff --git a/apps/website-new/src/components/common/webpack/register-plugin.mdx b/apps/website-new/src/components/common/webpack/register-plugin.mdx new file mode 100644 index 00000000000..b0880bdac45 --- /dev/null +++ b/apps/website-new/src/components/common/webpack/register-plugin.mdx @@ -0,0 +1,16 @@ +```js title='webpack.config.js' +const { + ModuleFederationPlugin, +} = require('@module-federation/enhanced/webpack'); +const mfConfig = require('./module-federation.config'); + +module.exports = { + devServer: { + port: 2000, + }, + output: { + publicPath: 'http://localhost:2000/', + }, + plugins: [new ModuleFederationPlugin(mfConfig)], +}; +``` diff --git a/packages/dts-plugin/src/core/configurations/remotePlugin.ts b/packages/dts-plugin/src/core/configurations/remotePlugin.ts index 9562a219126..e82463895ea 100644 --- a/packages/dts-plugin/src/core/configurations/remotePlugin.ts +++ b/packages/dts-plugin/src/core/configurations/remotePlugin.ts @@ -120,6 +120,7 @@ const readTsConfig = ( outputDir || configContent.options.outDir || 'dist', ); + const excludeExtensions = ['.mdx', '.md']; const filesToCompile = [ ...Object.values(mapComponentsToExpose), ...configContent.fileNames.filter( @@ -128,7 +129,9 @@ const readTsConfig = ( !filename.startsWith(outDirWithoutTypesFolder), ), ...additionalFilesToCompile, - ]; + ].filter( + (filename) => !excludeExtensions.some((ext) => filename.endsWith(ext)), + ); rawTsConfigJson.include = []; rawTsConfigJson.files = filesToCompile; diff --git a/packages/dts-plugin/src/core/lib/DTSManager.ts b/packages/dts-plugin/src/core/lib/DTSManager.ts index 22f592867a9..0dbefc9e728 100644 --- a/packages/dts-plugin/src/core/lib/DTSManager.ts +++ b/packages/dts-plugin/src/core/lib/DTSManager.ts @@ -148,6 +148,11 @@ class DTSManager { return; } + if (!tsConfig.files?.length) { + logger.info('No type files to compile, skip'); + return; + } + if (tsConfig.compilerOptions.tsBuildInfoFile) { try { const tsBuildInfoFile = path.resolve( diff --git a/packages/dts-plugin/src/core/lib/utils.ts b/packages/dts-plugin/src/core/lib/utils.ts index deb9a288740..fe77c51ce5c 100644 --- a/packages/dts-plugin/src/core/lib/utils.ts +++ b/packages/dts-plugin/src/core/lib/utils.ts @@ -42,7 +42,7 @@ export function retrieveTypesAssetsInfo(options: RemoteOptions) { try { const { tsConfig, remoteOptions, mapComponentsToExpose } = retrieveRemoteConfig(options); - if (!Object.keys(mapComponentsToExpose).length) { + if (!Object.keys(mapComponentsToExpose).length || !tsConfig.files.length) { return { zipPrefix, apiTypesPath, @@ -51,6 +51,7 @@ export function retrieveTypesAssetsInfo(options: RemoteOptions) { apiFileName: '', }; } + const mfTypesPath = retrieveMfTypesPath(tsConfig, remoteOptions); zipTypesPath = retrieveTypesZipPath(mfTypesPath, remoteOptions); if (remoteOptions.generateAPITypes) { diff --git a/packages/enhanced/src/index.ts b/packages/enhanced/src/index.ts index be7892e39fd..9ff68acb16b 100644 --- a/packages/enhanced/src/index.ts +++ b/packages/enhanced/src/index.ts @@ -27,10 +27,6 @@ export const container = { }, }; -export const createModuleFederationConfig = ( - options: moduleFederationPlugin.ModuleFederationPluginOptions, -): moduleFederationPlugin.ModuleFederationPluginOptions => { - return options; -}; +export { createModuleFederationConfig } from '@module-federation/sdk'; export type { moduleFederationPlugin }; diff --git a/packages/enhanced/src/rspack.ts b/packages/enhanced/src/rspack.ts index 59230e0eaef..ffc8d34a944 100644 --- a/packages/enhanced/src/rspack.ts +++ b/packages/enhanced/src/rspack.ts @@ -1,4 +1,6 @@ export { ModuleFederationPlugin, + GetPublicPathPlugin, PLUGIN_NAME, } from '@module-federation/rspack/plugin'; +export { createModuleFederationConfig } from '@module-federation/sdk'; diff --git a/packages/error-codes/src/desc.ts b/packages/error-codes/src/desc.ts index 658aea2be07..9116f82d418 100644 --- a/packages/error-codes/src/desc.ts +++ b/packages/error-codes/src/desc.ts @@ -9,6 +9,7 @@ import { RUNTIME_008, TYPE_001, BUILD_001, + BUILD_002, } from './error-codes'; export const runtimeDescMap = { @@ -29,6 +30,7 @@ export const typeDescMap = { export const buildDescMap = { [BUILD_001]: 'Failed to find expose module.', + [BUILD_002]: 'PublicPath is required in prod mode.', }; export const errorDescMap = { diff --git a/packages/error-codes/src/error-codes.ts b/packages/error-codes/src/error-codes.ts index bbe390df0bb..aa2d5850b78 100644 --- a/packages/error-codes/src/error-codes.ts +++ b/packages/error-codes/src/error-codes.ts @@ -9,3 +9,4 @@ export const RUNTIME_008 = 'RUNTIME-008'; export const TYPE_001 = 'TYPE-001'; export const BUILD_001 = 'BUILD-001'; +export const BUILD_002 = 'BUILD-002'; diff --git a/packages/manifest/src/StatsManager.ts b/packages/manifest/src/StatsManager.ts index 98fe5bfda11..405aa01a42a 100644 --- a/packages/manifest/src/StatsManager.ts +++ b/packages/manifest/src/StatsManager.ts @@ -129,7 +129,7 @@ class StatsManager { (this._options?.library?.type as RemoteEntryType | undefined) || 'global', }, - types: getTypesMetaInfo(this._options, compiler.context, compilation), + types: getTypesMetaInfo(this._options, compiler.context), globalName: globalName, pluginVersion: this._pluginVersion, }; diff --git a/packages/manifest/src/utils.ts b/packages/manifest/src/utils.ts index 36254a691ea..27e0b4c15bd 100644 --- a/packages/manifest/src/utils.ts +++ b/packages/manifest/src/utils.ts @@ -262,7 +262,6 @@ export function getFileName( export function getTypesMetaInfo( pluginOptions: moduleFederationPlugin.ModuleFederationPluginOptions, context: string, - compilation: Compilation, ): MetaDataTypes { const defaultRemoteOptions = { generateAPITypes: true, diff --git a/packages/modernjs/src/cli/ssrPlugin.ts b/packages/modernjs/src/cli/ssrPlugin.ts index 7b65c6d4011..5224a419f95 100644 --- a/packages/modernjs/src/cli/ssrPlugin.ts +++ b/packages/modernjs/src/cli/ssrPlugin.ts @@ -5,7 +5,7 @@ import { ModuleFederationPlugin as RspackModuleFederationPlugin } from '@module- import UniverseEntryChunkTrackerPlugin from '@module-federation/node/universe-entry-chunk-tracker-plugin'; import logger from '../logger'; import { isDev } from './utils'; -import { updateStatsAndManifest } from '@module-federation/rsbuild-plugin/manifest'; +import { updateStatsAndManifest } from '@module-federation/rsbuild-plugin/utils'; import { isWebTarget, skipByTarget } from './utils'; import type { diff --git a/packages/rsbuild-plugin/package.json b/packages/rsbuild-plugin/package.json index 195f641f641..9881b005955 100644 --- a/packages/rsbuild-plugin/package.json +++ b/packages/rsbuild-plugin/package.json @@ -27,11 +27,6 @@ "types": "./dist/constant.cjs.d.ts", "import": "./dist/constant.esm.js", "require": "./dist/constant.cjs.js" - }, - "./manifest": { - "types": "./dist/manifest.cjs.d.ts", - "import": "./dist/manifest.esm.js", - "require": "./dist/manifest.cjs.js" } }, "main": "./dist/index.cjs.js", @@ -46,9 +41,6 @@ ], "constant": [ "./dist/constant.cjs.d.ts" - ], - "manifest": [ - "./dist/manifest.cjs.d.ts" ] } }, diff --git a/packages/rsbuild-plugin/rollup.config.js b/packages/rsbuild-plugin/rollup.config.js index 81dd245a4f8..9770c7c7057 100644 --- a/packages/rsbuild-plugin/rollup.config.js +++ b/packages/rsbuild-plugin/rollup.config.js @@ -14,8 +14,7 @@ module.exports = (rollupConfig, _projectOptions) => { rollupConfig.input = { index: 'packages/rsbuild-plugin/src/cli/index.ts', utils: 'packages/rsbuild-plugin/src/utils/index.ts', - constant: 'packages/rsbuild-plugin/src/utils/constant.ts', - manifest: 'packages/rsbuild-plugin/src/cli/manifest.ts', + constant: 'packages/rsbuild-plugin/src/constant.ts', }; return rollupConfig; }; diff --git a/packages/rsbuild-plugin/src/cli/index.ts b/packages/rsbuild-plugin/src/cli/index.ts index f6ee1c0a9fb..71921b05a74 100644 --- a/packages/rsbuild-plugin/src/cli/index.ts +++ b/packages/rsbuild-plugin/src/cli/index.ts @@ -4,42 +4,52 @@ import { PLUGIN_NAME, } from '@module-federation/enhanced/rspack'; import { isRequiredVersion } from '@module-federation/sdk'; +import pkgJson from '../../package.json'; +import logger from '../logger'; import { isRegExp, autoDeleteSplitChunkCacheGroups, addDataFetchExposes, -} from '../utils/index'; -import pkgJson from '../../package.json'; -import logger from '../logger'; -import { createSSRMFConfig, createSSRREnvConfig, setSSREnv, SSR_ENV_NAME, SSR_DIR, -} from './ssr'; -import { updateStatsAndManifest } from './manifest'; + updateStatsAndManifest, + patchSSRRspackConfig, +} from '../utils'; import type { moduleFederationPlugin, sharePlugin, } from '@module-federation/sdk'; import type { RsbuildConfig, RsbuildPlugin, Rspack } from '@rsbuild/core'; +import { + CALL_NAME_MAP, + RSPRESS_BUNDLER_CONFIG_NAME, + RSPRESS_SSR_DIR, +} from '../constant'; type ModuleFederationOptions = moduleFederationPlugin.ModuleFederationPluginOptions; type RSBUILD_PLUGIN_OPTIONS = { ssr?: boolean; + // ssr dir, default is ssr + ssrDir?: string; + // target copy environment name, default is mf + environment?: string; }; type ExposedAPIType = { options: { nodePlugin?: ModuleFederationPlugin; browserPlugin?: ModuleFederationPlugin; + rspressSSGPlugin?: ModuleFederationPlugin; distOutputDir?: string; }; isSSRConfig: typeof isSSRConfig; + isRspressSSGConfig: typeof isRspressSSGConfig; }; export type { ModuleFederationOptions, ExposedAPIType }; @@ -82,12 +92,21 @@ export function isMFFormat(bundlerConfig: Rspack.Configuration) { const isSSRConfig = (bundlerConfigName?: string) => Boolean(bundlerConfigName === SSR_ENV_NAME); +const isRspressSSGConfig = (bundlerConfigName?: string) => { + return bundlerConfigName === RSPRESS_BUNDLER_CONFIG_NAME; +}; + export const pluginModuleFederation = ( moduleFederationOptions: ModuleFederationOptions, - rsbuildOptions?: RSBUILD_PLUGIN_OPTIONS, + rsbuildOptions: RSBUILD_PLUGIN_OPTIONS, ): RsbuildPlugin => ({ name: RSBUILD_PLUGIN_MODULE_FEDERATION_NAME, setup: (api) => { + const { + ssr = undefined, + ssrDir = SSR_DIR, + environment = DEFAULT_MF_ENVIRONMENT_NAME, + } = rsbuildOptions || {}; const { callerName } = api.context; const originalRsbuildConfig = api.getRsbuildConfig(); if (!callerName) { @@ -95,21 +114,20 @@ export const pluginModuleFederation = ( '`callerName` is undefined. Please ensure the @rsbuild/core version is higher than 1.3.21 .', ); } - const isRslib = callerName === 'rslib'; - const isSSR = Boolean(rsbuildOptions?.ssr); + const isRslib = callerName === CALL_NAME_MAP.RSLIB; + const isRspress = callerName === CALL_NAME_MAP.RSPRESS; + const isSSR = Boolean(ssr); if (isSSR && !isStoryBook(originalRsbuildConfig)) { - if (!isRslib) { + if (!isRslib && !isRspress) { throw new Error(`'ssr' option is only supported in rslib.`); } const rsbuildConfig = api.getRsbuildConfig(); if ( - !rsbuildConfig.environments?.[DEFAULT_MF_ENVIRONMENT_NAME] || + !rsbuildConfig.environments?.[environment] || Object.keys(rsbuildConfig.environments).some( - (key) => - key.startsWith(DEFAULT_MF_ENVIRONMENT_NAME) && - key !== DEFAULT_MF_ENVIRONMENT_NAME, + (key) => key.startsWith(environment) && key !== environment, ) ) { throw new Error( @@ -170,7 +188,7 @@ export const pluginModuleFederation = ( 'View https://module-federation.io/guide/troubleshooting/other.html#cors-warn for more details.', ]; - !isRslib && logger.warn(corsWarnMsgs.join('\n')); + !isRslib && !isRspress && logger.warn(corsWarnMsgs.join('\n')); config.server.headers['Access-Control-Allow-Origin'] = '*'; } @@ -199,8 +217,11 @@ export const pluginModuleFederation = ( ); } config.environments![SSR_ENV_NAME] = createSSRREnvConfig( - config.environments?.[DEFAULT_MF_ENVIRONMENT_NAME]!, + config.environments?.[environment]!, moduleFederationOptions, + ssrDir, + config, + callerName, ); } }); @@ -223,6 +244,7 @@ export const pluginModuleFederation = ( distOutputDir: undefined, }, isSSRConfig, + isRspressSSGConfig, }; api.expose( RSBUILD_PLUGIN_MODULE_FEDERATION_NAME, @@ -233,7 +255,7 @@ export const pluginModuleFederation = ( throw new Error('Can not get bundlerConfigs!'); } bundlerConfigs.forEach((bundlerConfig) => { - if (!isMFFormat(bundlerConfig)) { + if (!isMFFormat(bundlerConfig) && !isRspress) { return; } else if (isStoryBook(originalRsbuildConfig)) { bundlerConfig.output!.uniqueName = `${moduleFederationOptions.name}-storybook-host`; @@ -300,7 +322,8 @@ export const pluginModuleFederation = ( if ( !bundlerConfig.output?.chunkLoadingGlobal && - !isSSRConfig(bundlerConfig.name) + !isSSRConfig(bundlerConfig.name) && + !isRspressSSGConfig(bundlerConfig.name) ) { bundlerConfig.output!.chunkLoading = 'jsonp'; bundlerConfig.output!.chunkLoadingGlobal = `chunk_${moduleFederationOptions.name}`; @@ -323,7 +346,34 @@ export const pluginModuleFederation = ( generateMergedStatsAndManifestOptions.options.nodePlugin, ); return; + } else if (isRspressSSGConfig(bundlerConfig.name)) { + const mfConfig = { + ...createSSRMFConfig(moduleFederationOptions), + // expose in mf-ssg env + exposes: {}, + manifest: false, + library: undefined, + }; + patchSSRRspackConfig( + bundlerConfig, + mfConfig, + RSPRESS_SSR_DIR, + callerName, + false, + false, + ); + bundlerConfig.output ||= {}; + bundlerConfig.output.publicPath = '/'; + // MF depend on asyncChunks + bundlerConfig.output.asyncChunks = undefined; + generateMergedStatsAndManifestOptions.options.rspressSSGPlugin = + new ModuleFederationPlugin(mfConfig); + bundlerConfig.plugins!.push( + generateMergedStatsAndManifestOptions.options.rspressSSGPlugin, + ); + return; } + generateMergedStatsAndManifestOptions.options.browserPlugin = new ModuleFederationPlugin(moduleFederationOptions); generateMergedStatsAndManifestOptions.options.distOutputDir = @@ -355,4 +405,4 @@ export const pluginModuleFederation = ( }, }); -export { createModuleFederationConfig } from '@module-federation/enhanced'; +export { createModuleFederationConfig } from '@module-federation/sdk'; diff --git a/packages/rsbuild-plugin/src/constant.ts b/packages/rsbuild-plugin/src/constant.ts new file mode 100644 index 00000000000..b981d2d3aa8 --- /dev/null +++ b/packages/rsbuild-plugin/src/constant.ts @@ -0,0 +1,9 @@ +export const DEFAULT_ASSET_PREFIX = '/'; +export const DATA_FETCH_IDENTIFIER = 'data'; +export const DATA_FETCH_CLIENT_SUFFIX = '.client'; +export const CALL_NAME_MAP = { + RSPRESS: 'rspress', + RSLIB: 'rslib', +}; +export const RSPRESS_BUNDLER_CONFIG_NAME = 'node'; +export const RSPRESS_SSR_DIR = 'ssr'; diff --git a/packages/rsbuild-plugin/src/utils/addDataFetchExposes.ts b/packages/rsbuild-plugin/src/utils/addDataFetchExposes.ts index 8ec61daf765..1c00a46beed 100644 --- a/packages/rsbuild-plugin/src/utils/addDataFetchExposes.ts +++ b/packages/rsbuild-plugin/src/utils/addDataFetchExposes.ts @@ -3,7 +3,7 @@ import path from 'path'; import { TEMP_DIR } from '@module-federation/sdk'; import type { moduleFederationPlugin } from '@module-federation/sdk'; -import { DATA_FETCH_CLIENT_SUFFIX, DATA_FETCH_IDENTIFIER } from './constant'; +import { DATA_FETCH_CLIENT_SUFFIX, DATA_FETCH_IDENTIFIER } from '../constant'; const addDataFetchExpose = ( exposes: moduleFederationPlugin.ExposesObject, diff --git a/packages/rsbuild-plugin/src/utils/constant.ts b/packages/rsbuild-plugin/src/utils/constant.ts deleted file mode 100644 index 433019e2baf..00000000000 --- a/packages/rsbuild-plugin/src/utils/constant.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const DEFAULT_ASSET_PREFIX = '/'; -export const DATA_FETCH_IDENTIFIER = 'data'; -export const DATA_FETCH_CLIENT_SUFFIX = '.client'; diff --git a/packages/rsbuild-plugin/src/utils/index.ts b/packages/rsbuild-plugin/src/utils/index.ts index ed518143163..93ae8f4d8e5 100644 --- a/packages/rsbuild-plugin/src/utils/index.ts +++ b/packages/rsbuild-plugin/src/utils/index.ts @@ -4,6 +4,17 @@ export function isRegExp(target: any) { return util.types.isRegExp(target); } -export { DEFAULT_ASSET_PREFIX } from './constant'; +export { DEFAULT_ASSET_PREFIX } from '../constant'; export { autoDeleteSplitChunkCacheGroups } from './autoDeleteSplitChunkCacheGroups'; export { addDataFetchExposes } from './addDataFetchExposes'; + +export { updateStatsAndManifest } from './manifest'; + +export { + patchSSRRspackConfig, + createSSRREnvConfig, + createSSRMFConfig, + setSSREnv, + SSR_DIR, + SSR_ENV_NAME, +} from './ssr'; diff --git a/packages/rsbuild-plugin/src/cli/manifest.ts b/packages/rsbuild-plugin/src/utils/manifest.ts similarity index 100% rename from packages/rsbuild-plugin/src/cli/manifest.ts rename to packages/rsbuild-plugin/src/utils/manifest.ts diff --git a/packages/rsbuild-plugin/src/cli/ssr.spec.ts b/packages/rsbuild-plugin/src/utils/ssr.spec.ts similarity index 96% rename from packages/rsbuild-plugin/src/cli/ssr.spec.ts rename to packages/rsbuild-plugin/src/utils/ssr.spec.ts index 2a399461e4e..f2bd33758dd 100644 --- a/packages/rsbuild-plugin/src/cli/ssr.spec.ts +++ b/packages/rsbuild-plugin/src/utils/ssr.spec.ts @@ -86,7 +86,7 @@ describe('patchSSRRspackConfig', () => { it('should throw error if publicPath is not a string', () => { const config = JSON.parse(JSON.stringify(baseConfig)); config.output.publicPath = undefined; - expect(() => patchSSRRspackConfig(config, baseMfConfig)).toThrow( + expect(() => patchSSRRspackConfig(config, baseMfConfig, 'ssr')).toThrow( 'publicPath must be string!', ); }); @@ -94,20 +94,20 @@ describe('patchSSRRspackConfig', () => { it('should throw error if publicPath is "auto"', () => { const config = JSON.parse(JSON.stringify(baseConfig)); config.output.publicPath = 'auto'; - expect(() => patchSSRRspackConfig(config, baseMfConfig)).toThrow( + expect(() => patchSSRRspackConfig(config, baseMfConfig, 'ssr')).toThrow( 'publicPath can not be "auto"!', ); }); it('should update publicPath correctly', () => { const config = JSON.parse(JSON.stringify(baseConfig)); - const patchedConfig = patchSSRRspackConfig(config, baseMfConfig); + const patchedConfig = patchSSRRspackConfig(config, baseMfConfig, 'ssr'); expect(patchedConfig.output?.publicPath).toBe(`/test/${SSR_DIR}/`); }); it('should set target to async-node', () => { const config = JSON.parse(JSON.stringify(baseConfig)); - const patchedConfig = patchSSRRspackConfig(config, baseMfConfig); + const patchedConfig = patchSSRRspackConfig(config, baseMfConfig, 'ssr'); expect(patchedConfig.target).toBe('async-node'); }); @@ -115,7 +115,7 @@ describe('patchSSRRspackConfig', () => { const env = process.env.NODE_ENV; process.env.NODE_ENV = 'development'; const config = JSON.parse(JSON.stringify(baseConfig)); - const patchedConfig = patchSSRRspackConfig(config, baseMfConfig); + const patchedConfig = patchSSRRspackConfig(config, baseMfConfig, 'ssr'); expect(patchedConfig.plugins).toHaveLength(1); // @ts-expect-error default is a class expect(patchedConfig.plugins?.[0].constructor.name).toBe( @@ -136,7 +136,7 @@ describe('patchSSRRspackConfig', () => { const mfConfig: moduleFederationPlugin.ModuleFederationPluginOptions = { name: 'myApp', }; - const patchedConfig = patchSSRRspackConfig(config, mfConfig); + const patchedConfig = patchSSRRspackConfig(config, mfConfig, 'ssr'); expect(patchedConfig.output?.chunkFilename).toBe( 'js/[name]myApp-[chunkhash].js', ); @@ -152,7 +152,7 @@ describe('patchSSRRspackConfig', () => { plugins: [], }; const mfConfig: moduleFederationPlugin.ModuleFederationPluginOptions = {}; // No name in mfConfig - const patchedConfig = patchSSRRspackConfig(config, mfConfig); + const patchedConfig = patchSSRRspackConfig(config, mfConfig, 'ssr'); expect(patchedConfig.output?.chunkFilename).toBe( 'js/[name]myOutputUniqueName-[chunkhash].js', ); @@ -169,7 +169,7 @@ describe('patchSSRRspackConfig', () => { const mfConfig: moduleFederationPlugin.ModuleFederationPluginOptions = { name: 'myApp', }; - const patchedConfig = patchSSRRspackConfig(config, mfConfig); + const patchedConfig = patchSSRRspackConfig(config, mfConfig, 'ssr'); expect(typeof patchedConfig.output?.chunkFilename).toBe('function'); }); @@ -182,7 +182,7 @@ describe('patchSSRRspackConfig', () => { plugins: [], }; const mfConfig: moduleFederationPlugin.ModuleFederationPluginOptions = {}; // No name - const patchedConfig = patchSSRRspackConfig(config, mfConfig); + const patchedConfig = patchSSRRspackConfig(config, mfConfig, 'ssr'); expect(patchedConfig.output?.chunkFilename).toBe('js/[name].js'); }); @@ -198,7 +198,7 @@ describe('patchSSRRspackConfig', () => { const mfConfig: moduleFederationPlugin.ModuleFederationPluginOptions = { name: 'myApp', }; - const patchedConfig = patchSSRRspackConfig(config, mfConfig); + const patchedConfig = patchSSRRspackConfig(config, mfConfig, 'ssr'); expect(patchedConfig.output?.chunkFilename).toBe('js/myApp-[name].js'); }); }); diff --git a/packages/rsbuild-plugin/src/cli/ssr.ts b/packages/rsbuild-plugin/src/utils/ssr.ts similarity index 70% rename from packages/rsbuild-plugin/src/cli/ssr.ts rename to packages/rsbuild-plugin/src/utils/ssr.ts index 54859e125f0..433c9436419 100644 --- a/packages/rsbuild-plugin/src/cli/ssr.ts +++ b/packages/rsbuild-plugin/src/utils/ssr.ts @@ -1,7 +1,9 @@ import path from 'path'; import { encodeName } from '@module-federation/sdk'; -import type { EnvironmentConfig, Rspack } from '@rsbuild/core'; + +import type { EnvironmentConfig, RsbuildConfig, Rspack } from '@rsbuild/core'; import type { moduleFederationPlugin } from '@module-federation/sdk'; +import { CALL_NAME_MAP } from '../constant'; export const SSR_DIR = 'ssr'; export const SSR_ENV_NAME = 'mf-ssr'; @@ -18,15 +20,29 @@ const isDev = () => { export function patchSSRRspackConfig( config: Rspack.Configuration, mfConfig: moduleFederationPlugin.ModuleFederationPluginOptions, + ssrDir: string, + callerName?: string, + resetEntry = true, + modifyPublicPath = true, ) { - if (typeof config.output?.publicPath !== 'string') { - throw new Error('publicPath must be string!'); + config.output ||= {}; + if (modifyPublicPath) { + if (typeof config.output?.publicPath !== 'string') { + throw new Error('publicPath must be string!'); + } + const publicPath = config.output.publicPath; + if (publicPath === 'auto') { + throw new Error('publicPath can not be "auto"!'); + } + + const publicPathWithSSRDir = `${publicPath}${ssrDir}/`; + config.output.publicPath = publicPathWithSSRDir; } - const publicPath = config.output.publicPath; - if (publicPath === 'auto') { - throw new Error('publicPath can not be "auto"!'); + + if (callerName === CALL_NAME_MAP.RSPRESS && resetEntry) { + // set virtue entry, only need mf entry + config.entry = 'data:application/node;base64,'; } - config.output.publicPath = `${config.output.publicPath}${SSR_DIR}/`; config.target = 'async-node'; // @module-federation/node/universe-entry-chunk-tracker-plugin only export cjs const UniverseEntryChunkTrackerPlugin = @@ -51,6 +67,9 @@ export function patchSSRRspackConfig( export function createSSRREnvConfig( envConfig: EnvironmentConfig, mfConfig: moduleFederationPlugin.ModuleFederationPluginOptions, + ssrDir: string, + rsbuildConfig: RsbuildConfig, + callerName?: string, ) { const ssrEnvConfig: EnvironmentConfig = { ...envConfig, @@ -59,7 +78,7 @@ export function createSSRREnvConfig( if (environment.name !== SSR_ENV_NAME) { return; } - patchSSRRspackConfig(config, mfConfig); + patchSSRRspackConfig(config, mfConfig, ssrDir, callerName); }, }, }; @@ -70,7 +89,12 @@ export function createSSRREnvConfig( target: 'node', distPath: { ...ssrEnvConfig.output?.distPath, - root: path.join(ssrEnvConfig.output?.distPath?.root || '', SSR_DIR), + root: path.join( + ssrEnvConfig.output?.distPath?.root || + rsbuildConfig.output?.distPath?.root || + '', + ssrDir, + ), }, }; return ssrEnvConfig; diff --git a/packages/rspack/src/ModuleFederationPlugin.ts b/packages/rspack/src/ModuleFederationPlugin.ts index df0dc7b97be..575b5342e6c 100644 --- a/packages/rspack/src/ModuleFederationPlugin.ts +++ b/packages/rspack/src/ModuleFederationPlugin.ts @@ -281,3 +281,5 @@ export class ModuleFederationPlugin implements RspackPluginInstance { return this._statsPlugin?.resourceInfo; } } + +export const GetPublicPathPlugin = RemoteEntryPlugin; diff --git a/packages/rspack/src/RemoteEntryPlugin.ts b/packages/rspack/src/RemoteEntryPlugin.ts index 42e80a4323b..303ab34fa39 100644 --- a/packages/rspack/src/RemoteEntryPlugin.ts +++ b/packages/rspack/src/RemoteEntryPlugin.ts @@ -33,19 +33,11 @@ export class RemoteEntryPlugin implements RspackPluginInstance { this._options = options; } - apply(compiler: Compiler): void { - const { name, getPublicPath } = this._options; - if (!getPublicPath || !name) { - return; - } - const containerManager = new ContainerManager(); - containerManager.init(this._options); - if (!containerManager.enable) { - logger.warn( - "Detect you don't set exposes, 'getPublicPath' will not have effect.", - ); - return; - } + static addPublicPathEntry( + compiler: Compiler, + getPublicPath: string, + name: string, + ) { let code; const sanitizedPublicPath = escapeUnsafeChars(getPublicPath); @@ -65,4 +57,20 @@ export class RemoteEntryPlugin implements RspackPluginInstance { }).apply(compiler); }); } + + apply(compiler: Compiler): void { + const { name, getPublicPath } = this._options; + if (!getPublicPath || !name) { + return; + } + const containerManager = new ContainerManager(); + containerManager.init(this._options); + if (!containerManager.enable) { + logger.warn( + "Detect you don't set exposes, 'getPublicPath' will not have effect.", + ); + return; + } + RemoteEntryPlugin.addPublicPathEntry(compiler, getPublicPath, name); + } } diff --git a/packages/rspress-plugin/README.md b/packages/rspress-plugin/README.md new file mode 100644 index 00000000000..46a832785f3 --- /dev/null +++ b/packages/rspress-plugin/README.md @@ -0,0 +1,33 @@ +# @examples/mf-react-component + +This example demonstrates how to use Rslib to build a simple Module Federation React component. + +### Command + +Build package + +``` +nx build rslib-module +``` + +Serve package + +``` +nx serve rslib-module +``` + +Dev package + +1. + +``` +nx dev rslib-module +``` + +2. + +``` +nx storybook rslib-module +``` + +visit http://localhost:6006 diff --git a/packages/rspress-plugin/package.json b/packages/rspress-plugin/package.json new file mode 100644 index 00000000000..407e3552b5c --- /dev/null +++ b/packages/rspress-plugin/package.json @@ -0,0 +1,54 @@ +{ + "name": "@module-federation/rspress-plugin", + "version": "0.15.0", + "type": "module", + "description": "Module Federation plugin for Rspress", + "keywords": [ + "Module Federation", + "Rspress" + ], + "files": [ + "dist/", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/module-federation/core", + "directory": "packages/rsbuild-plugin" + }, + "license": "MIT", + "author": "hanric ", + "exports": { + ".": { + "types": "./dist/esm/plugin.d.ts", + "import": "./dist/esm/index.js" + } + }, + "module": "./dist/esm/index.js", + "types": "./dist/esm/plugin.d.ts", + "scripts": { + "build": "rslib build", + "dev": "rslib mf-dev", + "build:watch": "rslib build --watch" + }, + "devDependencies": { + "@rslib/core": "^0.9.2", + "@rspress/shared": "2.0.0-beta.16", + "@types/html-to-text": "^9.0.4", + "@types/lodash-es": "^4.17.12", + "@types/react": "^18.3.11" + }, + "dependencies": { + "cheerio": "1.0.0-rc.12", + "@module-federation/sdk": "workspace:*", + "fs-extra": "11.3.0", + "html-to-text": "^9.0.5", + "lodash-es": "^4.17.21", + "@module-federation/enhanced": "workspace:*", + "@module-federation/rsbuild-plugin": "workspace:*", + "@module-federation/error-codes": "workspace:*" + } +} diff --git a/packages/rspress-plugin/project.json b/packages/rspress-plugin/project.json new file mode 100644 index 00000000000..5a46bcb7028 --- /dev/null +++ b/packages/rspress-plugin/project.json @@ -0,0 +1,39 @@ +{ + "name": "rspress-plugin", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/rspress-plugin/src", + "projectType": "library", + "tags": ["type:pkg"], + "targets": { + "build": { + "executor": "nx:run-commands", + "options": { + "commands": ["npm run build --prefix packages/rspress-plugin"] + } + }, + "dev": { + "executor": "nx:run-commands", + "options": { + "commands": ["npm run dev --prefix packages/rspress-plugin"] + }, + "dependsOn": [ + { + "target": "build", + "dependencies": true + } + ] + }, + "pre-release": { + "executor": "nx:run-commands", + "options": { + "parallel": false, + "commands": [ + { + "command": "nx run webpack-bundler-runtime:build", + "forwardAllArgs": false + } + ] + } + } + } +} diff --git a/packages/rspress-plugin/rslib.config.ts b/packages/rspress-plugin/rslib.config.ts new file mode 100644 index 00000000000..7feec847cfb --- /dev/null +++ b/packages/rspress-plugin/rslib.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from '@rslib/core'; + +const shared = { + dts: { + bundle: false, + }, +}; + +export default defineConfig({ + source: { + entry: { + index: 'src/plugin.ts', + }, + }, + lib: [ + { + ...shared, + format: 'esm', + autoExternal: true, + output: { + distPath: { + root: './dist/esm', + }, + }, + }, + ], +}); diff --git a/packages/rspress-plugin/src/findSearchIndexPath.ts b/packages/rspress-plugin/src/findSearchIndexPath.ts new file mode 100644 index 00000000000..58d509fa279 --- /dev/null +++ b/packages/rspress-plugin/src/findSearchIndexPath.ts @@ -0,0 +1,24 @@ +import { SEARCH_INDEX_NAME } from '@rspress/shared'; + +import path from 'path'; +import fs from 'fs'; + +export function findSearchIndexPaths(outputDir: string) { + const staticDir = path.join(outputDir, 'static'); + if (!fs.existsSync(staticDir)) { + return undefined; + } + const files = fs.readdirSync(staticDir); + const searchIndexFiles = files.filter( + (file) => + file.startsWith(SEARCH_INDEX_NAME) && + file.endsWith('.json') && + fs.statSync(path.join(staticDir, file)).isFile(), + ); + if (searchIndexFiles) { + return searchIndexFiles.map((searchIndexFile) => + path.join(staticDir, searchIndexFile), + ); + } + return undefined; +} diff --git a/packages/rspress-plugin/src/logger.ts b/packages/rspress-plugin/src/logger.ts new file mode 100644 index 00000000000..b20716ba237 --- /dev/null +++ b/packages/rspress-plugin/src/logger.ts @@ -0,0 +1,5 @@ +import { createLogger } from '@module-federation/sdk'; + +const logger = createLogger('[ Module Federation Rspress Plugin ]'); + +export default logger; diff --git a/packages/rspress-plugin/src/plugin.ts b/packages/rspress-plugin/src/plugin.ts new file mode 100644 index 00000000000..6f22e7205fb --- /dev/null +++ b/packages/rspress-plugin/src/plugin.ts @@ -0,0 +1,217 @@ +import path from 'path'; +import fs from 'fs-extra'; +import { pluginModuleFederation as rsbuildPluginModuleFederation } from '@module-federation/rsbuild-plugin'; +import { + getShortErrorMsg, + BUILD_002, + buildDescMap, +} from '@module-federation/error-codes'; + +import logger from './logger'; + +import type { moduleFederationPlugin } from '@module-federation/sdk'; +import type { RspressPlugin, Route, RouteMeta } from '@rspress/shared'; +import { rebuildSearchIndexByHtml } from './rebuildSearchIndexByHtml'; + +type RspressPluginOptions = { + autoShared?: boolean; + rebuildSearchIndex?: boolean; +}; + +type ExtractObjectType = T extends (...args: any[]) => any ? never : T; +type OmitArrayConfiguration = + T extends Array ? (T extends (infer U)[] ? U : T) : ExtractObjectType; + +type ToolsRspack = NonNullable< + NonNullable['tools']>['rspack'] +>; +type RspackConfig = ExtractObjectType>; + +const isDev = () => process.env.NODE_ENV === 'development'; + +function replaceEntryWithBootstrapEntry(bundlerConfig: RspackConfig) { + const { entry } = bundlerConfig; + if (!entry) { + logger.error('No entry found!'); + process.exit(1); + } + if (typeof entry === 'function') { + logger.error('Not support entry function!'); + process.exit(1); + } + + const replaceWithAsyncEntry = ( + entries: string[] | string, + entryName: string, + ) => { + const entryPath = path.resolve( + process.cwd(), + `node_modules/.federation/${entryName}-bootstrap.js`, + ); + fs.ensureDirSync(path.dirname(entryPath)); + + if (typeof entries === 'string') { + fs.writeFileSync( + entryPath, + `const entry = import ('${entries}'); + const render = entry.then(({render})=>(render)); + const routes = entry.then(({routes})=>(routes)); + export { + render, + routes + };`, + ); + return entryPath; + } else { + fs.writeFileSync( + entryPath, + `const entry = import ('${entries.slice(-1)[0]}'); + const render = entry.then(({render})=>(render)); + const routes = entry.then(({routes})=>(routes)); + export { + render, + routes + };`, + ); + return entries.slice(0, -1).concat(entryPath); + } + }; + + if (typeof entry === 'object' && !Array.isArray(entry)) { + Object.keys(entry).forEach((entryName) => { + const entryValue = entry[entryName]; + if (!Array.isArray(entryValue)) { + logger.error(`Not support entry ${typeof entryValue}!`); + process.exit(1); + } + entry[entryName] = replaceWithAsyncEntry( + entryValue, + `${entryName}${bundlerConfig.name ? `-${bundlerConfig.name}` : ''}`, + ); + }); + return; + } + bundlerConfig.entry = replaceWithAsyncEntry( + entry, + bundlerConfig.name || 'index', + ); + return; +} + +export function pluginModuleFederation( + mfConfig: moduleFederationPlugin.ModuleFederationPluginOptions, + rspressOptions?: RspressPluginOptions, +): RspressPlugin { + const { autoShared = true, rebuildSearchIndex = true } = rspressOptions || {}; + + if (autoShared) { + mfConfig.shared = { + react: { + singleton: true, + }, + 'react-dom': { + singleton: true, + }, + '@mdx-js/react': { singleton: true, requiredVersion: false }, + '@rspress/runtime': { + singleton: true, + requiredVersion: false, + }, + ...mfConfig.shared, + }; + } + + let enableSSG = false; + let outputDir = ''; + let routes: RouteMeta[] = []; + + return { + name: 'plugin-module-federation', + async beforeBuild(config) { + if (!isDev() && config.ssg !== false) { + enableSSG = true; + } + + config.builderConfig ||= {}; + config.builderConfig.dev ||= {}; + if ( + isDev() && + typeof config.builderConfig.dev.lazyCompilation === 'undefined' + ) { + logger.warn( + 'lazyCompilation is not fully supported for module federation, set lazyCompilation to false', + ); + config.builderConfig.dev.lazyCompilation = false; + } + config.builderConfig.plugins ||= []; + config.builderConfig.plugins.push( + rsbuildPluginModuleFederation(mfConfig, { + ssr: enableSSG, + environment: 'node', + ssrDir: 'mf-ssg', + }), + ); + }, + builderConfig: { + plugins: [], + tools: { + rspack(config) { + replaceEntryWithBootstrapEntry(config); + if (config.name === 'node') { + if ( + (config.output.publicPath === '/' || + config.output.publicPath === 'auto') && + mfConfig.exposes + ) { + logger.error( + getShortErrorMsg(BUILD_002, buildDescMap, { + publicPath: config.output.publicPath, + }), + ); + process.exit(1); + } + + outputDir = config.output.path!; + } + }, + }, + }, + routeGenerated(routeMetaArr) { + routes = routeMetaArr; + }, + async afterBuild(config) { + if (!mfConfig.remotes || isDev() || !rebuildSearchIndex) { + return; + } + if (!enableSSG) { + logger.error('rebuildSearchIndex is only supported for ssg'); + process.exit(1); + } + const searchConfig = config?.search || {}; + const replaceRules = config?.replaceRules || []; + const domain = + searchConfig?.mode === 'remote' ? (searchConfig.domain ?? '') : ''; + + const versioned = + searchConfig && + searchConfig.mode !== 'remote' && + searchConfig.versioned; + + const searchCodeBlocks = + 'codeBlocks' in searchConfig ? Boolean(searchConfig.codeBlocks) : true; + + await rebuildSearchIndexByHtml(routes, { + outputDir, + versioned, + replaceRules, + domain, + searchCodeBlocks, + defaultLang: config.lang || 'en', + }); + + logger.info('rebuildSearchIndex success!'); + }, + }; +} + +export { createModuleFederationConfig } from '@module-federation/sdk'; diff --git a/packages/rspress-plugin/src/rebuildSearchIndexByHtml.ts b/packages/rspress-plugin/src/rebuildSearchIndexByHtml.ts new file mode 100644 index 00000000000..b2d41d2c7b3 --- /dev/null +++ b/packages/rspress-plugin/src/rebuildSearchIndexByHtml.ts @@ -0,0 +1,228 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import * as cheerio from 'cheerio'; +import { htmlToText } from 'html-to-text'; +import { groupBy } from 'lodash-es'; +import { SEARCH_INDEX_NAME } from '@rspress/shared'; + +import type { + RouteMeta, + PageIndexInfo, + ReplaceRule, + Header, +} from '@rspress/shared'; +import { findSearchIndexPaths } from './findSearchIndexPath'; +import logger from './logger'; + +interface TocItem { + text: string; + id: string; + depth: number; +} + +export type RebuildSearchIndexByHtmlOptions = { + domain: string; + searchCodeBlocks: boolean; + replaceRules: ReplaceRule[]; + versioned?: boolean; + outputDir: string; + defaultLang: string; +}; + +function generateTocFromHtml(html: string) { + const $ = cheerio.load(html); + const headings = $('h1, h2, h3, h4, h5, h6'); + const toc: TocItem[] = []; + let title = ''; + + headings.each((index, heading) => { + const $heading = $(heading); + const text = $heading.text(); + const id = $heading.attr('id'); + const depth = parseInt(heading.tagName.replace('h', ''), 10); + + if (id) { + toc.push({ text, id, depth }); + } + if (depth === 1) { + title = text; + } + }); + + return { toc, title }; +} + +const replaceHtmlExt = (filepath: string) => { + return filepath.replace(path.extname(filepath), '.html'); +}; + +async function extractPageDataFromHtml( + routes: RouteMeta[], + options: RebuildSearchIndexByHtmlOptions, +) { + return Promise.all( + routes.map(async (route) => { + const { domain, searchCodeBlocks, outputDir, defaultLang } = options; + const defaultIndexInfo: PageIndexInfo = { + title: '', + content: '', + _html: '', + _flattenContent: '', + routePath: route.routePath, + lang: route.lang, + toc: [], + domain, + frontmatter: {}, + version: route.version, + _filepath: route.absolutePath, + _relativePath: '', + }; + + const htmlPath = replaceHtmlExt( + path.join( + outputDir, + route.lang === defaultLang + ? route.relativePath.replace(route.lang, '') + : route.relativePath, + ), + ); + const html = await fs.readFile(htmlPath, 'utf-8'); + let { toc: rawToc, title } = generateTocFromHtml(html); + let content = html; + + content = htmlToText(html, { + // decodeEntities: true, // default value of decodeEntities is `true`, so that htmlToText can decode < > + wordwrap: 80, + selectors: [ + { + selector: 'a', + options: { + ignoreHref: true, + }, + }, + { + selector: 'img', + format: 'skip', + }, + { + // Skip code blocks + selector: 'pre > code', + format: searchCodeBlocks ? 'block' : 'skip', + }, + ...['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].map((tag) => ({ + selector: tag, + options: { + uppercase: false, + }, + })), + ], + tables: true, + longWordSplit: { + forceWrapOnLimit: true, + }, + }); + if (content.startsWith(title)) { + // Remove the title from the content + content = content.slice(title.length); + } + + // rawToc comes from mdx compile and it uses `-number` to unique toc of same id + // We need to find the character index position of each toc in the content thus benefiting for search engines + const toc: Header[] = rawToc.map((item) => { + const match = item.id.match(/-(\d+)$/); + let position = -1; + if (match) { + for (let i = 0; i < Number(match[1]); i++) { + // When text is repeated, the position needs to be determined based on -number + position = content.indexOf(`\n${item.text}#\n\n`, position + 1); + + // If the positions don't match, it means the text itself may exist -number + if (position === -1) { + break; + } + } + } + return { + ...item, + charIndex: content.indexOf(`\n${item.text}#\n\n`, position + 1), + }; + }); + + return { + ...defaultIndexInfo, + title, + toc, + // raw txt, for search index + content, + _html: html, + } satisfies PageIndexInfo; + }), + ); +} + +function deletePrivateField(obj: T): T { + if (typeof obj !== 'object' || obj === null) { + return obj; + } + const newObj: T = { ...obj }; + for (const key in newObj) { + if (key.startsWith('_')) { + delete newObj[key]; + } + } + return newObj; +} + +export async function rebuildSearchIndexByHtml( + routes: RouteMeta[], + options: RebuildSearchIndexByHtmlOptions, +) { + const { versioned, outputDir } = options; + const searchFilePaths = findSearchIndexPaths(outputDir); + + if (!searchFilePaths) { + logger.error('Cannot find search index files!'); + process.exit(1); + } + + const pages = await extractPageDataFromHtml(routes, options); + + const groupedPages = groupBy(pages, (page) => { + if (page.frontmatter?.pageType === 'home') { + return 'noindex'; + } + + const version = versioned ? page.version : ''; + const lang = page.lang || ''; + + return `${version}###${lang}`; + }); + // remove the pages marked as noindex + delete groupedPages.noindex; + + await Promise.all( + Object.keys(groupedPages).map(async (group) => { + // Avoid writing filepath in compile-time + const stringifiedIndex = JSON.stringify( + groupedPages[group].map(deletePrivateField), + ); + + const [version, lang] = group.split('###'); + const indexVersion = version ? `.${version.replace('.', '_')}` : ''; + const indexLang = lang ? `.${lang}` : ''; + const searchFilePath = searchFilePaths.find((filePath) => + filePath + .replace(`${path.dirname(filePath)}/`, '') + .startsWith(`${SEARCH_INDEX_NAME}${indexVersion}${indexLang}`), + ); + if (!searchFilePath) { + logger.error( + `Cannot find search index file for version ${version} and lang ${lang}!`, + ); + process.exit(1); + } + + await fs.writeFile(searchFilePath, stringifiedIndex); + }), + ); +} diff --git a/packages/rspress-plugin/tsconfig.json b/packages/rspress-plugin/tsconfig.json new file mode 100644 index 00000000000..e6cfa6be441 --- /dev/null +++ b/packages/rspress-plugin/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "paths": { + "*": ["./@mf-types/*"] + } + }, + "include": ["src/**/*", "src/stories"] +} diff --git a/packages/runtime-core/README.md b/packages/runtime-core/README.md index 21fdf58e127..0f3baf61ab4 100644 --- a/packages/runtime-core/README.md +++ b/packages/runtime-core/README.md @@ -6,7 +6,7 @@ ## Documentation -See [https://module-federation.io/guide/basic/runtime.html](https://module-federation.io/guide/basic/runtime.html) for details. +See [https://module-federation.io/guide/basic/runtime/runtime.html](https://module-federation.io/guide/basic/runtime/runtime.html) for details. ## License diff --git a/packages/runtime/README.md b/packages/runtime/README.md index 21fdf58e127..0f3baf61ab4 100644 --- a/packages/runtime/README.md +++ b/packages/runtime/README.md @@ -6,7 +6,7 @@ ## Documentation -See [https://module-federation.io/guide/basic/runtime.html](https://module-federation.io/guide/basic/runtime.html) for details. +See [https://module-federation.io/guide/basic/runtime/runtime.html](https://module-federation.io/guide/basic/runtime/runtime.html) for details. ## License diff --git a/packages/sdk/src/createModuleFederationConfig.ts b/packages/sdk/src/createModuleFederationConfig.ts new file mode 100644 index 00000000000..ae169b1e736 --- /dev/null +++ b/packages/sdk/src/createModuleFederationConfig.ts @@ -0,0 +1,7 @@ +import type { moduleFederationPlugin } from './types/plugins'; + +export const createModuleFederationConfig = ( + options: moduleFederationPlugin.ModuleFederationPluginOptions, +): moduleFederationPlugin.ModuleFederationPluginOptions => { + return options; +}; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 43c33934a9c..acac35e38d3 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -12,3 +12,4 @@ export * from './env'; export * from './dom'; export * from './node'; export * from './normalizeOptions'; +export { createModuleFederationConfig } from './createModuleFederationConfig'; diff --git a/packages/utilities/README.md b/packages/utilities/README.md index e27211c8ea0..820e5436735 100644 --- a/packages/utilities/README.md +++ b/packages/utilities/README.md @@ -1,6 +1,6 @@ # utils -For warning: [`@module-federation/runtime`](https://module-federation.io/guide/basic/runtime.html) is recommended as a replacement +For warning: [`@module-federation/runtime`](https://module-federation.io/guide/basic/runtime/runtime.html) is recommended as a replacement This library was generated with [Nx](https://nx.dev). diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 041f9a02698..30d3fce478b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2122,21 +2122,30 @@ importers: '@module-federation/error-codes': specifier: workspace:* version: link:../../packages/error-codes + '@module-federation/rspress-plugin': + specifier: workspace:* + version: link:../../packages/rspress-plugin '@rsbuild/plugin-sass': specifier: ^1.3.2 version: 1.3.2(@rsbuild/core@1.3.21) '@rspress/plugin-llms': - specifier: 2.0.0-beta.14 - version: 2.0.0-beta.14(@rspress/core@2.0.0-beta.14) + specifier: 2.0.0-beta.16 + version: 2.0.0-beta.16(@rspress/core@2.0.0-beta.16) framer-motion: specifier: ^10.0.0 version: 10.18.0(react-dom@18.3.1)(react@18.3.1) + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) rspress: - specifier: 2.0.0-beta.14 - version: 2.0.0-beta.14(@types/react@18.3.11)(acorn@8.14.0)(webpack@5.98.0) + specifier: 2.0.0-beta.16 + version: 2.0.0-beta.16(@types/react@18.3.11)(acorn@8.14.0)(webpack@5.98.0) rspress-plugin-annotation-words: specifier: 0.0.1 - version: 0.0.1(rspress@2.0.0-beta.14) + version: 0.0.1(rspress@2.0.0-beta.16) tailwindcss: specifier: ^3.2.7 version: 3.4.3 @@ -3028,6 +3037,49 @@ importers: specifier: ^1.0.2 version: 1.0.8(@swc/helpers@0.5.13) + packages/rspress-plugin: + dependencies: + '@module-federation/enhanced': + specifier: workspace:* + version: link:../enhanced + '@module-federation/error-codes': + specifier: workspace:* + version: link:../error-codes + '@module-federation/rsbuild-plugin': + specifier: workspace:* + version: link:../rsbuild-plugin + '@module-federation/sdk': + specifier: workspace:* + version: link:../sdk + cheerio: + specifier: 1.0.0-rc.12 + version: 1.0.0-rc.12 + fs-extra: + specifier: 11.3.0 + version: 11.3.0 + html-to-text: + specifier: ^9.0.5 + version: 9.0.5 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 + devDependencies: + '@rslib/core': + specifier: ^0.9.2 + version: 0.9.2(typescript@5.7.3) + '@rspress/shared': + specifier: 2.0.0-beta.16 + version: 2.0.0-beta.16 + '@types/html-to-text': + specifier: ^9.0.4 + version: 9.0.4 + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 + '@types/react': + specifier: ^18.3.11 + version: 18.3.11 + packages/runtime: dependencies: '@module-federation/error-codes': @@ -3213,7 +3265,7 @@ packages: react: '>=16.0.0' react-dom: '>=16.0.0' dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.25.7 '@emotion/hash': 0.8.0 '@emotion/unitless': 0.7.5 classnames: 2.5.1 @@ -9817,9 +9869,9 @@ packages: tsconfig-paths: optional: true dependencies: - '@babel/parser': 7.27.2 - '@babel/traverse': 7.27.1 - '@babel/types': 7.27.1 + '@babel/parser': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 '@modern-js/core': 2.67.6 '@modern-js/node-bundle-require': 2.67.6 '@modern-js/plugin': 2.67.6 @@ -9828,9 +9880,9 @@ packages: '@modern-js/plugin-v2': 2.67.6(react-dom@18.3.1)(react@18.3.1) '@modern-js/prod-server': 2.67.6(react-dom@18.3.1)(react@18.3.1) '@modern-js/rsbuild-plugin-esbuild': 2.67.6(@swc/core@1.7.26)(webpack-cli@5.1.4) - '@modern-js/server': 2.67.6(@babel/traverse@7.27.1)(@rsbuild/core@1.3.20)(react-dom@18.3.1)(react@18.3.1) + '@modern-js/server': 2.67.6(@babel/traverse@7.27.0)(@rsbuild/core@1.3.20)(react-dom@18.3.1)(react@18.3.1) '@modern-js/server-core': 2.67.6(react-dom@18.3.1)(react@18.3.1) - '@modern-js/server-utils': 2.67.6(@babel/traverse@7.27.1)(@rsbuild/core@1.3.20) + '@modern-js/server-utils': 2.67.6(@babel/traverse@7.27.0)(@rsbuild/core@1.3.20) '@modern-js/types': 2.67.6 '@modern-js/uni-builder': 2.67.6(@rspack/core@1.3.9)(esbuild@0.17.19)(styled-components@6.1.8)(typescript@5.0.4)(webpack-cli@5.1.4) '@modern-js/utils': 2.67.6 @@ -10604,26 +10656,6 @@ packages: - supports-color dev: true - /@modern-js/server-utils@2.67.6(@babel/traverse@7.27.1)(@rsbuild/core@1.3.20): - resolution: {integrity: sha512-q411M9JQjZ9NTeK6GgCjp4QbPlKegaVGqtaeJs1s3vLdwk4Rswdb+0ZK9EhisAoYkeh+ZtmUGRoHJEN6J//7xw==} - dependencies: - '@babel/core': 7.26.10 - '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.10) - '@babel/preset-env': 7.26.0(@babel/core@7.26.10) - '@babel/preset-react': 7.26.3(@babel/core@7.26.10) - '@babel/preset-typescript': 7.26.0(@babel/core@7.26.10) - '@modern-js/babel-compiler': 2.67.6 - '@modern-js/babel-plugin-module-resolver': 2.67.6 - '@modern-js/babel-preset': 2.67.6(@rsbuild/core@1.3.20) - '@modern-js/utils': 2.67.6 - '@swc/helpers': 0.5.17 - babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.26.10)(@babel/traverse@7.27.1) - transitivePeerDependencies: - - '@babel/traverse' - - '@rsbuild/core' - - supports-color - dev: true - /@modern-js/server@2.67.5(@babel/traverse@7.27.1)(@rsbuild/core@1.3.20)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-EeItN3EdOekrfm4Q1Ikzbg2WmeMTmyejuR7dx3dyXLMiM71471Y4MVi/lfDfKH2q8OUhXJpKbsToHaePi6Pnqg==} peerDependencies: @@ -10702,45 +10734,6 @@ packages: - utf-8-validate dev: true - /@modern-js/server@2.67.6(@babel/traverse@7.27.1)(@rsbuild/core@1.3.20)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-Qh7cojbn50tdRrlQhYeEfirEXgUcJo7k711uArXptZP7avaGi5I+Zop1VLnyflUkof+ljdrcFuOXzAIIMa9yXQ==} - peerDependencies: - devcert: ^1.2.2 - ts-node: ^10.1.0 - tsconfig-paths: '>= 3.0.0 || >= 4.0.0' - peerDependenciesMeta: - devcert: - optional: true - ts-node: - optional: true - tsconfig-paths: - optional: true - dependencies: - '@babel/core': 7.26.10 - '@babel/register': 7.25.7(@babel/core@7.26.10) - '@modern-js/runtime-utils': 2.67.6(react-dom@18.3.1)(react@18.3.1) - '@modern-js/server-core': 2.67.6(react-dom@18.3.1)(react@18.3.1) - '@modern-js/server-utils': 2.67.6(@babel/traverse@7.27.1)(@rsbuild/core@1.3.20) - '@modern-js/types': 2.67.6 - '@modern-js/utils': 2.67.6 - '@swc/helpers': 0.5.17 - axios: 1.8.2 - connect-history-api-fallback: 2.0.0 - http-compression: 1.0.6 - minimatch: 3.1.2 - path-to-regexp: 6.3.0 - ws: 8.18.0 - transitivePeerDependencies: - - '@babel/traverse' - - '@rsbuild/core' - - bufferutil - - debug - - react - - react-dom - - supports-color - - utf-8-validate - dev: true - /@modern-js/storybook-builder@2.67.6(@rspack/core@1.3.9)(@types/react-dom@18.3.0)(@types/react@18.2.79)(encoding@0.1.13)(esbuild@0.18.20)(react-dom@18.3.1)(react@18.3.1)(styled-components@6.1.8)(typescript@5.0.4)(webpack-cli@5.1.4)(webpack@5.98.0): resolution: {integrity: sha512-oHxbtDX7l5dlv9CCJmAhw+Bhxvh3jsa7CcJFxRYDGa7RITZfCGw7ZUaP+I1uIwvWd50PIt25XilRZFIR6kyk1g==} engines: {node: '>=16.0.0'} @@ -11595,7 +11588,10 @@ packages: /@module-federation/error-codes@0.14.0: resolution: {integrity: sha512-GGk+EoeSACJikZZyShnLshtq9E2eCrDWbRiB4QAFXCX4oYmGgFfzXlx59vMNwqTKPJWxkEGnPYacJMcr2YYjag==} - dev: false + + /@module-federation/error-codes@0.14.3: + resolution: {integrity: sha512-sBJ3XKU9g5Up31jFeXPFsD8AgORV7TLO/cCSMuRewSfgYbG/3vSKLJmfHrO6+PvjZSb9VyV2UaF02ojktW65vw==} + dev: true /@module-federation/error-codes@0.9.1: resolution: {integrity: sha512-q8spCvlwUzW42iX1irnlBTcwcZftRNHyGdlaoFO1z/fW4iphnBIfijzkigWQzOMhdPgzqN/up7XN+g5hjBGBtw==} @@ -11823,7 +11819,13 @@ packages: dependencies: '@module-federation/error-codes': 0.14.0 '@module-federation/sdk': 0.14.0 - dev: false + + /@module-federation/runtime-core@0.14.3: + resolution: {integrity: sha512-xMFQXflLVW/AJTWb4soAFP+LB4XuhE7ryiLIX8oTyUoBBgV6U2OPghnFljPjeXbud72O08NYlQ1qsHw1kN/V8Q==} + dependencies: + '@module-federation/error-codes': 0.14.3 + '@module-federation/sdk': 0.14.3 + dev: true /@module-federation/runtime-core@0.9.1: resolution: {integrity: sha512-r61ufhKt5pjl81v7TkmhzeIoSPOaNtLynW6+aCy3KZMa3RfRevFxmygJqv4Nug1L0NhqUeWtdLejh4VIglNy5Q==} @@ -11850,7 +11852,13 @@ packages: dependencies: '@module-federation/runtime': 0.14.0 '@module-federation/webpack-bundler-runtime': 0.14.0 - dev: false + + /@module-federation/runtime-tools@0.14.3: + resolution: {integrity: sha512-QBETX7iMYXdSa3JtqFlYU+YkpymxETZqyIIRiqg0gW+XGpH3jgU68yjrme2NBJp7URQi/CFZG8KWtfClk0Pjgw==} + dependencies: + '@module-federation/runtime': 0.14.3 + '@module-federation/webpack-bundler-runtime': 0.14.3 + dev: true /@module-federation/runtime-tools@0.5.1: resolution: {integrity: sha512-nfBedkoZ3/SWyO0hnmaxuz0R0iGPSikHZOAZ0N/dVSQaIzlffUo35B5nlC2wgWIc0JdMZfkwkjZRrnuuDIJbzg==} @@ -11892,7 +11900,14 @@ packages: '@module-federation/error-codes': 0.14.0 '@module-federation/runtime-core': 0.14.0 '@module-federation/sdk': 0.14.0 - dev: false + + /@module-federation/runtime@0.14.3: + resolution: {integrity: sha512-7ZHpa3teUDVhraYdxQGkfGHzPbjna4LtwbpudgzAxSLLFxLDNanaxCuSeIgSM9c+8sVUNC9kvzUgJEZB0krPJw==} + dependencies: + '@module-federation/error-codes': 0.14.3 + '@module-federation/runtime-core': 0.14.3 + '@module-federation/sdk': 0.14.3 + dev: true /@module-federation/runtime@0.5.1: resolution: {integrity: sha512-xgiMUWwGLWDrvZc9JibuEbXIbhXg6z2oUkemogSvQ4LKvrl/n0kbqP1Blk669mXzyWbqtSp6PpvNdwaE1aN5xQ==} @@ -11923,7 +11938,10 @@ packages: /@module-federation/sdk@0.14.0: resolution: {integrity: sha512-lg/OWRsh18hsyTCamOOhEX546vbDiA2O4OggTxxH2wTGr156N6DdELGQlYIKfRdU/0StgtQS81Goc0BgDZlx9A==} - dev: false + + /@module-federation/sdk@0.14.3: + resolution: {integrity: sha512-THJZMfbXpqjQOLblCQ8jjcBFFXsGRJwUWE9l/Q4SmuCSKMgAwie7yLT0qSGrHmyBYrsUjAuy+xNB4nfKP0pnGw==} + dev: true /@module-federation/sdk@0.5.1: resolution: {integrity: sha512-exvchtjNURJJkpqjQ3/opdbfeT2wPKvrbnGnyRkrwW5o3FH1LaST1tkiNviT6OXTexGaVc2DahbdniQHVtQ7pA==} @@ -11983,7 +12001,13 @@ packages: dependencies: '@module-federation/runtime': 0.14.0 '@module-federation/sdk': 0.14.0 - dev: false + + /@module-federation/webpack-bundler-runtime@0.14.3: + resolution: {integrity: sha512-hIyJFu34P7bY2NeMIUHAS/mYUHEY71VTAsN0A0AqEJFSVPszheopu9VdXq0VDLrP9KQfuXT8SDxeYeJXyj0mgA==} + dependencies: + '@module-federation/runtime': 0.14.3 + '@module-federation/sdk': 0.14.3 + dev: true /@module-federation/webpack-bundler-runtime@0.5.1: resolution: {integrity: sha512-mMhRFH0k2VjwHt3Jol9JkUsmI/4XlrAoBG3E0o7HoyoPYv1UFOWyqAflfANcUPgbYpvqmyLzDcO+3IT36LXnrA==} @@ -15960,7 +15984,18 @@ packages: '@swc/helpers': 0.5.17 core-js: 3.42.0 jiti: 2.4.2 - dev: false + + /@rsbuild/core@1.4.0-beta.2: + resolution: {integrity: sha512-cgMGolvlPkDghi0+tuoN5pYZERhOuOHQWXwxVU963/f5BSrXDRtwH6QzUevmUVyh+i1zFE5OdWM3YyVCahvG2Q==} + engines: {node: '>=16.10.0'} + hasBin: true + dependencies: + '@rspack/core': 1.3.15(@swc/helpers@0.5.17) + '@rspack/lite-tapable': 1.0.1 + '@swc/helpers': 0.5.17 + core-js: 3.42.0 + jiti: 2.4.2 + dev: true /@rsbuild/plugin-assets-retry@1.2.1(@rsbuild/core@1.3.20): resolution: {integrity: sha512-VJ4h8hw5Pdrx5h6O0ffdUNxcOYTHQ9to6oxGTdMFXJh/HRcIUy8+5mNoAYHkk5sqTQtyli8QOesJXhqGbrENfw==} @@ -16444,7 +16479,7 @@ packages: '@rspack/core': 0.7.5(@swc/helpers@0.5.3) caniuse-lite: 1.0.30001667 html-webpack-plugin: /html-rspack-plugin@5.7.2(@rspack/core@0.7.5) - postcss: 8.4.38 + postcss: 8.4.47 optionalDependencies: fsevents: 2.3.3 transitivePeerDependencies: @@ -16512,6 +16547,25 @@ packages: typescript: 5.7.3 dev: true + /@rslib/core@0.9.2(typescript@5.7.3): + resolution: {integrity: sha512-C5mZroofHKJiHl7V/b2hIp9WnFXRrKFnfOP/Aw+7DcxgH/ur593MypG3Zg5mVcaJv6OG36oNbvUtJ6+Wk5yqog==} + engines: {node: '>=16.7.0'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7 + typescript: ^5 + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + typescript: + optional: true + dependencies: + '@rsbuild/core': 1.4.0-beta.2 + rsbuild-plugin-dts: 0.9.2(@rsbuild/core@1.4.0-beta.2)(typescript@5.7.3) + tinyglobby: 0.2.14 + typescript: 5.7.3 + dev: true + /@rspack/binding-darwin-arm64@0.7.5: resolution: {integrity: sha512-mNBIm36s1BA7v4SL/r4f3IXIsjyH5CZX4eXMRPE52lBc3ClVuUB7d/8zk8dkyjJCMAj8PsZSnAJ3cfXnn7TN4g==} cpu: [arm64] @@ -16556,7 +16610,14 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: false + optional: true + + /@rspack/binding-darwin-arm64@1.3.15: + resolution: {integrity: sha512-f+DnVRENRdVe+ufpZeqTtWAUDSTnP48jVo7x9KWsXf8XyJHUi+eHKEPrFoy1HvL1/k5yJ3HVnFBh1Hb9cNIwSg==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true optional: true /@rspack/binding-darwin-arm64@1.3.9: @@ -16610,7 +16671,14 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: false + optional: true + + /@rspack/binding-darwin-x64@1.3.15: + resolution: {integrity: sha512-TfUvEIBqYUT2OK01BYXb2MNcZeZIhAnJy/5aj0qV0uy4KlvwW63HYcKWa1sFd4Ac7bnGShDkanvP3YEuHOFOyg==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true optional: true /@rspack/binding-darwin-x64@1.3.9: @@ -16664,7 +16732,14 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: false + optional: true + + /@rspack/binding-linux-arm64-gnu@1.3.15: + resolution: {integrity: sha512-D/YjYk9snKvYm1Elotq8/GsEipB4ZJWVv/V8cZ+ohhFNOPzygENi6JfyI06TryBTQiN0/JDZqt/S9RaWBWnMqw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true optional: true /@rspack/binding-linux-arm64-gnu@1.3.9: @@ -16718,7 +16793,14 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: false + optional: true + + /@rspack/binding-linux-arm64-musl@1.3.15: + resolution: {integrity: sha512-lJbBsPMOiR0hYPCSM42yp7QiZjfo0ALtX7ws2wURpsQp3BMfRVAmXU3Ixpo2XCRtG1zj8crHaCmAWOJTS0smsA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true optional: true /@rspack/binding-linux-arm64-musl@1.3.9: @@ -16772,7 +16854,14 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: false + optional: true + + /@rspack/binding-linux-x64-gnu@1.3.15: + resolution: {integrity: sha512-qGB8ucHklrzNg6lsAS36VrBsCbOw0acgpQNqTE5cuHWrp1Pu3GFTRiFEogenxEmzoRbohMZt0Ev5grivrcgKBQ==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true optional: true /@rspack/binding-linux-x64-gnu@1.3.9: @@ -16826,7 +16915,14 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: false + optional: true + + /@rspack/binding-linux-x64-musl@1.3.15: + resolution: {integrity: sha512-qRn6e40fLQP+N2rQD8GAj/h4DakeTIho32VxTIaHRVuzw68ZD7VmKkwn55ssN370ejmey35ZdoNFNE12RBrMZA==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true optional: true /@rspack/binding-linux-x64-musl@1.3.9: @@ -16880,7 +16976,14 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: false + optional: true + + /@rspack/binding-win32-arm64-msvc@1.3.15: + resolution: {integrity: sha512-7uJ7dWhO1nWXJiCss6Rslz8hoAxAhFpwpbWja3eHgRb7O4NPHg6MWw63AQSI2aFVakreenfu9yXQqYfpVWJ2dA==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true optional: true /@rspack/binding-win32-arm64-msvc@1.3.9: @@ -16934,7 +17037,14 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: false + optional: true + + /@rspack/binding-win32-ia32-msvc@1.3.15: + resolution: {integrity: sha512-UsaWTYCjDiSCB0A0qETgZk4QvhwfG8gCrO4SJvA+QSEWOmgSai1YV70prFtLLIiyT9mDt1eU3tPWl1UWPRU/EQ==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true optional: true /@rspack/binding-win32-ia32-msvc@1.3.9: @@ -16988,7 +17098,14 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: false + optional: true + + /@rspack/binding-win32-x64-msvc@1.3.15: + resolution: {integrity: sha512-ZnDIc9Es8EF94MirPDN+hOMt7tkb8nMEbRJFKLMmNd0ElNPgsql+1cY5SqyGRH1hsKB87KfSUQlhFiKZvzbfIg==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true optional: true /@rspack/binding-win32-x64-msvc@1.3.9: @@ -17079,7 +17196,20 @@ packages: '@rspack/binding-win32-arm64-msvc': 1.3.12 '@rspack/binding-win32-ia32-msvc': 1.3.12 '@rspack/binding-win32-x64-msvc': 1.3.12 - dev: false + + /@rspack/binding@1.3.15: + resolution: {integrity: sha512-utNPuJglLO5lW9XbwIqjB7+2ilMo6JkuVLTVdnNVKU94FW7asn9F/qV+d+MgjUVqU1QPCGm0NuGO9xhbgeJ7pg==} + optionalDependencies: + '@rspack/binding-darwin-arm64': 1.3.15 + '@rspack/binding-darwin-x64': 1.3.15 + '@rspack/binding-linux-arm64-gnu': 1.3.15 + '@rspack/binding-linux-arm64-musl': 1.3.15 + '@rspack/binding-linux-x64-gnu': 1.3.15 + '@rspack/binding-linux-x64-musl': 1.3.15 + '@rspack/binding-win32-arm64-msvc': 1.3.15 + '@rspack/binding-win32-ia32-msvc': 1.3.15 + '@rspack/binding-win32-x64-msvc': 1.3.15 + dev: true /@rspack/binding@1.3.9: resolution: {integrity: sha512-3FFen1/0F2aP5uuCm8vPaJOrzM3karCPNMsc5gLCGfEy2rsK38Qinf9W4p1bw7+FhjOTzoSdkX+LFHeMDVxJhw==} @@ -17205,7 +17335,21 @@ packages: '@rspack/lite-tapable': 1.0.1 '@swc/helpers': 0.5.17 caniuse-lite: 1.0.30001718 - dev: false + + /@rspack/core@1.3.15(@swc/helpers@0.5.17): + resolution: {integrity: sha512-QuElIC8jXSKWAp0LSx18pmbhA7NiA5HGoVYesmai90UVxz98tud0KpMxTVCg+0lrLrnKZfCWN9kwjCxM5pGnrA==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@swc/helpers': + optional: true + dependencies: + '@module-federation/runtime-tools': 0.14.3 + '@rspack/binding': 1.3.15 + '@rspack/lite-tapable': 1.0.1 + '@swc/helpers': 0.5.17 + dev: true /@rspack/core@1.3.9(@swc/helpers@0.5.13): resolution: {integrity: sha512-u7usd9srCBPBfNJCSvsfh14AOPq6LCVna0Vb/aA2nyJTawHqzfAMz1QRb/e27nP3NrV6RPiwx03W494Dd6r6wg==} @@ -17271,7 +17415,7 @@ packages: optional: true dependencies: error-stack-parser: 2.1.4 - html-entities: 2.5.2 + html-entities: 2.6.0 react-refresh: 0.14.2 dev: true @@ -17301,8 +17445,8 @@ packages: html-entities: 2.6.0 react-refresh: 0.17.0 - /@rspress/core@2.0.0-beta.14(@types/react@18.3.11)(acorn@8.14.0)(webpack@5.98.0): - resolution: {integrity: sha512-+uW1/Q/rO2L82oY+wuijCdBEQ3EzIV0zFVh3ixFYt2ffbiZ4Cm7Gm1THLSsFc1uQrDI9N/B6KGjKNexvZwLcQg==} + /@rspress/core@2.0.0-beta.16(@types/react@18.3.11)(acorn@8.14.0)(webpack@5.98.0): + resolution: {integrity: sha512-Jy1mZH/VirwoelFv7kfJaeQXeW/EQfmfPSVmPDMvLLUHSBJdRFgPNOSZbIz6sKOWQp0af9TpHxApHnVNVMkJRA==} engines: {node: '>=18.0.0'} dependencies: '@mdx-js/loader': 3.1.0(acorn@8.14.0)(webpack@5.98.0) @@ -17311,13 +17455,13 @@ packages: '@rsbuild/core': 1.3.22 '@rsbuild/plugin-react': 1.3.2(@rsbuild/core@1.3.22) '@rspress/mdx-rs': 0.6.6 - '@rspress/plugin-auto-nav-sidebar': 2.0.0-beta.14 - '@rspress/plugin-container-syntax': 2.0.0-beta.14 - '@rspress/plugin-last-updated': 2.0.0-beta.14 - '@rspress/plugin-medium-zoom': 2.0.0-beta.14(@rspress/runtime@2.0.0-beta.14) - '@rspress/runtime': 2.0.0-beta.14 - '@rspress/shared': 2.0.0-beta.14 - '@rspress/theme-default': 2.0.0-beta.14 + '@rspress/plugin-auto-nav-sidebar': 2.0.0-beta.16 + '@rspress/plugin-container-syntax': 2.0.0-beta.16 + '@rspress/plugin-last-updated': 2.0.0-beta.16 + '@rspress/plugin-medium-zoom': 2.0.0-beta.16(@rspress/runtime@2.0.0-beta.16) + '@rspress/runtime': 2.0.0-beta.16 + '@rspress/shared': 2.0.0-beta.16 + '@rspress/theme-default': 2.0.0-beta.16 '@shikijs/rehype': 3.6.0 '@types/unist': 3.0.3 '@unhead/react': 2.0.10(react@19.1.0) @@ -17437,35 +17581,35 @@ packages: '@rspress/mdx-rs-win32-x64-msvc': 0.6.6 dev: false - /@rspress/plugin-auto-nav-sidebar@2.0.0-beta.14: - resolution: {integrity: sha512-+XkFoCf16szNAbOIfYW+s3n+Fz4fMvrYjfZPRz5AuG68SEXdcigN2ER/SLZtSyPaQHtTpOCb1usDxj8H3AExaQ==} + /@rspress/plugin-auto-nav-sidebar@2.0.0-beta.16: + resolution: {integrity: sha512-6dP/DHyl0uWlIOG+89xE7Fm5r9/XQ7LtHZRHiD9f2AWXzkq2R2wbs3s3/nEgcEx5e7uu8cYrhRdcoZhzUVXUig==} engines: {node: '>=18.0.0'} dependencies: - '@rspress/shared': 2.0.0-beta.14 + '@rspress/shared': 2.0.0-beta.16 dev: false - /@rspress/plugin-container-syntax@2.0.0-beta.14: - resolution: {integrity: sha512-x9GXRA7e9ElEE66+TQ5n6S0oh5WJpunOV108yghChncyjr+8GpB/M7Nbuko/KLezP2ybVO0Lstzqrt886RUEkA==} + /@rspress/plugin-container-syntax@2.0.0-beta.16: + resolution: {integrity: sha512-xrZc/ALNKldwnjtK5MjoEZQbHsbWP0/cPTPNhUKreq10MoPeWJ9kyC2nUbjLuA9geDIIcZV7Zj0a/Di1Qw8kDA==} engines: {node: '>=18.0.0'} dependencies: - '@rspress/shared': 2.0.0-beta.14 + '@rspress/shared': 2.0.0-beta.16 dev: false - /@rspress/plugin-last-updated@2.0.0-beta.14: - resolution: {integrity: sha512-o3EwI5pZXSfy5kaZ68TbJwDoZWfz2p9wIPBq9i9acTEUTOtakPXhTYT9mUPgwvuwZv3n3tJKIKBWWJ0ojLvfeQ==} + /@rspress/plugin-last-updated@2.0.0-beta.16: + resolution: {integrity: sha512-O9YQpxEKnzL56lzd/kyYX9uIupe/4xHn2CGyoCnIFgy4QGUsi+TEw+nbCiDby6ClIUEofo+DzqFb/S8Rz7+mug==} engines: {node: '>=18.0.0'} dependencies: - '@rspress/shared': 2.0.0-beta.14 + '@rspress/shared': 2.0.0-beta.16 dev: false - /@rspress/plugin-llms@2.0.0-beta.14(@rspress/core@2.0.0-beta.14): - resolution: {integrity: sha512-sG4bWb+Ln26NtroYLe/sRlibY89ySsfk5fhKlOZQfkxzhiFoiGAtlXtVwVDI6edRCA/EjSsKhdoy8iGVhDJoOg==} + /@rspress/plugin-llms@2.0.0-beta.16(@rspress/core@2.0.0-beta.16): + resolution: {integrity: sha512-NzN/I3KAMt7/P7Z2zNbMIbne9Api9aqkdvJpHeWQss0WU2dJBepZ9MdB4Gb6yZ0BwCtYaaeLfvJNMhYCVK5Gfg==} engines: {node: '>=18.0.0'} peerDependencies: - '@rspress/core': ^2.0.0-beta.14 + '@rspress/core': ^2.0.0-beta.16 dependencies: - '@rspress/core': 2.0.0-beta.14(@types/react@18.3.11)(acorn@8.14.0)(webpack@5.98.0) - '@rspress/shared': 2.0.0-beta.14 + '@rspress/core': 2.0.0-beta.16(@types/react@18.3.11)(acorn@8.14.0)(webpack@5.98.0) + '@rspress/shared': 2.0.0-beta.16 remark-mdx: 3.1.0 remark-parse: 11.0.0 remark-stringify: 11.0.0 @@ -17476,44 +17620,43 @@ packages: - supports-color dev: false - /@rspress/plugin-medium-zoom@2.0.0-beta.14(@rspress/runtime@2.0.0-beta.14): - resolution: {integrity: sha512-UDra889u9W628GyNV1EPcNY7F1KXqQI+swWKkg61bmHU98S4Ed0mKgoLpC4Eiz9kZ4FS0HcRRDxvJgNrep4mog==} + /@rspress/plugin-medium-zoom@2.0.0-beta.16(@rspress/runtime@2.0.0-beta.16): + resolution: {integrity: sha512-vB1jfW2kOiQPt6QJNx/jWo2lTi8pzpUm4gIUBZRvMcLA4HALqbJVkXTNhxBgSYmRihuQ2Hz7jPpRVqUhitJNWg==} engines: {node: '>=18.0.0'} peerDependencies: - '@rspress/runtime': ^2.0.0-beta.14 + '@rspress/runtime': ^2.0.0-beta.16 dependencies: - '@rspress/runtime': 2.0.0-beta.14 + '@rspress/runtime': 2.0.0-beta.16 medium-zoom: 1.1.0 dev: false - /@rspress/runtime@2.0.0-beta.14: - resolution: {integrity: sha512-PZRI3IafLA7UEgL1FY2ffDXTwo3Yp0fH47o0pdnu65yWPsbTAEhiKskx27CcpB831w7QDefQSS/6AvKBCVGf8A==} + /@rspress/runtime@2.0.0-beta.16: + resolution: {integrity: sha512-S4ainpE+O5l4ct1XCbaQh0vvzh5IJPwlCKPdHQi7VJ4oHfED3OCqpz0LsU3rDtRmviVt9FBUXdvniUkf1SIicQ==} engines: {node: '>=18.0.0'} dependencies: - '@rspress/shared': 2.0.0-beta.14 + '@rspress/shared': 2.0.0-beta.16 '@unhead/react': 2.0.10(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) react-router-dom: 6.30.1(react-dom@19.1.0)(react@19.1.0) dev: false - /@rspress/shared@2.0.0-beta.14: - resolution: {integrity: sha512-0pjNhRU87nS4YiNnrjpPmYMfcFfTzwZW6L/7vH1YbtDrRqs/cYKf//CJ3hBjK4ZVFXKcbx1zhAvipCiZKpBgMg==} + /@rspress/shared@2.0.0-beta.16: + resolution: {integrity: sha512-eSbWPKfhTSMgpl2p37SiXO+uds4r8FHIFIsHwdtTO882xozX8RbVQWO6smMv1/Jxnpww8j0hSGPIuwbSfya9rg==} dependencies: '@rsbuild/core': 1.3.22 '@shikijs/rehype': 3.6.0 gray-matter: 4.0.3 lodash-es: 4.17.21 unified: 11.0.5 - dev: false - /@rspress/theme-default@2.0.0-beta.14: - resolution: {integrity: sha512-pvt+lw4ztkpkBOY8dK4GMHdZeNQeIlgL4wPPSndBz0Af39nfng3QP1wVXbXQPqGRxKg2C36rJzPfLcU5QFNotw==} + /@rspress/theme-default@2.0.0-beta.16: + resolution: {integrity: sha512-2L1gY0Iq4fCD+uFmIdiWdBJqEz8hiaL5XJSf84UAXxPZ6xeM1sHAk8ajZ+oPJBjWy7NRAsGc5RrWnuDpyZttBg==} engines: {node: '>=18.0.0'} dependencies: '@mdx-js/react': 2.3.0(react@19.1.0) - '@rspress/runtime': 2.0.0-beta.14 - '@rspress/shared': 2.0.0-beta.14 + '@rspress/runtime': 2.0.0-beta.16 + '@rspress/shared': 2.0.0-beta.16 '@unhead/react': 2.0.10(react@19.1.0) body-scroll-lock: 4.0.0-beta.0 copy-to-clipboard: 3.3.3 @@ -17818,7 +17961,6 @@ packages: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 hast-util-to-html: 9.0.5 - dev: false /@shikijs/engine-javascript@3.6.0: resolution: {integrity: sha512-7YnLhZG/TU05IHMG14QaLvTW/9WiK8SEYafceccHUSXs2Qr5vJibUwsDfXDLmRi0zHdzsxrGKpSX6hnqe0k8nA==} @@ -17826,20 +17968,17 @@ packages: '@shikijs/types': 3.6.0 '@shikijs/vscode-textmate': 10.0.2 oniguruma-to-es: 4.3.3 - dev: false /@shikijs/engine-oniguruma@3.6.0: resolution: {integrity: sha512-nmOhIZ9yT3Grd+2plmW/d8+vZ2pcQmo/UnVwXMUXAKTXdi+LK0S08Ancrz5tQQPkxvjBalpMW2aKvwXfelauvA==} dependencies: '@shikijs/types': 3.6.0 '@shikijs/vscode-textmate': 10.0.2 - dev: false /@shikijs/langs@3.6.0: resolution: {integrity: sha512-IdZkQJaLBu1LCYCwkr30hNuSDfllOT8RWYVZK1tD2J03DkiagYKRxj/pDSl8Didml3xxuyzUjgtioInwEQM/TA==} dependencies: '@shikijs/types': 3.6.0 - dev: false /@shikijs/rehype@3.6.0: resolution: {integrity: sha512-r0Rr2hvXXqLl5DJ1Lx7RImU81XsK2bjThaym/lujl2A0r7SId0u1s+bcWYfFKb+7mCLH7MXF+jdzCtdWGOcYCQ==} @@ -17850,24 +17989,20 @@ packages: shiki: 3.6.0 unified: 11.0.5 unist-util-visit: 5.0.0 - dev: false /@shikijs/themes@3.6.0: resolution: {integrity: sha512-Fq2j4nWr1DF4drvmhqKq8x5vVQ27VncF8XZMBuHuQMZvUSS3NBgpqfwz/FoGe36+W6PvniZ1yDlg2d4kmYDU6w==} dependencies: '@shikijs/types': 3.6.0 - dev: false /@shikijs/types@3.6.0: resolution: {integrity: sha512-cLWFiToxYu0aAzJqhXTQsFiJRTFDAGl93IrMSBNaGSzs7ixkLfdG6pH11HipuWFGW5vyx4X47W8HDQ7eSrmBUg==} dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 - dev: false /@shikijs/vscode-textmate@10.0.2: resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - dev: false /@sideway/address@4.1.5: resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} @@ -20497,6 +20632,10 @@ packages: resolution: {integrity: sha512-mm2HqV22l8lFQh4r2oSsOEVea+m0qqxEmwpc9kC1p/XzmjLWrReR9D/GRs8Pex2NX/imyEH9c5IU/7tMBQCHOA==} dev: true + /@types/html-to-text@9.0.4: + resolution: {integrity: sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ==} + dev: true + /@types/http-assert@1.5.5: resolution: {integrity: sha512-4+tE/lwdAahgZT1g30Jkdm9PzFRde0xwxBNUyRsCitRvCQB90iuA2uJYdUnhnANRcqGXaWOGY4FEoxeElNAK2g==} dev: true @@ -20587,6 +20726,12 @@ packages: dependencies: '@types/react': 18.3.11 + /@types/lodash-es@4.17.12: + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + dependencies: + '@types/lodash': 4.17.9 + dev: true + /@types/lodash.clonedeepwith@4.5.9: resolution: {integrity: sha512-bruhfxIJlj36oWYmYQ7KFbylCGgzyIi+TLypub+wcAd29mV4llKdvru8Pp9qwILX//I5vK3FIcJ0VzszElhLuA==} dependencies: @@ -20613,7 +20758,6 @@ packages: resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} dependencies: '@types/unist': 3.0.3 - dev: false /@types/mdx@2.0.13: resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} @@ -24867,7 +25011,6 @@ packages: /ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - dev: false /chai@4.5.0: resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} @@ -24947,7 +25090,6 @@ packages: /character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - dev: false /character-entities-legacy@1.1.4: resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} @@ -24955,7 +25097,6 @@ packages: /character-entities-legacy@3.0.0: resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - dev: false /character-entities@1.2.4: resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} @@ -24998,6 +25139,30 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + dependencies: + boolbase: 1.0.0 + css-select: 5.1.0 + css-what: 6.1.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + dev: false + + /cheerio@1.0.0-rc.12: + resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} + engines: {node: '>= 6'} + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.2.2 + htmlparser2: 8.0.2 + parse5: 7.1.2 + parse5-htmlparser2-tree-adapter: 7.1.0 + dev: false + /chokidar@2.1.8: resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==} dependencies: @@ -25386,7 +25551,6 @@ packages: /comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - dev: false /command-line-args@5.2.1: resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==} @@ -26326,7 +26490,7 @@ packages: boolbase: 1.0.0 css-what: 6.1.0 domhandler: 5.0.3 - domutils: 3.1.0 + domutils: 3.2.2 nth-check: 2.1.1 /css-to-react-native@3.2.0: @@ -27131,7 +27295,6 @@ packages: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} dependencies: dequal: 2.0.3 - dev: false /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -27279,13 +27442,6 @@ packages: domelementtype: 2.3.0 domhandler: 4.3.1 - /domutils@3.1.0: - resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - /domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} dependencies: @@ -27477,7 +27633,6 @@ packages: dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 - dev: false /enquirer@2.3.6: resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} @@ -28469,7 +28624,7 @@ packages: eslint-import-resolver-node: 0.3.9 eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.1) hasown: 2.0.2 - is-core-module: 2.16.1 + is-core-module: 2.15.1 is-glob: 4.0.3 minimatch: 3.1.2 object.fromentries: 2.0.8 @@ -28506,7 +28661,7 @@ packages: eslint-import-resolver-node: 0.3.9 eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.0.0) hasown: 2.0.2 - is-core-module: 2.16.1 + is-core-module: 2.15.1 is-glob: 4.0.3 minimatch: 3.1.2 object.fromentries: 2.0.8 @@ -30780,7 +30935,6 @@ packages: kind-of: 6.0.3 section-matter: 1.0.0 strip-bom-string: 1.0.0 - dev: false /gunzip-maybe@1.4.2: resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} @@ -31068,7 +31222,6 @@ packages: space-separated-tokens: 2.0.2 stringify-entities: 4.0.4 zwitch: 2.0.4 - dev: false /hast-util-to-jsx-runtime@2.3.6: resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} @@ -31123,7 +31276,6 @@ packages: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} dependencies: '@types/hast': 3.0.4 - dev: false /hastscript@6.0.0: resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==} @@ -31316,7 +31468,6 @@ packages: /html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - dev: false /html-webpack-plugin@5.6.2(@rspack/core@1.3.9)(webpack@5.98.0): resolution: {integrity: sha512-q7xp/FO9RGBVoTKNItkdX1jKLscLFkgn/dLVFNYbHVbfHLBk6DYW5nsQ8kCzIWcgKP/kUBocetjvav6lD8YfCQ==} @@ -34714,7 +34865,6 @@ packages: unist-util-position: 5.0.0 unist-util-visit: 5.0.0 vfile: 6.0.3 - dev: false /mdast-util-to-markdown@2.1.2: resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} @@ -35048,7 +35198,6 @@ packages: dependencies: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.2 - dev: false /micromark-util-chunked@2.0.1: resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} @@ -35088,7 +35237,6 @@ packages: /micromark-util-encode@2.0.1: resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} - dev: false /micromark-util-events-to-acorn@2.0.3: resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} @@ -35124,7 +35272,6 @@ packages: micromark-util-character: 2.1.1 micromark-util-encode: 2.0.1 micromark-util-symbol: 2.0.1 - dev: false /micromark-util-subtokenize@2.1.0: resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} @@ -35137,11 +35284,9 @@ packages: /micromark-util-symbol@2.0.1: resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} - dev: false /micromark-util-types@2.0.2: resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} - dev: false /micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} @@ -36595,7 +36740,6 @@ packages: /oniguruma-parser@0.12.1: resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} - dev: false /oniguruma-to-es@4.3.3: resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==} @@ -36603,7 +36747,6 @@ packages: oniguruma-parser: 0.12.1 regex: 6.0.1 regex-recursion: 6.0.2 - dev: false /only@0.0.2: resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} @@ -37023,6 +37166,13 @@ packages: parse5: 6.0.1 dev: true + /parse5-htmlparser2-tree-adapter@7.1.0: + resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} + dependencies: + domhandler: 5.0.3 + parse5: 7.1.2 + dev: false + /parse5@4.0.0: resolution: {integrity: sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==} @@ -38530,7 +38680,6 @@ packages: /property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - dev: false /proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -41180,17 +41329,14 @@ packages: resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} dependencies: regex-utilities: 2.3.0 - dev: false /regex-utilities@2.3.0: resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} - dev: false /regex@6.0.1: resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} dependencies: regex-utilities: 2.3.0 - dev: false /regexp.prototype.flags@1.5.2: resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} @@ -41783,6 +41929,28 @@ packages: typescript: 5.7.3 dev: true + /rsbuild-plugin-dts@0.9.2(@rsbuild/core@1.4.0-beta.2)(typescript@5.7.3): + resolution: {integrity: sha512-mVpf4J/auMSBy5iBNDaxTB8yYipENRTMUq8bQQJQdvzFuH2arQXrQ874ukEJ67XUZXhmxvc7ooEAR3UWKNiPtQ==} + engines: {node: '>=16.7.0'} + peerDependencies: + '@microsoft/api-extractor': ^7 + '@rsbuild/core': 1.x + typescript: ^5 + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + typescript: + optional: true + dependencies: + '@ast-grep/napi': 0.37.0 + '@rsbuild/core': 1.4.0-beta.2 + magic-string: 0.30.17 + picocolors: 1.1.1 + tinyglobby: 0.2.14 + tsconfig-paths: 4.2.0 + typescript: 5.7.3 + dev: true + /rsbuild-plugin-html-minifier-terser@1.1.1(@rsbuild/core@1.3.21): resolution: {integrity: sha512-rbDLv+XmGeSQo9JWKSwBst3Qwx1opLqtQCnQ3t9Z0F0ZTxKOC1S/ypPv5tSn/S3GMHct5Yb76mMgh6p80hjOAQ==} peerDependencies: @@ -41835,22 +42003,22 @@ packages: fs-extra: 11.3.0 dev: false - /rspress-plugin-annotation-words@0.0.1(rspress@2.0.0-beta.14): + /rspress-plugin-annotation-words@0.0.1(rspress@2.0.0-beta.16): resolution: {integrity: sha512-hEVwl+gUpt3RHCnKAmJhMcRO0TOCbciyAQo+U4JbRBu9WHuPpRtuVqetr3sB7aR/VN9U0impJD6WqWO8gvN99g==} engines: {node: '>=14.0.0'} peerDependencies: rspress: ^1 dependencies: - rspress: 2.0.0-beta.14(@types/react@18.3.11)(acorn@8.14.0)(webpack@5.98.0) + rspress: 2.0.0-beta.16(@types/react@18.3.11)(acorn@8.14.0)(webpack@5.98.0) dev: false - /rspress@2.0.0-beta.14(@types/react@18.3.11)(acorn@8.14.0)(webpack@5.98.0): - resolution: {integrity: sha512-gAcVJUbtiDwoyQfOKRdvxPmCFJcr/o3EkZR7tSZBgkIT2+5rlvlu3ClGS6g/PJPcRYwGc6NQcRYPxP5/F/ATJA==} + /rspress@2.0.0-beta.16(@types/react@18.3.11)(acorn@8.14.0)(webpack@5.98.0): + resolution: {integrity: sha512-0796hRpcOHi6YNUo6aL6EwBmC0yl7VMYCoXr72ZfLRchwJDnIhrEHouaSuyPIjR2hkpjqZylenkVHTAjpcVa9A==} hasBin: true dependencies: '@rsbuild/core': 1.3.22 - '@rspress/core': 2.0.0-beta.14(@types/react@18.3.11)(acorn@8.14.0)(webpack@5.98.0) - '@rspress/shared': 2.0.0-beta.14 + '@rspress/core': 2.0.0-beta.16(@types/react@18.3.11)(acorn@8.14.0)(webpack@5.98.0) + '@rspress/shared': 2.0.0-beta.16 cac: 6.7.14 chokidar: 3.6.0 picocolors: 1.1.1 @@ -42544,7 +42712,6 @@ packages: dependencies: extend-shallow: 2.0.1 kind-of: 6.0.3 - dev: false /secure-compare@3.0.1: resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==} @@ -42891,7 +43058,6 @@ packages: '@shikijs/types': 3.6.0 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 - dev: false /side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} @@ -43706,7 +43872,6 @@ packages: dependencies: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 - dev: false /strip-ansi@3.0.1: resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} @@ -43730,7 +43895,6 @@ packages: /strip-bom-string@1.0.0: resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} engines: {node: '>=0.10.0'} - dev: false /strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} @@ -45020,7 +45184,6 @@ packages: /trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - dev: false /trim-trailing-lines@1.1.4: resolution: {integrity: sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==} @@ -45234,7 +45397,7 @@ packages: webpack: ^5.0.0 dependencies: chalk: 4.1.2 - enhanced-resolve: 5.17.1 + enhanced-resolve: 5.18.1 micromatch: 4.0.8 semver: 7.6.3 typescript: 5.0.4 @@ -45911,7 +46074,6 @@ packages: is-plain-obj: 4.1.0 trough: 2.2.0 vfile: 6.0.3 - dev: false /unified@9.2.0: resolution: {integrity: sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==} @@ -45992,7 +46154,6 @@ packages: resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} dependencies: '@types/unist': 3.0.3 - dev: false /unist-util-remove-position@2.0.1: resolution: {integrity: sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==} @@ -46022,7 +46183,6 @@ packages: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} dependencies: '@types/unist': 3.0.3 - dev: false /unist-util-visit-children@3.0.0: resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==} @@ -46503,7 +46663,6 @@ packages: dependencies: '@types/unist': 3.0.3 unist-util-stringify-position: 4.0.0 - dev: false /vfile@4.2.1: resolution: {integrity: sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==} @@ -46528,7 +46687,6 @@ packages: dependencies: '@types/unist': 3.0.3 vfile-message: 4.0.2 - dev: false /video-react@0.16.0(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-138NHPS8bmgqCYVCdbv2GVFhXntemNHWGw9AN8iJSzr3jizXMmWJd2LTBppr4hZJUbyW1A1tPZ3CQXZUaexMVA==} @@ -48434,4 +48592,3 @@ packages: /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - dev: false