React 项目的 TypeScript monorepo

2025-06-07

React 项目的 TypeScript monorepo

更新:在社区的帮助下,一些问题已得到解决。What I did本节中的步骤并非全部更新,但GitHub 仓库包含所有最新更改

我想为 React 项目创建 TypeScript Monorepo。我尝试过,但结果并不满意。这篇文章描述了我的做法。有什么改进设置的建议吗?最后还有一点吐槽。源代码在这里

我想要实现的目标

  • Monorepo项目,能够轻松开发多个包,这些包可以单独使用,也可以一起使用
  • 使用 TypeScript
  • 对于 React 项目
  • 有了测试库,我想从 Jest 开始,但我们也可以选择其他东西
  • 使用 Storybook(或类似工具)进行 React 组件开发和展示
  • (最好有,但可选)ESlint 和eslint-config-react-app
  • (最好有,但可选)汇总以进行捆绑和压缩
  • (最好有,但可选)使用 Prettier 进行预提交

包结构

  • a- 实用程序库
  • b- React 组件库,依赖于a
  • c- 另一个 React 组件库,它依赖于a
  • stories- 展示bc包装的组件以及用于开发(初步计划,以后可能会更改)

我做了什么

yarn而不是npm,因为它支持workspaces链接交叉依赖项。

在没有版本的根目录中创建package.json,因为我们不会发布它,并且使用workspaces

"workspaces": [
  "packages/*"
]

勒纳

我们将使用lerna在所有包上运行命令并“提升”常见的依赖关系。

创造lerna.json

{
  "packages": ["packages/*"],
  "npmClient": "yarn",
  "useWorkspaces": true,
  "version": "0.0.1"
}

TypeScript

我们将使用typescript它来检查类型并将 TS 编译为所需的 JS 文件(ES5 或 ES2015、CommonJS 或 ES 模块)。

创建tsconfig.base.json。这是启用 monorepo 所需添加的内容:

{
  "include": ["packages/*/src"],
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "baseUrl": ".",
    "paths": {
      "@stereobooster/*": ["packages/*/src"]
    }
  }
}

创建packages/a/packages/b/packages/c/packages/stories/。添加tsconfig.json到每个:

{
  "include": ["src"],
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    // to override config from tsconfig.base.json
    "outDir": "lib",
    "rootDir": "src",
    // for references
    "baseUrl": "src"
  },
  // references required for monorepo to work
  "references": [{ "path": "../a" }]
}

package.jsonfor 包中bc添加:

"peerDependencies": {
  "@stereobooster/a": "0.0.1"
},
"devDependencies": {
  "@stereobooster/a": "*"
}

我们需要peerDependencies确保最终用户安装包( ab、 )时,它们将使用相同的包实例,否则,TypeScript 可能会报错类型不兼容(尤其是在使用继承和私有字段的情况下)。在 中我们指定了一个版本,但在 中我们不需要,因为我们只需要指示使用我们本地拥有的任何版本的包。capeerDependenciesdevDependenciesyarn

现在我们可以构建项目了。添加到 root package.json

"scripts": {
  "build": "lerna run build --stream --scope=@stereobooster/{a,b,c}"
}

package.json为了a,,bc

"scripts": {
  "build": "tsc"
}

问题 1:由于存在子依赖关系(包bc依赖项astories依赖于a,, ) bc我们需要相应地构建包,例如 first a、 secondbcthird stories。这就是为什么我们不能在 build 命令中使用--parallel标志 for 的原因。lerna

反应

安装@types/react@types/react-domreactreact-dom

添加tsconfig.base.json

"compilerOptions": {
  "lib": ["dom", "esnext"],
  "jsx": "react",
}

添加到子包package.json

"peerDependencies": {
  "react": "^16.8.0",
  "react-dom": "^16.8.0"
}

笑话

我们将使用jest来运行测试。安装@types/jest@types/react-test-rendererjestreact-test-renderer。添加jest.json。以打包 TypeScript:

