Артефакты этапа сборки
Модуль может поставлять каталог, который потребители используют на этапе
сборки, а не во время выполнения — чаще всего это пакет, относительно
которого компилируются другие модули. Wippy называет такие каталоги
артефактами: это обычные ресурсы файловой системы WAPP, помеченные
meta.artifact.format.
Именно так общий пакет попадает в модуль из другого репозитория. Псевдоним пути разрешается только внутри одного репозитория; артефакт же путешествует вместе с модулем.
Слой дизайна объясняет, что должно попадать в такой пакет, а что нет; эта страница описывает механизм его доставки.
Объявление артефакта
Производитель объявляет обычный fs.directory и помечает его форматом:
# src/_index.yaml
entries:
- name: package_fs
kind: fs.directory
meta:
comment: Пакет npm, который потребители материализуют на этапе сборки.
artifact:
format: node-package
directory: ./package
Больше ничего не меняется: ресурс встраивается в WAPP как любой другой
fs.directory — перечислите его в embed: в wippy.yaml или передайте
--embed в wippy publish и wippy pack; невстроенный каталог не попадает ни
в пакет, ни в валидацию. Объявленные артефакты валидируются при публикации
модуля и упаковке приложения, так что некорректный артефакт отклоняется при
публикации, а не у потребителя.
Форматы
Адаптер формата решает, как валидируется каталог, какую идентичность он имеет и куда он попадает. Wippy поставляется с одним встроенным:
| Формат | Владеет поддеревом | Валидирует |
|---|---|---|
node-package |
npm/ |
package.json |
node-package требует name и семантическую version, а также отклоняет
скрипты жизненного цикла preinstall, install, postinstall и prepare —
материализованный пакет не должен ничего выполнять при установке. Он пишет в
npm/<имя пакета> под корнем материализации.
Формат должен быть зарегистрирован в бинарнике, выполняющем работу. Хосты могут регистрировать дополнительные форматы; дублирующиеся имена и пересекающиеся корни отклоняются.
Материализация
Чаще всего запускать ничего не нужно. Материализованные выходные данные согласуются автоматически при:
- полном и точечном
wippy installиwippy update - холодном старте
- динамической установке, обновлении и удалении через Hub
Полная установка, обновление, холодный старт и согласование зависимостей во время выполнения выполняются точно: устаревшие выходные данные вычищаются. Точечная установка накладывает только выбранные модули и сохраняет выходные данные модулей, которые она не выбирала.
Локальные замены модулей проходят тот же цикл валидации и материализации, что и упакованные ресурсы, так что артефакт заменённого модуля ведёт себя как опубликованный.
Явная материализация
Для шага сборки, которому артефакт нужен до вовлечения среды выполнения, CLI предоставляет его напрямую:
wippy artifacts materialize <pack.wapp> <namespace:name> [--root <directory>]
--root по умолчанию равен .wippy. Ресурс должен объявлять
meta.artifact.format, и этот формат должен быть зарегистрирован в этом CLI.
Стоит чётко понимать, чего эта команда намеренно не делает: она не
разрешает зависимости модулей, не изменяет wippy.lock, не вызывает менеджеры
пакетов и не участвует в композиции времени выполнения. Она валидирует один
артефакт из одного WAPP и пишет его на диск.
Куда попадают выходные данные
artifact.materialization_root настраивает корень вывода, принадлежащий
приложению. По умолчанию это родительский каталог vendor-каталога
зависимостей. Каждый формат владеет непересекающимся поддеревом под ним, так
что вывод node-package всегда находится под <root>/npm/.
Материализация транзакционна. Содержимое валидируется и подготавливается, управляемые корни атомарно подменяются под блокировкой процесса, сбой откатывается вместе с окружающей транзакцией реестра, а прерванная подмена восстанавливается при следующем запуске.
Разбор примера: общий frontend-пакет
Модуль-производитель, единственная задача которого — опубликовать пакет; во время выполнения он ничего не обслуживает:
# platform/ui-kit/src/_index.yaml
version: "1.0"
namespace: kickside.ui_kit
entries:
- name: package_fs
kind: fs.directory
meta:
artifact:
format: node-package
directory: ./package
Потребитель материализует его в собственное дерево перед установкой зависимостей:
wippy artifacts materialize kickside-ui-kit-1.5.0.wapp \
kickside.ui_kit:package_fs --root ./.wippy
Это записывает ./.wippy/npm/@kickside/ui-kit. Потребитель подхватывает его
обычным glob-шаблоном workspaces, так что дальше разрешение — это обычное
разрешение node:
{
"workspaces": ["./.wippy/npm/@*/*"]
}
npm install
Из этой схемы стоит перенять две вещи:
- Пакет — это отдельный модуль, а не каталог внутри более крупного.
Артефакт несёт собственную версию
package.json, и привязка его к модулю, который меняется по не связанным причинам, вынуждает выпускать один каждый раз, когда меняется другой. - Потребитель разрешает его как обычную зависимость. После материализации нет никакого специфичного для Wippy пути импорта, и именно это позволяет собирать один и тот же исходный код внутри монорепозитория и вне его.
От начала до конца: разработка, цикл разработки, CI
Разработка производителя
Для пакета-артефакта обычно нечего собирать — каталог и есть поставляемый результат. Пакет со словарём CSS — это просто файлы плюс манифест:
platform/ui-kit/
├── src/_index.yaml # объявляет package_fs как артефакт
└── package/ # каталог, который становится npm-пакетом
├── package.json
├── kx-card.css
└── kx-state.css
{
"name": "@kickside/ui-kit",
"version": "1.5.0",
"type": "module",
"sideEffects": ["*.css"],
"exports": {
"./kx-card.css": "./kx-card.css",
"./kx-state.css": "./kx-state.css"
},
"files": ["kx-card.css", "kx-state.css", "package.json"]
}
sideEffects важен для пакета, состоящего только из CSS: без него бандлер
вправе счесть импортированную таблицу стилей мёртвым кодом и выбросить её.
Версия пакета должна совпадать с версией модуля. wippy publish проверяет
это и отклоняет несовпадение, так что поднимайте обе вместе. Это ещё одна
причина дать общему пакету собственный модуль, а не вкладывать его в более
крупный — иначе каждое несвязанное изменение в модуле-хосте вынуждает выпускать
пакет, и наоборот.
Публикация
# проверка без публикации
wippy publish --dry-run --version 1.5.0 --embed package_fs
# публикация
wippy publish --create --module-type library --module-visibility public --version 1.5.0 --embed package_fs
Объявленные артефакты валидируются в рамках публикации, так что package.json, не проходящий правила формата, отклоняется здесь, а не в сборке потребителя.
Цикл разработки
Публикация при каждой правке — это не цикл разработки. Упакуйте производителя локально и направьте шаг материализации потребителя на этот файл:
# из модуля-производителя
wippy pack /tmp/ui-kit-dev.wapp --embed package_fs
# потребители материализуют из локального пакета, а не из опубликованного
UI_KIT_WAPP=/tmp/ui-kit-dev.wapp make ui-kit MOD=workflows
Пусть это переопределение будет единственным отличием пути разработки от CI — переменная окружения, выбирающая файл пакета, и всё нижележащее идентично. Цикл разработки, который материализует иначе, чем CI, перестаёт предсказывать CI.
Встраивание в make и CI
Сделайте шаг материализации предусловием сборки потребителя, а не тем, что человек должен не забыть запустить:
UI_KIT_WAPP ?=
build:
@case " $(UI_KIT_CONSUMERS) " in *" $(MOD) "*) $(MAKE) ui-kit MOD=$(MOD);; esac
cd $(call fe_dir,$(MOD)) && npm run build
Тогда CI вообще не нужен отдельный шаг для артефактов: он запускает тот же
make build, UI_KIT_WAPP не задана, поэтому отрабатывает путь
«скачать и материализовать» относительно опубликованной версии, закреплённой в
build-inputs. Свежий чекаут не может собраться против устаревшего или
отсутствующего пакета, а контрибьютор, никогда не слышавший об артефактах,
всё равно получает корректную сборку.
Что всё ещё приходится делать вручную
wippy artifacts materialize намеренно узкая команда, поэтому сборка,
потребляющая артефакт, сейчас склеивает четыре шага сама. Знание того, какие
именно, избавляет от их повторного открытия:
1. Получение .wapp. Команда принимает путь к файлу пакета, а не ссылку
на модуль, и не разрешает зависимости — значит, что-то должно сначала скачать
производителя. Рабочий подход — крошечный проект Wippy, единственная задача
которого закрепить и скачать его:
# build-inputs/wippy.lock — проект, существующий только ради скачивания
directories:
modules: .wippy
src: ./src
modules:
- name: kickside/ui-kit
version: 1.5.0
hash: be1eafd5…
( cd build-inputs && wippy install )
wapp=$(ls build-inputs/.wippy/vendor/kickside/ui-kit-*.wapp | grep -v sha256 | sort | tail -1)
Закрепление здесь, а не в lock-файле приложения, держит вход этапа сборки вне графа зависимостей времени выполнения.
2. Материализация по одному разу на потребителя — в корень, видимый менеджеру пакетов потребителя:
wippy artifacts materialize "$wapp" kickside.ui_kit:package_fs --root ./ui/.wippy
3. Подключение package.json потребителя. Материализация пишет файлы; она
не правит манифесты. npm связывает пакет, только если потребитель объявляет
и glob workspaces, и зависимость:
{
"workspaces": ["./.wippy/npm/@*/*"],
"dependencies": { "@kickside/ui-kit": "*" }
}
Версия равна *, потому что материализованный пакет несёт свою собственную.
Автоматизируйте это и сделайте идемпотентным — если подключение отсутствует,
сборка падает намного позже с голым ENOENT на таблице стилей, что читается
как отсутствующий файл, а не как отсутствующее подключение.
4. Запуск менеджера пакетов. materialize его не вызывает, так что
npm install остаётся за вами, после шага 3.
Всё вместе, в цели, принимающей потребляющий модуль параметром:
ui-kit:
@set -e; \
( cd build-inputs && $(WIPPY) install ); \
wapp=$$(ls build-inputs/.wippy/vendor/kickside/ui-kit-*.wapp | grep -v sha256 | sort | tail -1); \
test -n "$$wapp" || { echo "no ui-kit .wapp; is the module published?"; exit 1; }; \
$(WIPPY) artifacts materialize "$$wapp" kickside.ui_kit:package_fs --root $(DIR)/.wippy; \
cd $(DIR) && node ../../scripts/wire-ui-kit.mjs && npm install --no-audit --no-fund
Сделайте всю цель предусловием сборки потребителя, чтобы свежий чекаут не мог собраться против устаревшего или отсутствующего пакета.
Вне области применения
Артефакты намеренно не вводят второй резолвер, реестр пакетов, формат архива, схему lock-файла, API Hub или манифест модуля. Семантика зависимостей только для сборки, политика распространения и валидация ABI хоста — это отдельные задачи, и здесь они не решаются.
Связанные материалы
- Управление зависимостями — разрешение модулей и локальные замены
- Публикация — что содержит опубликованный модуль
- Слой дизайна — почему общий frontend-словарь вообще поставляется как пакет