Monorepo最佳实战之react后台管理工程化项目搭建

960 阅读9分钟

阅读本文你能学到什么?

  • 了解monorepo基本概念以及如何落地开发
  • 了解pnpm基本命令,以及为什么不用npm,yarn
  • 了解什么是 turbo ,以及为什么使用 turbo
  • 了解一个后台管理模版是如何搭建的【重点】
  • 结尾提供源码和在线预览地址

为什么使用monorepo

随着研发代码的增多, 工程日益复杂,代码复用和版本管理显得格外的繁琐,版本升级没有日志,相互依赖的包需要手动管理版本,以往的组件库独立开发的方式并没有很好的区分组件和组件之间的关系,往往只需要一种类型的组件,例如图表,但还是不得不安装一整个组件库,并没有很好的对组件进行区分,如哪些是图表组件,哪些是功能组件,哪些是业务组件等,造成组件库越来越大,编译打包效率降低,每次一个小改动就不得不直接发布一整个包预览效果,且无法支持本地调试等以下相关痛点

  • 组件耦合严重,组件代码量大

  • 业务开发分工不明确,业务开发人员要关心非业务的代码

  • 编译慢,效率低

  • 无法对应用做增量编译&增量部署

  • 相关包基础依赖可能会重复打包,如: lodash,moment...

  • 管理、调试、追踪 bug 困难

  • 不同项目之间 nodenode-sasswebpack 等基础依赖版本不统一,切换增加心智负担

  • 不同项目可能存在技术栈不统一,如:状态管理,less,sass

  • 分支管理混乱

  • 多包多项目之间依赖关系复杂

  • 第三方依赖库版本可能不一致

  • 占用总空间大

  • 不利于团队协作

  • 无法针对主应用统一跑测试用例,发布时很难避免一些基本的错误发生

  • 需要频繁切换项目

  • 搭建独立的文档系统和其他子应用时,相关依赖又要单独管理,又有上述的症状
    针对上述问题我们引入了 Monorepo 的概念,把以往的单一组件库拆分为职责更细化的包,架构更清晰,解耦,子应用隔离

pnpm相关

我们所有的包管理都强制使用pnpm,在 monorepo 架构之上,pnpm 能极大发挥他的作用(设计初期就很好的考虑了当前复杂项目的痛点),相比 yarnnpmpnpm 能节约磁盘空间并提升安装速度,切避免了关于深度嵌套包的一些意外情况,如果你还没有接触了解过 pnpm,可以看看相关文章, 而且当前已有众多前端团队和大部分主流开源项目抛弃 npm,yarn,开始接入 pnpm

  • pnpm add sax 保存到 dependencies 配置项下
  • pnpm add -D sax 保存到 devDependencies 配置项下
  • pnpm add -O sax 保存到 optionalDependencies 配置项下
  • pnpm add -g sax 安装软件包到全局环境中
  • pnpm add sax@next 安装标记为 next 的版本
  • pnpm add sax@3.0.0 安装指定版本 3.0.0

参数

  • --save-prod, -P

  • 安装指定的软件包并添加到 dependencies 配置项中。

  • --save-dev, -D

  • 安装指定的软件包并添加到 devDependencies 配置项中。

  • --save-optional, -O

  • 安装指定的软件包并添加到 optionalDependencies 配置项中。

  • --save-exact, -E

  • Saved dependencies will be configured with an exact version rather than using pnpm's default semver range operator.

  • --save-peer

  • Using --save-peer will add one or more packages to peerDependencies and install them as dev dependencies.

  • --ignore-workspace-root-check

  • Adding a new dependency to the root workspace package fails, unless the --ignore-workspace-root-check or -w flag is used.

  • For instance, pnpm add debug -w.

  • --global, -g

  • 将软件包安装都全局环境中。

  • --workspace

  • 仅添加能在 workspace 中找到的依赖包。

turbo

Turborepo 是一个针对 JavaScript 和 TypeScript 代码库优化的智能构建系统

你的代码库的任务——比如lintbuildtest——没有尽可能快地运行。Turborepo 使用缓存来增强您的本地设置并加速您的 CI。

特征

  • 永远不要做同样的工作两次

  • Turborepo 会记住您运行的任何任务的输出 - 并且可以跳过已经完成的工作。

  • 最大的多任务处理

  • 您运行任务的方式可能未优化。Turborepo 通过智能调度加速它们,最大限度地减少空闲 CPU。

总结:就是快

后台管理模版搭建前期准备

相关技术栈

如何使用pnpm构建monorepo

首先进行全局安装 pnpm

npm install pnpm -g

然后在项目下使用 pnpm init 进行 package.json 的初始化。这跟 npm init 是一样的。

pnpm init    

得到 package.json 初始内容,然后把 package.json 中的 name 属性删掉,并且添加一个 "private": true 属性,因为它是不需要发布的。

