Русский
Русский
English
Статистика
Реклама

Node.js npm-scripts

Сборка сложных Node.js проектов утилитой run-z

02.09.2020 14:13:27 | Автор: admin

Есть несколько десятков взаимосвязанных пакетов в рабочем дереве (Yarn Workspaces).


Надо собирать несколько из них. Часто, быстро, и в правильном порядке.


Существующие инструменты либо собирают всё сразу и долго, либо собирают в произвольном порядке, что некорректно и не всегда возможно.


Решение run-z


Так выглядит сборка


Установка


npm install run-z --save-dev  # Используя NPMyarn add run-z --dev          # Используя Yarn

Теперь в package.json можно добавлять задачи


{  "scripts": {    "all": "run-z build lint,test",    "build": "run-z --then tsc -p .",    "clean": "run-z --then shx rm -rf ./dist",    "lint": "run-z --then eslint .",    "test": "run-z --then jest",    "z": "run-z"  }}

И запускать их


npm run all               # Запуск одной задачи, используя NPMyarn all                  # Запуск одной задачи, используя Yarnyarn clean build          # Запуск нескольких задач, используя Yarnnpm run clean -- build    # Запуск нескольких задач, используя NPMnpm run z -- clean build  # Запуск через пустую задачу `z`

Рекомендую всегда добавлять пустую задачу, например z. Она позволит передать дополнительные параметры в run-z, а не в npm или yarn. Например, вот так можно вызвать справку:


yarn z --helpnpm run z -- --help

Как видите, синтаксис вызова у Yarn проще, чем у NPM.


Ниже в тексте я буду использовать Yarn в примерах.


Задачи


Задачи записываются как обычные сценарии в разделе scripts файла package.json. Если такой сценарий запускает команду run-z, то последняя трактует все сценарии как свои задачи и может запустить сразу несколько.


Если перечислить несколько задач в командной строке run-z, то выполнение каждой из них станет предварительным условием для следующей:


run-z prerequisite1 prerequisite2 --then node ./my-script.js --arg

Такая задача запустит сначала prereqiusite1, затем, дождавшись её завершения prerequisite2, и только по её окончании запустит сценарий node ./my-script.js --arg.


Поддерживаются четыре типа задач:


  • Команда содержит опцию --then. Всё, что следует за этой опцией это команда с аргументами, которая будет выполнена.
  • Сценарий NPM это любой сценарий в разделе scripts файла package.json, отличный от run-z. run-z запускает такие сценарии через npm run или yarn run.
  • Группа содержит список задач для запуска, но не содержит команды. Список задач может быть пустым.
  • Неизвестная задача создаётся, если не соответствует ни одному сценарию в package.json. При попытке её запуска возникнет ошибка. Но задачи можно пропускать, тогда никакой ошибки не будет.

Параметры выполнения задач


Можно передавать дополнительные параметры в вызываемые задачи. Для этого предназначен особый синтаксис:


run-z test/--ci/--runInBand     # Передача `--ci` и `--runInBand`                                # в команду или сценарий NPM,                                # запущенный задачей `test`.run-z test //--ci --runInBand// # Несколько параметров сразу.

Отдельный синтаксис нужен, чтобы передавать параметры конкретной задаче, а не команде run-z. Впрочем, неопознанные параметры будут переданы задаче и так, без знака /.


Одиночные параметры отделяются от задачи одиночным знаком /. Перед ним можно добавлять пробелы.


Много параметров можно заключать между ограничителями из нескольких (двух или более) знаков /.


Атрибуты задач


Атрибуты это пары ключ/значение, которые можно передавать задачам примерно так же, как и параметры:


run-z test/attribute=value       # Атрибут `attribute` со значением `value`                                 # для задачи `test`run-z build test attribute=value # Атрибут `attribute` со значением `value`                                 # для самой задачи и всех предварительных задач.run-z test/=if-present           # Сокращённо `if-present=on`.

Атрибуты пока не очень полезны, но уже могут быть использованы в некоторых случаях:


  • Когда установлен атрибут if-present для задачи, отсутствующей в package.json, то попытки выполнить такую задачу не будет предпринято и ошибка не возникнет.
    Может быть полезно, когда задача выполняется в нескольких пакетах одновременно, но в некоторых пакетах такая задача не определена.
  • Когда для задачи установлен атрибут skip, то выполняться такая задача не будет.

