Monorepo 架构下的 React 项目实战:项目搭建

1,078 阅读12分钟

阅读本文你能学到

  1. Monorepo 概述:

    1. 什么是 Monorepo?
    2. Monorepo 的优缺点。
    3. 构建方法对比: pnpm 和 lerna。
  2. Monorepo 项目规划与工程目录创建:

    1. 如何初始化一个 Monorepo 项目?
    2. 如何配置 pnpm-workspace.yaml ?
    3. 项目规划目录及文件创建;
    4. Monorepo项目中包安装方法: 全局安装和指定项目安装;
  3. 项目统一规范及语法检测工具安装和配置

    1. 全局安装 eslint
    2. 全局安装 prettier
    3. 全局安装 typescript
  4. 创建公共工具包管理文件夹 libs

    1. 命名空间管理约规: 使用带有 @scope/ 前缀的命名方式
    2. 如何创建一个 logs 公共工具包?
    3. 如何在其他项目中安装 logs 包并使用?
  5. 创建并配置 react-demo 项目

    1. 从 0 到 1 搭建 react 工程 ( 不使用 CRA ): 依赖 / 构建工具链

Monorepo 概述

什么是 Monorepo?

Monorepo 是一种代码库管理方式,将多个项目或模块存储在同一个代码仓库中,以便于共享代码、统一管理依赖和配置,以及简化项目的构建和部署过程。

Monorepo 的出现是为了应对大型项目中多个代码库管理的复杂性和效率低下问题。随着项目规模和团队的扩展,传统的多仓库(multirepo)模式导致了代码共享困难、依赖管理繁琐和版本同步问题。Monorepo 通过将多个项目或模块集中在一个仓库中,简化了这些流程,促进了更好的协作和代码复用,从而提高了开发效率和项目的一致性。

简单来说,Monorepo 就是用一个代码库来管理多个项目,方便共享资源和统一管理。

Monorepo 的优缺点

Monorepo 的优点包括简化依赖管理、促进代码复用、增强团队协作以及提高构建和部署效率。然而,它也有缺点,如仓库规模庞大带来的性能问题、对版本控制工具的高要求,以及可能增加的代码冲突和管理复杂性。

构建方法对比: pnpm 和 lerna

pnpmLerna 都可以用来管理 Monorepo,但它们的侧重点不同:

  • pnpm 是一个高效的包管理器,它通过创建一个共享的依赖缓存来优化依赖安装和管理。在 Monorepo 中,pnpm 使用 pnpm-workspace 功能来管理多个包和统一依赖,提高构建速度和节省磁盘空间。
  • Lerna 是一个专门用于管理 Monorepo 的工具,它提供了版本控制、依赖管理和发布等功能。Lerna 关注于简化包的版本管理和发布流程。

pnpm 主要关注于高效的包管理和依赖优化,而 Lerna 主要集中在 Monorepo 的版本控制和发布管理。值得注意的是: lerna 没有默认软链需要自己去手动 link 而pnpm 会自动做软连接。

Monorepo 项目规划与工程目录创建

如何初始化一个 Monorepo 项目?

  1. 创建项目目录
  2. pnpm 初始化
  3. 创建 pnpm-workspace.yaml
# 创建一个新的目录来存放 Monorepo 项目
mkdir my-mono

# 进入新创建的目录
cd my-mono

# 初始化一个新的 pnpm 项目,并创建 package.json 文件
pnpm init

# 创建 pnpm-workspace.yaml 文件,用于定义工作空间
touch pnpm-workspace.yaml

如何配置 pnpm-workspace.yaml ?

# pnpm-workspace.yaml 配置文件
packages:
  # 指定工作空间中的包路径
  - 'packages/**'

pnpm-workspace.yaml 是 pnpm 包管理器的配置文件,用于定义 Monorepo 中的工作空间和管理多个包。它的主要功能包括:

定义工作空间:指定哪些目录包含了你的项目包,以便 pnpm 能够识别并管理这些包。例如,可以使用 glob 模式来匹配工作空间中的所有包。

统一管理依赖:通过 pnpm-workspace.yaml,你可以在 Monorepo 中统一管理所有包的依赖,这样可以避免重复安装依赖,并提升安装效率。

