Руководство по плагинам
Начиная с Yarn 2, Yarn теперь поддерживает плагины. Для получения дополнительной информации о том, что это такое и в каких случаях их следует использовать, обратитесь к специальной странице. Здесь мы поговорим о точных шагах, необходимых для написания плагина. Это довольно просто!
Как выглядит плагин?
Плагины — это скрипты, которые загружаются во время выполнения Yarn и могут вносить новые изменения в его поведение. Они также могут использовать некоторые пакеты, предоставляемые самим Yarn, такие как @yarnpkg/core. Это позволяет вам использовать точно такой же основной API, как в текущей двоичной программе Yarn, как будто это зависимость peer!
Важно: Поскольку плагины загружаются до запуска Yarn (и, следовательно, до первой установки), настоятельно рекомендуется писать ваши плагины таким образом, чтобы они работали без зависимостей. Если это окажется сложным, знайте, что мы предоставляем мощный инструмент (
@yarnpkg/builder, который может объединять ваши плагины в один JavaScript-файл, готовый к публикации.
Написание первого плагина
Откройте в текстовом редакторе новый файл под названием plugin-hello-world.js, и введите следующий код:
module.exports = {
name: `plugin-hello-world`,
factory: require => ({
// What is this `require` function, you ask? It's a `require`
// implementation provided by Yarn core that allows you to
// access various packages (such as @yarnpkg/core) without
// having to list them in your own dependencies - hence
// lowering your plugin bundle size, and making sure that
// you'll use the exact same core modules as the rest of the
// application.
//
// Of course, the regular `require` implementation remains
// available, so feel free to use the `require` you need for
// your use case!
})
};Наш плагин готов, но теперь нам нужно зарегистрировать его, чтобы Yarn знал, где его найти. Для этого мы просто добавим запись в файл .yarnrc.yml в корне репозитория:
plugins:
- ./plugin-hello-world.jsВот и всё! У вас есть ваш первый плагин, поздравляем! Конечно, он не делает многого (или вообще ничего), но мы увидим, как расширить его, чтобы сделать его более мощным.
Инструмент для создания плагинов
Как мы видели, плагины предназначены для того, чтобы быть автономными JavaScript-файлами. Их можно создавать вручную, особенно если вам нужен только небольшой плагин, но как только вы начнете добавлять несколько команд, это может стать немного сложнее. Чтобы облегчить этот процесс, мы поддерживаем пакет @yarnpkg/builder. Этот инструмент для Yarn такой же, как Next.js для разработки веб-приложений — это инструмент, разработанный для создания, сборки и управления сложными плагинами, написанными на TypeScript.
Его документация доступна на специальной странице, но помните, что вы не обязаны его использовать. Иногда достаточно простых скриптов!
Добавление команд
Плагины также могут регистрировать свои собственные команды. Для этого мы просто должны написать их, используя библиотеку clipanion — и нам даже не нужно добавлять её в зависимости!
Давайте посмотрим пример:
module.exports = {
name: `plugin-hello-world`,
factory: require => {
const {BaseCommand} = require(`@yarnpkg/cli`);
class HelloWorldCommand extends BaseCommand {
static paths = [[`hello`]];
async execute() {
this.context.stdout.write(`This is my very own plugin 😎\n`);
}
}
return {
commands: [
HelloWorldCommand,
],
};
}
};Теперь попробуйте запустить yarn hello. Вы увидите ваше сообщение! Обратите внимание, что вы можете использовать весь набор функций, предоставляемых clipanion, включая короткие и длинные опции, аргументы с переменным числом элементов и т. д. Вы даже можете валидировать свои опции, используя библиотеку typanion, которую мы предоставляем. Вот пример, где в качестве параметра принимаются только числа:
module.exports = {
name: `plugin-addition`,
factory: require => {
const {BaseCommand} = require(`@yarnpkg/cli`);
const {Option} = require(`clipanion`);
const t = require(`typanion`);
class AdditionCommand extends BaseCommand {
static paths = [[`addition`]];
// Show descriptive usage for a --help argument passed to this command
static usage = Command.Usage({
description: `hello world!`,
details: `
This command will print a nice message.
`,
examples: [[
`Add two numbers together`,
`yarn addition 42 10`,
]],
});
a = Option.String({validator: t.isNumber()});
b = Option.String({validator: t.isNumber()});
async execute() {
this.context.stdout.write(`${this.a}+${this.b}=${this.a + this.b}\n`);
}
}
return {
commands: [
AdditionCommand,
],
};
},
};Использование хуков
Плагины могут регистрироваться на различные события в жизненном цикле Yarn и получать дополнительную информацию для изменения своего поведения. Для этого вам просто нужно объявить новое свойство hooks в вашем плагине и добавить члены для каждого хука, на который вы хотите подписаться:
module.exports = {
name: `plugin-hello-world`,
factory: require => ({
hooks: {
setupScriptEnvironment(project, scriptEnv) {
scriptEnv.HELLO_WORLD = `my first plugin!`;
},
},
})
};В этом примере мы подписались на хук setupScriptEnvironment и использовали его для добавления аргумента в среду. Теперь каждый раз, когда вы запустите скрипт, вы увидите, что ваша переменная среды будет содержать новое значение под названием HELLO_WORLD!
Хуков много, и мы все еще работаем над ними. Некоторые могут быть добавлены, удалены или изменены в зависимости от ваших отзывов. Поэтому, если вы хотите сделать что-то, что хуки пока не позволяют, сообщите нам об этом!
Примечание: У нас пока нет списка хуков. Если вы хотите улучшить эту документацию, сгенерировав список хуков из нашего исходного кода, свяжитесь с нами на нашем сервере Discord!
Использование API Yarn
Большинство хуков Yarn вызываются с различными аргументами, которые предоставляют больше информации о контексте вызова хука. Точный список аргументов отличается для каждого хука, но в общем они имеют типы, определенные в @yarnpkg/core библиотеке.
В этом примере мы интегрируемся с хуком afterAllInstalled для вывода некоторой базовой информации о дереве зависимостей после каждой установки. Этот хук вызывается с дополнительным параметром, который представляет собой публичный экземпляр Project, где хранится большая часть информации, собранной Yarn о проекте: зависимости, манифесты пакетов, информация о рабочем пространстве и так далее.
const fs = require(`fs`);
const util = require(`util`);
module.exports = {
name: `plugin-project-info`,
factory: require => {
const {structUtils} = require(`@yarnpkg/core`);
return {
default: {
hooks: {
afterAllInstalled(project) {
let descriptorCount = 0;
for (const descriptor of project.storedDescriptors.values())
if (!structUtils.isVirtualDescriptor(descriptor))
descriptorCount += 1;
let packageCount = 0;
for (const pkg of project.storedPackages.values())
if (!structUtils.isVirtualLocator(pkg))
packageCount += 1;
console.log(`This project contains ${descriptorCount} different descriptors that resolve to ${packageCount} packages`);
}
}
}
};
}
};Это становится интересно. Как вы можете видеть, мы получили доступ к полям storedDescriptors и storedPackages из нашего экземпляра проекта и перебрали их, чтобы получить количество элементов, не являющихся виртуальными (виртуальные пакеты описаны более подробно здесь). Это очень простой пример, но мы могли бы сделать гораздо больше: корень проекта находится в свойстве cwd, рабочие пространства представлены как workspaces, связь между описателями и пакетами может быть установлена через storedResolutions и т. д.
Обратите внимание, что мы лишь затронули поверхность экземпляра класса Project! Ядро Yarn предоставляет множество других классов (и хуков), которые позволяют работать с кэшем, загружать пакеты, вызывать HTTP-запросы и многое другое, как указано в документации API. В следующий раз, когда вы захотите написать плагин, посмотрите на него — там почти наверняка есть утилиты, которые позволят вам избежать повторной реализации колеса.
Официальные хуки
afterAllInstalled
Вызывается после выполнения метода install из класса Project.
afterAllInstalled?: (
project: Project,
options: InstallOptions
) => void;
afterWorkspaceDependencyAddition
Вызывается при добавлении новой зависимости в рабочее пространство. Обратите внимание, что этот хук вызывается только командами командной строки, например, yarn add - ручное добавление зависимостей в манифест и запуск yarn install не вызовет его.
afterWorkspaceDependencyAddition?: (
workspace: Workspace,
target: suggestUtils.Target,
descriptor: Descriptor,
strategies: Array<suggestUtils.Strategy>
) => Promise<void>;
afterWorkspaceDependencyRemoval
Вызывается при удалении диапазона зависимостей из рабочего пространства. Обратите внимание, что этот хук вызывается только командами командной строки, например, yarn remove - ручное удаление зависимостей из манифеста и запуск yarn install не вызовет его.
afterWorkspaceDependencyRemoval?: (
workspace: Workspace,
target: suggestUtils.Target,
descriptor: Descriptor,
) => Promise<void>;
afterWorkspaceDependencyReplacement
Вызывается при замене диапазона зависимостей внутри рабочего пространства. Обратите внимание, что этот хук вызывается только командами командной строки, например, yarn add - ручное обновление зависимостей из манифеста и запуск yarn install не вызовет его.
afterWorkspaceDependencyReplacement?: (
workspace: Workspace,
target: suggestUtils.Target,
fromDescriptor: Descriptor,
toDescriptor: Descriptor,
) => Promise<void>;
beforeWorkspacePacking
Вызывается перед упаковкой рабочего пространства. Значение rawManifest, переданное в качестве параметра, разрешается изменять произвольно, но изменения применяются только к упакованному манифесту (исходный не будет изменен).
beforeWorkspacePacking?: (
workspace: Workspace,
rawManifest: object,
) => Promise<void> | void;
cleanGlobalArtifacts
Вызывается, когда пользователь запрашивает очистку глобального кэша. Плагины должны использовать этот хук для удаления своих собственных глобальных артефактов.
cleanGlobalArtifacts?: (
configuration: Configuration,
) => Promise<void>;
fetchHostedRepository
Вызывается при извлечении репозитория Git. Если функция возвращает null, репозиторий будет клонирован и упакован; в противном случае она должна вернуть значение, совместимое с тем, что вернула бы функция извлечения.
Основной случай использования этого хука — реализация более разумных стратегий клонирования в зависимости от хостинговой платформы. Например, GitHub поддерживает загрузку tarball репозитория, что более эффективно, чем клонирование репозитория (даже без его истории).
fetchHostedRepository?: (
current: FetchResult | null,
locator: Locator,
opts: FetchOptions,
) => Promise<FetchResult | null>;
fetchPackageInfo
Вызывается yarn info. Поле extra — это набор параметров, переданных флагу -X,--extra. Вызов registerData добавит новый набор данных, который будет добавлен к информации о пакете.
Например, плагин "аудит" может проверить в extra запросил ли пользователь информацию об аудировании (через -X audit) и вызвать registerData с этой информацией (полученной динамически), если это было сделано.
fetchPackageInfo?: (
pkg: Package,
extra: Set<string>,
registerData: (namespace: string, data: Array<formatUtils.Tuple> | {[key: string]: formatUtils.Tuple | undefined}) => void,
) => Promise<void>;
getBuiltinPatch
Регистрирует встроенную патч, которую можно ссылаться с помощью специального синтаксиса: patch:builtin<name>. Например, таким образом автоматически регистрируется патч TypeScript.
getBuiltinPatch?: (
project: Project,
name: string,
) => Promise<string | null | void>;
globalHashGeneration
Вызывается до сборки для вычисления глобального хэш-ключа, который мы будем использовать для определения необходимости перестроения пакетов (например, при изменении версии Node).
globalHashGeneration?: (
project: Project,
contributeHash: (data: string | Buffer) => void,
) => Promise<void>;
populateYarnPaths
Используется для уведомления ядра обо всех потенциальных артефактах доступных линковёров.
populateYarnPaths?: (
project: Project,
definePath: (path: PortablePath | null) => void,
) => Promise<void>;
reduceDependency
Вызывается во время разрешения, один раз для каждого разрешенного пакета и каждой из его зависимостей. Возвращая новый дескриптор зависимости, вы можете заменить исходный дескриптор другим диапазоном.
Обратите внимание, что когда несколько плагинов зарегистрированы в reduceDependency, они будут выполнены в порядке определения. В этом случае dependency всегда будет ссылаться на зависимость в ее текущем состоянии, а initialDependency — на дескриптор до попыток изменения им каким-либо плагином.
reduceDependency?: (
dependency: Descriptor,
project: Project,
locator: Locator,
initialDependency: Descriptor,
extra: {resolver: Resolver, resolveOptions: ResolveOptions},
) => Promise<Descriptor>;
registerPackageExtensions
Вызывается при настройке расширений пакета. Можно использовать для вставки новых. Например, плагин compat использует это, чтобы автоматически исправить пакеты с известными недостатками.
registerPackageExtensions?: (
configuration: Configuration,
registerPackageExtension: (descriptor: Descriptor, extensionData: PackageExtensionData) => void,
) => Promise<void>;
setupScriptEnvironment
Вызывается перед выполнением скрипта. Плагины могут изменять объект env, как им заблагорассудится, и любой вызов makePathWrapper приведет к инъекции двоичного файла с указанным именем куда-то в PATH (мы рекомендуем не изменять PATH самостоятельно, если это не требуется).
Ключи, которые вы получаете в env, гарантированно будут в верхнем регистре. Мы настоятельно рекомендуем использовать эту конвенцию для любого нового ключа, добавляемого в env (возможно, мы её в будущем и закрепим).
setupScriptEnvironment?: (
project: Project,
env: ProcessEnvironment,
makePathWrapper: (name: string, argv0: string, args: Array<string>) => Promise<void>,
) => Promise<void>;
validateProject
Вызывается во время Validation step метода install из класса Project.
validateProject?: (
project: Project,
report: {
reportWarning: (name: MessageName, text: string) => void;
reportError: (name: MessageName, text: string) => void;
}
) => void;
validateWorkspace
Вызывается во время Validation step метода install из класса Project плагином validateProject.
validateWorkspace?: (
workspace: Workspace,
report: {
reportWarning: (name: MessageName, text: string) => void;
reportError: (name: MessageName, text: string) => void;
}
) => void;
wrapNetworkRequest
Вызывается при выполнении сетевого запроса. Функция executor, когда её вызывают, инициирует сетевой запрос. Вы можете использовать этот механизм для обертывания сетевых запросов, например, для выполнения некоторой валидации или добавления логирования.
wrapNetworkRequest?: (
executor: () => Promise<any>,
extra: WrapNetworkRequestInfo
) => Promise<() => Promise<any>>;
wrapScriptExecution
Вызывается при выполнении скрипта. Функция executor, когда её вызывают, выполняет скрипт. Вы можете использовать этот механизм для обертывания выполнения скриптов, например, для выполнения некоторой валидации или добавления мониторинга производительности.
wrapScriptExecution?: (
executor: () => Promise<number>,
project: Project,
locator: Locator,
scriptName: string,
extra: {script: string, args: Array<string>, cwd: PortablePath, env: ProcessEnvironment, stdin: Readable | null, stdout: Writable, stderr: Writable},
) => Promise<() => Promise<number>>;
© 2016–present Yarn Contributors
Licensed under the BSD License.
https://v3.yarnpkg.com/advanced/plugin-tutorial