Дополнения к задачам


run-z запускает каждую задачу только раз, сколько бы раз она ни была вызвана. Даже если одна задача требуется для выполнения нескольких других. И каждый раз, как задача вызывается, ей можно передавать параметры и атрибуты. Параметры таких вызовов объединяются.


Также есть особый синтаксис вызова задачи, называемый дополнением. Если перед именем задачи поставить знак +, то параметры в задачу будут переданы, но вот выполнение задачи инициировано не будет.


Это можно использовать, например, для задания параметров задачи по умолчанию. Так что с таким package.json:


{  "scripts": {    "test": "run-z +z jest",    "z": "run-z +test/--runInBand"  }}

при исполнении задачи test, jest всегда будет вызываться с опцией --runInBand.


Параллельное и последовательное выполнение


Любые две задачи выполняются последовательно, если только им не разрешено выполняться параллельно.


Запятая между именами задач разрешает их параллельное выполнение.


Например, задача


run-z clean build lint,test

Выполнит lint и test параллельно, но лишь когда build завершится. А задача build начнёт выполняться, только когда завершится clean.


Также можно выполнять команды параллельно с другими задачами. Для этого достаточно опцию --then заменить на опцию --and:


run-z copy-assets --and tsc -p .  # Копирует файлы и компилирует                                  # TypeScript одновременно.

Число одновременно выполняемых задач ограничено. По умолчанию числом процессоров. Но можно это исправить опцией --max-jobs (сокращённо -j):


run-z build,lint,test -j2          # Только две задачи одновременно.run-z build,lint,test -max-jobs 1  # Отключает параллельное выполнение.run-z build,lint,test -j0          # Убирает ограничение.

Пакетное выполнение


Можно выполнить задачу в другом пакете:


run-z ../package1 build test . build test

Эта задача выполнит build и test в пакете из директории ../package1, а затем в текущем.


Опции командной строки ., .., а также начинающиеся с ./ и ../ это URL указывающие на директории с пакетами. Задачи, перечисленные после них, будут найдены и выполнены в целевом пакете.


В общем случае такие опции селекторы могут выбрать сразу несколько пакетов. Тогда задача с одним и тем же именем будет выполнена во всех. Партией:


run-z ./packages//  build # Выполнит `build` в каждом пакете                          # непосредственно внутри директории `./packages`.run-z ./packages/// build # Выполнит `build` в директории `./packages`                          # и в каждом пакете найденном как угодно глубже.

// выбирает непосредственно вложенные директории. /// выбирает директорию и её поддиректории на любую глубину. Скрытые директории и директории без файла package.json игнорируются.


Можно указать сразу несколько селекторов. Результаты выборок будет объединены:


run-z ./3rd-party// ./packages// build # Выполнит `build` в каждом пакете                                       # из директорий `./3rd-party`                                       # и `./packages`.

Порядок выполнения задач из партии определяется зависимостями между пакетами. То есть сначала задача выполняется для зависимости, а затем для зависящего от него пакета. Задачи в независимых пакетах выполняются параллельно.


Можно разрешить всем задачам в партии выполняться параллельно опцией --batch-parallel, сокращённо --bap:


run-z --batch-parallel ./packages// lint # Выполнит `lint` в каждом пакете                                         # внутри директории `./packages                                         # параллельно.

Подзадачи


Задача типа "группа" не только выполняет перечисленные задачи. Она также может быть использована для запуска произвольных задач в выбранных пакетах.


Для этого группе можно передать параметры вызова. И первым параметром будет имя (под-)задачи, которую нужно выполнить. Остальные параметры будут переданы уже в эту подзадачу.


Так что с таким package.json:


{  "scripts": {    "each": "run-z ./3rd-party// ./packages//"  }}

можно выполнить задачи партией внутри директорий 3rd-party/ и packages/:


yarn each /build each /test  # Выполнит `build`, а затем `test` во всех пакетах.  

Именованные партии задач


Для удобства работы в рабочем дереве (например Yarn Workspaces) партии задач можно именовать.


Допустим, есть корневой пакет с таким package.json:


{  "scripts": {    "all/*": "run-z ./packages//",    "z": "run-z"  }}

Здесь сценарий с именем "all/*" это описание именованной партии. С таким описанием становится возможным пакетное выполнение задач как находясь в корне, так и находясь во вложенных директориях:


yarn z build --all       # Выполняет `build` во всех пакетах.

Когда указана опция --all, run-z ищет самый верхний пакет, содержащий именованные партии задач, и выполняет задачи в этих партиях.


Имя именованной партии может быть "имя_партии/имя_задачи". Такая именованная партия используется вместо "имя_партии/*", когда выполняется задача "имя_задачи". Это полезно, например, когда нужно передать дополнительные опции в конкретную задачу:


{  "scripts": {    "all/*": "run-z ./packages//",    "all/test": "run-z ./packages// +test/--runInBand",    "z": "run-z"  }}

yarn z build --all  # Выполняет `build` партией.yarn z test --all   # Выполняет `test` партией с опцией `--runInBand`.

Граф зависимостей


Именованные партии позволяют выполнять задачи в подмножестве графа зависимостей текущего пакета:


yarn build --with-deps        # Выполняет `build` в зависимостях                              # и в самом пакете.yarn build --only-deps        # Выполняет `build` только в зависимостях.yarn build --with-dependants  # Выполняет `build` в пакете,                              # а затем во всех зависимых пакетах.yarn build --only-dependants  # Выполняет `build` только в зависимых пакетах.

Сравнение с npm-run-all


npm-run-all это весьма популярный инструмент для сборки. Прежде я использовал именно его и могу сравнивать.


Вот так выглядели сценарии сборки типичного проекта, использующего TypeScript, Rollup, ESLint и Jest:


{  "scripts": {    "all": "run-p --aggregate-output build:all \"test {@}\" --",    "build": "rollup --config ./rollup.config.js",    "build:all": "run-p --aggregate-output rebuild lint",    "ci:all": "run-p --aggregate-output build:all ci:test",    "ci:test": "jest --ci --runInBand",    "clean": "shx rm -rf d.ts dist target",    "lint": "eslint .",    "rebuild": "run-s clean build",    "test": "jest",  }}

run-p здесь выполняет перечисленные задачи параллельно. run-s последовательно.


И вот как это выглядит теперь:


{  "scripts": {    "all": "run-z build,lint,test",    "build": "run-z +z rollup --config ./rollup.config.js",    "ci:all": "run-z all +test/--ci/--runInBand",    "clean": "run-z +z --then shx rm -rf d.ts dist target",    "lint": "run-z +z --then eslint .",    "test": "run-z +z --then jest",    "z": "run-z +build,+doc,+lint,+test"  }}

  1. Вспомогательная задача "ci:test" для запуска тестов в окружении CI больше не нужна. Все необходимые параметры можно передать прямо в задачу "test".
  2. Вспомогательная задача "build:all" была нужна только чтобы выполнять задачи параллельно. Теперь их можно перечислить через запятую.
  3. Задача "rebuild" тоже стала не нужна, поскольку можно вызывать несколько задач прямо из командной строки: yarn clean build.
  4. Появилась задача "z". Она не просто для удобства, а ещё и разрешает параллельное выполнение некоторых задач. Так что yarn build lint test выполнит эти задачи параллельно. Не нужно каждый раз вспоминать, где поставить запятую.
  5. Все сценарии теперь начинаются с run-z. Это необязательно для простых сценариев, но даёт возможность применить настройки по умолчанию, а также запустить несколько задач сразу, например yarn clean build.
  6. Немаловажно также то, что run-z запускается единожды, сколько бы заданий не требовалось выполнить. А run-p или run-s запускаются каждым сценарием. Запуск V8 совсем не бесплатен.

Планы на будущее


В ближайших планах добавить поддержку расширений. Основной замысел в том, чтобы уметь запускать не только внешние команды, но и использовать thread_workers. Хотя бы вместо некоторых команд. Это позволит как сэкономить ресурсы, так и существенно ускорить сборку. Особенно для быстрых утилит типа shx rm. Ведь последняя тратит на порядки больше времени на свой запуск, чем, собственно, на работу.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru