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.js与app.css的构建原理(esbuild + Tailwind v4)、如何在不破坏框架约定前提下引入第三方库,并理解仓库模板与构建配置(app.css.eex、app.js.eex、config.exs.eex)之间的对应关系。
一、资产使用总则:开箱即用的两个 Bundle
规范第一条即明确:开箱即用(Out of the box)只支持app.js与app.css两个构建产物(bundle)。所有页面资源都必须汇聚到这两个入口文件中,由构建工具统一打包输出到priv/static/assets/。
这意味着两条硬性约束:
- 不能在布局模板中直接引用外部 vendor 脚本的
src或样式表的href(例如 CDN 上的 jQuery、某个图标库或字体 CSS); - 必须把 vendor 依赖
import进app.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.exs与config.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目录内完成:
- 将第三方库文件放入
assets/vendor/(如仓库自带的 topbar.js.eex,即 topbar 3.0.0 的 vendor 副本),然后在app.js中相对导入; - 或通过
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,才需要把phoenix、phoenix_html、phoenix_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_html、phoenix_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-loading、phx-submit-loading、phx-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.js与app.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),仅供参考