关于前端:🚀Turborepo发布当月就激增-38k-Star这款超神的新兴-Monorepo-方案你不打算尝试下吗

1. 2022年了,扩大你代码库的最好形式 🚀🚀

1.1 什么是monorepo ?

Monorepo是一种项目管理形式,在Monorepo之前,代码仓库治理形式是 MultiRepo,即每个我的项目都对应着一个独自的代码仓库每个我的项目进行扩散治理
这就会导致许多弊病,例如可能每个我的项目的基建以及工具库都是差不多的,根底代码的反复复用问题等等…

Monorepo就是把多个我的项目放在一个仓库外面, 对于monorepo的文章曾经很多了,并且目前能够搭建Monorepo的工具也很多,例如

工具 简述
Bit 用于组件驱动开发的工具链
Turborepo 用于 JavaScript 和 TypeScript 代码库的高性能构建零碎。
Rush 一个可扩大的 web 单仓库管理器。
Nx 具备一流的 monorepo 反对和弱小集成的下一代构建零碎。
Lerna 用于治理蕴含多个软件包的我的项目

咱们明天为大家带来高性能的构建 Monorepo工具,Turborepo

2. TurboRepo

2.1 什么是Turborepo

TurboRepo 是构建Javascript,Typescript的monorepo高性能构建零碎,Turborepo形象出所有烦人的配置、脚本和工具,缩小我的项目配置的复杂性,能够让咱们专一于业务的开发

咱们通常在构建monorepo 时须要搭配适当的工具来扩大 monorepo,Turborepo利用先进的构建技术和思维来减速开发,构建了无需配置简单的工作,只须要咱们通过脚本和工具,即可疾速搭建您的Monorepo TurboRepo 反对应用Yarn Npm Pnpm,
咱们本次应用Pnpm 来带大家领略 Tuborepo的魅力!

2.2 TurboRepo的劣势

2.2.1 多任务并行处理

Turbo反对多个工作的并行运行,咱们在对多个子包,编译打包的过程中,turbo会同时进行多个工作的解决

在传统的 monorepo 工作运行器中,就像lerna或者yarn本人的内置workspaces run命令一样,每个我的项目的script生命周期脚本都以拓扑形式运行(这是“依赖优先”程序的数学术语)或独自并行运行。依据 monorepo 的依赖关系图,CPU 内核可能处于闲暇状态——这样就会节约贵重的工夫和资源。[](https://turborepo.org/docs/gl…)

什么是拓扑 ?
拓扑 Topological Order
是一种排序 拓扑排序是依赖优先的术语, 如果 A 依赖于 B,B 依赖于 C,则拓扑程序为 C、B、A。

比方一个较大的工程往往被划分成许多子工程,咱们把这些子工程称作流动(activity)。在整个工程中,有些子工程(流动)必须在其它无关子工程实现之后能力开始,也就是说,一个子工程的开始是以它的所有前序子工程的完结为先决条件的

为了能够理解turbo如许弱小,下图比拟了turbovslerna工作执行工夫线:

Turbo它可能无效地安顿工作相似于瀑布能够同时异步执行多个工作,而lerna一次只能执行一项工作 所以Turbo的 性能显而易见。

2.2.2 更快的增量构建

如果咱们的我的项目过大,构建多个子包会造成工夫和性能的节约,turborepo中的缓存机制 能够帮忙咱们记住构建内容 并且跳过曾经计算过的内容,优化打包效率。

2.2.3 云缓存

Turbo通过其近程缓存性能能够帮忙多人近程构建云缓存实现了更快的构建。

2.2.4 工作管道

用配置文件定义工作之间的关系,而后让Turborepo优化构建内容和工夫。

2.2.5 基于约定的配置

通过约定升高复杂性,只需几行JSON 即可配置整个我的项目依赖,执行脚本的程序构造。

2.2.6 浏览器中的配置文件

生成构建配置文件并将其导入Chrome或Edge以理解哪些工作破费的工夫最长。

3 Turbo 外围概念

3.1 管道 (Pipeline)

Turborepo为开发人员提供了一种以惯例形式显式指定工作关系的办法。

  1. 新来的开发人员能够查看Turborepopipeline并理解工作之间的关系。
  2. turbo能够应用这个显式申明来执行基于多核处理器的丰盛可用性的优化和打算执行。

turbo在定义monorepo的工作依赖关系图,咱们须要在根目录定义turbo.json 执行各种调度,输入,缓存依赖, 打包等性能

turbo.json位于turborepo我的项目根目录接下来实战的局部会率领大家从头创立一个我的项目

pipeline中的每一个key都指向咱们在package.json中定义的script脚本执行命令,并且在pipeline中的每一个key 都是能够被turbo run所执行 执行pipeline的脚本的名称。您能够应用其下方的键以及与缓存相干的一些其余选项来指定其依赖项。

在咱们执行turbo run ***命令的时候 turbo 会依据咱们定义的 Pipelines
里对命令的各种配置去对咱们的每个package中的package.json 中 对应的script执行脚本进行有序的执行和缓存输入的文件。

// turbo.json
{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    "build": {
      // A package's `build` script depends on that package's
      // dependencies' and devDependencies'
      // `build` tasks  being completed first
      // (the `^` symbol signifies `upstream`).
      "dependsOn": ["^build"],
      // note: output globs are relative to each package's `package.json`
      // (and not the monorepo root)
      "outputs": [".next/**"]
    },
    "test": {
      // A package's `test` script depends on that package's
      // own `build` script being completed first.
      "dependsOn": ["build"],
      "outputs": [],
      // A package's `test` script should only be rerun when
      // either a `.tsx` or `.ts` file has changed in `src` or `test` folders.
      "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"]
    },
    "lint": {
      // A package's `lint` script has no dependencies and
      // can be run whenever. It also has no filesystem outputs.
      "outputs": []
    },
    "deploy": {
      // A package's `deploy` script depends on the `build`,
      // `test`, and `lint` scripts of the same package
      // being completed. It also has no filesystem outputs.
      "dependsOn": ["build", "test", "lint"],
      "outputs": []
    }
  }
}