简化构建和发布:配置文件帮助 pnpm 在多个包之间处理依赖关系,使得构建、测试和发布操作更加一致和高效。

官方文档传送门: pnpm.io/pnpm-worksp…

到目前为止,我们完成了创建和初始化 Monorepo 项目,配置了 pnpm 以高效管理多个包,并理解了 pnpm-workspace.yaml 的作用和配置方法。

下面我们正式开始项目的创建和细节配置,包括定义包的结构、编写必要的配置文件,以及设定构建和发布流程。

项目规划目录及文件创建

在本文的项目中,现阶段我们将在 packages 文件夹下管理两个前端项目(React 和 Vue)、两个前端公共组件库(React 组件和 Vue 组件),以及一个公共工具包。首先,我们将完成工程目录的创建,目录结构如下:

# 创建项目目录结构,包含不同类型的包和组件
mkdir -p packages/apps/react-demo      # 创建 React 应用包目录
mkdir -p packages/apps/vue-demo        # 创建 Vue 应用包目录
mkdir -p packages/components/react-components  # 创建 React 组件包目录
mkdir -p packages/components/vue-components    # 创建 Vue 组件包目录
mkdir -p packages/libs/cli             # 创建 CLI 库目录
mkdir -p packages/libs/logs            # 创建 日志库目录
mkdir -p packages/libs/utils           # 创建 工具库目录
├── package.json
├── packages
│   ├── apps
│   │   ├── react-demo
│   │   └── vue-demo
│   ├── components
│   │   ├── react-components
│   │   └── vue-compontents
│   └── libs
│       ├── cli
│       ├── logs
│       └── utils
├── pnpm-lock.yaml
└── pnpm-workspace.yaml

pnpm 构建的 Monorepo 项目中包安装方法

在 Monorepo 项目中,合理管理包的安装是确保高效开发的关键。主要的包安装方法包括以下几种:

  1. 全局安装: 通过运行 pnpm i lodash -w,可以在 Monorepo 的根目录下全局安装 lodash 包。此方法会将包安装到根目录,使所有子项目共享这一依赖,适用于需要在所有子项目中使用相同版本的包时。
  2. 局部安装: 使用 pnpm add <package> --filter <package-name> 可以在指定的子项目中安装包。例如,pnpm add react --filter react-demo 会将 react 安装到名为 react-demo 的子项目中。这种方法适用于只在特定子项目中添加依赖的情况。
  3. 内部安装: 类似于局部安装,pnpm add <package> --filter <package-name> 也可以用于将依赖安装到 Monorepo 中的特定内部包或模块。这种方式通常用于在项目内的特定模块中添加依赖,而不会影响其他子项目或全局配置。
  • 全局安装: pnpm i -w
  • 局部安装: pnpm add --filter
  • 内部安装: pnpm add < project-pkg-name> --filter

在接下来的例子中,我们将具体讲解如何使用这几种包安装方法。

项目统一规范及语法检测工具安装和配置

全局安装 eslint

ESLint 是一个用于识别和修复 JavaScript 和 TypeScript 代码中的问题和风格错误的工具。

# 在 Monorepo 根目录下全局安装 eslint 开发依赖
pnpm install eslint -D -w

# 使用 npx 初始化 eslint 配置
npx eslint --init

# 配置选项
✔ How would you like to use ESLint? · problems
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · none
✔ Does your project use TypeScript? · typescript
✔ Where does your code run? · browser, node
✔ Would you like to install them now? · No / Yes
✔ Which package manager do you want to use? · pnpm

执行之后会报一个错误:

 ERR_PNPM_ADDING_TO_ROOT  Running this command will add the dependency to the workspace root, which might not be what you want - if you really meant it, make it explicit by running this command again with the -w flag (or --workspace-root). If you don't want to see this warning anymore, you may set the ignore-workspace-root-check setting to true.
# 该错误提示表明你尝试在 Monorepo 的根目录下安装依赖,但没有明确指定这是一个根目录安装。pnpm 提示你可能误操作,如果确实要在根目录安装依赖,需要加上 -w 或 --workspace-root 标志。否则,可以通过设置 ignore-workspace-root-check 为 true 来忽略这个警告。

