Phoenix 资产管线完全指南:从 esbuild、Tailwind 到自定义构建脚本
Phoenix 资产管线完全指南从 esbuild、Tailwind 到自定义构建脚本【免费下载链接】phoenixPeace of mind from prototype to production项目地址: https://gitcode.com/gh_mirrors/ph/phoenix本文基于 Phoenix 官方指南 guides/asset_management.md 整理而成全面讲解 Phoenix v1.7 默认的资产Asset管理方案如何用 esbuild 打包 JavaScript、用 Tailwind 编译 CSS、引入第三方 JS 包、处理图片字体等外部资源以及如何替换默认构建工具esbuild、Tailwind、图标库。读完本文你将掌握从mix setup到mix assets.deploy的完整资产构建链路并能根据项目需求自由定制构建脚本。一、Phoenix 资产管线概览告别 Node.js 依赖除了生成 HTML绝大多数 Web 应用还需要处理各类静态资源JavaScript、CSS、图片、字体等。从 Phoenix v1.7 起新生成的应用通过 esbuild经由 Elixir 的 esbuild 封装库和 tailwindcss经由 Elixir 的 tailwindcss 封装库来准备资产。这种直接集成意味着新应用不再依赖 Node.js 或外部构建系统如 Webpack纯 Elixir 环境即可完成全部构建。Phoenix 资产的默认流向非常清晰JavaScript 源码放在assets/js/app.js由esbuild打包输出到priv/static/assets/js/app.js开发环境下这一过程由esbuild的 watcher文件监听器自动完成生产环境下通过运行mix assets.deploy完成构建esbuild也能处理 CSS但默认情况下 CSS 全部交由tailwind构建其余无需预处理的静态资源图片、字体、favicon 等直接放入priv/static目录。在仓库的安装器模板 installer/templates/phx_single/config/config.exs.eex 中可以看到新应用的默认配置esbuild版本锁定为0.25.4tailwind版本为4.3.0。Phoenix 项目自身也采用同样的策略——在 config/config.exs 中Phoenix 用 esbuild 0.25.4 将自己的assets/js/phoenix源码分别打包为 ESMphoenix.mjs、CJSphoenix.cjs.js和浏览器全局版phoenix.js/phoenix.min.js并在 mix.exs 中定义了assets.build、assets.watch别名这正是本文要讲的构建机制在 Phoenix 自身项目中的实践。二、引入第三方 JS 包三种可选方案如果你的应用需要引入 JavaScript 依赖有以下三种途径方案一本地内置Vendor把依赖源码直接放进项目里然后在assets/js/app.js中用相对路径导入import topbar from ../vendor/topbar这种方式最简单直接不引入任何包管理工具缺点是升级依赖需要手动同步源码。方案二使用 npm 管理在assets目录下执行npm install topbar --prefix assets这会在assets目录内创建package.json和package-lock.jsonesbuild会自动识别并解析这些依赖import topbar from topbar为了确保在检出项目或构建 release 时自动安装依赖需要在mix.exs的assets.deploy和assets.build步骤中加入cmd --cd assets npm ciassets.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中声明一个 git 依赖# mix.exs {:topbar, github: buunguyen/topbar, app: false, compile: false}运行mix deps.get拉取依赖然后照常导入import topbar from topbar新生成的应用正是用这种方案引入图标如 Heroicons好处有三不必在项目里内置一份所有图标的副本、不需要额外安装npm等系统依赖、同时还能通过 Mix 锁定精确版本。需要注意的是git 依赖无法被 Hex 包使用如果计划把项目发布到 Hex需要改用其他方案。提示如果使用了第三方 JS 包管理器可能需要调整部署步骤以正确包含这些包。若使用mix phx.gen.release --docker生成 Docker 部署请参考 Mix.Tasks.Phx.Gen.Release 中关于 Docker 的文档说明。三、图片、字体与外部文件--external与 loader当 CSS 或 JS 中引用了外部文件时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下的资源标记为 external。自 Phoenix v1.6.1 起新应用默认就带上了这一配置位于config/config.exsargs: ~w( js/app.js --bundle --formatesm --targetes2022 --outdir../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:. ),如果还需要引用其他目录请相应更新上述参数。另外运行mix phx.digest会为priv/static中所有资产生成带内容指纹digest的文件所以你的图片和字体依然能获得缓存失效cache-busting能力。关于 digest 的底层实现可参见 Phoenix.Digester 及 Phoenix.Digester.Gzip后者负责对.js/.map/.css/.txt/.text/.html/.json/.svg/.eot/.ttf等扩展名做 gzip 预压缩见 mix.exs 中的gzippable_exts配置。第三方库的字体与图片无法加载怎么办如果你导入的 Node 包依赖额外的字体或图片你可能会发现它们加载失败。原因在于这些资源虽然在 JS/CSS 中被引用但默认情况下 esbuild 不会处理或复制被引用的文件。解决办法是在config/config.exs中为 esbuild 增加 loader 参数让被引用的资源被复制到输出目录。下面的例子会把所有被引用的字体文件复制到输出目录args: ~w( js/app.js --bundle --formatesm --targetes2022 --outdir../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:. --loader:.woffcopy --loader:.ttfcopy --loader:.eotcopy --loader:.woff2copy ),更多细节可参考 esbuild 官方文档中关于 copy loader 的内容类型。四、使用 esbuild 插件自定义构建脚本Phoenix 默认的 esbuild 配置经由 Elixir 封装库不支持 esbuild 插件。如果你想使用插件——比如用 esbuild 把 SASS 编译成 CSS——就需要用自定义构建脚本替换默认构建系统。准备环境首先需要在开发环境安装 Node.js并确保生产构建步骤也能访问它。然后在assets目录下把esbuild加入 Node.js 包并安装 Phoenix 相关的 JS 包$ 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编写自定义构建脚本新建assets/build.jsconst 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为生产环境构建压缩版资产。接入 Phoenix 的三步配置第一步修改config/dev.exs让脚本在文件变化时自动运行替换原:esbuild在watchers下的配置config :hello, HelloWeb.Endpoint, ... watchers: [ node: [build.js, --watch, cd: Path.expand(../assets, __DIR__)] ], ...第二步修改mix.exs中的aliases让mix setup安装 npm 包并让mix assets.deploy使用新的 esbuilddefp 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.exs的deps函数中移除 esbuild 依赖至此完成切换。作为对照仓库安装器模板 installer/templates/phx_single/mix.exs.eex 中展示了新应用的默认别名结构assets.setup依次执行各构建器的install --if-missingassets.build执行compile加各构建器assets.deploy则对每个构建器加--minify后执行phx.digest开发环境的 watcher 配置见 installer/templates/phx_single/config/dev.exs.eexesbuild: {Esbuild, :install_and_run, [:your_app, ~w(--sourcemapinline --watch)]}与tailwind: {Tailwind, :install_and_run, [:your_app, ~w(--watch)]}。五、替换 JS 构建工具移除 esbuild如果你开发的是纯 API或想换用其他资产构建工具可以移除esbuildHex 包然后遵循所选第三方工具自身的步骤。移除 esbuild 共四步删除config/config.exs和config/dev.exs中的 esbuild 配置删除mix.exs中定义的assets.deploy任务从mix.exs移除 esbuild 依赖解锁 esbuild 依赖$ mix deps.unlock esbuild六、替换 CSS 框架移除 tailwind默认情况下Phoenix 使用tailwind库及其默认插件生成 CSS新应用还默认启用了 daisyUI 等插件见 installer/templates/phx_assets/app.css.eex。如果你想使用外部的 tailwind 插件或其他 CSS 框架应替换tailwindHex 包步骤见下之后既可以用 esbuild 插件如第四节所述也可以直接引入一套独立的框架。移除 tailwind 的步骤与移除 esbuild 类似删除config/config.exs和config/dev.exs中的 tailwind 配置删除mix.exs中定义的assets.deploy任务从mix.exs移除 tailwind 依赖解锁 tailwind 依赖$ mix deps.unlock tailwind如果不再需要也可以一并移除并删除heroicons依赖。七、替换图标库以 Remix Icon 为例Phoenix 内置了 Heroicons 展示了这一机制它是一个 Tailwind 插件遍历deps/heroicons/optimized下的四个尺寸目录24/outline、24/solid、20/solid、16/solid将每个 SVG 文件编码为data:image/svgxmlURL 并注册为hero-*组件类同时在assets/css/app.css中通过plugin ../vendor/heroicons;挂载。如果你偏爱其他图标集可以改造这段内嵌代码。下面以 Remix Icon 为例第一步把mix.exs中的heroicon仓库替换为remixicons{:remixicons, github: Remix-Design/RemixIcon, sparse: icons, tag: v4.6.0, app: false, compile: false, depth: 1},第二步把遍历 heroicons 依赖的assets/vendor/heroicons.js替换为遍历 remix icons 的assets/vendor/remixicons.jsconst 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/svgxml;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改为导入你的新插件。第四步更新lib/my_app_web/components/core_components.ex中的icon函数改为匹配ri-前缀。在新应用模板 installer/templates/phx_web/components/core_components.ex.eex 中icon函数只处理hero-前缀的图标如hero-information-circle、hero-x-mark、hero-arrow-path等替换后则按如下方式匹配 Remix 图标doc Renders a Remix Icon. You can customize the size and colors of the icons by setting width, height, and background color classes. ## Examples .icon nameri-github-fill / .icon nameri-github classml-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-hiddentrue/i end完成以上步骤后把应用里的 Heroicons 换成 Remix 图标即可。这套思路对其他图标库同样适用核心工作就是写一个 Tailwind 插件去遍历对应图标库的 SVG生成合适的 CSS 类。另外部分图标集也以常规 Hex 包的形式提供可以进一步简化集成。结语Phoenix 的资产管线设计围绕零 Node.js 依赖与默认配置开箱即用展开开发时 watcher 实时构建发布时mix assets.deploy一键产出压缩资产并配合phx.digest生成指纹缓存。当默认能力无法满足需求时无论是要用 esbuild 插件、更换 JS/CSS 构建工具还是替换图标库Phoenix 都提供了清晰的替换路径。理解这条管线能让你在从原型到生产的路上对前端资源的构建、部署与缓存机制做到心中有数。【免费下载链接】phoenixPeace of mind from prototype to production项目地址: https://gitcode.com/gh_mirrors/ph/phoenix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考