思路

项目管理

  • Monorepo : 是一种管理项目的方式,即将多个项目放在一个仓库中,这样可以方便地管理项目之间的依赖关系,提高代码复用性,减少重复代码,提高开发效率。

依赖管理

  • Pnpm : 可以加速 npm 安装依赖的速度,并减少磁盘空间占用。

微前端架构

  • qiankun : 一种基于微前端架构的前端应用解决方案,它将一个大型前端应用拆分成多个独立的子应用,每个子应用独立开发、测试、部署,互不干扰,最终组装成一个整体的应用。

UI 组件库、工具类等共用

  • npm : 由于 npm 包管理工具的存在,使得多个项目可以共用同一个包,不重复安装,提高开发效率。

业务组件共用

样式共用

  • Ant Design Token : 可以将多个项目的样式变量集中管理,并通过工具生成对应的样式文件,实现样式的共享。

仓库地址

搭建项目

创建 lerna 项目

  • 新建一个空文件夹

  • 在根目录下执行 npx lerna init, 初始化项目

开启 pnpm workspaces

  • 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
    # 进入 packages 目录
    cd ./packages

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

    # 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@latest

    # Project name
    demo1

    # Select a framework
    React

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

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

进入 packages 创建公共包 sdk

  • 这里每个文件夹通常是封装成单独的 npm 包, 这里写在一起是为了方便演示

  • 依次执行下面命令

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

    # 创建公共包 sdk
    npx create-father sdk

    # Pick target platform(s)
    Browser

    # Pick NPM client
    npm

    # nput NPM package name
    sdk

    # Input NPM package description
    sdk
  • 配置 packages/sdk/tsconfig.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    {
    "compilerOptions": {
    "resolveJsonModule": true, // 允许 import 命令导入 JSON 文件
    "esModuleInterop": true, // 修复了一些 CommonJS 和 ES6 模块之间的兼容性问题
    "moduleResolution": "node", // 确定模块路径的算法,即如何查找模块。采用 Node.js 的 CommonJS 模块算法
    "jsx": "react-jsx", // 设置如何处理.tsx文件。它一般以下三个值
    "module": "commonjs", // 指定编译产物的模块格式。它的默认值与target属性有关,如果target是ES3或ES5,它的默认值是commonjs,否则就是ES6/ES2015
    "target": "es5", // 指定编译出来的 JavaScript 代码的 ECMAScript 版本,比如es2021,默认是es3
    "allowJs": false, // 指定源目录的 JavaScript 文件是否原样拷贝到编译后的目录
    "noUnusedLocals": false, // 设置是否允许未使用的局部变量
    "preserveConstEnums": true, // 将const enum结构保留下来,不替换成常量值
    "skipLibCheck": true, // 跳过所有的声明文件(.d.ts)的类型检查
    "sourceMap": true, // 设置编译时是否生成 SourceMap 文件
    "inlineSources": true, // 设置将原始的.ts代码嵌入编译后的 JS 中
    "declaration": true, // 设置编译时是否为每个脚本生成类型声明文件.d.ts
    "experimentalDecorators": true, // 启用实验性的ES装饰器
    "downlevelIteration": true, // 编译器将迭代器转换为ECMAScript 3兼容代码
    "baseUrl": "./", // 指定 TypeScript 项目的基准目录
    "lib": ["ESNext"] // 描述项目需要加载的 TypeScript 内置类型描述文件,跟三斜线指令/// <reference lib="" />作用相同
    }
    }
  • 配置 packages/sdk/.fatherrc.ts

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

    // 文档:https://github.com/umijs/father/blob/master/docs/config.md
    export default defineConfig({
    esm: {
    // 将源码转换为 CommonJS 产物
    output: 'esm', // 输出目录
    },
    cjs: {
    // 将源码转换为 CommonJS 产物
    output: 'cjs', // 输出目录
    },
    umd: {
    // 将源码打包为 UMD 产物
    output: 'lib', // 输出目录
    sourcemap: true, // 为 JavaScript 构建产物生成 sourcemap 文件
    externals: {
    // 源码打包过程中需要处理的外部依赖
    react: 'React',
    'react-dom': 'ReactDOM',
    },
    },
    });
  • 配置 packages/sdk/package.json

    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
    {
    "name": "@zxiaosi/sdk",
    "version": "0.0.1",
    "description": "",
    "keywords": [],
    "authors": [],
    "main": "lib",
    "module": "esm",
    "types": "esm/index.d.ts",
    "files": ["lib", "esm", "cjs"],
    "exports": {
    ".": {
    "import": "./esm/index.js",
    "types": "./esm/index.d.ts"
    }
    },
    "scripts": {
    "dev": "father dev",
    "build": "father build",
    "build:deps": "father prebundle",
    "prepublishOnly": "father doctor && npm run build"
    },
    "license": "MIT",
    "publishConfig": {
    "access": "public"
    },
    "devDependencies": {
    "father": "^4.5.1"
    },
    "dependencies": {}
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    "name": 包名 (必填)
    "version": 版本号 (必填)

    "description": 描述
    "keywords": 关键字
    "authors": 作者
    "homepage": 主页
    "repository": 仓库地址

    "main": 入口文件
    "module": ES Module 入口文件
    "types": 类型文件
    "files": 需要发布的文件
    "exports": 导出的文件

    "scripts": 脚本

    "license": 许可证
    "publishConfig": 发布配置

    "devDependencies": 开发依赖
    "dependencies": 依赖

packages/app/package.jsonpackages/demo1/package.json 中引入 sdk

1
2
3
4
5
6
7
8
9
10
{
... // 其他配置
"dependencies": {
... // 其他配置
// 这里的包名要与 packages/sdk/packages.json 文件中的 name 一致
// 不想名称一致的话,也可以使用路径引入
// "@zxiaosi/sdk": "file:../sdk"
"@zxiaosi/sdk": "workspace:*"
}
}

最后在根目录下执行 pnpm install 安装依赖

项目启动顺序

  • 先启动 sdk 包, 进入 packages/sdk 目录执行 pnpm run dev

  • 再启动 demo1 子应用, 进入 packages/demo1 目录执行 pnpm run dev

  • 最后启动 app 主应用, 进入 packages/app 目录执行 pnpm run dev

  • 必须先启动 sdk 包,因为 demo1 子应用和 app 主应用都依赖 sdk 包。(通常情况下, sdk 包是在其他地方统一维护的 npm 包)

sdk 中配置公用包

项目目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|--- cjs # CommonJS
|--- esm # ES Module
|--- lib # UMD
|--- sdk
|--- src
|--- components # antd 组件
|--- index.ts # 入口文件
|--- icons # antd 图标
|--- index.ts # 入口文件
|--- pro-components # antd pro 组件
|--- index.ts # 入口文件
|--- index.ts # 入口文件
|--- .fatherrc.ts # father 配置
|--- package.json # 包配置
|--- tsconfig.json # TypeScript 配置

配置 components 包 (antd)

  • sdk 中安装 antd

    1
    2
    3
    4
    5
    # 进入 sdk 目录
    cd ./packages/sdk

    # 安装 antd
    pnpm add antd
  • packages/sdk/src/components/index.ts 导出 antd 组件

    1
    export * from 'antd';
  • 在 ``packages/sdk/package.json导出components` 组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    ... // 其他配置
    "exports": {
    ... // 其他配置
    "./components": {
    "import": "./esm/components/index.js",
    "types": "./esm/components/index.d.ts"
    }
    }
    }
  • 配置按需加载 antd 组件

    • 安装 babel-plugin-import 插件

      1
      2
      3
      4
      5
      # 进入 sdk 目录
      cd ./packages/sdk

      # 安装 babel-plugin-import
      pnpm add babel-plugin-import
    • packages/sdk/.fatherrc.ts 中配置 babel-plugin-import

      1
      2
      3
      4
      5
      6
      7
      8
      9
      import { defineConfig } from 'father';

      // 文档:https://github.com/umijs/father/blob/master/docs/config.md
      export default defineConfig({
      // ... 其他配置
      extraBabelPlugins: [
      ['babel-plugin-import', { libraryName: 'antd', style: true }, 'antd'],
      ],
      });
  • 最后在 app 或者 demo1 中使用 sdk 包的 components 组件

    1
    2
    3
    4
    5
    import { Button } from '@zxiaosi/sdk/components';

    export default function App() {
    return <Button type="primary">Button</Button>;
    }

配置 icons 包 (@ant-design/icons)

  • sdk 中安装 @ant-design/icons

    1
    2
    3
    4
    5
    # 进入 sdk 目录
    cd ./packages/sdk

    # 安装 @ant-design/icons
    pnpm add @ant-design/icons
  • packages/sdk/src/icons/index.ts 导出 @ant-design/icons 组件

    1
    export * from '@ant-design/icons';
  • 在 ``packages/sdk/package.json导出icons` 组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    ... // 其他配置
    "exports": {
    ... // 其他配置
    "./icons": {
    "import": "./esm/icons/index.js",
    "types": "./esm/icons/index.d.ts"
    }
    }
    }
  • 配置按需加载 @ant-design/icons 组件

    • 安装 babel-plugin-import 插件 (如果已经安装过,可以跳过)

      1
      2
      3
      4
      5
      # 进入 sdk 目录
      cd ./packages/sdk

      # 安装 babel-plugin-import
      pnpm add babel-plugin-import
    • packages/sdk/.fatherrc.ts 中配置 babel-plugin-import

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      import { defineConfig } from 'father';

      // 文档:https://github.com/umijs/father/blob/master/docs/config.md
      export default defineConfig({
      // ... 其他配置
      extraBabelPlugins: [
      // ... 其他配置
      [
      'babel-plugin-import',
      { libraryName: '@ant-design/icons', style: true },
      '@ant-design/icons',
      ],
      ],
      });
  • 最后在 app 或者 demo1 中引入 sdk 包的 icons 组件

    1
    2
    3
    4
    5
    import { UserOutlined } from '@zxiaosi/sdk/icons';

    export default function App() {
    return <UserOutlined />;
    }

