基于 Vite 和 TypeScript 改造 Phoenix 框架的前后端架构
前言
Phoenix 在早期(1.16 之前)还算一个务实的框架,它生成传统的前后端架构的项目模板。使用 Webpack 构建完全独立的前端部分。自 LiveView 发明以后,这个框架逐步偏离主流,直至移除了前端的整个 Node 技术栈。
现在 Phoenix 的前端部分基于 esbuild 和 Tailwind,推崇 LiveView 的新型渲染模型。虽然也是 SPA,但前端不再独立,基本是集成到后端模板里边了。和主流差异巨大,我们不再能方便的利用 Node 生态。
本文将告诉你如何将 Phoenix 改造为主流的前后端开发模式,并且使用更流行的 Vite 和 TypeScript。
集成原理
在旧的基于 Webpack 构建的前端架构中,Webpack 将 JS/CSS 的 bundle 和其它静态资产复制到 priv/static
目录下。我们每编辑一次前端代码,便会触发 Webpack 的重新构建,同时向 priv/static
目录写入新的文件。
但浏览器的刷新和 Webpack 无关,它是由 Phoenix.LiveReloader 监听到特定文件变化而触发的。这里的特定文件就是 assets 的输出目录,具体配置如:
config :app_name, AppNameWeb.Endpoint,
live_reload: [
patterns: [
~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$",
~r"priv/gettext/.*(po)$",
~r"lib/app_name_web/(controllers|live|components)/.*(ex|heex)$"
]
]
上面的 patterns
包括了所有被监听的文件范围。
以上我们能总结出两点,它们是将前端工具集成到 Phoenix 中所必要的:
- 前端构建工具不用提供 Web 服务,只需要监听前端代码变化自动执行构建。
- 前端构建工具必须支持复制 bundle 和静态资产到外部目录。
在旧的 Webpack 前端架构中,Phoenix 为我们生成的前端模板并不包含 webpack-dev-server
,且多了一个 copy-webpack-plugin
插件配置,就是为了满足上面两点。
新前端
好消息是新版 Phoenix 并没有改变上面那套机制,我们仍然可以少量修改便集成自己定制的新前端。
生成前端模板
按照以下步骤,生成一个独立的前端:
- 创建一个新目录,命名为
webapps
,作为新前端的根目录。 - 在
webapps
目录执行pnpm run vite .
生成一个基于 Vite 的前端模板。 - 删除
index.html
,因为我们不使用vite serve
。
修改前端模板
接下来我们需要重点修改两个配置文件,一个是 package.json
,另一个是 vite.config.ts
。
-
将
package.json
的scripts
部分全部删除,替换为以下内容:{ "watch": "vite build --watch --mode development", "build": "vite build --mode production" }
-
将
vite.config.ts
替换为以下内容:import process from "node:process"; import { defineConfig } from "vite"; export default defineConfig(({ mode }) => { // The development mode starts a watcher, we need to listen to stdin to avoid orphan processes. if (mode === "development") { // Terminate the watcher when Phoenix quits process.stdin.on("close", () => { process.exit(0); }); process.stdin.resume(); } return { build: { outDir: "../priv/static", emptyOutDir: true, rollupOptions: { input: { app: "./src/index.tsx", }, output: { entryFileNames: "assets/[name].js", // remove hash chunkFileNames: "assets/[name].js", assetFileNames: "assets/[name][extname]", }, }, }, }; });
这时候,我们执行 pnpm build
已经可以看到想要的结果,即新前端已被构建并输出到 priv/static
目录中。
对于上述修改,我还缺乏一些解释。后续本文会补充上更多解释,以照顾不太懂 Vite 和前端的人。
添加集成
到这里并未结束,因为 pnpm build
是发布生产环境版本时执行的,集成到 Phoenix 的开发模式中需调用 pnpm watch
。我们需要修改 config/dev.exs
:
watchers: [
# 省略可能存在的旧 watcher ...
pnpm: ["run", "watch", cd: Path.expand("../webapps", __DIR__)] # <- 添加新的 watcher
]
如果旧前端不想移除,你可以将新的命令添加在后面,因为 watchers
原本就支持配置多个。此时再启动 Phoenix 开发环境,便会看到与旧版本中默认集成的 Webpack 完全相同的效果。
watchers
配置了多个,请将 vite.config.ts
中的 build.emptyOutDir
改为 false
。否则 Vite 的构建会清空其它前端所生成的输出文件。修改后端模板
注意,到这里仍然没有结束。我们需要保证 Phoenix 生成的后端模板正确的引用了我们的前端资源。打开 lib/app_name_web/components/layouts/root.html.heex
文件,可以看到以下内容:
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
</script>
这里的 assets/app.<js|css>
和我们的前端输出在 priv/static
目录中的文件路径相对应即可。如果你保留有旧前端,那么通常需要新建一个模板布局,将其插入其中。我的前端一般不用 LiveView,会删掉 phx-track-static
这类节点属性。
我的模板
我个人使用自己的 Phoenix 模板,我将其定制成最简化和通用的样子。我总是使用最新的前端技术和版本,不使用 LiveView 但也没有移除它。仓库地址:Hentioe/phx_classic_template
为了使我的模板创建项目更加方便,我添加了一些 Mix 任务。这些任务可以将模板直接改造为自己的项目。
重命名模板:
mix phx.rename <your_app_name>
phx_classic_template
中的所有 app 名、模块名、文件、目录等命名改为自己的项目名。重新生成秘文:
mix phx.write.secrets
通常上述两个任务可以让一个静态的 Phoenix 项目的改造达到类似 mix phx.new
的动态效果,且更加便利。对于模板项目自身而言也具有更强的定制性,下游合并上游变更也更容易。
加入我们
如果你也是 Elixir 开发者/爱好者,这里有一些我创建的群组:
-
Telegram 交流群:
@elixircn_dev
-
QQ 交流群:
912763380
添加 QQ 群时请填写来源为“博客”。注意请不要灌水,谢谢。
相关文章
简单测试 Erlang/OTP 27 新增的 json 模块
前言 前不久 Erlang 出现了一个新的提案(EEP-68),该提案旨在将 JSON 的编码/解码功能引入到 Erlang/OTP 中。随着最近 27.0-rc2 版本的发布,新增的名为 json 的模块已经可用了
让 Void Linux 成为 Elixir 应用的基础镜像
前言 Void Linux 是一个忍不住想关注的发行版,它既可以较为精小,又可以相对膨胀。它同时维护 glibc 和 musl 两个不同 C 库的版本,又发布有内置 BusyBox 和 GNU Coreutils 两个不同工具集
Elixir 与 Rust 协作开发
前言 本文介绍的是我所使用的两门重要编程语言 Elixir 和 Rust,尤其是 Elixir 作为我个人的主力开发语言已有好几年时间。在前期我始终将它们独立使用,各自解决