Spec-Zone.ru › Phoenix

Управление ресурсами

Исходный код

Помимо генерации HTML, большинство веб-приложений используют различные ресурсы (JavaScript, CSS, изображения, шрифты и т. д.).

Начиная с Phoenix v1.7, новые приложения используют esbuild для подготовки ресурсов через обёртку esbuild для Elixir, а для CSS — tailwindcss через обёртку tailwindcss для Elixir. Прямая интеграция с esbuild и tailwind означает, что вновь сгенерированные приложения не зависят от Node.js или внешней системы сборки (например, Webpack).

Обычно JavaScript-код находится в "assets/js/app.js", а esbuild извлечёт его в "priv/static/assets/js/app.js". В режиме разработки это выполняется автоматически с помощью наблюдателя esbuild. В производственной среде это выполняется запуском mix assets.deploy.

esbuild также может обрабатывать файлы CSS, но по умолчанию за сборку всех CSS-файлов отвечает tailwind.

Наконец, все остальные ресурсы, которые обычно не требуют предварительной обработки, напрямую помещаются в "priv/static".

Сторонние пакеты JavaScript

Если вы хотите импортировать зависимости JavaScript, есть как минимум три способа добавить их в приложение:

  1. Добавьте эти зависимости в проект и импортируйте их в "assets/js/app.js" по относительному пути:

    import topbar from "../vendor/topbar"
  2. Вызовите npm install topbar --prefix assets, чтобы создать package.json и package-lock.json в каталоге ресурсов. После этого esbuild сможет автоматически обнаружить их:

    import topbar from "topbar"

    Чтобы убедиться, что npm install запускается при получении копии проекта или сборке релиза, добавьте этап "cmd --cd assets npm ci" в mix.exs перед этапами assets.deploy и assets.build:

      "assets.build": ["cmd --cd assets npm ci", "tailwind your_app", "esbuild your_app"],
      "assets.deploy": [
        "cmd --cd assets npm ci",
        "tailwind your_app --minify",
        "esbuild your_app --minify",
        "phx.digest"
      ]
  1. Используйте Mix для отслеживания зависимости из исходного репозитория:

    # mix.exs
    {:topbar, github: "buunguyen/topbar", app: false, compile: false}

    Запустите mix deps.get, чтобы получить зависимость, а затем импортируйте её:

    import topbar from "topbar"

    Новые приложения используют этот третий способ импорта значков, например Heroicons: так не нужно добавлять в проект копию всех значков или устанавливать дополнительные системные зависимости, такие как npm, при этом благодаря Mix по-прежнему можно отслеживать точные версии. Важно отметить, что git-зависимости нельзя использовать в пакетах Hex, поэтому, если вы планируете опубликовать проект в Hex, рассмотрите другие варианты.

Обратите внимание: если вы используете сторонние менеджеры пакетов JavaScript, возможно, потребуется скорректировать этапы развёртывания, чтобы пакеты были включены в сборку. Если вы используете mix phx.gen.release --docker, подробности см. в документации.

Изображения, шрифты и внешние файлы

Если в файлах CSS или JavaScript есть ссылка на внешний файл, esbuild попытается проверить его и управлять им, если не указать иное.

Например, предположим, что вы хотите сослаться из CSS-файла на priv/static/images/bg.png, доступный по адресу /images/bg.png:

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) как внешние, как и указано в сообщении об ошибке. В новых приложениях Phoenix делает это по умолчанию начиная с версии v1.6.1. В файле config/config.exs вы найдёте:

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

Если нужно ссылаться на другие каталоги, соответствующим образом обновите приведённые выше аргументы. Обратите внимание: запуск mix phx.digest создаст файлы с хешированными именами для всех ресурсов в priv/static, поэтому кеширование изображений и шрифтов по-прежнему будет сбрасываться.

Загрузка шрифтов и изображений из сторонних библиотек

Если вы импортируете пакет Node, который зависит от дополнительных шрифтов или изображений, они могут не загрузиться. Это происходит потому, что на них есть ссылки в JavaScript или CSS, но по умолчанию Esbuild не обрабатывает файлы, на которые ссылаются. Можно добавить аргументы esbuild в config/config.exs, чтобы указанные ресурсы копировались в выходной каталог. Следующий пример скопирует все файлы шрифтов, на которые есть ссылки, в выходной каталог и добавит к путям префикс /assets/:

args: ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/* --public-path=/assets/ --loader:.woff=copy  --loader:.ttf=copy --loader:.eot=copy --loader:.woff2=copy),

Дополнительную информацию см. в документации esbuild.

Плагины esbuild

Конфигурация esbuild по умолчанию в Phoenix (через обёртку для Elixir) не позволяет использовать плагины esbuild. Если вы хотите использовать плагин esbuild, например для компиляции файлов SASS в CSS, можно заменить систему сборки по умолчанию собственным скриптом сборки.

Ниже приведён пример собственной сборки с помощью esbuild через Node.js. Прежде всего, установите Node.js в среде разработки и обеспечьте его доступность на этапе производственной сборки.

Затем добавьте esbuild в пакеты Node.js и пакеты Phoenix. Выполните следующую команду в каталоге 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
];

// Define esbuild options
let opts = {
  entryPoints: ["js/app.js"],
  bundle: true,
  logLevel: "info",
  target: "es2022",
  outdir: "../priv/static/assets",
  external: ["*.css", "fonts/*", "images/*"],
  nodePaths: ["../deps"],
  loader: loader,
  plugins: plugins,
};

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

if (watch) {
  opts = {
    ...opts,
    sourcemap: "inline",
  };
  esbuild
    .context(opts)
    .then((ctx) => {
      ctx.watch();
    })
    .catch((_error) => {
      process.exit(1);
    });
} else {
  esbuild.build(opts);
}

Этот скрипт охватывает следующие сценарии:

  • node build.js: сборка для разработки и тестирования (полезно в CI)
  • node build.js --watch: как выше, но с непрерывным отслеживанием изменений
  • node build.js --deploy: сборка минифицированных ресурсов для производственной среды

Измените config/dev.exs, чтобы скрипт запускался при изменении файлов, заменив существующую конфигурацию :esbuild в watchers:

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

Измените задачу aliases в mix.exs, чтобы устанавливать пакеты npm во время mix setup и использовать новый esbuild для mix assets.deploy:

  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

Наконец, удалите конфигурацию esbuild из config/config.exs и удалите зависимость из функции deps в файле mix.exs — и готово!

Альтернативные инструменты сборки JavaScript

Если вы разрабатываете API или хотите использовать другой инструмент сборки ресурсов, можно удалить пакет Hex esbuild (см. инструкции ниже). Затем выполните дополнительные действия, необходимые для стороннего инструмента.

Удаление esbuild

  1. Удалите конфигурацию esbuild из config/config.exs и config/dev.exs,
  2. Удалите задачу assets.deploy, определённую в mix.exs,
  3. Удалите зависимость esbuild из mix.exs,
  4. Разблокируйте зависимость esbuild:
$ mix deps.unlock esbuild

Альтернативные CSS-фреймворки

По умолчанию Phoenix генерирует CSS с помощью библиотеки tailwind и её плагинов по умолчанию.

Если вы хотите использовать внешние плагины tailwind или другой CSS-фреймворк, замените пакет Hex tailwind (см. инструкции ниже). Затем можно использовать плагин esbuild (как описано выше) или даже подключить совершенно другой фреймворк.

Удаление tailwind

  1. Удалите конфигурацию tailwind из config/config.exs и config/dev.exs,
  2. Удалите задачу assets.deploy, определённую в mix.exs,
  3. Удалите зависимость tailwind из mix.exs,
  4. Разблокируйте зависимость tailwind:
$ mix deps.unlock tailwind

При желании можно также удалить зависимость heroicons.

Альтернативные библиотеки значков

В Phoenix входит библиотека значков Heroicons. Значки добавляются в виде CSS-классов, что благодаря Tailwind гарантирует отправку клиенту только тех значков, которые действительно используются приложением.

Если вы предпочитаете другой набор значков, можно изменить код, который добавляет Heroicons, чтобы использовать другую библиотеку. Рассмотрим, как это сделать на примере Remix Icon:

Сначала замените репозиторий heroicon в mix.exs на remixicons:

{:remixicons,
  github: "Remix-Design/RemixIcon",
  sparse: "icons",
  tag: "v4.6.0",
  app: false,
  compile: false,
  depth: 1},

Затем замените assets/vendor/heroicons.js, который обходит зависимость heroicons, на assets/vendor/remixicons.js, который вместо этого обходит remix icons:

const plugin = require("tailwindcss/plugin")
const fs = require("fs")
const path = require("path")

module.exports = plugin(function({matchComponents, theme}) {
  let baseDir = path.join(__dirname, "../../deps/remixicons/icons");
  let values = {};
  let icons = fs
    .readdirSync(baseDir, { withFileTypes: true })
    .filter((dirent) => dirent.isDirectory())
    .map((dirent) => dirent.name);

  icons.forEach((dir) => {
    fs.readdirSync(path.join(baseDir, dir)).map((file) => {
      let name = path.basename(file, ".svg");
      values[name] = { name, fullPath: path.join(baseDir, dir, file) };
    });
  });

  matchComponents(
    {
      ri: ({ name, fullPath }) => {
        let content = fs
          .readFileSync(fullPath)
          .toString()
          .replace(/\r?\n|\r/g, "");

        return {
          [`--ri-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
          "-webkit-mask": `var(--ri-${name})`,
          mask: `var(--ri-${name})`,
          "background-color": "currentColor",
          "vertical-align": "middle",
          display: "inline-block",
          width: theme("spacing.10"),
          height: theme("spacing.10"),
        };
      },
    },
    { values },
  );
})

После этого измените assets/css/app.css, чтобы импортировать новый плагин.

Наконец, обновите функцию icon в lib/my_app_web/components/core_components.ex, чтобы она распознавала префиксы ri-:

@doc """
Renders a [Remix Icon](https://remixicon.com).

You can customize the size and colors of the icons by
setting width, height, and background color classes.

## Examples

    <.icon name="ri-github-fill" />
    <.icon name="ri-github" class="ml-1 w-3 h-3 animate-spin" />
"""
attr :name, :string, required: true
attr :class, :any, default: "size-5"

def icon(%{name: "ri-" <> _} = assigns) do
  ~H"""
  <i class={[@name, @class]} aria-hidden="true"></i>
  """
end

Теперь замените Heroicons в приложении на Remix Icon — и готово!

Описанный выше подход может работать и с другими библиотеками: достаточно адаптировать плагин Tailwind, чтобы он обходил эти библиотеки и генерировал нужные классы. Некоторые наборы значков также могут быть доступны в виде обычных пакетов Hex.

← Предыдущая страница LiveView
Следующая страница → Telemetry

Пакет Hex Предварительный просмотр Hex (текущий файл) Скачать версию ePub

Создано с помощью ExDoc (v0.40.3) для языка программирования Elixir

© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix.hexdocs.pm/asset_management.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API