基于 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 中所必要的:

  1. 前端构建工具不用提供 Web 服务,只需要监听前端代码变化自动执行构建。
  2. 前端构建工具必须支持复制 bundle 和静态资产到外部目录。

在旧的 Webpack 前端架构中,Phoenix 为我们生成的前端模板并不包含 webpack-dev-server,且多了一个 copy-webpack-plugin 插件配置,就是为了满足上面两点。

新前端

好消息是新版 Phoenix 并没有改变上面那套机制,我们仍然可以少量修改便集成自己定制的新前端。

生成前端模板

按照以下步骤,生成一个独立的前端:

  1. 创建一个新目录,命名为 webapps,作为新前端的根目录。
  2. webapps 目录执行 pnpm run vite . 生成一个基于 Vite 的前端模板。
  3. 删除 index.html,因为我们不使用 vite serve
创建 Vite 项目模板时选择的框架并不重要,因为框架通常不影响集成。

修改前端模板

接下来我们需要重点修改两个配置文件,一个是 package.json,另一个是 vite.config.ts

  1. package.jsonscripts 部分全部删除,替换为以下内容:

    {
      "watch": "vite build --watch --mode development",
      "build": "vite build --mode production"
    }
    
  2. 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
这个任务可以将 phx_classic_template 中的所有秘文字符串重写为新的随机内容。包括 secret_key_basesigning_salt 等。

通常上述两个任务可以让一个静态的 Phoenix 项目的改造达到类似 mix phx.new 的动态效果,且更加便利。对于模板项目自身而言也具有更强的定制性,下游合并上游变更也更容易。

加入我们

如果你也是 Elixir 开发者/爱好者,这里有一些我创建的群组:

添加 QQ 群时请填写来源为“博客”。注意请不要灌水,谢谢。

本文由作者按照 CC BY 4.0 进行授权
分享:

相关文章