错误原因

默认脚本不会 -w, 需要手动安装依赖。

处理方式: 手动安装需要的依赖

pnpm add @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest -D -w 

PS: 在实际开发中遇到了一个 pnpm install 下载失败或卡住不动问题,可以尝试以下解决方案, 如果没遇到略过即可。

解决方法:

设置国内源: touch .npmrc 并写入 registry=registry.npm.taobao.org

# 创建 .npmrc 文件
touch .npmrc
# 将 npm 注册表设置为淘宝镜像
echo "registry=http://registry.npm.taobao.org" > .npmrc

# 验证配置
npm config get registry

临时禁用 ssl: pnpm config set strict-ssl false

全局安装 prettier

Prettier 是一个代码格式化工具,用于自动统一和优化代码的排版风格。

官方网站配置文件详情: prettier.io/docs/en/con…

pnpm add prettier -D -w
touch .prettierrc.json

基础配置

{
  "trailingComma": "es5",
  "tabWidth": 2,
  "semi": false,
  "singleQuote": true
}

webstorm 编辑器配置保存时根据 prettier 配置自动格式化

全局安装 typescript

在 Monorepo 根目录下全局安装 TypeScript 编译器 (tsc)、TypeScript 加载器 (ts-loader) 和 Babel TypeScript 预设 ( @babel/preset-typescript),作为开发依赖

pnpm add tsc ts-loader @babel/preset-typescript -w -D
# 生成一个默认的 tsconfig.json 文件,用于配置 TypeScript 编译选项。
tsc --init

tsc --init 是 TypeScript 编译器(tsc)的命令,用于在当前目录下生成一个默认的 tsconfig.json 配置文件。这个文件包含 TypeScript 编译器的基本配置选项,如编译目标、模块系统、包括的文件路径等,帮助你自定义 TypeScript 项目的编译设置。

创建第一个公用工具 libs/logs

项目初始化

cd packages/libs/logs
pnpm init
touch index.js

命名空间管理约规: 使用带有 @scope/ 前缀的命名方式

  1. 指定了包的名称为 @tong/logs,这是一个遵循命名空间的包名,表示该包属于 @tong 命名空间。
  2. "type": "module" 指定了项目中的 JavaScript 文件使用 ECMAScript 模块(ESM)语法。
{
  "name": "@tong/logs",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {},
  "keywords": [],
  "author": "",
  "license": "ISC"
}

在index.js中写一个测试函数并导出

echo 'export const getLog = () => console.log("This is Log!")' > index.js

在指定项目中使用公共工具 logs

react-demo 初始化

cd /my-mono/packages/apps/react-demo
pnpm init
touch index.js

配置 package.json

{
  "name": "@tong/react-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "start": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
}

安装 @tong/logs

pnpm add @tong/logs --filter @tong/react-demo

安装成功, 建立 @tong/logs 的软链接, 即 @tong/logs 改动后直接生效不需要再次更新。

在 index.js中引入 getLog 并执行

import {getLog} from "@tong/logs";

getLog()

执行代码

🍀 react-demo $ pnpm run start                                
> @tong/react-demo@1.0.0 start /my-mono/packages/apps/react-demo
> node index.js

This is Log!

修改 logs/index.js : export const getLog = () => console.log("This is Log2!")

再次执行

🍀 react-demo $ pnpm run start                                
> @tong/react-demo@1.0.0 start /my-mono/packages/apps/react-demo
> node index.js

This is Log2!

软链接, 修改即时生效。

创建并配置 react-demo 项目

从 0 到 1 搭建 react 工程 ( 不使用 CRA ): 依赖 / 构建工具链

在接下来的操作中,我们将详细介绍如何从头开始构建和配置 react-demo 项目。这包括初始化 TypeScript 配置、安装和配置 ESLint 插件进行代码检查、添加 React 及其相关依赖,以及设置构建工具链如 Webpack 和 Babel。我们还将配置各种加载器和插件,以支持 JavaScript 和 CSS 的解析、编译和优化。通过这些步骤,我们将建立一个完整的开发环境,确保项目能够顺利进行。