<br/>接下来咱们来解析每一个对象中的key到底是用来做什么的帮忙咱们更好的了解pipeline

3.1.1 DependsOn (依赖于)

比方以后咱们有三个子包package两个工具package一个playground用于测试其余两个package

  "pipeline": {
    "build": {
      "dependsOn": ["^build"]
    }
  }

3.1.2 惯例依赖

如果一个工作的执行,只依赖本人包其余的工作,那么能够把依赖的工作放在dependsOn数组里

{
    "turbo": {
        "pipeline": {
            "deploy": {
                "dependsOn": ["build", "test", "lint"]           
            } 
        }    
    }
}

3.1.3 拓扑依赖

能够通过^符号来显式申明该工作具备拓扑依赖性,须要依赖的包执行完相应的工作后能力开始执行本人的工作

{
    "turbo": {
        "pipeline": {
            "build": {
                "dependsOn": ["^build"],           
            }
        }    
    }
}

因为playground依赖于@relaxed/utils和@relaxed/hook,所以咱们以后playground子包的build存在依赖关系,依据build的dependsOn配置,会先执行依赖项的build命令,也就是@relaxed/utils和@relaxed/hook的build命令,依赖项执行完后才会执行playground的build命令。
如果咱们不增加"dependsOn": ["^build"]数组中的‘^’那么就代表咱们以后只须要执行咱们本人的build命令

dependsOn 示意以后命令所依赖的命令,^ 示意 dependencies 和 devDependencies 的所有依赖都执行完 build,才执行 build

3.1.4 空依赖

如果一个工作的dependsOn为[] 或者不申明这个属性,那么表明这个工作能够在任意工夫被执行

3.1.5 Output

outputs 表示命令执行输入的文件缓存目录

默认值为["dist/**", "build/**"]

咱们还能够通过传递一个空数组用来通知turbo工作是一个副作用,这样咱们不会输出任何文件

"pipeline": {
  "build": {
    // "Cache all files emitted to package's dist/** or .next
    // directories by a `build` task"
    "outputs": ["dist/**", ".next/**"],
    "dependsOn": ["^build"]
  },
 }

3.1.6 Cache

cache 示意是否缓存,通常咱们执行 dev 命令的时候会联合 watch 模式,所以咱们个别在我的项目启动模式下不须要开启 turbo 缓存机制

