WangShuXian6 / blog

FE-BLOG
https://wangshuxian6.github.io/blog/
MIT License
45 stars 10 forks source link

使用JavaScript和TypeScript构建可扩展的Monorepo项目 Building a Scalable Monorepo with JavaScript and TypeScript [进行中] #202

Open WangShuXian6 opened 1 week ago

WangShuXian6 commented 1 week ago

使用JavaScript和TypeScript构建可扩展的Monorepo项目 Building a Scalable Monorepo with JavaScript and TypeScript

01 - Introduction 01 - Scaling a codebase with a monorepo 02 - What you should know

02 - 1. Understanding Monorepos 01 - Working with a monorepo architecture 02 - Setting up the dev environment 03 - Creating a package 04 - Creating multiple packages 05 - Recursively running multiple packages

03 - 2. Configuring pnpm Workspaces 01 - Referencing local packages 02 - Challenge Sharing code across projects 03 - Solution Sharing code across projects 04 - Linking to local file paths

04 - 3. Exploring Turborepo 01 - Introducing Turborepo 02 - Installing external dependencies 03 - Creating an internal package 04 - Setting an internal dependency 05 - Writing a tsconfig file 06 - Running the build

05 - 4. Turborepo Integration 01 - Improving performance with build caching 02 - Utilizing remote caching 03 - Understanding developer mode features 04 - Handling environment variables

06 - Conclusion 01 - Next steps


01 - 引言
01 - 使用单一代码库(monorepo)扩展代码库
02 - 你需要了解的内容


02 - 1. 了解单一代码库(Monorepos)
01 - 使用单一代码库架构
02 - 设置开发环境
03 - 创建一个包
04 - 创建多个包
05 - 递归运行多个包


03 - 2. 配置 pnpm 工作区
01 - 引用本地包
02 - 挑战:在项目间共享代码
03 - 解决方案:在项目间共享代码
04 - 链接到本地文件路径


04 - 3. 探索 Turborepo
01 - 介绍 Turborepo
02 - 安装外部依赖项
03 - 创建内部包
04 - 设置内部依赖项
05 - 编写 tsconfig 文件
06 - 运行构建


05 - 4. Turborepo 集成
01 - 通过构建缓存提高性能
02 - 使用远程缓存
03 - 了解开发者模式功能
04 - 处理环境变量


06 - 总结
01 - 下一步

WangShuXian6 commented 1 week ago

01 - 引言

01 - 使用单一代码库(monorepo)扩展代码库

如果你曾参与过任何规模的 JavaScript 项目,你可能都遇到过管理依赖项和持续集成过程的压力。随着项目的扩大和团队中工程师的增加,这些问题会变得更加复杂。这时,单一代码库(Monorepo)架构便能发挥作用。通过集中代码,你可以简化开发过程并简化项目管理。在本课程中,我们将探索如何使用 JavaScript 和 TypeScript 构建可扩展的单一代码库。我们将从 pnpm 和 pnpm 工作区的介绍开始,随后探讨如何使用 Turborepo 管理大型项目,以及如何配置这些项目以实现最高效率和性能。那么,让我们开始吧,构建我们自己的可扩展单一代码库!

02 - 你需要了解的内容

图片

在进入课程之前,我们先聊一聊在学习单一代码库(monorepos)之前需要具备的知识。本课程中我们会用到一些 JavaScript 和 TypeScript。你不需要对这两者非常精通,但这是我们将使用的环境。我们还希望你对 Node.js 和包管理器有一定的了解,比如安装文件的基本操作等。我们会介绍 pnpm 作为管理这些包的工具,所以如果你对此不熟悉也没关系。

拥有一个代码编辑器也会很有帮助。我将使用 VS Code,但你可以选择任何你喜欢的编辑器,比如 Sublime Text 或 Atom 等。你只需要一个可以打开和运行文件的地方。好了,这就是我们开始所需要的一切。接下来的视频中,我们将一起开始构建项目。

WangShuXian6 commented 1 week ago

02 - 1. 了解单一代码库(Monorepos)

01 - 使用单一代码库架构

在我们实际构建自己的单一代码库(monorepo)之前,先聊聊为什么你可能会选择这种方式。

