Создайте пакет UX

Дата обновления перевода 2024-07-25

Создайте пакет UX

Tip

Перед тем как это читать, советуем просмотреть Наилучшие практики для повторно используемых пакетов.

Вот несколько секретов, чтобы сделать так, чтобы ваш пакет устанавливался как пакет UX.

Файл composer.json

Ваш файл composer.json должен иметь ключевое слово symfony-ux:

1
2
3
{
    "keywords": ["symfony-ux"]
}

Расположение ресурсов

Ваши ресурсы должны быть расположены в одном из следующих каталогов с файлом package.json, чтобы Flex мог их обрабатывать во время установки/обновления:

  • /assets (рекомендуется)
  • /Resources/assets
  • /src/Resources/assets

Файл package.json

Ваш файл package.json должен содержать конфигурацию symfony с определенными контроллерами, а также добавить необходимые пакеты в peerDependencies и importmap (список пакетов в importmap должен быть таким же, как и в peerDependencies):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
    "name": "@acme/feature",
    "version": "1.0.0",
    "symfony": {
        "controllers": {
            "slug": {
                "main": "dist/controller.js",
                "fetch": "eager",
                "enabled": true,
                "autoimport": {
                    "@acme/feature/dist/bootstrap4-theme.css": false,
                    "@acme/feature/dist/bootstrap5-theme.css": true
                }
            }
        },
        "importmap": {
            "@hotwired/stimulus": "^3.0.0",
            "slugify": "^1.6.5"
        }
    },
    "peerDependencies": {
        "@hotwired/stimulus": "^3.0.0",
        "slugify": "^1.6.5"
    }
}

В этом случае будет раскрыт файл, расположенный по адресу [assets directory]/dist/controller.js.

Tip

Вы можете либо написать сырой JS в этом файле dist/controller.js, либо вы можете, например, написать свой контроллер с помощью TypeScript и трансполировать его в JavaScript.

Вот пример, как это сделать:

  1. Добавьте следующее в ваш файл package.json:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.. code-block:: json

    {
        "scripts": {
            "build": "babel src --extensions .ts -d dist"
        },
        "devDependencies": {
            "@babel/cli": "^7.20.7",
            "@babel/core": "^7.20.12",
            "@babel/plugin-proposal-class-properties": "^7.18.6",
            "@babel/preset-env": "^7.20.2",
            "@babel/preset-typescript": "^7.18.6",
            "@hotwired/stimulus": "^3.2.1",
            "typescript": "^4.9.5"
        }
    }
  1. Добавьте следующее в ваш файл babel.config.js (должно располагаться рядом с вашим файлом package.json):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    module.exports = {
        presets: [
            ['@babel/preset-env', {
                "loose": true,
                "modules": false
            }],
            ['@babel/preset-typescript', { allowDeclareFields: true }]
        ],
        assumptions: {
            superIsCallableConstructor: false,
        },
    };
  2. Запустите npm install для установки новых зависимостей.
  3. Напишите ваш контроллер Stimulus с помощью TypeScript в файле src/controller.ts.
  4. Запустите npm run build или yarn run build, чтобы трансполировать ваш контроллер TypeScript в JavaScript.

Чтобы использовать ваш контроллер в шаблоне (например, определённом в вашем пакете), вы можете использовать его следующим образом:

1
2
3
4
5
6
7
8
9
10
<div
    {{ stimulus_controller('acme/feature/slug', { modal: 'my-value' }) }}
    {#
        will render:
        data-controller="acme--feature--slug"
        data-acme--feature--slug-modal-value="my-value"
    #}
>
    ...
</div>

Не забудьте добавить symfony/stimulus-bundle:^2.9 в качестве зависимости композитора для использования функций Twig stimulus_*.

Tip

Именование контроллеров: В этом примере name пакета PHP будет acme/feature, а имя контроллера в package.json - slug. Следовательно, полное имя контроллера для Stimulus будет таким: acme--feature--slug; хотя в функции stimulus_controller() вы можете использовать acme/feature/slug.

Каждый контроллер имеет ряд опций в файле package.json:

????? ????????
enabled ?????? ?? ???? ??????? ?????????? ?? ?????????.
main ???? ? ????? ???????????.
fetch ??? ?????????? ??????????? ? ??????????? ??? ???????? ????????. ??????????? eager (?? ?????????), ????? ???????? ?????????? ? ??????????? ? JavaScript, ??????? ??????????? ??? ???????? ????????. ??????????? lazy, ????? ??????? ?????????? ? ??????????? ?????????????? ? ????????? ????? ? ????????? ?? ?????? ??????????, ???? (? ?????) ?? ???????? ?????????? HTML ?????????? ??????.
autoimport ?????? ?????? ??? ??????? ? ????????????. ???????, ????????, ????? ???? ????????? ?????? CSS ? ??????????? ?? ????????????? ????????-?????????? (????????, Bootstrap 4 ??? 5, Tailwind CSS...). ???????? ?????? ???? ???????? ? ??????? ? ???????? ?????? ? ??????? ????????? ??? ??????? ?????, ????? ??????????, ????? ?? ????????????? ????.

Специфика Asset Mapper

Чтобы ресурсы вашего пакета работали с AssetMapper, вы должны добавить importmap в файл package.json, как указано выше, и добавить некоторые настройки к контейнеру:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
namespace Acme\FeatureBundle;

use Symfony\Component\AssetMapper\AssetMapperInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;

class AcmeFeatureBundle extends AbstractBundle
{
    public function prependExtension(ContainerConfigurator $configurator, ContainerBuilder $container): void
    {
        if (!$this->isAssetMapperAvailable($container)) {
            return;
        }

        $container->prependExtensionConfig('framework', [
            'asset_mapper' => [
                'paths' => [
                    __DIR__ . '/../assets/dist' => '@acme/feature-bundle',
                ],
            ],
        ]);
    }

    private function isAssetMapperAvailable(ContainerBuilder $container): bool
    {
        if (!interface_exists(AssetMapperInterface::class)) {
            return false;
        }

        // проверить, чтобы был установлен FrameworkBundle 6.3 или выше
        $bundlesMetadata = $container->getParameter('kernel.bundles_metadata');
        if (!isset($bundlesMetadata['FrameworkBundle'])) {
            return false;
        }

        return is_file($bundlesMetadata['FrameworkBundle']['path'] . '/Resources/config/asset_mapper.php');
    }
}