# Modern.js
:::tip
- 示例 Demo [Modern.js SSR](https://github.com/module-federation/module-federation-examples/tree/master/modernjs-ssr)
:::
## 环境准备
### Node.js
在开始使用前,你需要安装 [Node.js](https://nodejs.org/),并保证 Node.js 版本不低于 16.2.0,**我们推荐使用 Node.js 18 的 LTS 版本**。
你可以通过以下命令检查当前使用的 Node.js 版本:
```bash
node -v
```
如果你当前的环境中尚未安装 Node.js,或是安装的版本低于 16,可以通过 [nvm](https://github.com/nvm-sh/nvm) 或 [fnm](https://github.com/Schniz/fnm) 安装需要的版本。
下面是通过 nvm 安装 Node.js 18 LTS 版本的例子:
```bash
# 安装 Node.js 18 的长期支持版本
nvm install 18 --lts
# 将刚安装的 Node.js 18 设置为默认版本
nvm alias default 18
# 切换到刚安装的 Node.js 18
nvm use 18
```
:::tip nvm 和 fnm
nvm 和 fnm 都是 Node.js 版本管理工具。相对来说,nvm 较为成熟和稳定,而 fnm 是使用 Rust 实现的,比 nvm 提供了更好的性能。
:::
此外,在安装 nvm 或 fnm 后,然后只要仓库根目录下有内容为 `lts/hydrogen` 的 `.nvmrc` 文件,进入这个仓库时就会自动安装或切换到正确的 Node.js 版本。
### pnpm
推荐使用 [pnpm](https://pnpm.io/installation) 来管理依赖:
```bash
npm install -g pnpm@8
```
:::note
Modern.js 同样支持使用 `yarn`、`npm` 进行依赖管理。
:::
## 创建项目
Modern.js 提供了 `@modern-js/create` 工具来创建项目,不需要全局安装,直接使用 `npx` 按需运行即可。
### 创建消费者
```bash
npx @modern-js/create@latest modern-consumer
```
### 创建生产者
```bash
npx @modern-js/create@latest modern-provider
```
### 安装插件
Module Federation 为 Modern.js 提供了 配套的插件 `@module-federation/modern-js-v3`。
```bash
pnpm add @module-federation/modern-js-v3
```
## 设置模块联邦配置
### 生产者
#### 1. 创建配置文件
在项目根目录创建 `module-federation.config.ts` 文件,并写入下列内容:
```ts title='module-federation.config.ts'
import { createModuleFederationConfig } from '@module-federation/modern-js-v3';
export default createModuleFederationConfig({
name: 'provider',
filename: 'remoteEntry.js',
exposes: {
'./Image': './src/components/Image.tsx',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
});
```
#### 2. 应用插件
在 `modern.config.ts` 应用 `@module-federation/modern-js-v3`:
```ts title='modern.config.ts'
import { appTools, defineConfig } from '@modern-js/app-tools';
import { moduleFederationPlugin } from '@module-federation/modern-js-v3';
// https://modernjs.dev/en/configure/app/usage
export default defineConfig({
server: {
ssr: {
mode: 'stream',
},
port: 3006,
},
plugins: [appTools(), moduleFederationPlugin()],
});
```
#### 3. 创建导出组件
创建文件 `src/components/Image.tsx` ,内容如下:
```tsx title='Image.tsx'
import React from 'react';
import styles from './Image.module.css';
export default (): JSX.Element => (
<div
id="remote-components"
style={{
backgroundColor: '#1ee9c1',
color: 'lightgrey',
padding: '1rem',
}}
>
<h2>
<strong>remote</strong> image
</h2>
<button
id="remote-components-button"
style={{ marginBottom: '1rem' }}
onClick={() => alert('[remote-components] Client side Javascript works!')}
>
Click me to test i'm interactive!
</button>
<img
id="remote-components-image"
src="https://module-federation.io/module-federation-logo.svg"
style={{ width: '100px' }}
alt="serge"
/>
<button className={styles['button']}>Button from remote</button>
</div>
);
```
并创建对应的样式文件,内容如下:
```css title='Image.module.css'
.button {
background: red;
}
```
### 消费者
#### 1. 创建配置文件
在项目根目录创建 `module-federation.config.ts` 文件,并写入下列内容:
```ts title='module-federation.config.ts'
import { createModuleFederationConfig } from '@module-federation/modern-js-v3';
export default createModuleFederationConfig({
name: 'consumer',
remotes: {
remote: 'provider@http://localhost:3006/mf-manifest.json',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
});
```
#### 2. 应用插件
在 `modern.config.ts` 应用 `@module-federation/modern-js-v3`:
```ts title='modern.config.ts'
import { appTools, defineConfig } from '@modern-js/app-tools';
import { moduleFederationPlugin } from '@module-federation/modern-js-v3';
// https://modernjs.dev/en/configure/app/usage
export default defineConfig({
server: {
ssr: {
mode: 'stream',
},
port: 3007,
},
plugins: [appTools(), moduleFederationPlugin()],
});
```
#### 3. 引用类型
在 `tsconfig.json` 添加 `paths` 以获取生产者的类型:
```json
{
"compilerOptions": {
"paths": {
"*": ["./@mf-types/*"]
}
}
}
```
#### 4. 消费生产者
修改入口页面(`src/routes/page.tsx`),引用生产者提供的组件,内容如下:
```tsx title='page.tsx'
import ProviderImage from 'remote/Image';
import './index.css';
const Index = () => (
<div className="container-box">
<ProviderImage />
</div>
);
export default Index;
```
## CSS 闪烁问题
启动项目访问 `http://localhost:3007/`,发现SSR 正常工作,页面可以正常渲染,但是会有样式闪烁的问题。
这是因为生产者的样式文件无法注入到对应的 html 中。
此问题可以通过使用 `@module-federation/modern-js-v3` 提供的 [createremotessrcomponent](/zh/guide/framework/modernjs.md#createremotessrcomponent) 解决。
修改消费者引用生产者处的代码(`src/routes/page.tsx`):
```tsx title='page.tsx'
import { getInstance } from '@module-federation/modern-js-v3/runtime';
import { createLazyComponent } from '@module-federation/modern-js-v3/react'
import './index.css';
const RemoteSSRComponent = createLazyComponent({
instance: getInstance(),
loader: () => import('remote/Image'),
loading: 'loading...',
export: 'default',
fallback: ({ error }) => {
if (error instanceof Error && error.message.includes('not exist')) {
return <div>fallback - not existed id</div>;
}
return <div>fallback</div>;
},
});
const Index = () => (
<div className="container-box">
<RemoteSSRComponent />
</div>
);
export default Index;
```
修改后重新访问页面,可以观测返回的 html 中会自动注入生产者的样式文件,从而解决 CSS 闪烁问题。