首先,让我们谈谈多代码库(polyrepo)方法。

图片

在多代码库中,每个应用或服务都保存在其各自的独立仓库中。 图片

例如,我们的主仓库在这里是仓库 1,并且会有单独的服务或库,比如身份验证系统、设计系统、组件库、表单等,所有这些都会保存在各自的仓库中。然后,这些共享组件会发布到像 npm 这样的包管理器,以便其他项目可以引用它们。因此,从仓库 1 中,我们可以通过 npm 安装组件库,并在组件库更新时将其更新到主应用中。

现在,来看一下单一代码库(monorepo)方法。

图片

单一代码库是一个集中存放多个项目代码的仓库,通常包括相关的 JavaScript 或 TypeScript 包。这些项目既独立又互相连接,范围可以从底层的工具到高级的 Web 用户界面。这里的唯一不同之处在于我们如何存放代码。组织成小型、独立、自包含的组件,并定义良好的边界和接口依旧是一个好主意。

相比多代码库,单一代码库有几个独特的优势:

  1. 统一代码库:所有项目和组件都在一个地方,这使得代码库的管理和访问更加便捷,简化了组织中的导航和代码搜索。

  2. 共享依赖:共享依赖可以确保一致性并减少重复,从而实现更高效的构建和更小的总体依赖负担。

  3. 一致的工具链:在同一个框架下进行测试、构建和部署,使所有项目使用相同的工具,便于在各项目间实现一致性。

  4. 提升协作:所有代码集中在一个地方,开发人员可以更轻松地在团队和项目间进行协作,有助于实现更加紧密的产品开发体验。

这就是我们将要构建的目标:一个能容纳多个不同项目的单一代码库。

02 - 设置开发环境

在我们开始之前,确保已安装 Node.js。我们将检查其版本,通过运行 node -v 可以查看版本,我们需要的是版本 20 或更高。如果出现错误或版本低于 18,可以访问 nodejs.org 并按照安装说明进行设置。

在本课程中,我们还会使用一个叫做 pnpm 的工具。pnpm 是一个快速、高效的 JavaScript 包管理器,是 npm 的替代方案,并且更适合单一代码库(monorepo)工具和更高效的包管理。因此,我们需要确保已安装 pnpm。

https://pnpm.io/ 在 Windows 上可以使用 IWR 命令进行安装,如果是在 Docker 容器中,也有多种安装方式。你可以通过 npm、Homebrew 等工具进行安装。这里我将使用 Homebrew 进行安装。回到终端,输入以下命令:

brew install pnpm

这将进行安装,以便我们在所有不同的项目中全局使用 pnpm。安装过程中可能会提示我们需要通过 brew 安装 Node.js,这没关系,可以继续执行:

brew install node

安装完成后,再次运行 brew install pnpm。如果你已经有旧版本的 pnpm,也可以重新安装它。接下来,运行以下命令来检查 pnpm 的版本:

pnpm --version

输出应为 8.9.0 或更高版本,9.3、9.6 等都是可以的。

总结一下,pnpm 是 npm 的替代工具,更快速、高效的包管理器。我们将使用它来设置我们的单一代码库(monorepo)。

03 - 创建一个包

图片

让我们使用 pnpm 创建项目。我已在 VS Code 中打开了终端窗口,并确保导航到 01_03b 目录下。在项目根目录下运行 pnpm init,这会创建一个带有默认值的 package.json 文件。

接着,创建一个名为 packages 的目录。然后在其中再创建一个名为 snowtooth-one 的目录,它将成为单一代码库中的一个应用。导航到 packages/snowtooth-one 目录,并在这里运行 pnpm init,这会创建该应用的 package.json 文件。

snowtooth-one 文件夹中创建一个 index.js 文件,编写以下代码:

function calculateElevation(feet) {
    const meters = feet * 0.3048;
    return Math.round(meters);
}

const verticalFeet = 12000;
console.log(`The resort has an elevation of ${calculateElevation(verticalFeet)} meters.`);

接下来,打开 snowtooth-one 文件夹内的 package.json 文件,替换其中的 scripts 部分,加上 start 命令:

"scripts": {
    "start": "node index.js"
}

