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
- 展示b
和c
包装的组件以及用于开发(初步计划,以后可能会更改)
我做了什么
纱
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.json
for 包中b
并c
添加:
"peerDependencies": {
"@stereobooster/a": "0.0.1"
},
"devDependencies": {
"@stereobooster/a": "*"
}
我们需要peerDependencies
确保最终用户安装包( a
、b
、 )时,它们将使用相同的包实例,否则,TypeScript 可能会报错类型不兼容(尤其是在使用继承和私有字段的情况下)。在 中我们指定了一个版本,但在 中我们不需要,因为我们只需要指示使用我们本地拥有的任何版本的包。c
a
peerDependencies
devDependencies
yarn
现在我们可以构建项目了。添加到 root package.json
:
"scripts": {
"build": "lerna run build --stream --scope=@stereobooster/{a,b,c}"
}
并package.json
为了a
,,b
c
"scripts": {
"build": "tsc"
}
问题 1:由于存在子依赖关系(包b
和c
依赖项a
,stories
依赖于a
,, ) b
,c
我们需要相应地构建包,例如 first a
、 secondb
和c
third stories
。这就是为什么我们不能在 build 命令中使用--parallel
标志 for 的原因。lerna
反应
安装@types/react
,@types/react-dom
,react
,react-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-renderer
,jest
,react-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/c
。c
Storybook 会关注 的变化stories/src
,但不会关注a/src
、b/src
、的变化c/src
。我们需要使用 TypeScript 来监视其他包中的变化。
添加到package.json
、a
和b
包c
中:
"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,因为您不需要在编辑器和浏览器之间切换,而是需要切换到终端来检查是否有错误。b
c
咆哮
所以我花了不少时间(半天?)才弄清楚所有细节,结果却令人失望。尤其是问题 2和问题 4让我失望。更糟糕的是,我一行实际代码都没写出来。JS 生态系统竟然没有更重视“约定优于配置”的原则,这真是令人沮丧。我们需要在生态系统中引入更多 create-react-apps 和 Parcels。工具的构建应该考虑到交叉集成。
这个问题可能有解决方案,也许我需要尝试ava
修复esm
问题2,但我太失望了,竟然花了那么多时间去应对那些偶然出现的复杂性。因此,我决定暂停一下,写这篇文章。
文章来源:https://dev.to/stereobooster/typescript-monorepo-for-react-project-3cpa照片由 Ruby Schmank 在 Unsplash 上拍摄