{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    "dev": {
      "cache": false
    }
  }
}

3.1.7 Input

默认为[]。通知turbo在确定特定工作的包是否已更改时要思考的文件集。将其设置为文件输出地址将导致仅当与这些真正子包中须要配置输出匹配的文件产生更改时才从新运行工作。例如,如果您想跳过运行测试,除非源文件产生更改,这会很有帮忙。

指定[]意味着工作在任何文件产生更改时从新运行。

{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
   "test": {
      // A package's `test` task should only be rerun when
      // either a `.tsx` or `.ts` file has changed.
      "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
    }
  }
}

3.1.8 OutputMode

outputMode代表输入的模式类型是字符串

type
full
new-only
hash-only
none

full 也是默认值代表 显示工作的整个输入

hash-only仅显示计算的工作哈希

new-only显示缓存未命中的残缺输入和缓存命中的计算哈希值。意思就是返回带有hash的日志并且当如果有未命中的子包缓存或者打包谬误导致缓存未命中再次打包时会输入上一次缓存未命中的的子包残缺工作输入日志

none 应用“none”暗藏工作输入。意思就是不会在控制台中打印咱们拓扑程序以及打包输出的日志,然而仍然会正确执行 build 命令

{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputMode": "new-only"
    },
    "test": {
      "outputs": [],
      "dependsOn": ["build"],
    },
  }
}

3.2 过滤包 (Filtering Packages)

turbo中第二个外围概念filtering packages容许您将工作仅定位到你想要去操作的包。Turborepo反对一个相似pnpm--filter过滤命令然而他不同于传统pnpm –filter 命令 咱们须要在 –filter前面增加一个等号确定须要对过滤的包进行独自的指令,容许您抉择将充当“入口点”的包进入 monorepo 的包/工作图中,您能够通过包名称、包目录、包是否蕴含依赖项/依赖项以及 git 历史记录的更改来过滤您的我的项目。
语法 pnpm build --filter=@relaxed/utils

过滤器语法还有很多场景这里就不一一展现具体可见turbo 过滤包语法

3.3 缓存 (Caching)

Turborepo 查看文件内容变动时,会依据内容生成Hash来比照
turbo它能够缓存收回的文件和以前运行的命令的日志。它这样做是为了跳过曾经实现的工作,从而节俭大量工夫。
例如咱们执行三个子包的build命令咱们只须要执行turbo run build

turbo在每次打包的过程中会在每个build 的子包中生成 log 文件在下一次打包的时候
缓存构建内容,并跳过曾经计算过的内容,判断是否须要从新构建

在咱们在输出build命令的时候首先进入到pipeline中查看是否敞开缓存,而后通过以后子包中的turbo的log文件来hash 比照以后代码是否有扭转 如果没有产生扭转那么就 跳过构建

强制笼罩缓存

相同,如果要强制turbo从新执行之前缓存的工作,请增加--force标记:

#在所有包和利用中运行`Build`N脚本
#疏忽缓存命中。
turbo run build --force

3.4 近程缓存 (Remote Caching)

Turborepo速度的一个次要要害🔑 是它既懈怠又高效——它做的工作量尽可能少,并且它试图从不重做以前曾经实现的工作。 这是官网对turbo 近程缓存的一个总结
失常状况下 咱们应用turbo 的时候在构建过程中只能将咱们的工作缓存在本地零碎上turbo反对了一种多人开发共享缓存的模式

开发人员团队和/或继续集成 (CI) 零碎应用近程缓存来共享构建输入。如果您的构建是可重现的,那么一台机器的输入能够平安地在另一台机器上重复使用,这能够显着放慢构建速度。

如果要将本地 turborepo 链接到近程缓存,请首先应用 Vercel 帐户对 Turborepo CLI 进行身份验证:

npx turbo login

接下来,您能够通过运行以下命令将您的 turborepo 链接到近程缓存:

npx turbo link

启用后,对以后缓存的包或应用程序进行一些更改,并应用turbo run. 您的缓存工件当初将存储在本地您的近程缓存中。

要验证,请应用以下命令删除本地Turborepo 缓存:

rm -rf ./node_modules/.cache/turbo

而后再次运行雷同的构建。如果一切正常,turbo则不应在本地执行工作,而是从近程缓存下载日志和工件并将它们重放给您。