这样,每当我运行 pnpm run start 时,它将会启动 Node.js 运行 index.js 文件。现在,我们在终端中运行 pnpm run start,可以看到输出的海拔高度以米为单位显示出来。

接下来,我们希望利用单一代码库结构。进入项目根目录 01_03bpackage.json 文件,添加以下命令:

"scripts": {
    "snowtooth-one:start": "pnpm -r --filter @snowtooth-mountain/snowtooth-one run start"
}

其中,-r 参数让 pnpm 在单一代码库中的每个包中递归运行命令,--filter 参数用于指定包名 @snowtooth-mountain/snowtooth-one

在根目录 01_03b 中,将其名字替换为 snowtooth-mountain。现在回到根目录运行:

pnpm run snowtooth-one:start

该命令将进入 snowtooth-one 文件夹并运行 node index.js,结果会显示我们的度假村的海拔高度为 3658 米。

04 - 创建多个包

图片

现在让我们在项目中创建另一个包。首先,复制 snowtooth-one 文件夹,并将其粘贴到 packages 目录中。复制的文件夹将命名为 snowtooth-one copy,我们将其重命名为 snowtooth-two

接着,将原有的 calculateElevation 函数替换为新的函数 calculateHikingTime。新函数接收 trailLengthavgSpeed 参数,用于计算徒步时间。代码如下:

function calculateHikingTime(trailLength, avgSpeed) {
    return trailLength / avgSpeed;
}

const length = 10;
const avgSpeed = 3;
console.log(`It would take approximately ${Math.round(calculateHikingTime(length, avgSpeed))} hours to hike the trail.`);

将以上代码粘贴到 snowtooth-two 文件夹中的 index.js 文件中。然后,打开 snowtooth-two 文件夹中的 package.json 文件,替换 scripts 部分,以便可以使用 start 命令:

"scripts": {
    "start": "node index.js"
}

在项目根目录的 package.json 文件中,添加以下命令以便可以在根目录中运行 snowtooth-two 包的脚本:

"scripts": {
    "snowtooth-one:start": "pnpm -r --filter @snowtooth-mountain/snowtooth-one run start",
    "snowtooth-two:start": "pnpm -r --filter @snowtooth-mountain/snowtooth-two run start"
}

确保在适当的目录结构下,并在终端中运行以下命令来启动 snowtooth-two 包:

pnpm run snowtooth-two:start

这将输出徒步所需的时间,大约为 3 小时。现在,我们有了两个不同的包可以正常运行。在接下来的视频中,我们将探讨如何创建 pnpm 工作区以管理这两个包。

05 - 递归运行多个包

现在我们有了两个独立的包,每个包都有自己的 package.jsonstart 脚本。接下来,我们将创建一个新脚本,用于同时运行这两个包。

目前,我们可以在根目录运行其中一个脚本,例如:

pnpm run snowtooth-one:start

或者:

pnpm run snowtooth-two:start

但为了方便,我们希望能够同时运行这两个脚本。为此,我们将创建一个可以同时运行两个包的 start 脚本。具体操作如下:

  1. 在根目录的 package.json 文件中,添加以下脚本:

    "scripts": {
       "start": "pnpm -r run start"
    }

    这里的 -r 参数表示递归运行,即会在单一代码库(monorepo)中的每个包内运行指定的命令。

  2. 在项目根目录下,创建一个名为 pnpm-workspace.yaml 的新文件,用于定义工作区。内容如下: 图片

    packages:
     - "packages/**"

    这段配置将告诉 pnpm 在 packages 文件夹中查找所有嵌套的包。

  3. 完成以上步骤后,可以在根目录运行以下命令:

    pnpm run start

    这会同时运行我们两个包的 start 脚本。输出会显示当前在两个工作区项目中运行脚本。

通过这种方式,我们可以递归运行 monorepo 中的命令,从而更方便地管理多个包,将它们视为一个整体来操作。

WangShuXian6 commented 1 week ago

03 - 2. 配置 pnpm 工作区

01 - 引用本地包

