Перейти к содержимому

12. Пакеты

Пакет — это каталог, содержащий манифест package.archspace и любое количество файлов .arch под ним. Манифест задаёт имя пакета, объявляет его зависимости от других пакетов, указывает на скрипт виджетов и выбирает, какие виды импортируются в область видимости.

name: acme.shop
version: "1.4.0"
dependencies {
acme.shared: "../shared"
acme.payments: "../packages/payments"
}
use * from arch.modules
use * from arch.kinds
use Database, Cache from acme.shared

Это полноценный манифест. В этой главе разбирается каждое поле, каждое диагностическое сообщение и правила разрешения.

Анонимные пакеты

Каталог без манифеста — это анонимный пакет: загрузчик обходит все файлы .arch под ним и разрешает их вместе без имени, без зависимостей, без экспорта. Анонимные пакеты предназначены для маленьких черновиков и обратной совместимости. В реальных проектах всегда есть манифест.

Поля

name: (обязательное, ровно один раз)

Идентификатор пакета. Точечная форма принята как соглашение:

name: acme.shop
  • Минимум один сегмент.
  • По соглашению в нижнем регистре; не принуждается.
  • Префикс arch.* зарезервирован за встроенной стандартной библиотекой. Пользовательский пакет вне каталога stdlib/ инструментария, претендующий на arch.<что-либо>, вызывает ошибку RESERVED_PACKAGE_NAMESPACE.

Дублирующиеся строки name: — ошибка; побеждает первое вхождение, чтобы у инструментария было нечто стабильное.

version: (необязательное, ровно один раз)

version: "1.4.0"

Строка произвольной формы. На данный момент только информационная — загрузчик не разбирает, не сравнивает и не ограничивает версии. Поле зарезервировано на будущее; будущий инструментарий будет использовать диапазоны в стиле semver.

widgets: (необязательное, ровно один раз)

Путь (относительно каталога манифеста) к JS/TS-модулю, который регистрирует виджеты-пользовательские элементы через customElements.define():

widgets: "./widgets.js"

Просмотрщик динамически импортирует этот скрипт при запуске и переимпортирует его при изменениях файла (после полной перезагрузки страницы — customElements.define срабатывает один раз для имени тега).

Загрузчик проверяет существование файла во время загрузки. Опечатка немедленно вызывает WIDGETS_FILE_NOT_FOUND. Конвейер виджетов — тема Главы 20.

dependencies { ... } (необязательное, не более одного блока)

Отображение имени пакета-зависимости на путь в файловой системе:

dependencies {
acme.shared: "../shared"
acme.payments: "../../packages/payments"
}

Для каждой записи загрузчик:

  1. Разрешает путь относительно каталога манифеста.
  2. Рекурсивно загружает пакет-зависимость (его собственные зависимости тоже загружаются).
  3. Проверяет, что name: загруженного пакета совпадает с именем, объявленным зависящим. Несовпадение вызывает DEP_NAME_MISMATCH.

Ромбовидные зависимости (A→B, A→C, обе →D) загружают D один раз и используют один и тот же экземпляр — загрузчик кэширует по абсолютному пути.

Циклы (A→B, B→A) вызывают DEP_CYCLE для каждого пакета вдоль цикла.

Пакетам arch.* запись не нужна. Загрузчик автоматически разрешает их через встроенную стандартную библиотеку инструментария (настраивается через ARCHLANG_STDLIB).

use … from <pkg> (ноль или более)

Импортирует типы из зависимости или пакета стандартной библиотеки:

// Импорт одного типа
use database from arch.modules
// Несколько
use service, frontend from arch.modules
// Wildcard — все экспортированные типы
use * from arch.modules
// Переименование
use database as managed_db from arch.modules
// Реэкспорт, чтобы потребители ЭТОГО пакета тоже его видели
export use payments_provider from acme.payments
ФормаЭффект
use X from pИмпортирует X напрямую. X должен быть отмечен как export в p — иначе USE_TYPE_NOT_EXPORTED.
use * from pИмпортирует каждый тип, отмеченный как export в p.
use X as Y from pИмпортирует X под локальным именем Y.
export use X from pРеэкспортирует X, чтобы wildcard-импортёры этого пакета тоже получили X.

