简介

  • Lerna 是一个快速、领先的构建系统,用于管理和发布来 自同一源码仓库的多个 JavaScript/TypeScript 软件包。

lerna8 + npm + workspaces + umi

包版本

1
2
3
"node": "^20.18.0",
"npm": "^10.8.2",
"lerna": "^8.1.8",

开启 npm workspaces

  • 命令行执行 npx lerna init, 初始化项目

  • lerna.json 中添加下面配置

    1
    2
    3
    4
    5
    {
    "npmClient": "npm",
    "packages": ["packages/*"],
    ... // 其他配置
    }
  • package.json 中添加下面配置

    1
    2
    3
    {
    "workspaces": ["packages/*"]
    }
  • 然后在根目录下创建 packages 目录 (注意:名称要与上面配置的 packages/* 目录名称一致)

注意:packages 下的项目中每个包的 package.json 都要配置 name,否则会报错

进入 packages 创建主应用 app

  • 依次执行下面命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 进入 packages 目录
    cd ./packages

    # 创建主应用 app
    npx create-umi@latest

    # What's the target folder name?
    app

    # Pick Umi App Template
    Ant Design Pro

    # Pick Npm Client
    npm

    # Pick Npm Registry (这一步会在 根目录 下创建 `.npmrc` 文件)
    taobao
  • packages/app/package.json 中配置 name

    1
    2
    3
    4
    {
    "name": "app",
    ... // 其他配置
    }

进入 packages 创建子应用 demo1, demo2

  • 依次执行下面命令(这里只演示 demo1, demo2 创建同下)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 进入 packages 目录
    cd ./packages

    # 创建子应用 demo1
    npx create-umi@latest

    # What's the target folder name?
    demo1

    # Pick Umi App Template
    Simple App

    # Pick Npm Client
    npm

    # Pick Npm Registry (这一步会在 根目录 下创建 `.npmrc` 文件, 不需要可以删除)
    taobao
  • packages/app/package.json 中配置 name

    1
    2
    3
    4
    {
    "name": "demo1",
    ... // 其他配置
    }

设置应用的启动端口

  • 在根目录下安装 cross-env 包 (不是项目根目录)

    1
    2
    3
    npm i cross-env -D -w=app -w=demo1 -w=demo2
    # 或者
    npm i cross-env -D --workspace=app --workspace=demo1 --workspace=demo2
  • 配置主应用 app 启动端口,在 packages/app/package.json 中修改 scripts

    1
    2
    3
    4
    5
    6
    {
    "scripts": {
    "dev": "cross-env PORT=8000 max dev",
    ... // 其他配置
    }
    }
  • 配置子应用 demo1 启动端口,在 packages/demo1/package.json 中修改 scripts

    1
    2
    3
    4
    5
    6
    {
    "scripts": {
    "dev": "cross-env PORT=8001 umi dev",
    ... // 其他配置
    }
    }
  • 配置子应用 demo2 启动端口,在 packages/demo2/package.json 中修改 scripts

    1
    2
    3
    4
    5
    6
    {
    "scripts": {
    "dev": "cross-env PORT=8002 umi dev",
    ... // 其他配置
    }
    }
  • 启动应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 进入 packages/app 目录
    cd ./packages/app
    # 启动主应用
    npm run dev

    # 进入 packages/demo1 目录
    cd ./packages/demo1
    # 启动子应用
    npm run dev

    # 进入 packages/demo2 目录
    cd ./packages/demo2
    # 启动子应用
    npm run dev
    1
    2
    3
    4
    5
    6
    7
    8
    # 根目录下启动主应用
    npm run dev -w=app

    # 根目录下启动子应用
    npm run dev -w=demo1

    # 根目录下启动子应用
    npm run dev -w=demo2

主应用链接子应用

  • 在主应用 ./packages/app/.umirc.ts 中添加下面内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    export default {
    routes: [
    { path: '/', redirect: '/home' },
    { name: '首页', path: '/home', component: './Home' },
    { name: '权限演示', path: '/access', component: './Access' },
    { name: ' CRUD 示例', path: '/table', component: './Table' },
    // 新增微应用示例
    { name: 'Demo1', path: '/demo1/*', microApp: 'demo1' },
    { name: 'Demo2', path: '/demo2/*', microApp: 'demo2' },
    ],
    qiankun: {
    master: {},
    },
    ... // 其他配置
    };
  • 修改父应用的 ./packages/app/src/app.ts 文件,导出 qiankun 对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    export const qiankun = {
    apps: [
    {
    name: 'demo1',
    entry: '//localhost:8001',
    },
    {
    name: 'demo2',
    entry: '//localhost:8002',
    },
    ],
    };
  • 子应用安装 qiankun 插件

    1
    npm i @umijs/plugins -D -w=demo1 -w=demo2
  • 在子应用 ./packages/demo1/.umirc.ts./packages/demo2/.umirc.ts 文件中引入 qiankun 插件

    1
    2
    3
    4
    5
    6
    7
    export default {
    plugins: ['@umijs/plugins/dist/qiankun'],
    qiankun: {
    slave: {},
    },
    ... // 其他配置
    };
  • 重新启动主应用与子应用,即可看到主应用中新增了 Demo1Demo2 两个子应用的链接

更多微前端配置

添加快捷命令

  • 在根目录 package.json 中添加下面配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    {
    "scripts": {
    "app": "npm run dev -w=app",
    "bootstrap": "lerna bootstrap",
    "clean": "lerna clean && rimraf ./node_modules",
    "demo1": "npm run dev -w=demo1",
    "demo2": "npm run dev -w=demo2",
    "preinstall": "npx only-allow npm"
    },
    ... // 其他配置
    }
  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 安装包
    npm install
    npm run bootstrap

    # 启动主应用
    npm run app

    # 启动子应用
    npm run demo1

    # 启动子应用
    npm run demo2

    # 清空依赖
    npm run clean

lerna8 + pnpm + workspaces + umi

包版本

1
2
3
4
"node": "^20.18.0",
"npm": "^10.8.2",
"lerna": "^8.1.8",
"pnpm": "^9.14.4"

开启 pnpm workspaces

  • 命令行执行 npx lerna init, 初始化项目

  • lerna.json 中添加下面配置

    1
    2
    3
    4
    5
    {
    "npmClient": "pnpm",
    "packages": ["packages/*"],
    ... // 其他配置
    }
  • 在根目录下创建 pnpm-workspace.yaml 并添加下面配置

    1
    2
    packages:
    - 'packages/*'
  • 然后在根目录下创建 packages 目录 (注意:名称要与上面配置的 packages/* 目录名称一致)

进入 packages 创建主应用 app

  • 依次执行下面命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 进入 packages 目录
    cd ./packages

    # 创建主应用 app
    npx create-umi@latest

    # What's the target folder name?
    app

    # Pick Umi App Template
    Ant Design Pro

    # Pick Npm Client
    pnpm

    # Pick Npm Registry (这一步会在 根目录 下创建 `.npmrc` 文件)
    taobao
  • packages/app/package.json 中配置 name

    1
    2
    3
    4
    {
    "name": "app",
    ... // 其他配置
    }

进入 packages 创建子应用 demo1, demo2

  • 依次执行下面命令(这里只演示 demo1, demo2 创建同下)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 进入 packages 目录
    cd ./packages

    # 创建子应用 demo1
    npx create-umi@latest

    # What's the target folder name?
    demo1

    # Pick Umi App Template
    Simple App

    # Pick Npm Client
    pnpm

    # Pick Npm Registry (这一步会在 根目录 下创建 `.npmrc` 文件, 不需要可以删除)
    taobao
  • packages/app/package.json 中配置 name

    1
    2
    3
    4
    {
    "name": "demo1",
    ... // 其他配置
    }

设置应用的启动端口

  • 在根目录下安装 cross-env 包 (不是项目根目录)

    1
    2
    3
    pnpm -F=app -F=demo1 -F=demo2 i cross-env -D
    # 或者
    pnpm --filter=app --filter=demo1 --filter=demo2 i cross-env -D
  • 配置主应用 app 启动端口,在 packages/app/package.json 中修改 scripts

    1
    2
    3
    4
    5
    6
    {
    "scripts": {
    "dev": "cross-env PORT=8000 max dev",
    ... // 其他配置
    }
    }
  • 配置子应用 demo1 启动端口,在 packages/demo1/package.json 中修改 scripts

    1
    2
    3
    4
    5
    6
    {
    "scripts": {
    "dev": "cross-env PORT=8001 umi dev",
    ... // 其他配置
    }
    }
  • 配置子应用 demo2 启动端口,在 packages/demo2/package.json 中修改 scripts

    1
    2
    3
    4
    5
    6
    {
    "scripts": {
    "dev": "cross-env PORT=8002 umi dev",
    ... // 其他配置
    }
    }
  • 启动应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 进入 packages/app 目录
    cd ./packages/app
    # 启动主应用
    pnpm run dev

    # 进入 packages/demo1 目录
    cd ./packages/demo1
    # 启动子应用
    pnpm run dev

    # 进入 packages/demo2 目录
    cd ./packages/demo2
    # 启动子应用
    pnpm run dev
    1
    2
    3
    4
    5
    6
    7
    8
    # 根目录下启动主应用
    pnpm -F=app run dev

    # 根目录下启动子应用
    pnpm -F=demo1 run dev

    # 根目录下启动子应用
    pnpm -F=demo2 run dev
    1
    2
    # 根目录下启动所有应用
    pnpm -r dev

主应用链接子应用

  • 在主应用 ./packages/app/.umirc.ts 中添加下面内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    export default {
    routes: [
    { path: '/', redirect: '/home' },
    { name: '首页', path: '/home', component: './Home' },
    { name: '权限演示', path: '/access', component: './Access' },
    { name: ' CRUD 示例', path: '/table', component: './Table' },
    // 新增微应用示例
    { name: 'Demo1', path: '/demo1/*', microApp: 'demo1' },
    { name: 'Demo2', path: '/demo2/*', microApp: 'demo2' },
    ],
    qiankun: {
    master: {},
    },
    ... // 其他配置
    };
  • 修改父应用的 ./packages/app/src/app.ts 文件,导出 qiankun 对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    export const qiankun = {
    apps: [
    {
    name: 'demo1',
    entry: '//localhost:8001',
    },
    {
    name: 'demo2',
    entry: '//localhost:8002',
    },
    ],
    };
  • 子应用安装 qiankun 插件

    1
    pnpm -F=demo1 -F=demo2 i @umijs/plugins -D
  • 在子应用 ./packages/demo1/.umirc.ts./packages/demo2/.umirc.ts 文件中引入 qiankun 插件

    1
    2
    3
    4
    5
    6
    7
    export default {
    plugins: ['@umijs/plugins/dist/qiankun'],
    qiankun: {
    slave: {},
    },
    ... // 其他配置
    };
  • 重新启动主应用与子应用,即可看到主应用中新增了 Demo1Demo2 两个子应用的链接

更多微前端配置

添加快捷命令

  • 在根目录 package.json 中添加下面配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "scripts": {
    "preinstall": "npx only-allow pnpm",
    "all": "pnpm -r dev",
    "app": "pnpm -F app dev",
    "bootstrap": "lerna bootstrap",
    "clean": "lerna clean && rimraf ./node_modules",
    "demo1": "pnpm -F demo1 dev",
    "demo2": "pnpm -F demo2 dev"
    },
    ... // 其他配置
    }
  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 安装包
    pnpm install
    pnpm run bootstrap

    # 启动主应用
    pnpm run app

    # 启动子应用
    pnpm run demo1

    # 启动子应用
    pnpm run demo2

    # 清空依赖
    pnpm run clean

lerna8 + pnpm + workspaces + vite

包版本

1
2
3
4
5
"node": "^20.18.0",
"npm": "^10.8.2",
"lerna": "^8.1.8",
"pnpm": "^9.14.4",
"vite": "^6.0.2"

开启 pnpm workspaces

  • 命令行执行 npx lerna init, 初始化项目

  • lerna.json 中添加下面配置

    1
    2
    3
    4
    5
    {
    "npmClient": "pnpm",
    "packages": ["packages/*"],
    ... // 其他配置
    }
  • 在根目录下创建 pnpm-workspace.yaml 并添加下面配置

    1
    2
    packages:
    - 'packages/*'
  • 然后在根目录下创建 packages 目录 (注意:名称要与上面配置的 packages/* 目录名称一致)

进入 packages 创建主应用 app

  • 依次执行下面命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 进入 packages 目录
    cd ./packages

    # 创建主应用 app
    pnpm create vite

    # Project name
    app

    # Select a framework
    React

    # Pick Npm Client
    pnpm

    # Select a variant
    TypeScript + SWC
  • packages/app/vite.config.ts 文件中配置端口号

    1
    2
    3
    4
    5
    6
    export default defineConfig({
    server: {
    port: 8000,
    },
    ... // 其他配置
    });

进入 packages 创建子应用 demo1

  • 依次执行下面命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 进入 packages 目录
    cd ./packages

    # 创建子应用 demo1
    pnpm create vite

    # Project name
    demo1

    # Select a framework
    React

    # Pick Npm Client
    pnpm

    # Select a variant
    TypeScript + SWC
  • packages/demo1/vite.config.ts 文件中配置端口号

    1
    2
    3
    4
    5
    6
    export default defineConfig({
    server: {
    port: 8001,
    },
    ... // 其他配置
    });

进入 packages 创建子应用 demo2

  • 依次执行下面命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 进入 packages 目录
    cd ./packages

    # 创建子应用 demo2
    pnpm create vite

    # Project name
    demo2

    # Select a framework
    React

    # Pick Npm Client
    pnpm

    # Select a variant
    TypeScript + SWC
  • packages/demo2/vite.config.ts 文件中配置端口号

    1
    2
    3
    4
    5
    6
    export default defineConfig({
    server: {
    port: 8002,
    },
    ... // 其他配置
    });

安装所需依赖

  • 删除根目录下的 node_modules 文件夹与 package-lock.json 文件

  • 在根目录下运行 pnpm install 安装依赖

  • appdemodemo2 安装所需的包 (这里可以将公用的包提取到 npm 仓库,然后在各个项目中安装, 这里不做演示)

    1
    2
    # 在根目录下安装antd与react-router-dom
    pnpm -F=app -F=demo1 -F=demo2 i antd react-router-dom

配置主应用

  • app 项目中安装 qiankun

    1
    2
    # 根目录下执行
    pnpm -F=app i qiankun
  • 文件目录结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ├── public
    ├── src
    ├── layout
    ├── index.tsx
    ├── microApp.tsx
    ├── router
    ├── index.tsx
    ├── App.tsx
    ├── index.css
    ├── main.tsx
    ├── vite.config.ts
  • 修改 packages/app/src/layout/microApp.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import { start } from 'qiankun';
    import { useEffect } from 'react';

    const MicroApp = () => {
    /** 加载子应用 */
    const handleLoadMicroApp = () => {
    if (!window.qiankunStarted) {
    window.qiankunStarted = true;
    start();
    }
    };

    useEffect(() => {
    handleLoadMicroApp();
    }, []);

    return (
    <div>
    <div id="subapp"></div>
    </div>
    );
    };

    export default MicroApp;
  • 修改 packages/app/src/layout/index.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    import React, { useState } from 'react';
    import { Layout, Menu } from 'antd';
    import { Outlet, useNavigate } from 'react-router-dom';
    const { Header, Content, Footer, Sider } = Layout;
    import MicroApp from './microApp';

    /** 菜单栏配置 */
    const menus = [
    { key: 'home', label: 'Home', meta: { path: '/home' } },
    {
    key: 'docs',
    label: 'Docs',
    children: [
    { key: 'test1', label: 'Test1', meta: { path: '/docs/test1' } },
    { key: 'test2', label: 'Test2', meta: { path: '/docs/test2' } },
    ],
    },
    {
    key: 'demo1',
    label: 'Demo1',
    children: [
    { key: 'temp1', label: 'Temp1', meta: { path: '/demo1/temp1' } },
    { key: 'temp2', label: 'Temp2', meta: { path: '/demo1/temp2' } },
    ],
    },
    {
    key: 'demo2',
    label: 'Demo2',
    meta: { path: '/demo2' },
    },
    ];

    const App: React.FC = () => {
    const navigate = useNavigate();

    const [selectedKeys, setSelectedKeys] = useState<string[]>(['home']); // 控制菜单栏的选中状态

    /** 菜单栏点击事件 */
    const handleClick = (e: any) => {
    setSelectedKeys([e.key]);
    navigate(e?.item?.props?.meta?.path);
    };

    return (
    <Layout style={{ minHeight: '100vh' }}>
    <Sider>
    <Menu theme="dark" selectedKeys={selectedKeys} mode="inline" items={menus} onClick={handleClick} />
    </Sider>

    <Layout>
    <Header style={{ padding: 0, background: '#fff' }} />
    <Content style={{ margin: '0 16px' }}>
    <div
    style={{
    padding: 24,
    minHeight: 360,
    background: '#fff',
    borderRadius: 16,
    margin: '16px 0',
    }}>
    {/* 这里渲染子路由 */}
    <Outlet />
    {/* 这里挂载微应用 */}
    <MicroApp />
    </div>
    </Content>
    <Footer style={{ textAlign: 'center' }}>zxiaosi ©{new Date().getFullYear()}</Footer>
    </Layout>
    </Layout>
    );
    };

    export default App;
  • 修改 packages/app/src/router/index.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    import { createBrowserRouter, Navigate, Outlet } from 'react-router-dom';
    import BaseLayout from '../layout/index';

    /** 路由配置 */
    const router = createBrowserRouter([
    {
    path: '/',
    element: <Navigate to="/home" replace={true} />, // 重定向
    },
    {
    path: '/',
    element: <BaseLayout />,
    errorElement: <>BaseLayout Error</>,
    children: [
    {
    path: '/home',
    element: <>主应用 Home</>,
    },
    {
    path: '/docs',
    element: <Outlet />,
    children: [
    { path: '/docs/test1', element: <>主应用 Docs Test1</> },
    { path: '/docs/test2', element: <>主应用 Docs Test2</> },
    ],
    },
    {
    path: '/demo1',
    element: <Navigate to="/demo1/temp1" replace={true} />,
    errorElement: <>子应用 demo1 Error</>,
    children: [
    {
    path: '/demo1/temp1',
    element: <></>,
    },
    {
    path: '/demo1/temp2',
    element: <></>,
    },
    ],
    },
    {
    path: '/demo2',
    element: <></>,
    errorElement: <>子应用 demo2 Error</>,
    },
    ],
    },
    {
    path: '/login',
    element: <>Login</>,
    },
    ]);

    export default router;
  • 修改 packages/app/src/App.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    import router from './router';
    import { RouterProvider } from 'react-router-dom';

    function App() {
    return <RouterProvider router={router} />;
    }

    export default App;
  • 修改 packages/app/src/index.css 文件内容如下

    1
    2
    3
    4
    body {
    margin: 0;
    padding: 0;
    }
  • 修改 packages/app/src/main.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    import { StrictMode } from 'react';
    import { createRoot } from 'react-dom/client';
    import App from './App.tsx';
    import './index.css';
    import { registerMicroApps, RegistrableApp } from 'qiankun';

    createRoot(document.getElementById('root')!).render(
    <StrictMode>
    <App />
    </StrictMode>
    );

    /** 子应用配置 */
    const microApps: Array<RegistrableApp<any>> = [
    {
    name: 'demo1',
    entry: '//localhost:8001',
    container: '#subapp', // 子应用挂载的容器
    activeRule: '/demo1',
    },
    {
    name: 'demo2',
    entry: '//localhost:8002',
    container: '#subapp', // 子应用挂载的容器
    activeRule: '/demo2',
    },
    ];

    /** 注册子应用 */
    registerMicroApps(microApps);