现在我们有两个不同的包,各自包含了可以在项目中跨包使用的辅助函数。在 snowtooth-one 包中有 calculateElevation 函数,而在 snowtooth-two 包中有 calculateHikingTime 函数。假设我们想在 snowtooth-one 中使用 calculateHikingTime 函数,可以通过以下步骤实现跨包导入。

步骤 1:将每个包设为模块

在每个包的 package.json 中添加以下内容,以将其声明为 ECMAScript 模块:

"type": "module"

步骤 2:导出 snowtooth-two 中的函数

snowtooth-two 中确保导出 calculateHikingTime 函数:

export function calculateHikingTime(trailLength, avgSpeed) {
    return trailLength / avgSpeed;
}

步骤 3:在 snowtooth-one 中导入函数

接下来,在 snowtooth-one 中的代码中导入 calculateHikingTime 函数:

import { calculateHikingTime } from "@snowtooth-mountain/snowtooth-two";

步骤 4:设置依赖关系

snowtooth-onepackage.json 文件中,添加依赖项:

"dependencies": {
    "@snowtooth-mountain/snowtooth-two": "workspace:*"
}

"workspace:*" 告诉 pnpm 查找在 pnpm-workspace.yaml 文件中定义的工作区路径下的依赖包。

步骤 5:安装依赖

导航到 snowtooth-one 目录并运行以下命令以安装依赖:

pnpm install

然后回到根目录,并运行以下命令以启动项目:

pnpm run start

结果

运行结果会在控制台显示两个包的输出。如果 calculateHikingTime 函数成功导入并运行,则表示跨包引用已正确配置并生效。这就是在 Monorepo 结构中跨包使用代码的流程!

02 - 挑战:在项目间共享代码

现在,当我们导航到 02_02b 并运行 pnpm start 时,我们可以成功导入 calculateHikingTime() 函数,并将其导出到另一个包中。

接下来,我们将完成相反的操作:把 calculateElevation 函数导出并在 snowtooth-two 中导入。具体任务如下:

  1. 导出 calculateElevation 函数
    snowtooth-oneindex.js 文件中,确保 calculateElevation 函数被导出:

    export function calculateElevation(feet) {
       const meters = feet * 0.3048;
       return Math.round(meters);
    }
  2. snowtooth-two 中导入该函数
    snowtooth-twoindex.js 文件中,导入 calculateElevation 函数:

    import { calculateElevation } from "@snowtooth-mountain/snowtooth-one";
  3. 运行 pnpm run start
    当运行 pnpm run start 时,确保在 snowtooth-twoindex.js 文件中调用 calculateElevation,以便我们可以看到两个不同的 console.log 输出。这样在运行 snowtooth-two 的索引文件时,应该能看到两个函数的日志输出。

试试看!在下一个视频中,我将展示解决方案。

03 - 解决方案:在项目间共享代码

现在让我们实现跨包的反向导入。我们已将 calculateElevation 函数从 snowtooth-one 导出,接下来要在 snowtooth-two 中导入它。步骤如下:

步骤 1:导出函数

snowtooth-one 中,确保 calculateElevation 函数已被导出:

export function calculateElevation(feet) {
    const meters = feet * 0.3048;
    return Math.round(meters);
}

步骤 2:在 snowtooth-two 中导入

打开 snowtooth-twoindex.js 文件,并添加导入语句:

import { calculateElevation } from "@snowtooth-mountain/snowtooth-one";

然后在代码中调用 calculateElevation 函数,以便能够使用它。

步骤 3:添加依赖项

snowtooth-twopackage.json 文件中,确保添加对 @snowtooth-mountain/snowtooth-one 的依赖:

"dependencies": {
    "@snowtooth-mountain/snowtooth-one": "workspace:*"
}

这表示依赖来自于工作区内定义的包。

步骤 4:安装依赖

导航到 snowtooth-two 目录,运行以下命令安装依赖:

pnpm install

步骤 5:运行项目

返回项目根目录,并运行以下命令:

pnpm start

此时,终端应显示两个包的日志输出,分别调用了 calculateElevationcalculateHikingTime 函数。这些步骤使得我们可以在不同项目和包中重用代码,从而提高代码复用性并确保应用的高性能。

04 - 链接到本地文件路径

