Phoenix 前端资产与样式开发规范:Tailwind CSS、JS/CSS 构建与 UI/UX 设计指南
2026/9/20 18:41:32 网站建设 项目流程

Phoenix 前端资产与样式开发规范:Tailwind CSS、JS/CSS 构建与 UI/UX 设计指南

【免费下载链接】phoenixPeace of mind from prototype to production项目地址: https://gitcode.com/gh_mirrors/ph/phoenix

导读

本指南基于 Phoenix 仓库中随应用脚手架一起生成的usage-rules/assets.md规范文档,系统讲解在 Phoenix 项目中编写 JavaScript 与 CSS 的硬性规则、Tailwind CSS 与 daisyUI 的正确使用方式、vendor 依赖的唯一合法引入途径,以及 UI/UX 设计的落地准则。读完本文,你将掌握 Phoenix 生成应用assets/目录下app.jsapp.css的构建原理(esbuild + Tailwind v4)、如何在不破坏框架约定前提下引入第三方库,并理解仓库模板与构建配置(app.css.eex、app.js.eex、config.exs.eex)之间的对应关系。

一、资产使用总则:开箱即用的两个 Bundle

规范第一条即明确:开箱即用(Out of the box)只支持app.jsapp.css两个构建产物(bundle)。所有页面资源都必须汇聚到这两个入口文件中,由构建工具统一打包输出到priv/static/assets/

这意味着两条硬性约束:

  • 不能在布局模板中直接引用外部 vendor 脚本的src或样式表的href(例如 CDN 上的 jQuery、某个图标库或字体 CSS);
  • 必须把 vendor 依赖importapp.js/app.css后使用,让依赖参与本地打包。

从脚手架模板可以清楚看到这两个入口的原始形态:

  • JS 入口 app.js.eex 中注释明确给出了两种引入 vendor 依赖的推荐方式:
    • 将依赖放入assets/vendor目录,使用相对路径导入:import "../vendor/some-package.js"
    • 或在assets目录执行npm install some-package --prefix assets,再以包名导入:import "some-package"
  • CSS 入口 app.css.eex 采用 Tailwind CSS v4 的@import "tailwindcss" source(none);作为根指令,并通过多个@source指令声明扫描范围(见下一节)。

模板还特别提示:若某个依赖会尝试引入 CSS,esbuild 会为其生成独立的app.css文件,此时需要在root.html.heex中追加第二个<link>标签来加载它——这是“只支持两个 bundle”原则下唯一的例外路径,且仍属于本地打包产物,而非外部链接。

二、样式开发规范:Tailwind 优先、禁止@apply、不用 daisyUI 组件

2.1 使用 Tailwind CSS 类与自定义 CSS 规则

规范要求使用Tailwind CSS 类 + 自定义 CSS 规则打造精致、响应式、视觉出众的界面。仓库生成的 app.css.eex 展示了 Tailwind v4 的标准接线方式:

@import "tailwindcss" source(none); @import "phoenix-colocated/<%= @web_app_name %>/colocated.css"; @source "../css"; @source "../js"; @source "../../lib/<%= @lib_web_name || @app_name %>"; /* Required for Tailwind to automatically pick up changes in colocated CSS files in dev */ @source "<%= if @in_umbrella do %>../../<% end %>../../_build/dev/phoenix-colocated/<%= @web_app_name %>/*/";
  • source(none)表示关闭 Tailwind 的自动内容探测,由显式@source指令接管扫描范围;
  • @source "../css"@source "../js"扫描assets目录下的样式与脚本;
  • @source "../../lib/..."让 Tailwind 能识别.heex模板与 colocated CSS 中的类名;
  • 最后一行确保开发环境下 colocated CSS 的变更能被 Tailwind 自动拾取。

2.2 绝对禁止在原始 CSS 中使用@apply

规范原文为"Never use@applywhen writing raw css",即手写原始 CSS 时严禁使用 Tailwind 的@apply指令。这条约束避免了@apply与 Tailwind v4 新 CSS 引擎之间潜在的编译歧义,也强制开发者以“纯 CSS 自定义属性 + Tailwind 工具类”的方式组织样式,保证生成的 CSS 可预测、可排查。

2.3 用自研 Tailwind 组件替代 daisyUI

规范要求编写自己的 Tailwind 组件,而不是使用 daisyUI,以获得独特、世界级(world-class)的设计风格。需要说明的是:仓库模板虽在 app.css.eex 中通过@plugin "daisyui/packages/bundle/daisyui"@plugin "daisyui/packages/bundle/daisyui-theme"引入了 daisyUI 的主题系统(内置一套受 Phoenix 配色启发的浅色主题与受 Elixir 配色启发的深色主题,均以 oklch 色值定义),但规范明确要求界面组件(按钮、卡片、表单等)应由开发者基于 Tailwind 自行实现,daisyUI 仅作为主题变量来源,不直接复用其现成组件类。

