AI prompts
base on Module Federation for vite & rollup English | [简体中文](./README-zh.md)
# vite-plugin-federation
<p align="center">
<a href="https://bestpractices.coreinfrastructure.org/projects/5752"><img src="https://bestpractices.coreinfrastructure.org/projects/5752/badge"></a>
<a href="https://api.securityscorecards.dev/projects/github.com/originjs/vite-plugin-federation"><img src="https://api.securityscorecards.dev/projects/github.com/originjs/vite-plugin-federation/badge"></a>
<a href="https://github.com/originjs/vite-plugin-federation/actions/workflows/ci.yml"><img src="https://github.com/originjs/vite-plugin-federation/actions/workflows/ci.yml/badge.svg?branch=main" alt="Build Status"></a>
<a href="https://www.npmjs.com/package/@originjs/vite-plugin-federation"><img src="https://badgen.net/npm/v/@originjs/vite-plugin-federation" alt="Version"></a>
<a href="https://nodejs.org/en/about/releases/"><img src="https://img.shields.io/node/v/vite.svg" alt="Node Compatibility"></a>
<a href="https://www.npmjs.com/package/@originjs/vite-plugin-federation"><img src="https://badgen.net/npm/license/@originjs/vite-plugin-federation" alt="License"></a>
</p>
A Vite/Rollup plugin which support Module Federation.
Inspired by Webpack and compatible with [Webpack Module Federation](https://webpack.js.org/concepts/module-federation/).
## Running results
![Preview](./README-Preview.gif)
## Install
```
npm install @originjs/vite-plugin-federation --save-dev
```
or
```
yarn add @originjs/vite-plugin-federation --dev
```
## Usage
Using the `Module Federation` usually requires more than 2 projects, one as the `host side` and one as the `remote side`.
#### Step 1: Configure the remote side.
- for a vite project, modify `vite.config.js`:
```js
// vite.config.js
import federation from "@originjs/vite-plugin-federation";
export default {
plugins: [
federation({
name: 'remote-app',
filename: 'remoteEntry.js',
// Modules to expose
exposes: {
'./Button': './src/Button.vue',
},
shared: ['vue']
})
]
}
```
- for a rollup project, modify `rollup.config.js`:
```js
// rollup.config.js
import federation from '@originjs/vite-plugin-federation'
export default {
input: 'src/index.js',
plugins: [
federation({
name: 'remote-app',
filename: 'remoteEntry.js',
// Modules to expose
exposes: {
'./Button': './src/button'.
},
shared: ['vue']
})
]
}
```
#### Step 2: Configure the host side
- for a vite project, modify `vite.config.js`:
```js
// vite.config.js
import federation from "@originjs/vite-plugin-federation";
export default {
plugins: [
federation({
name: 'host-app',
remotes: {
remote_app: "http://localhost:5001/assets/remoteEntry.js",
},
shared: ['vue']
})
]
}
```
- for a rollup project, modify `rollup.config.js`:
```js
// rollup.config.js
import federation from '@originjs/vite-plugin-federation'
export default {
input: 'src/index.js',
plugins: [
federation({
name: 'host-app',
remotes: {
remote_app: "http://localhost:5001/remoteEntry.js",
},
shared: ['vue']
})
]
}
```
#### Step 3: Using remote modules on the host side
Using a Vue project as an example
```js
import { createApp, defineAsyncComponent } from "vue";
const app = createApp(Layout);
...
const RemoteButton = defineAsyncComponent(() => import("remote_app/Button"));
app.component("RemoteButton", RemoteButton);
app.mount("#root");
```
Using remote components in templates
```vue
<template>
<div>
<RemoteButton />
</div>
</template>
```
## Example projects
| Examples | Host | Remote |
| --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | ----------------------------------- |
| [basic-host-remote](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/basic-host-remote) | `rollup`+`esm` | `rollup`+`esm` |
| [react-in-vue](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/react-in-vue) | `vite`+`esm` | `vite`+`esm` |
| [simple-react-esm](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/simple-react-esm) | `rollup`+`esm` | `rollup`+`esm` |
| [simple-react-systemjs](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/simple-react-systemjs) | `rollup`+`systemjs` | `rollup`+`systemjs` |
| [simple-react-webpack](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/simple-react-webpack) | `rollup`+`systemjs` | `webpack`+`systemjs` |
| [vue2-demo](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/vue2-demo) | `vite`+`esm` | `vite`+`esm` |
| [vue3-advanced-demo](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/vue3-advanced-demo) | `vite`+`esm` <br/>`vue-router`/`pinia` | `vite`+`esm`<br/>`vue-router`/`pinia` |
| [vue3-demo-esm](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/vue3-demo-esm) | `vite`+`esm` | `vite`+`esm` |
| [vue3-demo-systemjs](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/vue3-demo-systemjs) | `vite`+`systemjs` | `vite`+`systemjs` |
| [vue3-demo-webpack-esm-esm](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/vue3-demo-webpack-esm-esm) | `vite/webpack`+`esm` | `vite/webpack`+`esm` |
| [vue3-demo-webpack-esm-var](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/vue3-demo-webpack-esm-var) | `vite`+`esm` | `webpack`+`var` |
| [vue3-demo-webpack-systemjs](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/vue3-demo-webpack-systemjs) | `vite`+`systemjs` | `webpack`+`systemjs` |
| [react-vite](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/react-vite) | `vite`+`react` | `vite` + `react`
## Features
### Integration with Webpack
It is now possible to use Module Federation without the restrictions of `Vite` and `Webpack`! That is, you can choose to use the components exposed by `vite-plugin-federation` in `Webpack` or the components exposed by `Webpack ModuleFederationPlugin` in `Vite`. But you need to pay attention to the configuration in `remotes`, for different frameworks you need to specify `remotes.from` and `remotes.format` to make them work better. A couple of example projects can be found here.
* [vue3-demo-webpack-esm-esm](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/vue3-demo-webpack-esm-esm)
* [vue3-demo-webpack-esm-var](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/vue3-demo-webpack-esm-var)
* [vue3-demo-webpack-systemjs](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/vue3-demo-webpack-systemjs)
⚠️ **Note:**
1. `Vite` is relatively easy to use with the `Webpack` component, but `Webpack` is best used with the `vite-plugin-federation` component using the `esm` format, as the other formats lack complete test cases for now.
2. It is not recommended to mix `Vite` and `Webpack` in `React` projects, as there is no guarantee that `Vite/Rollup` and `Webpack` will generate the same `chunk` when packaging `commonjs`, which may cause problems with `shared`.
### Vite Dev mode
As Vite is built on esbuild in dev development mode, we provide separate support for dev mode to take advantage of Vite's high performance development server in the case of remote module deployment.
⚠️ **Note:**
* Only the Host side supports dev mode, the Remote side requires the RemoteEntry.js package to be generated using `vite build`. This is because Vite Dev mode is **Bundleless** and you can use `vite build --watch` to achieve a hot update effect.
### Static import
Static import and dynamic import of components are supported, the following shows the difference between the two methods, you can see examples of dynamic import and static import in the project in `examples`, here is a simple example.
+ Vue
```javascript
// dynamic import
const myButton = defineAsyncComponent(() => import('remote/myButton'));
app.component('my-button' , myButton);
// or
export default {
name: 'App',
components: {
myButton: () => import('remote/myButton'),
}
}
```
```javascript
// static import
import myButton from 'remote/myButton';
app.component('my-button' , myButton);
// or
export default {
name: 'App',
components: {
myButton: myButton
}
}
```
+ React
```js
// dynamic import
const myButton = React.lazy(() => import('remote/myButton'))
// static import
import myButton from 'remote/myButton'
```
⚠️ **Note:**
* Static imports may rely on the browser `Top-level await` feature, so you will need to set build.target in the configuration file to `next` or use the plugin [`vite-plugin-top-level-await`](https://github.com/Menci/vite-plugin-top-level-await). You can see the [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#browser_) of top-level await here compatibility)
*
## Configuration
### `name: string`
Required as the module name of the remote module.
### `filename:string`
As the entry file of the remote module, not required, default is `remoteEntry.js`
### `transformFileTypes:string[]`
* In most cases, the file types that the plug-in needs to process do not need to be configured, because these types are set by default.['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs', '.vue', '.svelte'],When you customize some file types and need the `vite-plugin-federation` plugin processing, please add it to the array configuration.
### `exposes`
* As the remote module, the list of components exposed to the public, required for the remote module.
```js
exposes: {
// 'externally exposed component name': 'externally exposed component address'
'./remote-simple-button': './src/components/Button.vue',
'./remote-simple-section': './src/components/Section.vue'
},
```
* If you need a more complex configuration
```js
exposes: {
'./remote-simple-button': {
import: './src/components/Button.vue',
name: 'customChunkName',
dontAppendStylesToHead: true
},
},
```
The `import` property is the address of the module. If you need to specify a custom chunk name for the module use the `name` property.
The `dontAppendStylesToHead` property is used if you don't want the plugin to automatically append all styles of the exposed component to the `<head>` element, which is the default behavior. It's useful if your component uses a ShadowDOM and the global styles wouldn't affect it anyway. The plugin will then expose the addresses of the CSS files in the global `window` object, so that your exposed component can append the styles inside the ShadowDOM itself. The key under the `window` object used for styles will be `css__{name_of_the_app}__{key_of_the_exposed_component}`. In the above example it would be `css__App__./remote-simple-button`, assuming that the global `name` option (not the one under exposed component configuration) is `App`. The value under this key is an array of strings, which contains the addresses of CSS files. In your exposed component you can iterate over this array and manually create `<link>` elements with `href` attribute set to the elements of the array like this:
```js
const styleContainer = document.createElement("div");
const hrefs = window["css__App__./remote-simple-button"];
hrefs.forEach((href: string) => {
const link = document.createElement('link')
link.href = href
link.rel = 'stylesheet'
styleContainer.appendChild(link);
});
```
----
### `remotes`
The remote module entry file referenced as a local module
#### `external:string|Promise<string>`
* remote module address, e.g. https://localhost:5011/remoteEntry.js
* You can simply configure it as follows
```js
remotes: {
// 'remote module name': 'remote module entry file address'
'remote-simple': 'http://localhost:5011/remoteEntry.js',
}
```
* Or do a slightly more complex configuration, if you need to use other fields
``` javascript
remotes: {
remote-simple: {
external: 'http://localhost:5011/remoteEntry.js',
format: 'var',
}
}
```
#### `externalType: 'url'|'promise'`
* `default: 'url'`
* Set the type of external. If you want to use a dynamic url address, you can set the `external` as `promise`, but please note that you need to set the `externalType` as 'promise' at the same time, and please ensure that the code of the `promise` part is correct, otherwise the package may fail,here is a simple example.
``` js
remotes: {
home: {
external: `Promise.resolve('your url')`,
externalType: 'promise'
},
},
// or from networke
remotes: {
remote-simple: {
external: `fetch('your url').then(response=>response.json()).then(data=>data.url)`,
externalType: 'promise'
}
}
```
#### `format:'esm'|'systemjs'|'var'`
* `default:'esm'`
* Specify the format of the remote component, this is more effective when the host and the remote use different packaging formats, for example the host uses vite + esm and the remote uses webpack + var, in which case you need to specify `type` : `'var'`
#### `from` : `'vite'|'webpack'`
* `default : 'vite'`
* Specify the source of the remote component, from `vite-plugin-federation` select `vite`, from `webpack` select `webpack`
----
### `shared`
Dependencies shared by local and remote modules. Local modules need to configure the dependencies of all used remote modules; remote modules need to configure the dependencies of externally provided components.
#### `import: boolean`
* `default: true`
* The default is `true`, whether to add shared to the module, only for the `remote` side, `remote` will reduce some of the packaging time when this configuration is turned on, because there is no need to package some of the `shared`, but once there is no `shared` module available on the `host` side, it will report an error directly, because there is no fallback module available
#### `shareScope: string`
* `default: 'default'`
* Default is `default`, the shared domain name, just keep the `remote` and `host` sides the same
#### `version: string`
Only works on `host` side, the version of the shared module provided is `version` of the `package.json` file in the shared package by default, you need to configure it manually only if you can't get `version` by this method
#### `requiredVersion: string`
Only for the `remote` side, it specifies the required version of the `host shared` used, when the version of the `host` side does not meet the `requiredVersion` requirement, it will use its own `shared` module, provided that it is not configured with `import=false`, which is not enabled by default
#### `packagePath: string`
* `supportMode: only serve`
* Allow custom packages to be shared via packagePath (previously limited to those under node_modules),
For Example
You can only define similar shared
```js
shared :{
packageName:{
...
}
}
```
* packageName must be a package under node_modules, such as vue, react, etc., but you cannot define your own package.
But now you can share a custom package by specifying the package path, for example
```js
shared: {
packageName: {
packagePath: './src/a/index.js'
}
}
```
#### `generate : boolean`
* `default: true`
* generate a shared chunk file or not , if you make sure that the host side has a share that can be used, then you can set not to generate a shared file on the remote side to reduce the size of the remote's chunk file, which is only effective on the remote side, the host side will generate a shared chunk no matter what.
```js
shared: {
packageName: {
generate: false
}
}
```
## Add other example projects?
First of all, you need to determine whether the test is suitable for `dev` mode or `build&serve` mode, or both.
In addition, the current test will directly access `localhost:5000` for testing, which means that the startup port of `host` must be `5000`, otherwise it will directly lead to test failure.
### How to set the test of `dev` mode or `build&serve` mode?
According to the file name of the test file.
For example, `vue3-demo-esm.dev&serve.spec.ts` means that tests will be built in `dev` mode and `build&serve` mode.
The `vue3-demo-esm.dev.spec.ts` will only build tests in `dev` mode, as summarized as follows
| Mode | File Name |
| ------------------------ | ------------------- |
| Only for `dev` mode | *.dev.spec.ts |
| Only for `build&serve` mode | *.serve.spec.ts |
| `dev` and `build&serve` mode | *.dev&serve.spec.ts |
### Testing in `Dev` mode
Since the current plug-in only supports the `dev` mode of `vite` on the `host` end, the `dev` mode test will execute the following code on the root path of the test project in turn.
1. `pnpm run dev:host`
2. `pnpm run build:remotes`
3. `pnpm run serve:remotes`
4. Execute test cases
5. `pnpm run stop`
This also means that there are at least four instructions in the `package.json` file of the project in `dev` mode.
``` json
"scripts": {
"build:remotes": "pnpm --filter \"./remote\" build",
"serve:remotes": "pnpm --filter \"./remote\" serve",
"dev:hosts": "pnpm --filter \"./host\" dev",
"stop": "kill-port --port 5000,5001"
},
"workspaces": [
"host",
"remote"
]
```
### Testing in `Build&Serve` mode
The `build&serve` mode will execute the following instructions in turn
1. `pnpm run build`
2. `pnpm run serve`
3. Execute test cases
4. `pnpm run stop`
This also means that there are at least three instructions in the `package.json` file of the project in `build&serve` mode.
``` json
"scripts": {
"build": "pnpm --parallel --filter \"./**\" build",
"serve": "pnpm --parallel --filter \"./**\" serve ",
"stop": "kill-port --port 5000,5001"
},
"workspaces": [
"host",
"remote"
]
```
## FAQ
### ERROR: `Top-level` await is not available in the configured target environment
The solution is to set `build.target` to `esnext`, which you can find at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await to see the support for this feature in each browser.
```ts
build: {
target: "esnext"
}
```
or
```js
build: {
target: ["chrome89", "edge89", "firefox89", "safari15"]
}
```
Or you can try using the plugin [`vite-plugin-top-level-await`](https://github.com/Menci/vite-plugin-top-level-await) to eliminate `top-level-await`, as demonstrated in [vue3-demo- esm](https://github.com/originjs/vite-plugin-federation/tree/main/packages/examples/vue3-demo-esm) demonstrates this usage
### Is not generating chunk properly?
Please check if you have started the project in `dev` mode with `vite`, currently only the fully pure host side can use `dev` mode, the `remote` side must use `build` mode to make the plugin take effect.
### React uses federation for some questions
It is recommended to check this [Issue](https://github.com/originjs/vite-plugin-federation/issues/173), which contains most of the `React` related issues
### The remote module failed to load the share of the local module, for example`localhost/:1 Uncaught (in promise) TypeError: Failed to fetch dynamically imported module: http://your url`
Reason: Vite has auto fetch logic for `IP` and Port when starting the service, no full fetch logic has been found in the `Plugin`, and in some cases a fetch failure may occur.
Solutions:
Explicitly declaring IP, Port, `cacheDir` in the local module ensures that our `Plugin` can correctly fetch and pass the dependent addresses.
Local module's `vite.config.ts`
```ts
export default defineConfig({
server:{
https: "http",
host: "192.168.56.1",
port: 5100,
},
cacheDir: "node_modules/.cacheDir",
}
```
### error TS2307: Cannot find module
Add declarations in the d.ts file, like this
```ts
declare module "router-remote/*"{}
```
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=originjs/vite-plugin-federation&type=Date)](https://star-history.com/#originjs/vite-plugin-federation&Date)
## Wiki
[Detailed design](https://github.com/originjs/vite-plugin-federation/wiki)
", Assign "at most 3 tags" to the expected json: {"id":"9222","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"