Skip to main content

约束

信息

此页面记录了新的基于 JavaScript 的约束。基于 Prolog 的旧约束仍然受支持,但应视为已弃用。可以在 此处 中找到它们的文档。

¥This page documents the new JavaScript-based constraints. The older constraints, based on Prolog, are still supported but should be considered deprecated. Their documentation can be found here.

概述

¥Overview

约束允许跨 workspace 包执行规则。例如,这些规则可能包括以下内容:

¥Constraints allow enforcement of rules across workspace packages. For example, these rules might include the following:

  • 确保所有软件包的依赖版本相同。

    ¥Ensuring the same version of dependencies across packages.

  • 禁止在包中使用特定依赖。

    ¥Prohibiting the use of specific dependencies in packages.

我们可以执行什么?

¥What can we enforce?

我们的约束引擎目前支持两个主要目标:

¥Our constraint engine currently supports two main targets:

  • 工作区依赖

    ¥Workspace dependencies

  • 任意 package.json 字段

    ¥Arbitrary package.json fields

它目前不支持以下内容,但将来可能会支持(欢迎 PR!):

¥It currently doesn't support the following, but might in the future (PRs welcome!):

  • 传递依赖

    ¥Transitive dependencies

  • 项目结构

    ¥Project structure

创建约束

¥Creating a constraint

通过在项目(存储库)的根目录下添加 yarn.config.cjs 文件来创建约束。此文件应使用 constraints 方法导出对象。此方法将由约束引擎调用,并且必须使用提供的 API 定义要在项目上执行的规则。

¥Constraints are created by adding a yarn.config.cjs file at the root of your project (repository). This file should export an object with a constraints method. This method will be called by the constraints engine, and must define the rules to enforce on the project, using the provided API.

例如,以下 yarn.config.cjs 将强制将所有 react 依赖设置为 18.0.0

¥For example, the following yarn.config.cjs will enforce that all react dependencies are set to 18.0.0.

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

以下内容将强制在所有工作区中正确设置 engines.node 字段:

¥And the following will enforce that the engines.node field is properly set in all workspaces:

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

声明式模型

¥Declarative model

尽可能使用声明式模型定义约束:你声明预期状态应该是什么,Yarn 检查它是否与现实相符。如果没有,Yarn 要么会抛出错误(当调用没有参数的 yarn constraints 时),要么会尝试自动修复问题(当调用 yarn constraints --fix 时)。

¥As much as possible, constraints are defined using a declarative model: you declare what the expected state should be, and Yarn checks whether it matches the reality or not. If it doesn't, Yarn will either throw an error (when calling yarn constraints without arguments), or attempt to automatically fix the issue (when calling yarn constraints --fix).

由于这种声明式模型,你不需要自己检查实际值。例如,此处的 if 条件是多余的,应将其删除:

¥Because of this declarative model, you don't need to check the actual values yourself. For instance, the if condition here is extraneous and should be removed:

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 支持

¥TypeScript support

Yarn 附带的类型使编写约束变得更加容易。要使用它们,请将依赖添加到你的项目中:

¥Yarn ships types that make it easier to write constraints. To use them, add the dependency to your project:

$ yarn add @yarnpkg/types

然后,在你的 yarn.config.cjs 文件中,导入类型,特别是自动输入配置方法的 defineConfig 函数:

¥Then, in your yarn.config.cjs file, import the types, in particular the defineConfig function which automatically type the configuration methods:

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

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

你还可以手动检索类型,如果你将一些规则提取到辅助函数中,这将很有用:

¥You can also retrieve the types manually, which can be useful if you extract some rules into helper functions:

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

你可以为类型添加别名,使它们更易于使用:

¥You can alias the types to make them a little easier to use:

/**

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

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

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

将它们放在一起

¥Putting it all together

本节重新组合了几个约束示例。我们正在考虑稍后将其中一些作为内置助手提供,尽管它们往往包含每个团队/公司独有的一些逻辑。

¥This section regroups a couple of constraint examples. We are thinking to provide some of them as builtin helpers later on, although they tend to often contain some logic unique to each team / company.

限制工作区之间的依赖

¥Restrict dependencies between workspaces

此代码可确保项目中没有两个工作区可以在其 dependenciesdevDependencies 字段中列出相同的包,但具有不同的关联引用。

¥This code ensures that no two workspaces in your project can list the same packages in their dependencies or devDependencies fields but with different associated references.

// @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);
},
});