Область видимости

  • use, объявленный в package.archspace, имеет область пакета: каждый файл .arch в этом пакете может ссылаться на импортированные имена.
  • use, объявленный внутри файла .arch, имеет область файла: только этот файл может на них ссылаться. Ссылки из соседнего файла вызывают KIND_NOT_VISIBLE_IN_FILE.

То же имя, импортированное из того же исходного пакета в нескольких файлах, — нормально; области накапливаются. То же имя из разных исходных пакетов вызывает USE_NAME_COLLISION; переименуйте одно через as.

Анонимные пакеты пропускают проверку export: каждый тип доступен. Как только пакет получает манифест, типы должны быть отмечены export, чтобы быть видимыми зависимым.

Диагностические коды

КодКогда
MANIFEST_PARSE_ERRORНеправильный синтаксис манифеста
RESERVED_PACKAGE_NAMESPACEПакет вне стандартной библиотеки претендует на префикс arch.*
DEP_LOAD_FAILEDПуть объявленной зависимости не существует или не разбирается
DEP_NAME_MISMATCHname: загруженного пакета отличается от ожидания зависящего
DEP_CYCLEЦикл в графе зависимостей пакетов
STDLIB_NOT_FOUNDИмпорт arch.* не удалось разрешить через настроенную стандартную библиотеку
USE_PACKAGE_NOT_FOUNDuse ... from <pkg> ссылается на пакет, которого нет ни в dependencies, ни в arch.*
USE_TYPE_NOT_FOUNDНазванный тип не существует в исходном пакете
USE_TYPE_NOT_EXPORTEDТип существует, но не отмечен как export
USE_NAME_COLLISIONОдно и то же локальное имя импортировано из двух разных исходных пакетов
WIDGETS_FILE_NOT_FOUNDПуть в widgets: не разрешается в существующий файл

Проработанные примеры

Минимальный черновик:

name: scratch

Черновой проект, который ни от чего не зависит. Каждый файл .arch в каталоге загружается и может импортировать друг из друга; никакие типы arch.* не видны, потому что use не объявлен.

Стандартный проект со стандартной библиотекой:

name: acme.shop
version: "0.4.0"
use * from arch.modules
use * from arch.kinds

Самая распространённая форма. Подтягивает встроенные виды модулей (service, database, …) и виды интерфейсов взаимодействия (command, query, event, …).

Многопакетный монорепозиторий:

name: acme.app
version: "1.0.0"
widgets: "./widgets.js"
dependencies {
acme.shared: "../shared"
acme.payments: "../packages/payments"
}
use * from arch.modules
use Database, Cache from acme.shared
export use payments_provider from acme.payments

Объявляет зависимости, импорты для всего проекта плюс реэкспорт, чтобы всё, что зависит от acme.app, также подхватывало payments_provider.

Библиотечный пакет:

name: acme.shared
version: "2.1.0"

Библиотека определяет типы в своих файлах .arch и помечает публичные через export. Потребители подключают их по имени через use; неэкспортированные типы остаются внутренними.

Структура файлов

my-project/
├── package.archspace # этот файл
├── widgets.js # регистрации пользовательских элементов (необязательно)
├── orders.arch # типы / модули / процессы / проекции
├── kinds.arch # типы, локальные для проекта
└── packages/
└── shared/
├── package.archspace # вложенный пакет — СВОЯ единица
└── lib.arch

Вложенный package.archspace — это жёсткая граница: обход файлов родителя останавливается на каталоге вложенного манифеста, а вложенный пакет разрешается как собственная единица, когда на него явно ссылаются через dependencies или он автоматически загружается как arch.*.

Резюме

  • В каждом проекте есть манифест package.archspace в корне.
  • Поля: name: (обязательное), version:, widgets:, dependencies { }, use … from ….
  • arch.* зарезервировано за стандартной библиотекой и разрешается автоматически; пользовательским зависимостям нужен путь.
  • use в манифесте имеет область пакета; use в файле .arch имеет область файла.
  • Ромбовидные зависимости разделяют один экземпляр; циклы вызывают DEP_CYCLE.

Что дальше

Глава 13: Стабильные идентификаторы → — идентичность, переживающая переименования, и где идентификаторы применимы, а где нет.