> For the complete documentation index, see [llms.txt](https://docs.johncms.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.johncms.com/10.0/moduli/publikaciya-modulya.md).

# Публикация модуля

Модуль, который распространяется другим сайтам, — обычный composer-пакет с одним значимым отличием: тип `johncms-module`.

## composer.json пакета

```json
{
    "name": "vasya/blog",
    "description": "Блог для JohnCMS",
    "type": "johncms-module",
    "license": "MIT",
    "require": {
        "php": "^8.4"
    },
    "autoload": {
        "psr-4": {
            "Vasya\\Blog\\": "src/"
        }
    }
}
```

`composer require vasya/blog` положит пакет в `vendor/vasya/blog`, и CMS найдёт его там: она спрашивает у composer, какие пакеты типа `johncms-module` установлены. Плагин для перемещения файлов не нужен, ничего никуда не переносится.

После установки пакета composer напечатает, что делать дальше — установка модуля остаётся отдельным шагом, потому что именно она выполняет миграции.

## Манифест

Пакет обязан нести `module.php` рядом с `composer.json`:

```php
<?php

declare(strict_types=1);

return [
    'key'      => 'vasya/blog',
    'alias'    => 'blog',
    'name'     => 'Блог',
    'version'  => '1.0.0',
    'requires' => [
        'php'     => '^8.4',
        'johncms' => '^10.0',
    ],
    'autoload' => ['psr-4' => ['Vasya\\Blog\\' => 'src/']],
];
```

* `key` должен совпадать с именем пакета и с путём к папке модуля;
* `alias` — короткое имя: пространство имён шаблонов (`@blog`), домен переводов, источник миграций. **После выпуска его менять нельзя** — под ним записан журнал миграций;
* `version` сравнивается при обновлении и подставляется в адреса ассетов;
* `requires.modules` перечисляет модули, без которых ваш не работает: система откажется выключить их, пока ваш установлен.

Пространство имён объявляется дважды — в `composer.json` и в манифесте. Первое нужно composer'у для пакета в `vendor/`, второе — системе, если модуль распакуют из архива в `modules/`.

## Zip-архив

Для сайтов без SSH соберите архив: одна папка, внутри неё `module.php` и всё остальное.

```
blog-1.0.0.zip
└── blog-1.0.0/
    ├── module.php
    ├── composer.json
    ├── config/
    ├── src/
    ├── templates/
    ├── migrations/
    └── public/
```

Имя папки внутри архива значения не имеет — куда попадёт модуль, решает `key` из манифеста. Если модуль тянет собственные библиотеки, положите в архив каталог `vendor/` и укажите его в манифесте:

```php
'autoload' => [
    'psr-4' => ['Vasya\\Blog\\' => 'src/'],
    'files' => ['vendor/autoload.php'],
],
```

Старайтесь обходиться без своих зависимостей: библиотека, которая уже есть в ядре, но другой версии, — источник трудноуловимых ошибок.

## Стили и скрипты

Модуль привозит **уже собранные** файлы: на обычном сайте нет ни node, ни сборщика.

```php
'assets' => [
    'source'  => 'public',
    'entries' => ['public' => ['js/blog.js', 'css/blog.css']],
],
```

При установке они копируются в `public/modules/<алиас>/`, при выключении — убираются. В document root попадают только файлы, которые загружает браузер: `.php` среди ассетов не окажется.

Отдельный файл подключается в шаблоне: `{{ module_asset('blog', 'images/logo.png') }}`. Всё перечисленное в `entries` макет темы выводит сам.

## Пункт меню

Чтобы модуль было видно, объявите пункт меню — реализуйте `Johncms\View\Menu\MenuItemProviderInterface`:

```php
public function menuItems(): iterable
{
    return [
        new MenuItem(MenuArea::Main, d__('blog', 'Блог'), '/blog/', icon: 'book', weight: 50),
        new MenuItem(MenuArea::Admin, d__('blog', 'Блог'), '/admin/blog', permission: 'blog.manage'),
    ];
}
```

Пункт, недоступный посетителю по правам, до шаблона не дойдёт. Тема выводит такие пункты сама, править её не нужно.

## Установка и удаление данных

Класс `src/Install/Installer.php` (наследник `Johncms\Modules\Installer`) — четыре метода, каждый из которых должен быть безопасен при повторном запуске:

| Метод                | Когда вызывается                                                                    |
| -------------------- | ----------------------------------------------------------------------------------- |
| `install()`          | после миграций при установке — настройки, справочники, каталоги                     |
| `update($from, $to)` | после миграций при обновлении                                                       |
| `uninstall()`        | при удалении, пока модуль ещё загружен — загруженные файлы, записи в общих таблицах |
| `installDemoData()`  | если при установке попросили демо-данные                                            |

Таблицы здесь не создаются: за них отвечают миграции модуля (`migrations/`). Если хотя бы одна миграция не умеет откатываться, удаление с очисткой данных будет отклонено — модуль не сможет убрать за собой.
