返回部落格

Turborepo 0.4.0

Jared Palmer
姓名
Jared Palmer
X
@jaredpalmer

我很高興宣佈 Turborepo v0.4.0 的發佈!

使用 Go 語言重寫

雖然我最初是在 TypeScript 中原型設計 turbo,但很明顯,路線圖上的某些項目需要更好的效能。經過大約一個月的努力,我很高興終於發佈 turbo CLI 的 Go 版本。它不僅能在幾毫秒內啟動,而且新的 Go 實作在雜湊方面比 Node.js 實作快 10 到 100 倍。有了這個新的基礎(以及您即將讀到的一些功能),Turborepo 現在可以擴展到星際級大小的專案,同時保持閃電般的速度,這一切都要歸功於 Go 出色的並發控制。

更好的雜湊

v0.4.0 中的雜湊不僅更快,而且也智慧。

主要的變更是 turbo 不再在其雜湊器中包含根鎖定檔的內容雜湊(該演算法負責判斷快取中是否存在給定任務或是否需要執行)。相反,turbo 現在會根據根鎖定檔雜湊套件的 dependenciesdevDependencies 的解析版本集合。

舊的行為會在根鎖定檔以任何方式變更時使快取爆炸。使用此新行為,變更鎖定檔只會使受新增/變更/移除依賴項影響的套件的快取失效。雖然這聽起來很複雜,但它的意思仍然是,當您從 npm 安裝/移除/更新依賴項時,只有實際受到變更影響的套件才需要重建。

實驗性功能:修剪後的工作區

我們最大的客戶痛點/要求之一是改善使用大型 Yarn 工作區(或任何工作區實作)時的 Docker 建置時間。核心問題是,工作區的最佳功能 — 將您的單一程式碼倉儲縮減為單一鎖定檔 — 在 Docker 層快取方面也是最糟糕的功能。

為了幫助闡明問題以及 turbo 現在如何解決它,讓我們來看一個範例。

假設我們有一個具有 Yarn 工作區的單一程式碼倉儲,其中包含一組名為 frontendadminuibackend 的套件。我們也假設 frontendadmin 是 Next.js 應用程式,它們都依賴於相同的內部 React 元件庫套件 ui。現在,我們也假設 backend 包含一個 Express TypeScript REST API,它與我們單一程式碼倉儲的任何其他部分沒有太多程式碼共用。

以下是 frontend Next.js 應用程式的 Dockerfile 的樣子

Dockerfile
FROM node:alpine AS base
RUN apk update
WORKDIR /app
 