4. 命令行的应用

4.1 选项语法

options选项语法能够通过 turbo 应用不同的形式来进行传递

  • 传递值的选项
  --<option>=<value>
  // like
  pnpm build --filter=vue-devui
  
  pnpm build --filter=@relaxed/hook

4.2 全局参数 (常用命令)

4.2.1 --continue

默认为false.该标记通知turbo是否在呈现谬误(即工作的非零退出代码)时继续执行。默认状况下,指定--parallel标记将主动设置--continue为,true除非明确设置为false。当--continueis时trueturbo将以执行期间遇到的最高退出代码值退出。

turbo run build --continue

4.2.2 --parallel

默认false。脚本程序并行运行命令并疏忽依赖关系图。这对于应用实时从新加载进行开发很有用。例如咱们启动vite我的项目的时候咱们就须要疏忽其余可能呈现的dependsOn依赖关系

  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", "build/**"],
      "outputMode": "new-only"
    },
    "lint": {
      "outputs": []
    },
    "dev": {
      "cache": false,
      //
      "dependsOn": ["^build"]
    }
  }
}

因为咱们在pipeline中设置了dependson的build命令依赖优先的问题所以咱们能够指定--parallel并行执行并且阻止默认依赖 build 指令

turbo run lint --parallel --no-cache
turbo run dev --parallel --no-cache

4.2.3 --filter

指定包/应用程序、目录和 git 提交的组合作为执行的入口点。

能够组合多个过滤器来抉择不同的指标集。此外,过滤器还能够排除指标。匹配任何过滤器且未明确排除的指标将在最终入口点抉择中。

无关--filter标记和过滤的更多详细信息,请参阅咱们文档中的专用页面

turbo run build --filter=my-pkg
turbo run test --filter=...^@scope/my-lib
turbo run build --filter=./apps/* --filter=!./apps/admin

4.2.4 --force

疏忽现有的缓存工件并强制从新执行所有工作(笼罩重叠的工件)

turbo run build --force

4.2.5 --no-cache

默认false。不要缓存工作的后果。next dev这对于诸如or之类的监督命令很有用react-scripts start

turbo run build --no-cache
turbo run dev --parallel --no-cache

4.2.6 --only

默认false。将执行限度为指定package中的指定的工作。这与默认状况下如何lernapnpm运行工作的形式十分类似。如果咱们指定了 在依赖前须要执行build命令 然而如果咱们设置 --only 将默认排除 build命令

鉴于此管道turbo.json

{
  "$schema": "https://turborepo.org/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": [
        "^build"
      ]
    },
    "test": {
      "dependsOn": [
        "^build"
      ]
    }
  }
}
turbo run test --only

将只会执行每个包中的test工作。它不会build

本文介绍几种罕用的 turbo 命令, 当然 turbo 提供了许多在命令行中应用的指令, 具体能够查看

命令行参考

5. Demo实战✨✨

疾速开始

咱们能够通过现有的monorepo 革新 也能够 间接创立turbo 我的项目,

turbo 的另一大特色就是革新您现有的monorepo也很简略, 只须要装置 turbo 依赖 根目录增加 turbo.json 所有就尽在把握了

咱们本次体验一下 应用 turbo 命令行创立我的项目, demo仅展现turbo和monorepo的联合应用形式,具体构建形式,以及残缺我的项目 能够查看github模板

咱们在命令行输出

npx create-turbo@latest

创立咱们的工程名

>>> TURBOREPO

>>> Welcome to Turborepo! Let's get you set up with a new codebase.

? Where would you like to create your turborepo? (./my-turborepo)

咱们举荐应用Pnpm 构建

? Which package manager do you want to use?
  npm
> pnpm
  yarn

turbo 会主动依据咱们抉择的包管理器为咱们创立绝对应的我的项目
而后咱们进入我的项目

为了更好的体验咱们应用vite更直观的展现

咱们在根目录命令行输出npm init vue@3创立一个名为playground的vue我的项目

而后在pnpm-workspace.yaml中增加一个playground字段代表把playground包增加到pnpm monorepo的治理

packages:
  - 'packages/*'
  - 'playground'

紧接着在 packages 外面新建 @relaxed/hook, @relaxed/utils, @relaxed/tsconfig 文件夹前两个作为测试工具库 剩下作为咱们 tsconfig 共享配置库

  • @relaxed/utils

根目录新建 index.ts

export function Accumulation(...value: any[]) {
  return value.reduce((t, v) => t + v, 0)
}

export function Multiplication(...value: any[]) {
  return value.reduce((t, v) => t * v, 1)
}

而后咱们在package.json为了测试咱们应用tsc打包新增build命令增加tsc

  • @relaxed/hook同理

根目录新建 index.ts

import { ref } from 'vue'
export default function useBoolean(initValue = false) {
  const bool = ref(initValue)

  function setBool(value: boolean) {
    bool.value = value
  }
  function setTrue() {
    setBool(true)
  }
  function setFalse() {
    setBool(false)
  }
  function toggle() {
    setBool(!bool.value)
  }
  return {
    bool,
    setBool,
    setTrue,
    setFalse,
    toggle
  } 
}
  • @relaxed/tsconfig

在tsconfig目录下新建base.json

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Default",
  "compilerOptions": {
    "composite": false,
    "declaration": true,
    "declarationMap": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "inlineSources": false,
    "isolatedModules": true,
    "moduleResolution": "node",
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "preserveWatchOutput": true,
    "skipLibCheck": true,
    "strict": true
  },
  "exclude": ["node_modules"]
}

package.json中

{
  "name": "@relaxed/tsconfig",
  "version": "0.0.0",
  "private": true,
  "files": [
    "base.json"
  ]
}

首先咱们须要把tsconfig共享模块增加到两个工具库中

能够间接在@relaxed/hook @relaxed/utils的package.json中增加

  "devDependencies": {
    "@relaxed/tsconfig": "workspace:*",
    "vue": "^3.2.37"
  }

或者

pnpm add @relaxed/tsconfig --filter=@relaxed/hook

pnpm add @relaxed/tsconfig --filter=@relaxed/utils

而后咱们如何在playground中应用呢?

第一种办法:在 playground中的package中 新增 两个工具子包的依赖项

  "dependencies": {
    "@relaxed/hook": "workspace:1.0.0",
    "@relaxed/utils": "workspace:*"
  },

而后咱们执行
pnpm install

第二种办法: 咱们间接通过命令装置

pnpm add @relaxed/utils  @relaxed/hook --filter=playground

*当前工作目录下的代表最新版本
而后咱们执行 pnpm dev turbo先在pipeline 中寻找 dev指令 而后 依据你是否配置了管道中的执行工作,最初执行子包中的dev命令,就代表执行playground中的dev命令

<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import HelloWorld from './components/HelloWorld.vue'
import { Accumulation, Multiplication } from '@relaxed/utils'
import useBoolean from '@relaxed/hook'
const { bool, setBool, setTrue, setFalse, toggle } = useBoolean(false)
</script>

<template>
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
    <div style="margin: 10px 0">
      <d-tag type="warning">
        {{ bool }}
      </d-tag>
    </div>
    <d-button type="tertiary" @click="setFalse"> {{ bool }} </d-button>
    <d-button type="primary" @click="setTrue"> {{ bool }} </d-button>
    <d-button type="info" @click="toggle"> Toogle </d-button>
  </div>
  <HelloWorld msg="Vite + Vue" />
</template>


而后到了构建环节,咱们执行 pnpm build 这时候 咱们须要在 turbo.json 中 pipeline 中的 build 设置

"dependsOn": ["^build"],

只有加了 ^ 这样 咱们能力 先去 执行 依赖 的 三个子包的build 命令 因为 咱们playground 中 依赖了 其它的子包 所以咱们必须 加上 ^ 否则turbo 会阻止build 因为咱们存在子包的相互依赖,
所以 Turborepo 能够有序的帮忙咱们进行治理我的项目之间的逻辑

4. 总结

Turborepo 能够帮忙咱们更好的治理Monorepo我的项目, 凭借本身优良的任务调度治理和增量构建缓存等等, 都能够帮忙咱们在将来解决monorepo目前存在的一些问题,进而进步咱们的开发效率,以及晋升整个我的项目在构建等方面的性能。

5. 参考

  • Turborepo
  • Pnpm

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理