Phoenix 1.7-rc 发布

发布于 2022 年 11 月 7 日,作者 Chris McCord


Phoenix 1.7 的第一个候选版本发布了!Phoenix 1.7 包含了许多期待已久的新功能,例如已验证的路由、Tailwind 支持、LiveView 身份验证生成器、统一的 HEEx 模板等等。这是一个向后兼容的版本,包含一些弃用。大多数用户只需要更改几个依赖项就可以更新。

**注意**:在 rc 期间,您需要显式地从 hex 安装 phx.new 生成器来尝试一个新的项目

mix archive.install hex phx_new 1.7.0-rc.0

已验证的路由

已验证的路由用基于符号的 (~p) 编译时验证方法替换了路由助手。

**注意**:已验证的路由利用了 Elixir 1.14 的新编译器功能。Phoenix 仍然支持旧版本的 Elixir,但您需要更新才能享受新的编译时验证功能。

在实践中,这意味着之前您使用自动生成的函数,例如

  # router
  get "/oauth/callbacks/:id", OAuthCallbackController, :new

  # usage
  MyRouter.Helpers.o_auth_callback_path(conn, :new, "github")
  # => "/oauth/callbacks/github"

  MyRouter.Helpers.o_auth_callback_url(conn, :new, "github")
  # => "http://localhost:4000/oauth/callbacks/github"

现在您可以执行

  # router
  get "/oauth/callbacks/:id", OAuthCallbackController, :new

  # usage
  ~p"/oauth/callbacks/github"
  # => "/oauth/callbacks/github"

  url(~p"/oauth/callbacks/github")
  # => "http://localhost:4000/oauth/callbacks/github"

这有许多优点。不再需要猜测哪个函数被变位 - 是 Helpers.oauth_callback_path 还是 o_auth_callback_path 等等。您也不再需要在 99% 的时间里都已知应该使用哪个端点配置的情况下,在所有地方都包含 %Plug.Conn{},或 %Phoenix.Socket{},或端点模块。

现在还存在您在路由器中编写的路由与您使用 ~p 调用它们的路由之间的一对一映射关系。您只需像在应用程序中所有地方硬编码字符串一样编写它 - 除了没有与硬编码字符串相关联的维护问题。我们可以轻松地获得易用性和维护性方面的最佳效果,因为 ~p 是针对路由器中路由进行的编译时验证。

例如,假设我们对路由进行了拼写错误

<.link href={~p"/userz/profile"}>Profile</.link>

编译器将在编译时针对您的路由器调度所有 ~p,并在找不到匹配路由时通知您

    warning: no route path for AppWeb.Router matches "/postz/#{post}"
      lib/app_web/live/post_live.ex:100: AppWeb.PostLive.render/1

动态的“命名参数”也像普通字符串一样简单地进行插值,而不是任意函数参数

~p"/posts/#{post.id}"

此外,插值的 ~p 值通过 Phoenix.Param 协议进行编码。例如,您的应用程序中的 %Post{} 结构可以派生 Phoenix.Param 协议以生成基于 slug 的路径而不是基于 ID 的路径。这允许您在整个应用程序中使用 ~p"/posts/#{post}" 而不是 ~p"/posts/#{post.slug}"

已验证的路由也支持查询字符串,无论是传统查询字符串形式

~p"/posts?page=#{page}"

还是作为关键字列表或值映射

params = %{page: 1, direction: "asc"}
~p"/posts?#{params}"

与路径段类似,查询字符串参数经过正确 URL 编码,可以直接插值到 ~p 字符串中。

一旦您尝试了这个新功能,您将无法再返回到路由助手。新的 phx.gen.html|live|json|auth 生成器使用已验证的路由。

基于组件的 Tailwind 生成器

Phoenix 1.7 默认情况下附带了 TailwindCSS,无需系统上的 nodejs 依赖项。TailwindCSS 是我在 20 年的 Web 开发生涯中发现的构建界面最佳方法。它的实用优先方法比我使用过的任何 CSS 系统或框架更易于维护和生产。它的并置方法也与函数组件和 LiveView 的格局完美契合。

Tailwind 团队还慷慨地为新项目设计了新的项目着陆页、CRUD 页面和身份验证系统页面,为您提供了一个构建应用程序的一流和完善的起点。

一个新的 phx.new 项目将包含一个 CoreComponents 模块,其中包含一组核心 UI 组件,例如表格、模态框、表单和数据列表。Phoenix 生成器套件 (phx.gen.html|live|json|auth) 利用了核心组件。这有许多不错的优点。

首先,您可以根据自己的需要、设计和品味来定制核心 UI 组件。如果您想使用 Bulma 或 Bootstrap 而不是 Tailwind - 没问题!只需用您的框架/UI 特定实现替换 core_components.ex 中的函数定义,生成器将继续为新功能提供一个很棒的起点,无论您是初学者还是经验丰富的专家正在构建定制的产品功能。

在实践中,生成器为您提供使用核心组件的模板,看起来像这样

<.header>
  New Post
  <:subtitle>Use this form to manage post records in your database.</:subtitle>
</.header>

<.simple_form :let={f} for={@changeset} action={~p"/posts"}>
  <.error :if={@changeset.action}>
    Oops, something went wrong! Please check the errors below.
  </.error>

  <input field={{f, :title}} type="text" label="Title" />
  <input field={{f, :views}} type="number" label="Views" />

  <:actions>
    <.button>Save Post</.button>
  </:actions>
</.simple_form>

<.back navigate={~p"/posts"}>Back to posts></.back>