配置子应用 demo1

node 版本小于 18, 安装 vite-plugin-qiankun 后启动会报错 ReferenceError: ReadableStream is not defined

解决方案:更改 cheerio 的版本, 详见:cheerio upgrade problem

demo1/package.json 下添加下面内容 注意不要带 ^ 符号

1
2
3
4
5
6
7
{
... // 其他配置
"devDependencies": {
... // 其他依赖
"cheerio": "1.0.0-rc.12"
}
}

最后删除 node_modules 文件夹与 package-lock.json 文件,重新安装依赖 (重要!!!)

  • demo1 项目中安装 vite-plugin-qiankun

    1
    2
    # 在根目录下执行
    pnpm -F=demo1 i vite-plugin-qiankun
  • 文件目录结构

    1
    2
    3
    4
    5
    6
    7
    8
    ├── public
    ├── src
    ├── router
    ├── index.tsx
    ├── App.tsx
    ├── index.css
    ├── main.tsx
    ├── vite.config.ts
  • 修改 packages/app/src/router/index.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import { createBrowserRouter } from 'react-router-dom';

    /** 路由配置 */
    const router = createBrowserRouter([
    {
    path: '/demo1/temp1',
    element: <>temp1</>,
    },
    {
    path: '/demo1/temp2',
    element: <>temp2</>,
    },
    ]);

    export default router;
  • 修改 packages/app/src/App.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import router from './router';
    import { RouterProvider } from 'react-router-dom';

    function App() {
    return (
    <>
    <h1>子应用 demo1</h1>
    <RouterProvider router={router} />
    </>
    );
    }

    export default App;
  • 修改 packages/app/src/index.css 文件内容如下

    1
    2
    3
    4
    body {
    margin: 0;
    padding: 0;
    }
  • 修改 packages/app/src/main.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    import { StrictMode } from 'react';
    import { createRoot } from 'react-dom/client';
    import App from './App.tsx';
    import './index.css';
    import { QiankunProps, renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';

    /** 渲染函数 */
    const render = (container?: HTMLElement) => {
    const app = container || (document.getElementById('root') as HTMLDivElement);
    createRoot(app).render(
    <StrictMode>
    <App />
    </StrictMode>
    );
    };

    /** Qiankun 生命周期钩子 */
    const qiankun = () => {
    renderWithQiankun({
    bootstrap() {
    console.warn('App bootstrap');
    },
    async mount(props: QiankunProps) {
    console.warn('App mount');
    render(props.container);
    },
    update: (props: QiankunProps) => {
    console.warn('App update', props);
    },
    unmount: (props: QiankunProps) => {
    console.warn('App unmount', props);
    },
    });
    };

    // 检查是否在 Qiankun 环境中
    console.log('qiankunWindow', qiankunWindow.__POWERED_BY_QIANKUN__);

    if (qiankunWindow.__POWERED_BY_QIANKUN__) qiankun();
    else render();
  • 修改 packages/demo1/vite.config.ts 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react-swc';
    import qiankun from 'vite-plugin-qiankun';

    // https://vitejs.dev/config/
    // https://cloud.tencent.com/developer/article/2138139
    export default ({ mode }) => {
    const useDevMode = mode === 'development';
    const host = '127.0.0.1';
    const port = 8001;
    const subAppName = 'demo1';
    const base = useDevMode ? `http://${host}:${port}/${subAppName}` : `/${subAppName}`;

    return defineConfig({
    base,
    server: {
    port,
    cors: true, // 作为子应用时,如果不配置,则会引起跨域问题
    origin: `http://${host}:${port}`, // 必须配置,否则无法访问静态资源
    },
    plugins: [
    // 在开发模式下需要把react()关掉
    // https://github.com/tengmaoqing/vite-plugin-qiankun?tab=readme-ov-file#3dev%E4%B8%8B%E4%BD%9C%E4%B8%BA%E5%AD%90%E5%BA%94%E7%94%A8%E8%B0%83%E8%AF%95
    ...[useDevMode ? [] : [react()]],
    qiankun(subAppName, { useDevMode }),
    ],
    });
    };

配置子应用 demo2

node 版本小于 18, 安装 vite-plugin-qiankun 后启动会报错 ReferenceError: ReadableStream is not defined

解决方案:更改 cheerio 的版本, 详见:cheerio upgrade problem

demo2/package.json 下添加下面内容 注意不要带 ^ 符号

1
2
3
4
5
6
7
{
... // 其他配置
"devDependencies": {
... // 其他依赖
"cheerio": "1.0.0-rc.12"
}
}

最后删除 node_modules 文件夹与 package-lock.json 文件,重新安装依赖 (重要!!!)

  • demo2 项目中安装 vite-plugin-qiankun

    1
    2
    # 在根目录下执行
    pnpm -F=demo2 i vite-plugin-qiankun
  • 文件目录结构

    1
    2
    3
    4
    5
    6
    7
    8
    ├── public
    ├── src
    ├── router
    ├── index.tsx
    ├── App.tsx
    ├── index.css
    ├── main.tsx
    ├── vite.config.ts
  • 修改 packages/app/src/router/index.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import { createBrowserRouter } from 'react-router-dom';

    /** 路由配置 */
    const router = createBrowserRouter([
    {
    path: '/demo2',
    element: <>demo2</>,
    },
    ]);

    export default router;
  • 修改 packages/app/src/App.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import router from './router';
    import { RouterProvider } from 'react-router-dom';

    function App() {
    return (
    <>
    <h1>子应用 demo2</h1>
    <RouterProvider router={router} />
    </>
    );
    }

    export default App;
  • 修改 packages/app/src/index.css 文件内容如下

    1
    2
    3
    4
    body {
    margin: 0;
    padding: 0;
    }
  • 修改 packages/app/src/main.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    import { StrictMode } from 'react';
    import { createRoot } from 'react-dom/client';
    import App from './App.tsx';
    import './index.css';
    import { QiankunProps, renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';

    /** 渲染函数 */
    const render = (container?: HTMLElement) => {
    const app = container || (document.getElementById('root') as HTMLDivElement);
    createRoot(app).render(
    <StrictMode>
    <App />
    </StrictMode>
    );
    };

    /** Qiankun 生命周期钩子 */
    const qiankun = () => {
    renderWithQiankun({
    bootstrap() {
    console.warn('App bootstrap');
    },
    async mount(props: QiankunProps) {
    console.warn('App mount');
    render(props.container);
    },
    update: (props: QiankunProps) => {
    console.warn('App update', props);
    },
    unmount: (props: QiankunProps) => {
    console.warn('App unmount', props);
    },
    });
    };

    // 检查是否在 Qiankun 环境中
    console.log('qiankunWindow', qiankunWindow.__POWERED_BY_QIANKUN__);

    if (qiankunWindow.__POWERED_BY_QIANKUN__) qiankun();
    else render();
  • 修改 packages/demo2/vite.config.ts 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react-swc';
    import qiankun from 'vite-plugin-qiankun';

    // https://vitejs.dev/config/
    // https://cloud.tencent.com/developer/article/2138139
    export default ({ mode }) => {
    const useDevMode = mode === 'development';
    const host = '127.0.0.1';
    const port = 8002;
    const subAppName = 'demo2';
    const base = useDevMode ? `http://${host}:${port}/${subAppName}` : `/${subAppName}`;

    return defineConfig({
    base,
    server: {
    port,
    cors: true, // 作为子应用时,如果不配置,则会引起跨域问题
    origin: `http://${host}:${port}`, // 必须配置,否则无法访问静态资源
    },
    plugins: [
    // 在开发模式下需要把react()关掉
    // https://github.com/tengmaoqing/vite-plugin-qiankun?tab=readme-ov-file#3dev%E4%B8%8B%E4%BD%9C%E4%B8%BA%E5%AD%90%E5%BA%94%E7%94%A8%E8%B0%83%E8%AF%95
    ...[useDevMode ? [] : [react()]],
    qiankun(subAppName, { useDevMode }),
    ],
    });
    };

更多微前端配置

添加快捷命令

  • 在根目录 package.json 中添加下面配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "scripts": {
    "preinstall": "npx only-allow pnpm",
    "all": "pnpm -r dev",
    "app": "pnpm -F app dev",
    "bootstrap": "lerna bootstrap",
    "clean": "lerna clean && rimraf ./node_modules",
    "demo1": "pnpm -F demo1 dev",
    "demo2": "pnpm -F demo2 dev"
    },
    ... // 其他配置
    }
  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 安装包
    pnpm install
    pnpm run bootstrap

    # 启动主应用
    pnpm run app

    # 启动子应用
    pnpm run demo1

    # 启动子应用
    pnpm run demo2

    # 清空依赖
    pnpm run clean

lerna6 + npm + workspaces + vite

包版本

1
2
3
4
"node": "^16.13.2",
"npm": "^8.1.0",
"lerna": "^6.5.1",
"vite": "^5.4.10"

开启 npm workspaces

  • 命令行执行 npx lerna init, 初始化项目

  • lerna.json 中添加下面配置

    1
    2
    3
    4
    {
    "npmClient": "npm",
    "packages": ["packages/*"]
    }
  • package.json 中添加下面配置

    1
    2
    3
    {
    "workspaces": ["packages/*"]
    }
  • 然后在根目录下创建 packages 目录 (注意:名称要与上面配置的 packages/* 目录名称一致)

进入 packages 创建主应用 app

  • 依次执行下面命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 进入 packages 目录
    cd ./packages

    # 创建主应用 app
    npm create vite@5

    # Project name
    app

    # Select a framework
    React

    # Select a variant
    TypeScript + SWC
  • packages/app/vite.config.ts 文件中配置端口号

    1
    2
    3
    4
    5
    6
    export default defineConfig({
    server: {
    port: 8000,
    },
    ... // 其他配置
    });

进入 packages 创建子应用 demo1

  • 依次执行下面命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 进入 packages 目录
    cd ./packages

    # 创建子应用 demo1
    npm create vite@5

    # Project name
    demo1

    # Select a framework
    React

    # Select a variant
    TypeScript + SWC
  • packages/demo1/vite.config.ts 文件中配置端口号

    1
    2
    3
    4
    5
    6
    export default defineConfig({
    server: {
    port: 8001,
    },
    ... // 其他配置
    });

进入 packages 创建子应用 demo2

  • 依次执行下面命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 进入 packages 目录
    cd ./packages

    # 创建子应用 demo2
    npm create vite@5

    # Project name
    demo2

    # Select a framework
    React

    # Select a variant
    TypeScript + SWC
  • packages/demo2/vite.config.ts 文件中配置端口号

    1
    2
    3
    4
    5
    6
    export default defineConfig({
    server: {
    port: 8002,
    },
    ... // 其他配置
    });

安装所需依赖

  • 删除根目录下的 node_modules 文件夹与 package-lock.json 文件

  • 在根目录下运行 npm install 安装依赖

  • appdemodemo2 安装所需的包 (这里可以将公用的包提取到 npm 仓库,然后在各个项目中安装, 这里不做演示)

    1
    2
    # 在根目录下安装antd与react-router-dom
    npm i antd react-router-dom -w=app -w=demo1 -w=demo2

配置主应用

  • app 项目中安装 qiankun

    1
    2
    # 根目录下执行
    npm i qiankun -w=app
  • 文件目录结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ├── public
    ├── src
    ├── layout
    ├── index.tsx
    ├── microApp.tsx
    ├── router
    ├── index.tsx
    ├── App.tsx
    ├── index.css
    ├── main.tsx
    ├── vite.config.ts
  • 修改 packages/app/src/layout/microApp.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import { start } from 'qiankun';
    import { useEffect } from 'react';

    const MicroApp = () => {
    /** 加载子应用 */
    const handleLoadMicroApp = () => {
    if (!window.qiankunStarted) {
    window.qiankunStarted = true;
    start();
    }
    };

    useEffect(() => {
    handleLoadMicroApp();
    }, []);

    return (
    <div>
    <div id="subapp"></div>
    </div>
    );
    };

    export default MicroApp;
  • 修改 packages/app/src/layout/index.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    import React, { useState } from 'react';
    import { Layout, Menu } from 'antd';
    import { Outlet, useNavigate } from 'react-router-dom';
    const { Header, Content, Footer, Sider } = Layout;
    import MicroApp from './microApp';

    /** 菜单栏配置 */
    const menus = [
    { key: 'home', label: 'Home', meta: { path: '/home' } },
    {
    key: 'docs',
    label: 'Docs',
    children: [
    { key: 'test1', label: 'Test1', meta: { path: '/docs/test1' } },
    { key: 'test2', label: 'Test2', meta: { path: '/docs/test2' } },
    ],
    },
    {
    key: 'demo1',
    label: 'Demo1',
    children: [
    { key: 'temp1', label: 'Temp1', meta: { path: '/demo1/temp1' } },
    { key: 'temp2', label: 'Temp2', meta: { path: '/demo1/temp2' } },
    ],
    },
    {
    key: 'demo2',
    label: 'Demo2',
    meta: { path: '/demo2' },
    },
    ];

    const App: React.FC = () => {
    const navigate = useNavigate();

    const [selectedKeys, setSelectedKeys] = useState<string[]>(['home']); // 控制菜单栏的选中状态

    /** 菜单栏点击事件 */
    const handleClick = (e: any) => {
    setSelectedKeys([e.key]);
    navigate(e?.item?.props?.meta?.path);
    };

    return (
    <Layout style={{ minHeight: '100vh' }}>
    <Sider>
    <Menu theme="dark" selectedKeys={selectedKeys} mode="inline" items={menus} onClick={handleClick} />
    </Sider>

    <Layout>
    <Header style={{ padding: 0, background: '#fff' }} />
    <Content style={{ margin: '0 16px' }}>
    <div
    style={{
    padding: 24,
    minHeight: 360,
    background: '#fff',
    borderRadius: 16,
    margin: '16px 0',
    }}>
    {/* 这里渲染子路由 */}
    <Outlet />
    {/* 这里挂载微应用 */}
    <MicroApp />
    </div>
    </Content>
    <Footer style={{ textAlign: 'center' }}>zxiaosi ©{new Date().getFullYear()}</Footer>
    </Layout>
    </Layout>
    );
    };

    export default App;
  • 修改 packages/app/src/router/index.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    import { createBrowserRouter, Navigate, Outlet } from 'react-router-dom';
    import BaseLayout from '../layout/index';

    /** 路由配置 */
    const router = createBrowserRouter([
    {
    path: '/',
    element: <Navigate to="/home" replace={true} />, // 重定向
    },
    {
    path: '/',
    element: <BaseLayout />,
    errorElement: <>BaseLayout Error</>,
    children: [
    {
    path: '/home',
    element: <>主应用 Home</>,
    },
    {
    path: '/docs',
    element: <Outlet />,
    children: [
    { path: '/docs/test1', element: <>主应用 Docs Test1</> },
    { path: '/docs/test2', element: <>主应用 Docs Test2</> },
    ],
    },
    {
    path: '/demo1',
    element: <Navigate to="/demo1/temp1" replace={true} />,
    errorElement: <>子应用 demo1 Error</>,
    children: [
    {
    path: '/demo1/temp1',
    element: <></>,
    },
    {
    path: '/demo1/temp2',
    element: <></>,
    },
    ],
    },
    {
    path: '/demo2',
    element: <></>,
    errorElement: <>子应用 demo2 Error</>,
    },
    ],
    },
    {
    path: '/login',
    element: <>Login</>,
    },
    ]);

    export default router;
  • 修改 packages/app/src/App.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    import router from './router';
    import { RouterProvider } from 'react-router-dom';

    function App() {
    return <RouterProvider router={router} />;
    }

    export default App;
  • 修改 packages/app/src/index.css 文件内容如下

    1
    2
    3
    4
    body {
    margin: 0;
    padding: 0;
    }
  • 修改 packages/app/src/main.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    import { StrictMode } from 'react';
    import { createRoot } from 'react-dom/client';
    import App from './App.tsx';
    import './index.css';
    import { registerMicroApps, RegistrableApp } from 'qiankun';

    createRoot(document.getElementById('root')!).render(
    <StrictMode>
    <App />
    </StrictMode>
    );

    /** 子应用配置 */
    const microApps: Array<RegistrableApp<any>> = [
    {
    name: 'demo1',
    entry: '//localhost:8001',
    container: '#subapp', // 子应用挂载的容器
    activeRule: '/demo1',
    },
    {
    name: 'demo2',
    entry: '//localhost:8002',
    container: '#subapp', // 子应用挂载的容器
    activeRule: '/demo2',
    },
    ];

    /** 注册子应用 */
    registerMicroApps(microApps);

配置子应用 demo1

node 版本小于 18, 安装 vite-plugin-qiankun 后启动会报错 ReferenceError: ReadableStream is not defined

解决方案:更改 cheerio 的版本, 详见:cheerio upgrade problem

demo1/package.json 下添加下面内容 注意不要带 ^ 符号

1
2
3
4
5
6
7
{
... // 其他配置
"devDependencies": {
... // 其他依赖
"cheerio": "1.0.0-rc.12"
}
}

最后删除 node_modules 文件夹与 package-lock.json 文件,重新安装依赖 (重要!!!)

  • demo1 项目中安装 vite-plugin-qiankun

    1
    2
    # 在根目录下执行
    npm i vite-plugin-qiankun -w=demo1
  • 文件目录结构

    1
    2
    3
    4
    5
    6
    7
    8
    ├── public
    ├── src
    ├── router
    ├── index.tsx
    ├── App.tsx
    ├── index.css
    ├── main.tsx
    ├── vite.config.ts
  • 修改 packages/app/src/router/index.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import { createBrowserRouter } from 'react-router-dom';

    /** 路由配置 */
    const router = createBrowserRouter([
    {
    path: '/demo1/temp1',
    element: <>temp1</>,
    },
    {
    path: '/demo1/temp2',
    element: <>temp2</>,
    },
    ]);

    export default router;
  • 修改 packages/app/src/App.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import router from './router';
    import { RouterProvider } from 'react-router-dom';

    function App() {
    return (
    <>
    <h1>子应用 demo1</h1>
    <RouterProvider router={router} />
    </>
    );
    }

    export default App;
  • 修改 packages/app/src/index.css 文件内容如下

    1
    2
    3
    4
    body {
    margin: 0;
    padding: 0;
    }
  • 修改 packages/app/src/main.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    import { StrictMode } from 'react';
    import { createRoot } from 'react-dom/client';
    import App from './App.tsx';
    import './index.css';
    import { QiankunProps, renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';

    /** 渲染函数 */
    const render = (container?: HTMLElement) => {
    const app = container || (document.getElementById('root') as HTMLDivElement);
    createRoot(app).render(
    <StrictMode>
    <App />
    </StrictMode>
    );
    };

    /** Qiankun 生命周期钩子 */
    const qiankun = () => {
    renderWithQiankun({
    bootstrap() {
    console.warn('App bootstrap');
    },
    async mount(props: QiankunProps) {
    console.warn('App mount');
    render(props.container);
    },
    update: (props: QiankunProps) => {
    console.warn('App update', props);
    },
    unmount: (props: QiankunProps) => {
    console.warn('App unmount', props);
    },
    });
    };

    // 检查是否在 Qiankun 环境中
    console.log('qiankunWindow', qiankunWindow.__POWERED_BY_QIANKUN__);

    if (qiankunWindow.__POWERED_BY_QIANKUN__) qiankun();
    else render();
  • 修改 packages/demo1/vite.config.ts 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react-swc';
    import qiankun from 'vite-plugin-qiankun';

    // https://vitejs.dev/config/
    // https://cloud.tencent.com/developer/article/2138139
    export default ({ mode }) => {
    const useDevMode = mode === 'development';
    const host = '127.0.0.1';
    const port = 8001;
    const subAppName = 'demo1';
    const base = useDevMode ? `http://${host}:${port}/${subAppName}` : `/${subAppName}`;

    return defineConfig({
    base,
    server: {
    port,
    cors: true, // 作为子应用时,如果不配置,则会引起跨域问题
    origin: `http://${host}:${port}`, // 必须配置,否则无法访问静态资源
    },
    plugins: [
    // 在开发模式下需要把react()关掉
    // https://github.com/tengmaoqing/vite-plugin-qiankun?tab=readme-ov-file#3dev%E4%B8%8B%E4%BD%9C%E4%B8%BA%E5%AD%90%E5%BA%94%E7%94%A8%E8%B0%83%E8%AF%95
    ...[useDevMode ? [] : [react()]],
    qiankun(subAppName, { useDevMode }),
    ],
    });
    };

配置子应用 demo2

node 版本小于 18, 安装 vite-plugin-qiankun 后启动会报错 ReferenceError: ReadableStream is not defined

解决方案:更改 cheerio 的版本, 详见:cheerio upgrade problem

demo2/package.json 下添加下面内容 注意不要带 ^ 符号

1
2
3
4
5
6
7
{
... // 其他配置
"devDependencies": {
... // 其他依赖
"cheerio": "1.0.0-rc.12"
}
}

最后删除 node_modules 文件夹与 package-lock.json 文件,重新安装依赖 (重要!!!)

  • demo2 项目中安装 vite-plugin-qiankun

    1
    2
    # 在根目录下执行
    npm i vite-plugin-qiankun -w=demo2
  • 文件目录结构

    1
    2
    3
    4
    5
    6
    7
    8
    ├── public
    ├── src
    ├── router
    ├── index.tsx
    ├── App.tsx
    ├── index.css
    ├── main.tsx
    ├── vite.config.ts
  • 修改 packages/app/src/router/index.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import { createBrowserRouter } from 'react-router-dom';

    /** 路由配置 */
    const router = createBrowserRouter([
    {
    path: '/demo2',
    element: <>demo2</>,
    },
    ]);

    export default router;
  • 修改 packages/app/src/App.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import router from './router';
    import { RouterProvider } from 'react-router-dom';

    function App() {
    return (
    <>
    <h1>子应用 demo2</h1>
    <RouterProvider router={router} />
    </>
    );
    }

    export default App;
  • 修改 packages/app/src/index.css 文件内容如下

    1
    2
    3
    4
    body {
    margin: 0;
    padding: 0;
    }
  • 修改 packages/app/src/main.tsx 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    import { StrictMode } from 'react';
    import { createRoot } from 'react-dom/client';
    import App from './App.tsx';
    import './index.css';
    import { QiankunProps, renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';

    /** 渲染函数 */
    const render = (container?: HTMLElement) => {
    const app = container || (document.getElementById('root') as HTMLDivElement);
    createRoot(app).render(
    <StrictMode>
    <App />
    </StrictMode>
    );
    };

    /** Qiankun 生命周期钩子 */
    const qiankun = () => {
    renderWithQiankun({
    bootstrap() {
    console.warn('App bootstrap');
    },
    async mount(props: QiankunProps) {
    console.warn('App mount');
    render(props.container);
    },
    update: (props: QiankunProps) => {
    console.warn('App update', props);
    },
    unmount: (props: QiankunProps) => {
    console.warn('App unmount', props);
    },
    });
    };

    // 检查是否在 Qiankun 环境中
    console.log('qiankunWindow', qiankunWindow.__POWERED_BY_QIANKUN__);

    if (qiankunWindow.__POWERED_BY_QIANKUN__) qiankun();
    else render();
  • 修改 packages/demo2/vite.config.ts 文件内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react-swc';
    import qiankun from 'vite-plugin-qiankun';

    // https://vitejs.dev/config/
    // https://cloud.tencent.com/developer/article/2138139
    export default ({ mode }) => {
    const useDevMode = mode === 'development';
    const host = '127.0.0.1';
    const port = 8002;
    const subAppName = 'demo2';
    const base = useDevMode ? `http://${host}:${port}/${subAppName}` : `/${subAppName}`;

    return defineConfig({
    base,
    server: {
    port,
    cors: true, // 作为子应用时,如果不配置,则会引起跨域问题
    origin: `http://${host}:${port}`, // 必须配置,否则无法访问静态资源
    },
    plugins: [
    // 在开发模式下需要把react()关掉
    // https://github.com/tengmaoqing/vite-plugin-qiankun?tab=readme-ov-file#3dev%E4%B8%8B%E4%BD%9C%E4%B8%BA%E5%AD%90%E5%BA%94%E7%94%A8%E8%B0%83%E8%AF%95
    ...[useDevMode ? [] : [react()]],
    qiankun(subAppName, { useDevMode }),
    ],
    });
    };

更多微前端配置

添加快捷命令

1
2
3
4
5
6
7
8
9
10
11
{
"scripts": {
"app": "npm run dev -w=app",
"bootstrap": "lerna bootstrap",
"clean": "lerna clean && rimraf ./node_modules",
"demo1": "npm run dev -w=demo1",
"demo2": "npm run dev -w=demo2",
"preinstall": "npx only-allow npm"
},
... // 其他配置
}
  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 安装包
    npm install
    npm run bootstrap

    # 启动主应用
    npm run app

    # 启动子应用
    npm run demo1

    # 启动子应用
    npm run demo2

    # 清空依赖
    npm run clean

yarn 版本过多, 后续有时间会尝试 👀