配置 react-demno 项目的 typescript

tsc --init 修改 tsconfig.json 配置

{
  "compilerOptions": {
    "target": "es2016" ,
    "lib": ["DOM", "DOM.Iterable", "ESNext"] ,
    "jsx": "react",     
    "experimentalDecorators": true,              
    "emitDecoratorMetadata": true,    
    "module": "ESNext" ,
     "moduleResolution": "Node",   
     "allowJs": true,      
    "noEmit": true,        
     "isolatedModules": true,   
     "allowSyntheticDefaultImports": true,
    "esModuleInterop": true ,
    "skipLibCheck": true
  },
  "include": ["./src/**/*"],
  "exclude": ["node_modules","dist"]
}

安装 ts eslint 检查插件

pnpm add @typescript-eslint/eslint-plugin -D -w

安装 react 的相关依赖

pnpm add react react-dom react-router-dom @types/react @types/react-dom @types/react-router-dom --filter=@tong/react-demo

react react-dom react-router-dom: React 相关的核心库和路由库。其中,react 和 react-dom 是 React 的主要库,react-router-dom 是用于在 React 应用中处理路由的库。

@types/react @types/react-dom @types/react-router-dom: 这些是相应库的 TypeScript 类型定义包,用于为 TypeScript 提供类型检查和智能提示。

--filter=@tong/react-demo: 指定将这些依赖安装到名为 @tong/react-demo 的子项目中,而不是整个 Monorepo 的根目录。这个标志确保依赖只添加到指定的项目或包中。

React Webpack 构建工具链安装

我们将为 @tong/react-demo 项目配置构建工具链,以便高效地开发和构建项目:

pnpm add webpack webpack-dev-server webpack-cli webpack-merge -D --filter=@tong/react-demo
# 解析 js相关
pnpm add babel-loader @babel/core @babel/preset-react @babel/preset-typescript @babel/preset-env  -D --filter=@tong/react-demo
# 解析 css
pnpm add style-loader css-loader less-loader less postcss-loader tailwindcss postcss autoprefixer -D --filter=@tong/react-demo
# 压缩 CSS
pnpm add css-minimizer-webpack-plugin terser-webpack-plugin mini-css-extract-plugin html-webpack-plugin -D --filter=@tong/react-demo
# file-loader webpackHtmlPlugin 内置了

Webpack 及相关工具:安装 webpack、webpack-dev-server、webpack-cli 和 webpack-merge,用于打包代码、开发服务器支持和配置管理。

JavaScript 解析:使用 babel-loader 和 Babel 的预设 (@babel/core、@babel/preset-react、@babel/preset-typescript 和 @babel/preset-env) 处理 JavaScript 和 TypeScript 文件。

CSS 处理:通过 style-loader、css-loader、less-loader、postcss-loader、tailwindcss 和 autoprefixer 解析和优化 CSS,包括 Less 和 PostCSS 处理。

压缩和优化:安装 css-minimizer-webpack-plugin、terser-webpack-plugin、mini-css-extract-plugin 和 html-webpack-plugin 进行 CSS 和 JavaScript 压缩,以及生成 HTML 文件。


总结

在本篇实战系列的第一节中,我们全面探讨了如何从零开始构建和配置一个 React 项目。内容包括 Monorepo 的概述、优缺点、以及构建方法对比;详细讲解了如何初始化 Monorepo 项目、配置 pnpm-workspace.yaml、以及设立项目目录结构。接着,我们介绍了如何统一项目规范和安装语法检测工具如 ESLint 和 Prettier,同时涵盖了 TypeScript 的安装和配置。

我们还深入探讨了如何创建公共工具包,并展示了如何在不同项目中安装和使用这些工具包。此外,详细讲解了从头搭建 React 工程的过程,包括依赖安装、构建工具链配置等。

这一节为后续的实战提供了坚实的基础。接下来的章节将继续深入,进一步探讨更多实用的技术细节和实战技巧。敬请期待未来的内容,我们将继续带来更多实用的开发实践和技巧分享。