依次输入项目名称: (monorepo-demo)
选择包管理器:(pnpm workspaces)
上述命令创建了一个基本的monorepo项目,包含了以下几个部分:
>>> Created a new Turborepo with the following:
apps
- apps/docs
- apps/web
packages
- packages/eslint-config-custom
- packages/tsconfig
- packages/uiapps/* 目录下是完整的应用的项目,可能会共享packages/*目录下的包
packages/* 目录下是一些共享的工具组件和配置包
每个 app 和 package 都是一个工作区 workspace,包含一个package.json文件。在 pnpm 项目根目录下,会有一个pnpm-workspace.yaml文件。该文件定义工作区的根目录,并允许在工作区中包含/排除目录。
packages:
- "apps/*"
- "packages/*"这是由 pnpm 提供的monorepo功能,这样,就能在每个工作区中相互引用依赖。比如,在 apps/web 中 引用packages/ui作为依赖项,只需在web下的 package.json文件中指定,添加包名:
{
// ...
"dependencies": {
"ui": "workspace:*"
}
}
// * 允许我们引用最新版本的依赖项。如果包的版本发生变化,它使我们无需更改依赖项的版本。第一次clone项目安装依赖直接执行pnpm install,会看到项目根目录和每个workspace中都会出现node_modules目录。
如果一个workspace单独安装/移除/升级包,执行下面的命令:
# 安装:
pnpm add <package> --filter <workspace>
# 移除:
pnpm uninstall <package> --filter <workspace>
# 升级
pnpm update <package> --filter <workspace>最后看下turborepo的配置文件turbo.json。
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
},
"lint": {},
"dev": {
"cache": false,
"persistent": true
}
}
}
上面配置中重点关注管道pipeline和dependsOn。
其中pipeline 表示项目的任务依赖关系,turbo 根据其中的配置安排、执行和缓存项目中任务的输出。
pipeline对象中的每个键都是一个可以被turbo run执行的任务名称。如果 turbo 在所有workspace中的package.json中找到具有相同键名的script,这个npm脚本就会在管道任务执行时运行。
dependsOn 表示该任务所依赖的任务列表。在其中某一项前添加^前缀是告诉turbo该管道任务的执行需要它的工作空间内的依赖先执行完成^前缀标识的任务。比如:上面配置中build任务只会在它的dependencies和devDependencies中依赖完成自己的build命令后执行一次。
假设一个场景:我们需要创建一个公共的React组件库,在组件库内部存在:1. ui 核心组件库,2. helper 辅助函数库,3. hooks 公共hooks库;同时还需要有个example的示例项目和 docs 的文档项目。接下来将基于之前的基础结构进行改造和配置。
首先,turborepo并没有固定的目录结构,可以按照自己的习惯搭建,比如我们想将eslint 和 typescript 配置从packages中拿出来放到一个专门的配置目录 configs下面, 只需移动文件完成后在pnpm-workspace.yaml中新增配置:
packages:
- "apps/*"
- "packages/*"
- "configs/*" # 新增清空packages里的所有子项目,开始创建新的package项目:
cd packages
mkdir helper & cd helper
pnpm init在package.json中,修改 name,为当前的包指定一个scope,该操作可选,防止在发布时包名已被占。
{
"name": "@42arch/helper",
}在根目录下安装tsup打包工具:
pnpm add tsup --filter @42arch/helper进入helper目录下新建tsup.config.ts配置文件:
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts'],
splitting: false,
sourcemap: true,
clean: true,
format: ['esm', 'cjs'],
dts: true
})在package.json中添加脚本命令:
{
"scripts": {
"build": "tsup",
"dev": "pnpm run build --watch"
}
}引入tsconfig配置,在package.json中添加:
{
"devDependencies": {
"tsconfig": "workspace:*"
}
}新建tsconfig.json文件:
{
"extends": "tsconfig/base.json",
"include": ["."],
"exclude": ["dist", "node_modules"],
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"]
}
}编写代码
新建src/index.ts文件:
export const add = (a: number, b: number) => {
return a + b;
}
export const multiply = (a: number, b: number) => {
return a * b;
}接下来,用类似的步骤处理 ui 组件库,不同的是,组件库需要安装react等依赖和jsx打包配置。
// package.json
{
"name": "@42arch/ui",
"version": "0.0.1",
"description": "",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist/**"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch"
},
"dependencies": {
"react": "^18.2.0",
"styled-components": "^6.1.0"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/react": "^18.2.30",
"@types/react-dom": "^18.2.14",
"tsconfig": "workspace:*",
"tsup": "^7.2.0"
}
}tsup.config.ts 配置
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.tsx'],
splitting: false,
sourcemap: true,
clean: true,
format: ['esm', 'cjs'],
dts: true,
external: ['react']
})删除apps中以后的项目,使用vite新建一个名为example新的react项目:
pnpm create vite@latest依次命名项目名example, 选择react + typescript模版,这样创建了一个react项目。
在example的package.json文件中添加 packages 目录下的包作为依赖:
{
"dependencies": {
// ...
"@42arch/helper": "workspace:*",
"@42arch/ui": "workspace:*"
},
}修改src/App.tsx:
import { Button } from '@42arch/ui'
import { add } from '@42arch/helper'
import { useState } from 'react'
import './App.css'
function App() {
const [count, setCount] = useState(1)
const onAdd = (count: number) => {
return add(count, 4)
}
return (
<>
<h1>Count: {count}</h1>
<div className="card">
<Button onClick={() => {
const currentCount = onAdd(count)
setCount(currentCount)
}}>Increment</Button>
</div>
</>
)
}
export default App开发模式:
在根目录下运行:
pnpm dev在命令行中可以看到 turbo 启动了所有workspace的 dev 脚本,打开example项目的默认链接http://127.0.0.1:5173/, 可以看到组件和辅助函数都正常执行了。在开发模式下修改packages下的相关代码,example的页面也会自动热更新。