另一种引用包的方法是通过文件路径进行引用,而不是使用 workspace:*。我们可以通过指定路径来引用 snowtooth-one 包。以下是具体步骤:

步骤 1:删除 node_modules 文件夹

首先,如果 snowtooth-two 中存在 node_modules 文件夹,请确保将其删除,以便清除现有依赖项。

步骤 2:通过路径引用 snowtooth-one

snowtooth-twopackage.json 文件中,将 @snowtooth-mountain/snowtooth-one 的依赖路径替换为相对路径,例如:

"dependencies": {
    "@snowtooth-mountain/snowtooth-one": "file:../snowtooth-one"
}

这将直接指向本地 snowtooth-one 包的文件路径。

步骤 3:安装依赖项

保存更改后,在 snowtooth-two 目录中运行以下命令:

pnpm install

这会安装该依赖项并确保 snowtooth-two 正确引用了 snowtooth-one 包。

测试错误处理

为了验证路径引用是否正常工作,可以尝试使用一个不存在的路径,例如将依赖路径更改为 Pancake,然后再次运行 pnpm install。这会提示警告,因为引用路径无效。此时运行 pnpm start,你会看到一系列错误,因为路径找不到对应的文件。

修复并重试

将路径修复为正确的 ../snowtooth-one,然后再次删除 node_modules 文件夹并重新安装依赖项:

pnpm install

最后,在根目录运行:

pnpm start

现在应该能够正常启动。这是一种引用本地文件的方式。选择这种方法时,只需确保路径正确指向所需的文件即可。

WangShuXian6 commented 1 week ago

04 - 3. 探索 Turborepo

01 - 介绍 Turborepo

https://turbo.build/

一个很好的开发工具是 Turbo。你可能听说过它的其他名称,例如 Turbo RepoTurbo Pack。Turbo Repo 是一个用于 JavaScript 和 TypeScript 项目的构建系统,而 Turbo Pack 是一个 Beta 项目,专门用于服务器组件和 TypeScript 代码库。

Turbo Repo 和 Turbo Pack 都是用 Rust 编写的,目标是实现高性能,确保项目构建快速。它们的默认结构适用于单一代码库(monorepo)。如果你正在进行一个 monorepo 项目,并希望使用 Vercel 的开发工具(例如 Next.js、AI SDK 等),可以考虑使用 Turbo。

安装 Turbo

我们将在项目中安装 Turbo,确保处于正确的目录 0301B 下,运行以下命令:

npx create-turbo@latest

或者 生成带Vite的项目目录 https://turbo.build/repo/docs/guides/frameworks/vite

npx create-turbo@latest -e with-vite

这个命令会创建一个新项目。我们将其命名为 Snow Tooth,选择 PNPM 作为包管理器,这样就会生成一个 Snow Tooth 文件夹。

目录结构

新创建的项目包含以下几个文件夹和文件:

这种结构是启动项目的快捷方式。在下一个视频中,我们将探讨如何将现有文件结构与 PNPMTurbo Repo 结合使用。 图片

snowtooth\pnpm-workspace.yaml

packages:
  - "apps/*"
  - "packages/*"

02 - 安装外部依赖项

在上一个视频中,我们生成了一个包含多个应用和内部包的项目,包括一个文档站点和一个网站应用,以及 eslint-configtypescript-config 等包。需要注意的是,每个应用和包都拥有独立的 package.json 文件,而整个项目也有一个全局的 package.json 文件。我们在不同的包中安装依赖项,遵循一个基本原则:在使用依赖的包中直接安装它们,而不是安装在全局项目中。

示例:安装依赖 Jest

假设我们想在 docs 项目中添加 Jest(一个 React 测试库):

  1. 确保当前位于 docs 目录下,运行以下命令将 Jest 添加为开发依赖:

    pnpm install jest --save-dev

    这样 Jest 就被安装到 docs 项目的 package.json 中,作为开发依赖。

  2. 递归安装依赖: 如果我们希望在多个项目中安装 Jest,可以回到根目录并使用递归安装。示例如下:

    pnpm install jest --save-dev --recursive --filter=web --filter=@repo/ui

    --recursive表示浏览嵌套文件夹

    该命令将 Jest 安装到 web@repo/ui 目录下的 package.json 文件中,作为开发依赖。--filter 参数指定了具体安装的文件夹或包。