我们喜欢 Tailwind 团队为新应用程序设计的东西,但我们也迫不及待地想看到社区发布他们自己针对各种框架选择的 core_components.ex 的可直接替换方案。

跨控制器和 LiveView 的统一函数组件

由 HEEx 提供的函数组件,具有声明性分配和插槽,是我们编写 Phoenix 项目中 HTML 的方式的重大变化。函数组件提供 UI 构建块,允许封装功能并比 Phoenix.View 中之前的模板方法更好地扩展。您获得了更自然的方式来编写动态标记、可由调用方扩展的可重用 UI 以及编译时功能,使编写基于 HTML 的应用程序成为真正一流的体验。

函数组件带来了一种在 Phoenix 中编写 HTML 应用程序的新方法,并提供了一套新的约定。此外,用户一直在为如何在应用程序中将基于控制器的 Phoenix.View 功能与 Phoenix.LiveView 功能结合起来而苦苦挣扎。用户发现自己会在基于控制器的模板中编写 render("table", user: user),而他们的 LiveView 则使用了新的 <.table rows={@users}> 功能。没有很好的方法可以在应用程序中共享这些方法。

出于这些原因,Phoenix 团队统一了 HTML 渲染方法,无论是来自控制器请求还是来自 LiveView。这种转变也使我们能够重新审视约定并与 LiveView 将模板和应用程序代码并置在一起的方法保持一致。

新应用程序(以及 phx 生成器)删除了 Phoenix.View 作为依赖项,转而使用新的 Phoenix.Template 依赖项,该依赖项使用函数组件作为框架中所有渲染的基础。

您的控制器仍然保持相同

defmodule AppWeb.UserController do
  use MyAppWeb, :controller

  def index(conn, _params) do
    users = ...
    render(conn, :index, users: users)
  end
end

但是,控制器不再调用 AppWeb.UserView.render("index.html", assigns),现在将首先在视图模块上查找 index/1 函数组件,如果存在则调用该函数进行渲染。此外,我们还重命名了变位视图模块,以便查找 AppWeb.UserHTMLAppWeb.UserJSON 等等,以便采用每个格式一个视图的方法来渲染模板。所有这些都是以向后兼容的方式完成的,并且是基于对 use Phoenix.Controller 的选项进行的选择。

然后,所有 HTML 渲染都基于函数组件,这些组件可以直接在模块中编写,或者使用 Phoenix.Component 提供的新 embed_templates 宏从外部文件嵌入。新应用程序中的 PageHTML 模块看起来像这样

defmodule AppWeb.PageHTML do
  use AppWeb, :html

  embed_templates "page_html/*"
end

新的目录结构看起来像这样

lib/app_wb
├── controllers
│   ├── page_controller.ex
│   ├── page_html.ex
│   ├── error_html.ex
│   ├── error_json.ex
│   └── page_html
│       └── home.html.heex
├── live
│   ├── home_live.ex
├── components
│   ├── core_components.ex
│   ├── layouts.ex
│   └── layouts
│       ├── app.html.heex
│       └── root.html.heex
├── endpoint.ex
└── router.ex

您基于控制器的渲染或基于 LiveView 的渲染现在都共享相同的函数组件和布局。无论是运行 phx.gen.htmlphx.gen.live 还是 phx.gen.auth,新生成的模板都利用了您 components/core_components.ex 的定义。

此外,我们将视图模块并置在它们对应的控制器文件旁边。这带来了与 LiveView 并置相同的优势 - 高度耦合的文件在一起。现在,需要一起更改的文件在一起,无论是编写 LiveView 功能还是控制器功能。

这些更改都是关于编写基于 HTML 的应用程序的更好方法,但它们也简化了渲染其他格式,例如 JSON。例如,基于 JSON 的视图模块遵循相同的约定 - Phoenix 将首先在渲染索引模板时查找 index/1 函数,然后再尝试 render/2。这使我们能够简化一般的 JSON 渲染,并摒弃了诸如 Phoenix.View.render_one|render_many 这样的概念。

例如,这是由 phx.gen.json 生成的 JSON 视图

defmodule AppWeb.PostJSON do
  alias AppWeb.Blog.Post

  @doc """
  Renders a list of posts.
  """
  def index(%{posts: posts}) do
    %{data: for(post <- posts, do: data(post))}
  end

  @doc """
  Renders a single post.
  """
  def show(%{post: post}) do
    %{data: data(post)}
  end

  defp data(%Post{} = post) do
    %{
      id: post.id,
      title: post.title
    }
  end
end

注意,它只是常规的 Elixir 函数 - 本应该如此!

这些功能为应用程序提供了统一的渲染模型,以便在未来使用一种新的改进的方式来编写 UI,但它们偏离了之前的做法。大多数大型、成熟的应用程序可能最好继续依赖 Phoenix.View

替代 Web 服务器支持

感谢 Mat Trudel 的工作,我们现在有了 Plug 和 Phoenix 中一流的 Web 服务器支持的基础,允许将其他 Web 服务器(例如 Bandit)交换到 Phoenix 中,同时享受所有功能,例如 WebSockets、频道和 LiveView。如果您对纯 Elixir HTTP 服务器感兴趣,请关注 Bandit 项目,或在您自己的 Phoenix 项目中试用它!

与往常一样,逐步升级指南 可以在那里将您现有的 1.6.x 应用程序升级到 1.7。

完整的更改日志可以在 此处 找到。

如果您遇到问题,请在 Elixir Slack 或 论坛 上找到我们。

编码愉快!

–Chris