模板中可直接观察到这套主题接线:

@plugin "daisyui/packages/bundle/daisyui" { themes: false; } @plugin "daisyui/packages/bundle/daisyui-theme" { name: "dark"; default: false; prefersdark: true; color-scheme: "dark"; --color-base-100: oklch(30.33% 0.016 252.42); /* ... 其余语义色、圆角、边框、深度等设计令牌 ... */ } @plugin "daisyui/packages/bundle/daisyui-theme" { name: "light"; default: true; prefersdark: false; /* ... */ }

浅色主题为默认(default: true),深色主题通过prefersdark: true跟随系统偏好。开发时可通过data-theme=dark属性手动切换——这一点在文件末尾有专门声明:

/* Use the data attribute for dark mode */ @custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));

三、构建管线:esbuild 与 Tailwind 的配置对应关系

规范中“只支持两个 bundle”的落点,是脚手架mix.exsconfig.exs中的构建配置。以单应用(非 umbrella)模板为例:

在 mix.exs.eex 中声明构建工具依赖(仅在 dev 环境作为 runtime 依赖,避免生产环境重复执行构建):

{:esbuild, "~> 0.10", runtime: Mix.env() == :dev}, {:tailwind, "~> 0.5", runtime: Mix.env() == :dev}, {:heroicons, github: "tailwindlabs/heroicons", tag: "v2.2.0", sparse: "optimized", app: false, compile: false, depth: 1}, {:daisyui, github: "saadeghi/daisyui", tag: "v5.5.20", sparse: "packages/bundle", app: false, compile: false, depth: 1},

对应在 config.exs.eex 中给出 esbuild 与 Tailwind 的二进制版本与参数:

# Configure esbuild (the version is required) config :esbuild, version: "0.25.4", <%= @app_name %>: [ args: ~w( js/app.js --bundle --format=esm --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:@=. ), cd: Path.expand(".../assets", __DIR__), env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]} ] # Configure tailwind (the version is required) config :tailwind, version: "4.3.0", <%= @app_name %>: [ args: ~w( --input=assets/css/app.css --output=priv/static/assets/css/app.css ), cd: Path.expand("...", __DIR__), ... ]