{
  "moduleFileExtensions": ["ts", "tsx", "js"],
  "transform": {
    "\\.tsx?$": "ts-jest"
  },
  "testMatch": ["**/__tests__/**/*.test.*"],
  "globals": {
    "ts-jest": {
      "tsConfig": "tsconfig.base.json"
    }
  }
}

启用 monorepo:

"moduleNameMapper": {
  "@stereobooster/(.*)$": "<rootDir>/packages/$1"
}

我们还需要进行更改tsconfig.base.json,因为Jest 不支持 ES 模块

"compilerOptions": {
  "target": "es5",
  "module": "commonjs",
}

添加命令package.json

"scripts": {
  "pretest": "yarn build",
  "test": "jest --config=jest.json"
}

问题 2:我们将以 ES5 + CommonJS 的形式发布模块,这对于 React 包来说毫无意义,因为它需要某种捆绑器来使用包,例如 Parcel 或 Webpack。

问题 3:存在子依赖项,因此我们需要先构建所有包,然后才能运行测试。这就是为什么我们需要pretest脚本。

故事书

根据官方说明安装故事书。

我们需要以下东西package.json

"scripts": {
  "start": "start-storybook -p 8080",
  "build": "build-storybook -o dist"
},
"dependencies": {
  "@stereobooster/a": "*",
  "@stereobooster/b": "*",
  "@stereobooster/c": "*"
},
"devDependencies": {
  "@babel/core": "7.4.3",
  "@storybook/addon-info": "^5.0.11",
  "@storybook/addons": "5.0.6",
  "@storybook/core": "5.0.6",
  "@storybook/react": "5.0.6",
  "@types/storybook__addon-info": "^4.1.1",
  "@types/storybook__react": "4.0.1",
  "awesome-typescript-loader": "^5.2.1",
  "babel-loader": "8.0.5",
  "react-docgen-typescript-loader": "^3.1.0"
}

在中创建配置.storybook(同样基于官方说明)。现在我们可以在 for 包中/src/b创建故事b/src/cc

Storybook 会关注 的变化stories/src,但不会关注a/srcb/src、的变化c/src。我们需要使用 TypeScript 来监视其他包中的变化。

添加到package.jsonabc中:

"scripts": {
  "start": "tsc -w"
}

并到根部package.json

"scripts": {
  "prestart": "yarn build",
  "start": "lerna run start --stream --parallel"
}

现在开发人员可以运行yarn start(在一个终端)和yarn test --watch(在另一个终端)来获取开发环境 - 脚本将监视更改并重新加载。

问题 3:存在子依赖项,因此我们需要先构建所有包,然后才能运行启动脚本。这就是我们需要prestart脚本的原因。

问题 4:如果故事中有类型错误,它将显示在浏览器中,但如果或包中有类型错误a它将仅显示在终端中,这会破坏所有 DX,因为您不需要在编辑器和浏览器之间切换,而是需要切换到终端来检查是否有错误。bc

咆哮

所以我花了不少时间(半天?)才弄清楚所有细节,结果却令人失望。尤其是问题 2问题 4让我失望。更糟糕的是,我一行实际代码都没写出来。JS 生态系统竟然没有更重视“约定优于配置”的原则,这真是令人沮丧。我们需要在生态系统中引入更多 create-react-apps 和 Parcels。工具的构建应该考虑到交叉集成。

这个问题可能有解决方案,也许我需要尝试ava修复esm问题2,但我太失望了,竟然花了那么多时间去应对那些偶然出现的复杂性。因此,我决定暂停一下,写这篇文章。

照片由 Ruby Schmank 在 Unsplash 上拍摄

文章来源:https://dev.to/stereobooster/typescript-monorepo-for-react-project-3cpa
PREV
要成为一名软件架构师我需要知道什么?
NEXT
使用 Cypress 测试可访问性