Управление ресурсами
Исходный кодПомимо генерации 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, есть как минимум три способа добавить их в приложение:
-
Добавьте эти зависимости в проект и импортируйте их в "assets/js/app.js" по относительному пути:
import topbar from "../vendor/topbar"
-
Вызовите
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"
]
-
Используйте 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
- Удалите конфигурацию
esbuildизconfig/config.exsиconfig/dev.exs, - Удалите задачу
assets.deploy, определённую вmix.exs, - Удалите зависимость
esbuildизmix.exs, - Разблокируйте зависимость
esbuild:
$ mix deps.unlock esbuild
Альтернативные CSS-фреймворки
По умолчанию Phoenix генерирует CSS с помощью библиотеки tailwind и её плагинов по умолчанию.
Если вы хотите использовать внешние плагины tailwind или другой CSS-фреймворк, замените пакет Hex tailwind (см. инструкции ниже). Затем можно использовать плагин esbuild (как описано выше) или даже подключить совершенно другой фреймворк.
Удаление tailwind
- Удалите конфигурацию
tailwindизconfig/config.exsиconfig/dev.exs, - Удалите задачу
assets.deploy, определённую вmix.exs, - Удалите зависимость
tailwindизmix.exs, - Разблокируйте зависимость
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.
© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix.hexdocs.pm/asset_management.html