总结

这种安装依赖的方法遵循了最佳实践,即在使用的地方安装依赖。这有助于保持依赖的清晰性、提高缓存能力,并在大型项目中更灵活地管理依赖。

在此示例中,通过递归安装,我们能够在单一代码库的多个文件夹中有效地添加开发依赖,并确保项目结构合理、依赖安装明确。

03 - 创建内部包

图片

现在我们学习了如何安装外部依赖项,接下来将创建一个内部依赖项,以便将项目内的不同包相互连接。内部包是应用的基础构件,比如 eslint-configtypescript-configUI 等包,这些包可以被其他应用程序使用,就像一个内部的 NPM 包注册库。

创建内部包

图片

  1. 创建新目录:在 packages 文件夹中创建一个新目录 elevation

  2. 初始化 package.json:导航到 elevation 目录下,并运行以下命令:

    pnpm init

    这会在 elevation 目录下生成一个 package.json 文件。

  3. 配置 package.json: 在 package.json 中添加以下内容:

    • Scripts

      "scripts": {
       "dev": "tsc --watch",
       "build": "tsc"
      }

      dev 脚本将启动 TypeScript 编译器并监视文件变化,build 脚本用于编译 TypeScript 文件。

    • 开发依赖: 添加 @repo/typescript-configtypescript 为开发依赖:

      "devDependencies": {
       "@repo/typescript-config": "workspace:*",
       "typescript": "latest"
      }
    • 包名称和模块类型: 为保持一致性,将包命名为 @repo/elevation,并指定模块类型为 ES 模块:

      "name": "@repo/elevation",
      "type": "module"
  4. 安装依赖: 在 elevation 目录下运行以下命令来安装依赖:

    pnpm install

    该命令会安装 @repo/typescript-config 和最新版本的 TypeScript,以便我们在 elevation 包中使用这些配置。

完成这些步骤后,我们的 elevation 包已配置完毕,并可在整个单一代码库中使用。在下一节中,我们将设置 TypeScript 配置并编写该包的源代码。

04 - 设置内部依赖项

现在我们已经创建了 elevation 包,接下来需要创建源文件目录并编写我们要导出的函数。

步骤 1:创建 src 文件夹

elevation 目录下,创建一个名为 src 的新文件夹,然后在 src 文件夹中创建一个名为 calculateElevation.ts 的 TypeScript 文件。

步骤 2:编写 calculateElevation 函数

calculateElevation.ts 文件中,编写以下函数并添加 TypeScript 类型注解:

function calculateElevation(feet: number): number {
    const meters: number = feet * 0.3048;
    return Math.round(meters);
}

// 导出函数
export { calculateElevation };

此函数将接收一个 feet 参数,并返回一个以米为单位的值。

步骤 3:更新 package.json 文件

接下来,需要在 elevation 包的 package.json 中定义导出入口,以便其他包可以导入该函数。

  1. 打开 package.json,添加一个 exports 字段来定义包的入口:

    "exports": {
       "./calculateElevation": {
           "types": "./src/calculateElevation.ts",
           "default": "./dist/calculateElevation.js"
       }
    }
    • "types":指向 TypeScript 文件,供开发时使用类型信息。
    • "default":指向编译后的 JavaScript 文件(编译后会存放在 dist 目录中)。

