Modern.js

TIP

环境准备

Node.js

在开始使用前,你需要安装 Node.js,并保证 Node.js 版本不低于 16.2.0,我们推荐使用 Node.js 18 的 LTS 版本

你可以通过以下命令检查当前使用的 Node.js 版本:

node -v

如果你当前的环境中尚未安装 Node.js,或是安装的版本低于 16,可以通过 nvmfnm 安装需要的版本。

下面是通过 nvm 安装 Node.js 18 LTS 版本的例子:

# 安装 Node.js 18 的长期支持版本
nvm install 18 --lts

# 将刚安装的 Node.js 18 设置为默认版本
nvm alias default 18

# 切换到刚安装的 Node.js 18
nvm use 18
nvm 和 fnm

nvm 和 fnm 都是 Node.js 版本管理工具。相对来说,nvm 较为成熟和稳定,而 fnm 是使用 Rust 实现的,比 nvm 提供了更好的性能。

此外,在安装 nvm 或 fnm 后,然后只要仓库根目录下有内容为 lts/hydrogen.nvmrc 文件,进入这个仓库时就会自动安装或切换到正确的 Node.js 版本。

pnpm

推荐使用 pnpm 来管理依赖:

npm install -g pnpm@8
NOTE

Modern.js 同样支持使用 yarnnpm 进行依赖管理。

创建项目

Modern.js 提供了 @modern-js/create 工具来创建项目,不需要全局安装,直接使用 npx 按需运行即可。

创建消费者

npx @modern-js/create@latest modern-consumer

创建生产者

npx @modern-js/create@latest modern-provider

安装插件

Module Federation 为 Modern.js 提供了 配套的插件 @module-federation/modern-js

pnpm add @module-federation/modern-js

设置模块联邦配置

生产者

1. 创建配置文件

在项目根目录创建 module-federation.config.ts 文件,并写入下列内容:

module-federation.config.ts
import { createModuleFederationConfig } from '@module-federation/modern-js';

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

modern.config.ts
import { appTools, defineConfig } from '@modern-js/app-tools';
import { moduleFederationPlugin } from '@module-federation/modern-js';

// https://modernjs.dev/en/configure/app/usage
export default defineConfig({
  runtime: {
    router: true,
  },
  server: {
    ssr: {
      mode: 'stream',
    },
    port: 3006,
  },
  plugins: [appTools(), moduleFederationPlugin()],
});

3. 创建导出组件

创建文件 src/components/Image.tsx ,内容如下:

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>&nbsp;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>
);

并创建对应的样式文件,内容如下:

Image.module.css
.button {
  background: red;
}

消费者

1. 创建配置文件

在项目根目录创建 module-federation.config.ts 文件,并写入下列内容:

module-federation.config.ts
import { createModuleFederationConfig } from '@module-federation/modern-js';

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

modern.config.ts
import { appTools, defineConfig } from '@modern-js/app-tools';
import { moduleFederationPlugin } from '@module-federation/modern-js';

// https://modernjs.dev/en/configure/app/usage
export default defineConfig({
  runtime: {
    router: true,
  },
  server: {
    ssr: {
      mode: 'stream',
    },
    port: 3007,
  },
  plugins: [appTools(), moduleFederationPlugin()],
});

3. 引用类型

modern-app-env.d.ts 文件增加 /// <reference types='@module-federation/modern-js/types' /> 以获取运行时 @@modern-js/runtime/mf 类型支持。

modern-app-env.d.ts
+ /// <reference types='@module-federation/modern-js/types' />

tsconfig.json 添加 paths 以获取生产者的类型:

{
  "compilerOptions": {
    "paths": {
      "*": ["./@mf-types/*"]
    }
  }
}

4. 消费生产者

修改入口页面(src/routes/page.tsx),引用生产者提供的组件,内容如下:

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 提供的 createremotessrcomponent 解决。

修改消费者引用生产者处的代码(src/routes/page.tsx):

page.tsx
import { createRemoteSSRComponent } from '@modern-js/runtime/mf'
import './index.css';

const RemoteSSRComponent = createRemoteSSRComponent({
  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 闪烁问题。