配置 pro-components 包 (@ant-design/pro-components)

  • sdk 中安装 @ant-design/pro-components

    1
    2
    3
    4
    5
    # 进入 sdk 目录
    cd ./packages/sdk

    # 安装 @ant-design/pro-components
    pnpm add @ant-design/pro-components
  • packages/sdk/src/pro-components/index.ts 导出 @ant-design/pro-components 组件

    1
    export * from '@ant-design/pro-components';
  • 在 ``packages/sdk/package.json导出pro-components` 组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    ... // 其他配置
    "exports": {
    ... // 其他配置
    "./pro-components": {
    "import": "./esm/pro-components/index.js",
    "types": "./esm/pro-components/index.d.ts"
    }
    }
    }
  • 配置按需加载 @ant-design/pro-components 组件

    • 安装 babel-plugin-import 插件 (如果已经安装过,可以跳过)

      1
      2
      3
      4
      5
      # 进入 sdk 目录
      cd ./packages/sdk

      # 安装 babel-plugin-import
      pnpm add babel-plugin-import
    • packages/sdk/.fatherrc.ts 中配置 babel-plugin-import

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      import { defineConfig } from 'father';

      // 文档:https://github.com/umijs/father/blob/master/docs/config.md
      export default defineConfig({
      // ... 其他配置
      extraBabelPlugins: [
      // ... 其他配置
      [
      'babel-plugin-import',
      { libraryName: '@ant-design/pro-components', style: true },
      '@ant-design/pro-components',
      ],
      ],
      });
  • 最后在 app 或者 demo1 中引入 sdk 包的 pro-components 组件

    1
    2
    3
    4
    5
    import { ProCard } from '@zxiaosi/sdk/pro-components';

    export default function App() {
    return <ProCard>123</ProCard>;
    }