设置 "./calculateElevation": { 是因为 需要从该路径导入函数即

import {calculateElevation} from '@repo/elevation'
  1. 更新名称并保持一致性:

    "name": "@repo/elevation"

结果

配置完成后,elevation 包就可以被其他包引用了。只需在其他包中导入即可,例如:

import { calculateElevation } from "@repo/elevation";

在下一个视频中,我们将配置 TypeScript 设置,使我们的单一代码库能够处理 TypeScript 代码。

05 - 编写 tsconfig 文件

为了在项目中使用 TypeScript,需要配置一个 TypeScript 配置文件(tsconfig.json)。幸运的是,我们已经在项目的 packages 文件夹根目录中有了一个 TypeScript 配置文件。在此基础上,我们可以在 elevation 项目中扩展配置。

步骤 1:创建 tsconfig.json 文件

elevation 文件夹的根目录中,创建 tsconfig.json 文件,并编写以下内容:

{
    "extends": "@repo/typescript-config/base.json",
    "compilerOptions": {
        "outDir": "dist",
        "rootDir": "src"
    },
    "include": ["src"],
    "exclude": ["node_modules", "dist"]
}

解释:

步骤 2:运行 TypeScript 编译

为了验证配置正确性,导航到 elevation 文件夹,并使用先前定义的 build 脚本运行 TypeScript 编译器:

pnpm build

该命令会运行 TypeScript 编译器,将 src 文件夹中的 TypeScript 文件转换为 JavaScript 文件,并输出到 dist 文件夹中。这样可以确保类型检查正常并且配置文件工作无误。

在成功编译后,我们可以将 elevation 包导入到其他项目中使用。接下来的视频中,我们将演示如何在另一个项目中导入这个包。

06 - 运行构建

我们已创建了 elevation 包,并完成了配置,现在可以在项目中的其他地方引用这个包。在这个例子中,我们将在 Web 应用程序中使用它。

步骤 1:导入 elevation

web 应用的 page.tsx 文件中,我们将导入并使用 calculateElevation 函数:

import { calculateElevation } from "@repo/elevation/calculateElevation";

const elevation = calculateElevation(10000);
<h2>{elevation}</h2>

步骤 2:更新 package.jsonturbo.json

\snowtooth\apps\web\package.json

  1. package.json:将 @repo/elevation 添加为依赖项。

snowtooth\turbo.json

  1. turbo.json:添加 dist/**outputs,以便 Turbo Repo 在构建时正确引用 elevation 包的产物。
"outputs": ["dist/**"]

步骤 3:安装并构建

web 文件夹中运行以下命令来安装 @repo/elevation

pnpm install

然后在项目根目录运行 Turbo 的构建命令:

turbo build

步骤 4:启动开发服务器

web 文件夹中运行开发服务器,查看应用:

pnpm run dev

打开浏览器访问 localhost:3000,你应该能看到 10,000 英尺的转换结果,以米为单位显示在页面上。

总结

恭喜!我们的 Turbo 和 pnpm 设置都已成功完成,并且所有子项目和包的构建和管理都运行顺利。

WangShuXian6 commented 1 week ago

05 - 4. Turborepo 集成

01 - 通过构建缓存提高性能

当我们选择像 Turborepo 这样的 monorepo 工具时,通常会关注如何加快速度,因为 monorepo 往往包含大量文件和工具,每次构建可能需要较长时间。Turborepo 提供了一个强大的功能——缓存,这可以显著缩短构建时间。它可以在我们未更改文件的情况下避免重新编译。

Turborepo 的缓存示例

  1. 查看当前缓存:在 snowtooth 目录中,运行以下命令:

    pnpm run build

    Turborepo 将会检测哪些任务是缓存的并加快执行速度。比如,如果上次构建的任务没有发生变化,那么它会直接从缓存中读取结果。

  2. 代码更改触发缓存失效

    • elevation 函数进行修改,将其转换为将米数转换为英尺:
      function calculateElevation(meters: number): number {
      const feet = meters / 0.3048;
      return Math.round(feet);
      }
    • 运行构建命令:
      pnpm run build

      由于对 elevation 函数进行了更改,Turborepo 会检测到缓存失效,并重新构建受影响的模块。

  3. 验证缓存功能

    • 再次运行构建命令:
      pnpm run build

      因为没有新的更改,此次所有构建任务都将从缓存中加载,大大缩短了构建时间。

远程缓存

在下一步中,您可以考虑配置远程缓存。通过远程缓存,您可以在多个开发环境中共享构建缓存,从而进一步提高开发效率。

Turborepo 的缓存机制使得在大型项目中管理和加快构建过程变得更加高效。

02 - 使用远程缓存

在前面的视频中,我们介绍了本地缓存如何加速构建过程,但如果您与团队合作,远程缓存会更高效。使用远程缓存,团队成员可以共享缓存结果,无需重复构建。

远程缓存设置

  1. 登录 Turbo:确保您在项目目录下,例如 snowtooth 目录,打开终端并输入以下命令登录:

    turbo login

    这将基于您的电子邮件地址进行授权。

  2. 链接到远程缓存:接下来,运行以下命令以启用远程缓存:

    npx turbo link

    系统会询问您是否希望启用远程缓存,并且您可以选择账号和团队名称,例如 Moon Highway。成功之后,Turborepo CLI 将被授权使用该账户。

  3. 使用远程缓存构建:在项目中做出更改,比如在 elevation 函数中添加日志输出:

    console.log(meters);

    运行以下命令进行构建:

    turbo build

    第一次运行时,由于更改未被缓存,因此会出现缓存未命中的情况。再次运行构建命令时,由于使用了远程缓存,构建速度显著提升。

  4. 清除本地缓存:可以使用以下命令清除本地缓存:

    rm -rf .turbo/cache

    或者手动删除该目录。删除缓存后,运行 turbo build,Turborepo 将从远程缓存中加载构建结果,大大提高速度。

远程缓存非常适合团队合作项目,使整个团队的构建速度得以提升。每次构建时,Turborepo 会从远程缓存中检索资源,减少重复计算。

03 - 了解开发者模式功能

Turborepo 提供了开发模式的配置,以便更高效地处理日常开发任务。以下是一些配置和命令,帮助我们在开发模式下优化项目的操作:

开发模式配置

turbo.json 文件中,我们可以看到一个 dev 命令示例:

运行开发任务

  1. 运行所有项目的开发任务

    turbo dev

    该命令会在本地运行所有项目并持续监视文件更改。

  2. 监控单个项目: 通过过滤选项,我们可以只监控一个项目。例如,如果只想监控 docs 项目,可以使用以下命令:

    turbo dev --filter docs

    这样只会在 dev 模式下运行 docs 项目,这样更有助于集中精力处理单个项目的开发。

其他建议

在日常开发中,可以通过这些命令有针对性地工作在不同的项目模块上,同时利用 Turborepo 的监视功能确保更改能够快速反映在项目上。这样既可以提高效率,也减少了无关模块的编译时间。

04 - 处理环境变量

在 Turborepo 中处理环境变量时,特别是在使用 Next.js 项目时,以下是一些关键步骤和建议:

配置环境变量文件

在 Next.js 应用中,通常使用 .env.local 文件来存储环境变量。你可以创建一个名为 MH_API_KEY 的变量,例如:

MH_API_KEY=your-real-api-key-here

使用环境变量

通过在代码中引用这些环境变量,可以确保它们在运行时被正确加载。例如:

console.log(process.env.MH_API_KEY);

配置 Turbo 缓存依赖于环境变量

如果你对 .env.local 文件中的值进行了更改,Turborepo 会检测到缓存失效,因为这些变量被视为输入。当环境变量发生变化时,Turborepo 会重建项目。

你可以在 turbo.json 文件中指定环境变量文件作为输入:

{
  "pipeline": {
    "build": {
      "inputs": ["**/*", ".env.local"]
    }
  }
}

你还可以根据需要添加其他文件,例如 .environment 文件。将它们列入 inputs 中,这样 Turborepo 就会自动检测这些文件中的更改并重新运行必要的任务。

更复杂的环境配置

可以在 turbo.json 中进一步配置多个环境变量文件,以确保它们在适当时生效。通过合理的配置,你可以确保不同的开发、测试和生产环境下使用不同的变量文件,从而提高应用的灵活性和可维护性。

WangShuXian6 commented 1 week ago

06 - 总结

01 - 下一步

在继续深入 Turborepo 使用的基础上,下一步可以开始考虑如何将它集成到你的持续集成(CI)管道中。不同的项目会有不同的需求,因此可以根据具体情况,使用以下一些 CI 工具:

此外,建议探索 Turbopack,这是 Turborepo 的一个补充工具,目前还在 beta 阶段,未来会更稳定。Turbopack 专注于 React Server 组件和 TypeScript 项目,可以显著加快构建速度,并优化缓存的效率。

通过在你的 CI 管道中使用 Turborepo 和 Turbopack,可以为开发团队带来更高效的体验,为企业级项目打下坚实的基础。