跳至主要內容

約束

資訊

此頁面說明新的基於 JavaScript 的約束。舊的基於 Prolog 的約束仍受支援,但應視為已棄用。其說明文件可在此處找到 在此處

概觀

約束是一種非常基本需求的解決方案:您有很多 工作區,並且您需要確保它們使用相同版本的相依項。或者它們不依賴於特定套件。或者它們使用特定類型的相依項。無論確切邏輯為何,您的目標都是相同的:您希望自動對所有工作區強制執行某種規則。這正是約束的用途。

我們可以強制執行什麼?

我們的約束引擎目前支援兩個主要目標

  • 工作區相依性
  • 任意 package.json 欄位

目前不支援下列項目,但未來可能會支援(歡迎提交 PR!)

  • 遞移相依性
  • 專案結構

建立約束

約束是透過在專案(儲存庫)根目錄新增一個 yarn.config.cjs 檔案來建立的。此檔案應匯出一個包含 constraints 方法的物件。此方法會由約束引擎呼叫,且必須使用提供的 API 定義要在專案上強制執行的規則。

例如,下列 yarn.config.cjs 會強制執行所有 react 相依性都設定為 18.0.0

module.exports = {
async constraints({Yarn}) {
for (const dep of Yarn.dependencies({ ident: 'react' })) {
dep.update(`18.0.0`);
}
},
};

而下列會強制執行所有工作區都正確設定 engines.node 欄位

module.exports = {
async constraints({Yarn}) {
for (const workspace of Yarn.workspaces()) {
workspace.set('engines.node', `20.0.0`);
}
},
};

宣告式模型

盡可能使用宣告式模型來定義約束:宣告預期的狀態應為何,而 Yarn 會檢查它是否與實際情況相符。如果不相符,Yarn 會擲回錯誤(在沒有參數的情況下呼叫 yarn constraints),或嘗試自動修正問題(在呼叫 yarn constraints --fix 時)。

由於這個宣告式模型,您不需要自己檢查實際值。例如,此處的 if 條件是多餘的,應予以移除

module.exports = {
async constraints({Yarn}) {
for (const dep of Yarn.dependencies({ ident: 'ts-node' })) {
// No need to check for the actual value! Just always call `update`.
if (dep.range !== `18.0.0`) {
dep.update(`18.0.0`);
}
}
},
};

TypeScript 支援

Yarn 提供類型,讓撰寫約束變得更容易。若要使用它們,請將相依性新增至您的專案

$ yarn add @yarnpkg/types

然後,在您的 yarn.config.cjs 檔案中,匯入類型,特別是 defineConfig 函式,它會自動輸入組態方法的類型

/** @type {import('@yarnpkg/types')} */
const { defineConfig } = require('@yarnpkg/types');

module.exports = defineConfig({
async constraints({Yarn}) {
// `Yarn` is now well-typed ✨
},
});

您也可以手動擷取類型,這在您將一些規則萃取至輔助函式時會很有用

/** @param {import('@yarnpkg/types').Yarn.Constraints.Workspace} dependency */
function expectMyCustomRule(dependency) {
// ...
}

你可以別名類型,讓它們更容易使用

/**
* @typedef {import('@yarnpkg/types').Yarn.Constraints.Workspace} Workspace
* @typedef {import('@yarnpkg/types').Yarn.Constraints.Dependency} Dependency
*/

/** @param {Workspace} dependency */
function expectMyCustomRule(dependency) {
// ...
}

將所有內容組合在一起

此區段重新組合幾個約束範例。我們考慮稍後提供其中一些範例作為內建的輔助程式,儘管它們通常包含一些對每個團隊/公司而言獨特的邏輯。

限制工作區之間的相依性

此程式碼確保專案中的兩個工作區無法在其 dependenciesdevDependencies 欄位中列出相同的套件,但具有不同的關聯參照。

// @ts-check

/** @type {import('@yarnpkg/types')} */
const {defineConfig} = require(`@yarnpkg/types`);

/**
* This rule will enforce that a workspace MUST depend on the same version of
* a dependency as the one used by the other workspaces.
*
* @param {Context} context
*/
function enforceConsistentDependenciesAcrossTheProject({Yarn}) {
for (const dependency of Yarn.dependencies()) {
if (dependency.type === `peerDependencies`)
continue;

for (const otherDependency of Yarn.dependencies({ident: dependency.ident})) {
if (otherDependency.type === `peerDependencies`)
continue;

dependency.update(otherDependency.range);
}
}
}

module.exports = defineConfig({
constraints: async ctx => {
enforceConsistentDependenciesAcrossTheProject(ctx);
},
});