画像やJSをロード、"AssetMapper"

[Symfony Component Advent Calendar 2023](https://qiita.com/advent-calendar/2023/symfony-component)の10日目の記事です。


## 画像やJSをロード、"AssetMapper"

[AssetMapper](https://symfony.com/components/AssetMapper)はアセットのマッピングと、公開領域へのファイルの生成を行うコンポーネントです。
Symfony 6.3から導入された新しいコンポーネントですが、実はSymfonyでのフロントエンドの扱いが大きく変わるコンポーネントとなっており、Symfony公式ドキュメントでは[Webpack Encore](https://symfony.com/doc/current/frontend/encore/index.html)ではなく、AssetMapperを推奨しています。

### インストール

```shell
composer require symfony/asset-mapper
```

assetsディレクトリへのアクセスは別途`Asset`コンポーネントが必要です。また、当然ながら`Twig`も必要です。

```shell
composer require symfony/asset symfony/twig-pack
```

インストールすると、以下のファイルが作成されます。

- `assets/app.js`
- `assets/styles/app.css`
- `config/packages/asset_mapper.yaml`
- `importmap.php`

また、`templates/base.html.twig`が更新されます。

```diff
{% block javascripts %}
+     {{ importmap('app') }}
{% endblock %}
```

### 使い方

AssetMapperは主にふたつの機能があります。一つが`assets`ディレクトリに置いたファイルへアクセス・公開する機能、もう一つが、[importMap](https://developer.mozilla.org/ja/docs/Web/HTML/Element/script/type/importmap)を使ったJavaScriptのインポートです。

#### Assetへのアクセス
assetsディレクトリ内の画像などのURLは　`asset('{assetsディレクトリからの相対パス}')` を使うことで自動的に生成されます。

```twig
<img src="{{ asset('images/sample.png') }}">
<!-- /assets/images/sample-{バージョン文字列}.png -->
```
バージョンの文字列はファイルが更新されるたびに新しい文字列になります。

開発環境では特になにもせずにこの記述でアクセスできますが、本番環境ではassetsディレクトリのファイルを公開領域である`public`に移動される必要があります。移動させる場合は以下のコマンドを実行します。

```shell
bin/console asset-map:compile
```

実行すると、`public`にバージョン文字列を付与した状態でファイルがコピーされます。

#### ImportMap

開発環境では、JavaScriptは `templates/base.html.twig`に追記された`{{ importmap('app') }}`によって自動で読み込まれます。本番環境では先ほどの`bin/cosole asset-map:compile`を実行すればおなじく`public`領域に移動できます。

例：
```javascript:assets/sample.js
import Person from "./person.js";

export default class {
    hello() {
        const person = new Person()
        return person.hello('PHPer')
    }
}
```

```javascript:assets/person.js
export default class {
    hello = name => 'Hello ' + name;
}
```

```javascript:assets/app.js
import Sample from "./sample.js"

console.log(Sample.prototype.hello())
```

ブラウザのコンソール
```
Hello PHPer
```

### サードパーティ製のパッケージの読み込み

サードパーティ製は`import`時にCDNでアクセスすれば読み込めます。

```javascript
import { Alert } from 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/+esm';
```

ですが、めんどくさいです。AssetMapperはnpmパッケージ上のサードパーティ製パッケージの情報を`importmap.php`に追記することで、HTML出力時に自動的にパッケージをimportします。追記する場合は、以下のコマンドを使うと簡単に追記できます。

```shell
bin/console importmap:require bootstrap
```

```php:importmap.php
return [
    'app' => [
        'path' => './assets/app.js',
        'entrypoint' => true,
    ],
    'bootstrap' => [
        'version' => '5.3.0',
    ],
];
```

このようにすれば、`import`時の記述はこのようにできます。

```javascript
import { Alert } from 'bootstrap';
```

さて、上記のコマンドを実行してなぜサードパーティのパッケージにアクセスできるかというと、`{{ importmap('app') }}`の出力が以下のようになっているからです。

```html
<script type="importmap">
{
    "imports": {
        "app": "/assets/app-f389ccbe276b2074b6a0fa768c244921.js",
        "/assets/sample.js": "/assets/sample-248cf7faeba93e0400a862a6d448a0da.js",
        "/assets/person.js": "/assets/person-b64cf9aac50c2f6da0b6a2ba027b68e6.js",
        "bootstrap": "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/+esm",
    }
}
</script>
```

これにより、`import`時に該当のモジュールを解決することができます。
同様にサードパーティのCSSもapp.jsに`import`すれば利用できます。

上記のようにimportmapが作成され、CDN経由で該当のスクリプトをロードします。ここまでのコマンドを実行していただくとわかるのですが、**`packages.json`は作られません**。つまり、このコンポーネントでサードパーティ製のパッケージを利用するのであれば、npmは不要です。

:::note alert
なぜかSymfony7になるとapp.jsで読み込んだjsの中のimport（上記例だとperson.js）が読み込まれない模様。修正が入ると思います。
:::

## まとめ

今回は`AssetMapper`について紹介しました。以前紹介した[Symfony UX](https://qiita.com/ippey_s/items/fa19147cc4ff1e91ead8)もWebpack EncoreとAssetMapperのどちらでも動作するようになっており、今後の動向が非常に気になるコンポーネントです。