# Add lockfile and package.jsons
FROM base AS builder
COPY *.json yarn.lock ./
COPY packages/ui/*.json ./packages/ui/
COPY packages/frontend/*.json ./packages/frontend/
RUN yarn install
 
# Copy source files
COPY packages/ui/ ./packages/ui/
COPY packages/frontend/ ./packages/frontend/
 
# Build
RUN yarn --cwd=packages/ui/ build
RUN yarn --cwd=packages/frontend/ build
 
# Start the Frontend Next.js application
EXPOSE 3000
RUN ['yarn', '--cwd', 'packages/frontend', 'start']

雖然這可行,但有些事情可以做得更好

最後一個問題尤其令人痛苦,因為當您的單一程式碼倉儲越來越大時,對此鎖定檔的任何變更都會觸發幾乎完全的重建,無論應用程式是否實際上受到新增/變更的依賴項的影響。

...直到現在。

有了全新的 turbo prune 命令,您現在可以透過確定性地為目標套件產生一個稀疏/部分單一程式碼倉儲以及修剪後的鎖定檔來解決這個噩夢 — 而無需安裝您的 node_modules

讓我們看看如何在 Docker 中使用 turbo prune

Dockerfile
FROM node:alpine AS base
RUN apk update && apk add git
 
## Globally install `turbo`
RUN npm i -g turbo
 
# Prune the workspace for the `frontend` app
FROM base as pruner
WORKDIR /app
COPY . .
RUN turbo prune frontend --docker
 
# Add pruned lockfile and package.json's of the pruned subworkspace
FROM base AS installer
WORKDIR /app
COPY --from=pruner /app/out/json/ .
COPY --from=pruner /app/out/yarn.lock ./yarn.lock
# Install only the deps needed to build the target
RUN yarn install
 
# Copy source code of pruned subworkspace and build
FROM base AS builder
WORKDIR /app
COPY --from=pruner /app/.git ./.git
COPY --from=pruner /app/out/full/ .
COPY --from=installer /app/ .
RUN turbo run build frontend
 
# Start the app
FROM builder as runner
EXPOSE 3000
RUN ['yarn', '--cwd', 'packages/frontend', 'start']

那麼 turbo prune 的輸出到底是什麼?一個名為 out 的資料夾,其中包含以下內容

感謝以上功能,現在可以設定 Docker,以便僅在有真正的原因時才重建每個應用程式。因此,frontend 只有在其原始程式碼或依賴項(無論是內部還是來自 npm)實際上已變更時才會重建。adminbackend 也是如此。對 ui 的變更(無論是原始程式碼還是依賴項)都會觸發 frontendadmin 的重建,但不會觸發 backend 的重建。

雖然此範例看起來很瑣碎,但想像一下,如果每個應用程式都需要長達 20 分鐘才能建置和部署。這些節省真的開始迅速累積,尤其是在大型團隊中。

管線

為了讓您更好地控制 Turborepo,我們在 turbo 的配置中新增了 pipeline。此新欄位可讓您指定單一程式碼倉儲中的 npm 指令碼如何相互關聯,以及一些額外的每個任務選項。然後 turbo 使用此資訊來最佳化排程單一程式碼倉儲中的任務,從而摺疊原本會存在的水瀑。

以下是其運作方式

./package.json
{
  "turbo": {
    "pipeline": {
      "build": {
        // This `^` tells `turbo` that this pipeline target relies on a topological target being completed.
        // In english, this reads as: "this package's `build` command depends on its dependencies' or
        // devDependencies' `build` command being completed"
        "dependsOn": ["^build"]
      },
      "test": {
        //  `dependsOn` without `^` can be used to express the relationships between tasks at the package level.
        // In English, this reads as: "this package's `test` command depends on its `lint` and `build` command first being completed"
        "dependsOn": ["lint", "build"]
      },
      "lint": {},
      "dev": {}
    }
  }
}

然後,turbo 將會解譯上述配置,以便最佳化排程執行。

這實際上是什麼意思?在過去(如 Lerna 和 Nx),turbo 只能以拓撲順序執行任務。隨著管線的加入,turbo 現在除了實際依賴項圖之外,還會建構一個拓撲「動作」圖,它會使用此圖來判斷應該以最大並發方式執行任務的順序。最終結果是您不再浪費閒置的 CPU 時間等待完成(即不再出現水瀑)。

Turborepo scheduler

改進的快取控制

感謝 pipeline,我們現在有一個很好的地方可以根據每個任務開啟 turbo 的快取行為。

在上面的範例的基礎上,您現在可以設定整個單一程式碼倉儲的快取輸出慣例,如下所示

./package.json
{
  "turbo": {
    "pipeline": {
      "build": {
        // Cache anything in dist or .next directories emitted by a `build` command
        "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
        "dependsOn": ["^build"]
      },
      "test": {
        // Cache the test coverage report
        "outputs": ["coverage/**"],
        "dependsOn": ["lint", "build"]
      },
      "dev": {
        // Never cache the `dev` command
        "cache": false
      },
      "lint": {},
    }
  }
}

注意:目前,pipeline 存在於專案層級,但在後續版本中,這些將會在每個套件的基礎上可覆寫。

接下來是什麼?

我知道這很多,但還有更多。以下是 Turborepo 路線圖上的下一步。

鳴謝