Chapter 16

アセット管理

koga1020
koga1020
2021.12.25に更新

アセット管理

ほとんどのWebアプリケーションは、HTMLを生成する以外に、さまざまなアセット(JavaScript、CSS、画像、フォントなど)を持っています。

Phoenix v1.6からは、新しいアプリケーションはesbuildを使い、Elixirのesbuildラッパーを介してアセットを準備します。このように esbuild と直接統合されたことで、新しく生成されたアプリケーションは、Node.jsや外部のビルドシステム(Webpackなど)に依存することがなくなりました。

通常、JavaScriptは "assets/js/app.js" に配置され、esbuild はそれを "priv/static/assets/app.js" に抽出します。開発環境では、この処理は esbuild ウォッチャーによって自動的に行われます。本番環境では、mix assets.deploy を実行することで行われます。

esbuild はCSSファイルも扱うことができます。そのためには、"assets/js/app.js "の先頭に、"import "../css/app.css" を記述します。他の方法については後述します。

最後に、通常は前処理の必要がないその他のアセットは、"priv/static" に直接格納されます。

サードパーティのJSパッケージ

JavaScript の依存関係をインポートしたい場合、アプリケーションに追加するには2つの方法があります。

  1. プロジェクト内に依存パッケージを配置して、"assets/js/app.js" に相対パスでインポートする

    import topbar from "../vendor/topbar"
    
  2. assetsディレクトリ内で npm install topbar -save を実行し、esbuild が自動的にそれらをピックアップする

    import topbar from "topbar"
    

CSS

esbuild はCSSを基本的にサポートしています。メインの .js ファイルの先頭で .css ファイルをインポートすると、esbuild はそれをバンドルして、最終的な app.js と同じディレクトリに書き込みます。これはPhoenixのデフォルトの動作です。

import "../css/app.css"

一方、CSSフレームワークを使用したい場合は、別途ツールを使用する必要があります。そのための選択肢をいくつか紹介します。

  • スタンドアロン Tailwind または スタンドアロン SASS を使うことができます。どちらも esbuild と同様です。

  • esbuild プラグインを使うことができます(npm が必要です)。以下の「Esbuildプラグイン」のセクションを参照してください。

その際、JavaScriptファイルから import "../css/app.css" を削除することを忘れないでください。

画像、フォント、外部ファイル

CSSファイルやJavaScriptファイルで外部ファイルを参照する場合、esbuild はとくに指示がない限り、検証と管理を試みます。

たとえば、/images/bg.png で提供されている priv/static/images/bg.png をCSSファイルから参照したいとします。

body {
  background-image: url(/images/bg.png);
}

上記の場合、以下のようなメッセージで失敗することがあります。

error: Could not resolve "/images/bg.png" (mark it as external to exclude it from the bundle)

画像はすでにPhoenixで管理されているので、エラーメッセージにあるように、/images(および /fonts)からのすべてのリソースを外部リソースとしてマークする必要があります。これは、v1.6.1以降の新しいアプリでは、Phoenixがデフォルトで行っていることです。config/config.exs には、次のように書かれています。

args: ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),

他のディレクトリを参照する必要がある場合は、上記の引数を適宜更新する必要があります。注意: mix phx.digest を実行すると、priv/static にあるすべてのアセットのダイジェストファイルが作成されるので、画像やフォントはキャッシュが削除されたままになります。

Esbuildプラグイン

Phoenix のデフォルト設定の esbuild (Elixirラッパー経由) では、esbuildプラグイン を使うことができません。SASSファイルをCSSにコンパイルするなど、esbuildプラグインを使用したい場合は、デフォルトのビルドシステムをカスタムビルドスクリプトで置き換えることができます。

以下に、Node.JS経由でesbuildを使ったカスタムビルドの例を示します。まず、開発用にNode.jsをインストールして、本番のビルドステップで利用できるようにします。

次に、Node.js のパッケージとPhoenixのパッケージに esbuild を追加します。assets ディレクトリの中で、実行します。

$ npm install esbuild --save-dev
$ npm install ../deps/phoenix ../deps/phoenix_html ../deps/phoenix_live_view --save

または、Yarnの場合は

$ yarn add --dev esbuild
$ yarn add ../deps/phoenix ../deps/phoenix_html ../deps/phoenix_live_view

次に、カスタムのJavaScriptビルドスクリプトを追加します。ここでは、例として assets/build.js とします。

const esbuild = require('esbuild')

const args = process.argv.slice(2)
const watch = args.includes('--watch')
const deploy = args.includes('--deploy')

const loader = {
  // Add loaders for images/fonts/etc, e.g. { '.svg': 'file' }
}

const plugins = [
  // Add and configure plugins here
]

let opts = {
  entryPoints: ['js/app.js'],
  bundle: true,
  target: 'es2017',
  outdir: '../priv/static/assets',
  logLevel: 'info',
  loader,
  plugins
}

if (watch) {
  opts = {
    ...opts,
    watch,
    sourcemap: 'inline'
  }
}

if (deploy) {
  opts = {
    ...opts,
    minify: true
  }
}

const promise = esbuild.build(opts)

if (watch) {
  promise.then(_result => {
    process.stdin.on('close', () => {
      process.exit(0)
    })

    process.stdin.resume()
  })
}

このスクリプトは、以下のユースケースをカバーしています。

  • node build.js: 開発とテストのためのビルド(CIで有用)
  • node build.js --watch: 上記に似ていますが、継続的に変更を監視します。
  • node build.js --deploy: 最小化されたアセットをプロダクション用にビルドします。

configure/dev.exs を変更して、ファイルを変更するたびにスクリプトが実行されるようにし、watchers の下にある既存の :esbuild 設定を置き換えます。

config :hello, HelloWeb.Endpoint,
  ...
  watchers: [
    node: ["build.js", "--watch", cd: Path.expand("../assets", __DIR__)]
  ],
  ...

mix.exsaliases タスクを修正して、mix setup の間に npm パッケージをインストールし、mix assets.deploy で新しい esbuild を使用するようにしました。

  defp aliases do
    [
      setup: ["deps.get", "ecto.setup", "cmd --cd assets npm install"],
      ...,
      "assets.deploy": ["cmd --cd assets node build.js --deploy", "phx.digest"]
    ]
  end

最後に config/config.exs から esbuild の設定を削除し、mix.exsdeps 関数から依存関係を削除すれば完了です。

esbuildを削除する

APIを書いている場合や、その他の理由でアセットを提供する必要がない場合は、アセット管理を完全に無効にできます。

  1. config/config.exsconfig/dev.exs にある esbuild の設定を削除する
  2. mix.exs で定義されている assets.deploy タスクを削除する
  3. mix.exs から esbuild の依存を削除する
  4. esbuild の依存をunlockする
$ mix deps.unlock esbuild