{
  "private": true,
  "version": "0.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

配置 pnpm 的 monorepo 工作区

在我们这个仓库下,我们需要管理多个项目,就可以采用 pnpm 的 monorepo。我们在仓库的根目录下创建一个 pnpm-workspace.yaml 文件,可以在 pnpm-workspace.yaml 配置文件中指定这个仓库中有多少个项目。

packages:
  - test # 存放组件测试的代码
  - docs # 存放组件文档
  - packages/* # packages 目录下都是组件包

可以在 test 目录中运行我们写好的组件,相当于一个测试环境,在开发的时候可以知道效果是否达到预期;还需要一个组件说明文档的项目目录:docs; packages 目录则是所有组件的项目目录了,在 packages 目录中又可以放很多包的项目目录,比如,组件包目录:components、主题包目录:theme-chalk、工具包目录:utils 等。然后每一个包目录里面也需要一个 package.json 文件进行声明这是一个 NPM 包目录。所以我们需要进入每个包目录进行初始一个 package.json 文件。

以 components 包为例,我们进入到 components 目录底下初始化一个 package.json 文件,更改包名:components。文件内容如下:

{
  "name": "components",
  "version": "0.0.0",
  "main": "index.ts",
  "types": "./index.ts",
  "license": "MIT",
  "scripts": {
    "lint": "eslint \"**/*.ts\"",
    "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
  },
  "devDependencies": {
    "eslint-config-custom": "workspace:*",
    "tsconfig": "workspace:*",
  },
  "peerDependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.8.1"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.8.1",
    "hooks": "workspace:*",
    "utils": "workspace:*"
  }
}

其他包创建过程类似:utils、store、ui等

仓库项目内的包相互调用

componentsutils 这几个包要互相进行调用呢,就需要把它们安装到仓库根目录下的 node_modules 目录中。

然后我们在根目录下进行安装操作。

pnpm install components -w
pnpm install utils -w

-w表示安装到共公模块的 packages.json 中,也就是根目录下的 packages.json。

安装后根目录下的 package.json 的内容为:

{
  "dependencies": {
    "components": "workspace:*",
    "utils": "workspace:*"
  },
}

注意:workspace:* 将来发布的时候会被转换成具体的版本号。

TypeScript 初始化配置文件

接下来继续安装一些我们开发时所需要的依赖。

pnpm install react typescript @types/node -D -w

因为 reacttypescript@types/node 只是开发环境需要的,所以安装的时候需要添加一个 -D 参数表示安装到开发环境,-w 表示安装到共公模块的 packages.json 中,也就是根目录下的 packages.json。

因为我们使用了 TypeScript,这样我们想要去校验我们的代码,让我们代码有提示,并且可以按照一些规则来解析我们的语法,给我们更友好的提示,我们就需要去初始化一下这个 TypeScript 配置命令。 又因为我们安装了 typescript,所以在 node_modules 目录下 bin 目录里面就会存在一个 tsc 的命令,这个命令,就可以帮我们进行初始化,我们可以使用 npm tsc --init 来初始化,也可以使用 pnpm tsc --init 那么执行这个命令,它就会去 node_modules 目录下 bin 目录找这个 tsc 命令进行执行。

pnpm tsc --init

实现 admin 项目环境

pnpm create vite admin --template react-ts

在根目录下通过以上命令创建了一个 React + TS 的项目了。

接着我们进入 admin 目录进行安装项目依赖:

pnpm i

接着我们可以在 play 目录下运行 pnpm run dev 运行 admin 项目,但这样每次运行都需要进入到 admin 目录下的话,太麻烦了,我们希望在根目录下就可以运行 admin 项目,我们可以在根目录的 package.json 文件的 scripts 选项进行以下配置:

{
"scripts": {
    "dev": "pnpm -C admin dev",
  },
}

这样我们就可以在根目录通过 pnpm run dev 启动 play 项目里面 package.json 文件中 scripts 选项中对应的 dev 命令了。

实践

  • 以下是刚创建好的文件夹目录

  • 我们看到有几个重要文件夹可以关注

  • apps/web 主要项目放置地方

  • packages 主要放置公共包,比如eslint、tsconfig、ui、utils、components等

创建admin项目

packages 搭建

  • 创建components 子 packages 存放公共业务组件,比如Layout、Text

  • components

  • 创建hooks 子 packages 存放公共hooks,比如 useLocationListen、KeepAlive、Context

  • hooks

  • 创建store 子 packages 存放公共store,全局store,对外提供 store、 Provider、useSelector、 useDispatch

  • store

  • 创建utils 子 packages 存放公共utils

  • utils

  • 如何使用?

  • 在需要使用的项目

  •   // package.json添加如下包
      "utils": "workspace:*"
      // APP.tsx
      import { default } from "utils";
      
    

peerDependencies

  • 很多子packages里面会有这个概念

  • 大概通俗的意思就是:如果你安装我,那么你最好也安装X,Y和Z.

    "peerDependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.8.1" },

TS相关的内容预计还可以出一个章节,敬请期待吧..

总结

至此一个通过 pnpm 方式配置的 monorepo 组件库基础环境就搭建好了。

我们的标题是组件库工程化实战,我们已经完成了一个组件库的开发环境配置和admin项目创建,那么这个过程中到底什么是工程化的含义呢?在配置这个组件库的开发环境的过程中,我们好像只是使用了一堆工具进行各种配置,那么是否意味着前端工程化就是工具化呢?

其实不是,工程化的核心并非工具,而是以工具为实现媒介进行规范工作流程。而我们在上述文章中讨论的是组件库项目文件组织结构的规范,TypeScript 编译和类型检测流程的规范,还有工具链的统一。可以看到我们是通过工具流程规范具体化在项目结构中,这样就可以在一定程度上将开发者限定使用统一的工具链、遵循统一的规范进行业务代码的编写,这样便有利于多人协作和项目代码的维护。

也就是通过工具表达你的思想,通过工具规范你的项目,通过工具管理写代码的人员。

项目地址 github

线上预览地址 react-admin-vite-antd