要点解读:

  • esbuild 以js/app.js为入口,输出 ESM 格式(--format=esm),目标环境es2022,产物落入priv/static/assets/js
  • --external:/fonts/*--external:/images/*将字体、图片资源排除在打包之外,交由运行时路径解析;
  • NODE_PATH指向deps,使得 JS 里import "phoenix_html"import "phoenix_live_view"这类裸包名导入能解析到 mix 依赖目录;
  • Tailwind v4 以assets/css/app.css为输入、产出priv/static/assets/css/app.css,正是第一节中那套@import指令所在的文件。

配合mix.exs中定义的 aliases,日常资产操作全部封装为 mix 命令:

"assets.setup": ["esbuild.install --if-missing", "tailwind.install --if-missing"], "assets.build": ["compile", "esbuild <%= @app_name %>", "tailwind <%= @app_name %>"], "assets.deploy": ["esbuild <%= @app_name %> --minify", "tailwind <%= @app_name %> --minify", "phx.digest"],

开发中运行mix assets.setup安装构建二进制,mix assets.build增量编译;生产部署运行mix assets.deploy(压缩 + 指纹化 + 静态资源摘要)。这些命令与“只支持app.js/app.css”的约束共同保证了priv/static/assets/下产物的确定性。

四、vendor 依赖的唯一合法引入途径

规范将外部资源访问收敛为一条路径:把依赖打进本地 bundle。具体分为两步,全部在assets目录内完成:

  1. 将第三方库文件放入assets/vendor/(如仓库自带的 topbar.js.eex,即 topbar 3.0.0 的 vendor 副本),然后在app.js中相对导入;
  2. 或通过npm install <pkg> --prefix assets安装后以包名导入。

对应地,root.html.heex布局中不允许出现指向外部 CDN 的<script src="..."><link href="...">;自定义脚本也必须收敛进app.js严禁在模板内联<script>custom js</script>(见第五节)。

这条规则的底层原因可以从 tsconfig.json.eex 窥见:脚手架项目默认没有node_modules,esbuild 通过paths别名把裸导入映射到../deps/*,即 mix 依赖目录;若项目引入了package.json,才需要把phoenixphoenix_htmlphoenix_live_view等声明进 dependencies 指向../deps/...。换言之,不建立额外 node_modules 环境、依赖统一由 Hex/mix 管理,是这套资产体系的设计前提。

一个典型的app.js接线(来自 app.js.eex,LiveView 场景)展示了 vendor 依赖如何汇入单入口:

import "phoenix_html" import {Socket} from "<%= @phoenix_js_path %>" import {LiveSocket} from "phoenix_live_view" import {hooks as colocatedHooks} from "phoenix-colocated/<%= @web_app_name %>" import topbar from "../vendor/topbar" const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") const liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 2500, params: {_csrf_token: csrfToken}, hooks: {...colocatedHooks}, }) topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) liveSocket.connect() window.liveSocket = liveSocket

其中phoenix_htmlphoenix_live_view均为经NODE_PATH解析的 mix 依赖,topbar则是vendor目录中的本地副本——两类 vendor 引入方式在同一入口文件里并存示范。开发环境下,phx:live_reload:attached事件还会启用浏览器控制台的服务端日志流式输出,并支持按住c键点击元素跳转到调用处、按住d键跳转到组件定义处(配合PLUG_EDITOR)。

五、模板内禁止内联脚本

规范最后一条硬约束:绝不(Never)在模板中编写内联<script>custom js</script>标签。所有行为逻辑必须进入app.js及其导入的模块。理由包括:

  • 内联脚本无法参与 esbuild 打包,无法享受依赖解析、压缩、指纹化等收益;
  • 内联脚本绕过 CSP 与内容安全策略的审计面,且无法被 LiveView 的更新机制管理;
  • 与“单一 bundle 入口”约定冲突,导致行为代码分散、难以测试与复用。

非 LiveView 页面若有少量 DOM 行为(如 flash 消息关闭),也应像 app.js.eex 非 LiveView 分支那样以模块化方式集中编写:

document.querySelectorAll("[role=alert][data-flash]").forEach((el) => { el.addEventListener("click", () => { el.setAttribute("hidden", "") }) })

六、UI/UX 与设计规范

规范第二部分给出了与资产工程配套的设计产出要求,核心是“世界级 UI”(world-class UI designs),要点如下:

  • 可用性、美学与现代设计原则并重:布局、配色、层级以用户体验为先;
  • 实现微妙的微交互:如按钮 hover 效果、平滑过渡(smooth transitions),用最小成本提升操作反馈;
  • 保证排版、间距与布局平衡:追求精致、高端(premium)的视觉质感;
  • 关注令人愉悦的细节:hover 状态、加载状态(loading states)、平滑的页面转场。

这些设计准则在工程层面与资产规范互相支撑:例如加载状态可直接复用 topbar.js.eex 实现的页面顶部进度条(配合phx:page-loading-start/stop事件),而 hover 与过渡效果则依赖 Tailwind 工具类(hover:transition-*)在 app.css.eex 中声明的 LiveView 加载变体(phx-click-loadingphx-submit-loadingphx-change-loading@custom-variant)得以呈现。

七、与 UI 组件体系的协同约定

虽然assets.md主要面向资产与样式,但其规则与仓库其他 usage-rules(如 phoenix.md)中 UI 组件约定是配套的:图标一律使用core_components.ex引入的<.icon>组件(内部由 heroicons.js.eex 生成的hero-*Tailwind 组件类驱动,类名如hero-x-mark会自动从deps/heroicons/optimized目录读取对应 SVG 并以内联 data URI + mask 方式渲染),表单输入优先使用<.input>组件——这些组件本身正是“基于 Tailwind 自定义组件”这一规范的最佳实践产物,也解释了为何不推荐用 daisyUI 组件类:统一由core_components提供的组件族才能保证全站风格一致且可定制。

结语

Phoenix 的前端资产规范可以概括为三句话:一切资源汇入app.jsapp.css两个 bundle;样式以 Tailwind 类为主、原始 CSS 不用@apply、组件自研不套 daisyUI;模板内零内联脚本、零外部 vendor 链接。配合脚手架中 esbuild/Tailwind 的版本化配置与mix assets.*系列任务,这套体系让团队在获得现代构建能力的同时,保持产物的可预测、可审计与可部署性。开发者在遵循以上规则时,可随时对照仓库模板(app.css.eex、app.js.eex、config.exs.eex、mix.exs.eex)验证自己的接线方式是否与脚手架约定一致。

【免费下载链接】phoenixPeace of mind from prototype to production项目地址: https://gitcode.com/gh_mirrors/ph/phoenix

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询