Project Icon

live_svelte

Phoenix LiveView 与 Svelte 的端到端响应式集成方案

LiveSvelte 是一个将 Svelte 组件集成到 Phoenix LiveView 的工具,实现了端到端的响应式体验。它支持服务器端渲染、Svelte 预处理、Tailwind 和 Dead View 等功能,简化了客户端状态管理和服务器实时通信。LiveSvelte 适用于复杂本地状态管理、JavaScript 生态系统集成、Svelte 动画和作用域 CSS 等场景,有助于提高开发效率和应用性能。

LiveSvelte

GitHub Hex.pm

在Phoenix LiveView中使用Svelte,实现无缝端到端响应式

logo

功能特性资源演示安装使用部署

功能特性

  • ⚡ 与LiveView实现端到端响应式
  • 🔋 服务器端渲染(SSR)Svelte
  • 🪄 Sigil作为替代LiveView DSL
  • ⭐ 支持使用svelte-preprocess进行Svelte预处理
  • 🦄 支持Tailwind
  • 💀 支持Dead View
  • 🤏 支持live_json
  • 🦥 插槽互操作性(实验性)

资源

演示

完整介绍和演示请查看YouTube介绍视频

/examples/advanced_chat

https://user-images.githubusercontent.com/3637265/229902870-29166253-3d18-4b24-bbca-83c4b6648578.webm


Svelte处理聊天的外观和感觉,而LiveView负责同步。对Svelte组件的端到端响应式意味着我们不需要真正获取任何东西!输入名字的"登录"是一个简单的LiveView表单。混合使用!

/examples/breaking_news

https://user-images.githubusercontent.com/3637265/229902860-f7ada6b4-4a20-4105-9ee9-79c0cbad8d72.webm


新闻项目与服务器同步,而速度仅在客户端控制。

为什么选择LiveSvelte

Phoenix LiveView通过服务器渲染的HTML实现丰富的实时用户体验。它通过websocket通信任何状态变化并实时更新DOM。无需编写任何客户端代码就能获得非常好的用户体验。

LiveSvelte在Phoenix LiveView的基础上,允许轻松管理客户端状态,同时仍然允许通过websocket进行通信。

使用LiveSvelte的理由

  • 你有(复杂的)本地状态
  • 你想充分利用JavaScript的生态系统
  • 你想利用Svelte的动画
  • 你想要作用域CSS
  • 你喜欢Svelte及其开发体验 :)

要求

为了使服务器端渲染(SSR)正常工作,你需要在环境中安装node(19版或更高版本)。

确保你在生产环境中也安装了它。你可能在构建步骤中使用node,但实际上它可能没有安装在你的生产环境中。

你可以通过在项目目录中运行node --version来确保安装了node

如果你不想使用SSR,可以通过不在application.ex中设置NodeJS.Supervisor来禁用它。更多相关内容请参阅本文档的SSR部分。

安装

如果你正在从旧版本更新,请确保查看CHANGELOG.md以了解破坏性变更。

  1. mix.exs中将live_svelte添加到Phoenix应用的依赖列表中:
defp deps do
  [
    {:live_svelte, "~> 0.13.3"}
  ]
end
  1. 调整mix.exs中的setupassets.deploy别名:
  • Windows系统
defp aliases do
  [
    setup: ["deps.get", "ecto.setup", "cmd --cd assets npm install"],
    ...,
    "assets.deploy": ["tailwind <app_name> --minify", "cmd --cd assets node build.js --deploy", "phx.digest"]
  ]
end
  • Linux/MacOS系统
defp aliases do
  [
    setup: ["deps.get", "ecto.setup", "npm install --prefix assets"],
    ...,
    "assets.deploy": ["tailwind <app_name> --minify", "node build.js --deploy --prefix assets", "phx.digest"]
  ]
end

注意:只有在使用Tailwind时,assets.deploy别名中才需要tailwind <app_name> --minify。如果你不使用Tailwind,可以从列表中移除它。

  1. 在终端中运行以下命令
mix deps.get
mix live_svelte.setup
  1. /lib/<app_name>_web.ex文件的html_helpers/0中添加import LiveSvelte,如下所示:
# /lib/<app_name>_web.ex

defp html_helpers do
  quote do

    # ...

    import LiveSvelte  # <-- 添加此行

    # ...

  end
end
  1. 对于Tailwind支持,在tailwind.config.js文件中将"./svelte/**/*.svelte"添加到content
...
content: [
  ...
  "./svelte/**/*.svelte"
],
...
  1. 最后,从config/config.exs中移除esbuild配置,并从mix.exsdeps函数中移除依赖,这样就完成了!

我们做了什么?

Phoenix的esbuild默认配置(通过Elixir包装器)不允许你使用esbuild插件。标准的Elixir esbuild包对于使用Phoenix钩子的简单项目来说效果很好,但要使用LiveSvelte,我们需要一个更复杂的设置。

为了使用插件,Phoenix建议用构建脚本替换默认的构建系统。因此,在设置中,我们从使用标准的esbuild包转向直接使用esbuild作为node_module

因此,你会注意到一些相关的变化:

  • /assets 中创建了一些文件。
  • /lib 中有一些代码更改。
  • 我们不再使用标准的 Elixir esbuild 观察器,而是创建了一个执行相同功能的新观察器。

设置过程注释掉了一些代码行,比如 dev.exs 中的配置。如果您愿意,可以安全地删除被注释掉的代码。

使用方法

Svelte 组件需要放在 assets/svelte 目录中

属性:

  • name:指定 Svelte 组件
  • props(可选):提供您想要使用的应该是响应式的 props,作为 props 字段的映射
  • class(可选):提供 class 来设置 Svelte 根元素的 class 属性
  • ssr(可选):将 ssr 设置为 false 以禁用服务器端渲染

例如,如果您的组件名为 assets/svelte/Example.svelte

def render(assigns) do
  ~H"""
  <.svelte name="Example" props={%{number: @number}} socket={@socket} />
  """
end

如果您的组件在一个目录中,例如 assets/svelte/some-directory/SomeComponent.svelte,您需要在名称中包含目录:some-directory/SomeComponent

Components 宏

还有一个 Elixir 宏,它会检查您的 assets/svelte 文件夹中的任何 Svelte 组件,并将这些组件的本地函数 def 注入到调用模块中。

这允许在 Liveviews 中使用更类似 JSX 的编写体验。

例如,在下面的示例中,一个名为 Example 的 Svelte 组件可以在 Liveview 模板中调用:

use LiveSvelte.Components

def render(assigns) do
  ~H"""
  <.Example number={@number} socket={@socket} />
  """
end

示例

示例可以在 /examples/example_project 目录中找到。

大多数 /example_project 示例在 YouTube 演示视频 中可见。

我建议克隆 live_svelte 并通过运行以下命令来运行 /example_project 中的示例项目:

git clone https://github.com/woutdp/live_svelte.git
mix assets.build
cd ./live_svelte/example_project
npm install --prefix assets
mix deps.get
mix phx.server

服务器应该在 localhost:4000 上运行

如果您有想要添加的示例,欢迎创建 PR,我很乐意添加它们。

创建 Svelte 组件

<script>
    // number prop 是响应式的,
    // 这意味着如果服务器分配了 number,它将在前端更新
    export let number = 1
    // live 包含所有可供前端使用的导出 LiveView 方法
    export let live

    function increase() {
        // 这会通过 websocket 推送事件
        // 最后一个参数是可选的。它是事件完成时的回调。
        // 例如,如果事件需要较长时间,您可以设置一个加载状态,直到事件完成。
        live.pushEvent("set_number", {number: number + 1}, () => {})

        // 注意,我们实际上从未在前端设置 number!
        // 我们只是将事件推送到服务器。
        // 这就是端到端响应性的作用!
        // number 将通过 LiveView websocket 自动更新
    }

    function decrease() {
        live.pushEvent("set_number", {number: number - 1}, () => {})
    }
</script>

<p>数字是 {number}</p>
<button on:click={increase}>+</button>
<button on:click={decrease}>-</button>

注意:这里我们使用 pushEvent 函数,但如果您愿意,也可以使用 phx-clickphx-value-number

live 上可用的方法有:

  • pushEvent
  • pushEventTo
  • handleEvent
  • removeHandleEvent
  • upload
  • uploadTo

这些需要在客户端运行,不能在 SSR 中运行。确保它们在某个动作(例如点击按钮)上被调用,或者用 onMount 包装它们。

更多信息请参阅 LiveView 文档中关于 js-interop 的部分

创建 LiveView

# `/lib/app_web/live/live_svelte.ex`
defmodule AppWeb.SvelteLive do
  use AppWeb, :live_view

  def render(assigns) do
    ~H"""
    <.svelte name="Example" props={%{number: @number}} socket={@socket} />
    """
  end

  def handle_event("set_number", %{"number" => number}, socket) do
    {:noreply, assign(socket, :number, number)}
  end

  def mount(_params, _session, socket) do
    {:ok, assign(socket, :number, 5)}
  end
end
# `/lib/app_web/router.ex`
import Phoenix.LiveView.Router

scope "/", AppWeb do
  ...
  live "/svelte", SvelteLive
  ...
end

LiveSvelte 作为替代 LiveView DSL

相关的博客文章

我们可以更进一步,使用 LiveSvelte 作为标准 LiveView DSL 的替代品。这个想法受到 Surface UI 的启发。

看看下面的例子:

defmodule ExampleWeb.LiveSigil do
  use ExampleWeb, :live_view

  def render(assigns) do
    ~V"""
    <script>
      export let number = 5
      let other = 1

      $: combined = other + number
    </script>

    <p>这是 number:{number}</p>
    <p>这是 other:{other}</p>
    <p>这是 other + number:{combined}</p>

    <button phx-click="increment">增加</button>
    <button on:click={() => other += 1}>增加</button>
    """
  end

  def mount(_params, _session, socket) do
    {:ok, assign(socket, :number, 1)}
  end

  def handle_event("increment", _value, socket) do
    {:noreply, assign(socket, :number, socket.assigns.number + 1)}
  end
end

使用 ~V 符号而不是 ~H,您的 LiveView 将使用 Svelte 而不是 HEEx 模板。

安装

  1. 如果在 html_helpers/0 中尚未导入,请在项目的 live_view 函数中添加 import LiveSvelte,这可以在 /lib/<app_name>_web.ex 中找到:
def live_view do
  quote do
    use Phoenix.LiveView,
      layout: {ExampleWeb.Layouts, :app}

    import LiveSvelte
```elixir
unquote(html_helpers())
end
end
  1. 在你的 .gitignore 中忽略构建文件。sigil 会创建 Svelte 文件,这些文件会被 esbuild 拾取,这些文件不需要包含在你的 git 仓库中:
# 忽略由 ~V sigil 自动生成的 Svelte 文件
/assets/svelte/_build/

Neovim Treesitter 配置

要在 Neovim 中启用 Treesitter 的语法高亮,创建以下文件:

~/.config/nvim/after/queries/elixir/injections.scm

; extends

; Svelte
(sigil
  (sigil_name) @_sigil_name
  (quoted_content) @svelte
(#eq? @_sigil_name "V"))

同时确保在 Treesitter 中安装了 Svelte 和 Elixir。

选项

可以通过在 mount 中设置 svelte_opts 来传递选项,请查看以下示例:

def mount(_params, _session, socket) do
  {:ok, assign(socket, some_value: 1, svelte_opts: %{ssr: false, class: "example-class"})}
end

LiveView 实时导航事件

在 Svelte 中你可以定义实时导航链接。这些链接可以在不刷新页面的情况下从一个 LiveView 导航到另一个。

例如,当你有一个 Svelte store 并且想在导航过程中保持这个 store 状态时,这会很有用。Svelte store 使用的示例可以在 /examples/store 中找到。

push_navigate

<a href="/your-liveview-path" data-phx-link="redirect" data-phx-link-state="push">重定向</a>

push_patch

<a href="/your-liveview-path" data-phx-link="patch" data-phx-link-state="push">补丁</a>

LiveView JavaScript 互操作性

LiveView 允许很多互操作性,你可以在这里阅读更多相关内容: https://hexdocs.pm/phoenix_live_view/js-interop.html

预处理器

要使用预处理器,请安装所需的预处理器。

例如 Typescript

cd assets && npm install --save-dev typescript

SSR (服务器端渲染)

如果你不熟悉 SSR (服务器端渲染),它是 Svelte 的一个功能,用于在服务器上渲染 Svelte。这意味着在首次页面加载时,你会看到 HTML 而不是空白页面。紧接着首次页面加载之后,页面会被"水合",这是一个花哨的词,表示为你的组件添加交互性。这个过程在后台进行,你不会注意到这个步骤的发生。

LiveSvelte 通过 LiveView 更新自身的方式是让 Svelte 处理所有的 HTML 编辑。通常 LiveView 会通过 websocket 传递消息来编辑 HTML。在我们的情况下,我们只通过 websocket 将我们在 props 属性中提供的数据传递给 Svelte。LiveView 不会触及任何 HTML,Svelte 负责处理这些。

如前所述,没有 SSR 你会看到未渲染内容的短暂闪烁。有时你可以不在服务器上渲染 Svelte,例如当你的 Svelte 组件在首次页面加载时不做任何渲染,需要用户手动切换可见性时。或者当它是一个没有视觉组件的组件,比如跟踪你的鼠标光标并将其发送回服务器。

在这些情况下,你可以关闭 SSR。

禁用 SSR

当你安装 LiveSvelte 时,SSR 默认是启用的。如果你不想使用 Svelte 的服务器端渲染,你有两个选择:

全局

如果你不想在任何组件上使用 SSR,你可以全局禁用它。 如果你没有在 application.ex 文件中包含 NodeJS 监督者,这将自动成为默认情况。

组件

要在特定组件上禁用 SSR,将 ssr 属性设置为 false。像这样:

<.svelte name="Example" ssr={false} />

live_json

LiveSvelte 支持 live_json

默认情况下,LiveSvelte 通过 LiveView 将整个 json 对象发送到网络上。如果你的 json 对象很大并且经常变化,这可能会很昂贵。

另一方面,live_json 允许你只向 Svelte 发送 json 的差异。随着你的 json 对象越来越大,这非常有用。

与直觉相反,你并不总是想使用 live_json。有时重新发送整个对象更便宜。虽然差异很小,但它们确实会给你的 json 添加一些数据。所以如果你的 json 相对较小,我建议不要使用 live_json,但这是需要根据你的用例进行实验的。

用法

  1. 安装 live_json

  2. 在你的项目中使用 live_json 和 LiveSvelte。例如:

def render(assigns) do
  ~H"""
    <.svelte name="Component" live_json_props={%{my_prop: @ljmy_prop}} socket={@socket} />
  """
end

def mount(_, _, socket) do
  # 以某种方式获取 `my_big_json_object`
  {:ok, LiveJson.initialize("my_prop", my_big_json_object)}
end

def handle_info(%Broadcast{event: "update", payload: my_big_json_object}, socket) do
  {:noreply, LiveJson.push_patch(socket, "my_prop", my_big_json_object)}
end

示例

你可以在这里找到一个示例。

结构体和 Ecto

我们使用 Jason 来序列化你在 props 中传递的任何数据,以便 Javascript 可以处理。 默认情况下 Jason 不知道如何处理结构体,所以你需要自己定义它。

结构体

例如,如果你有一个像这样的常规结构体:

defmodule User do
  defstruct name: "John", age: 27, address: "Main St"
end

你必须定义 @derive

defmodule User do
  @derive Jason.Encoder
  defstruct name: "John", age: 27, address: "Main St"
end

但要小心,因为你可能会不小心泄露某些你不希望客户端访问的字段,你可以包含要序列化的字段:

defmodule User do
  @derive {Jason.Encoder, only: [:name, :age]}
  defstruct name: "John", age: 27, address: "Main St"
end

Ecto

在 ecto 的情况下,重要的是也要省略 __meta__ 字段,因为它不可序列化。

请查看以下示例:

defmodule Example.Planets.Planet do
  use Ecto.Schema
  import Ecto.Changeset
  @derive {Jason.Encoder, except: [:__meta__]}
```elixir
schema "planets" do
  field :diameter, :integer
  field :mass, :integer
  field :name, :string

  timestamps()
end

...
end

文档

更多相关文档:

注意事项

插槽互操作性

插槽互操作性仍处于试验阶段,请谨慎使用!

Svelte 没有官方的方法在挂载 Svelte 对象时设置插槽或在后续更改时更新它,这与属性不同。这使得在 Liveview 中对 Svelte 组件使用插槽变得脆弱。

服务器端渲染的初始 Svelte 渲染确实支持插槽,所以应该能按预期工作。

插槽最终可能会达到稳定状态,任何帮助都将不胜感激。如果你对 Svelte 的内部原理了解很多,你的帮助可能在这里是无价的!

欢迎报告任何相关的错误,特别欢迎提交 PR!

"秘密状态"

使用 LiveView 很容易保密。假设你有一个只在某些条件为 true 时才渲染的条件语句,在 LiveView 中,在显示之前无法知道该条件语句会显示什么,因为 HTML 是通过网络发送的。

对于 LiveSvelte,我们处理的是发送到 Svelte 的 JSON,Svelte 接收 JSON 数据并有条件地渲染内容。即使我们没有将条件设置为 true,Svelte 代码也会包含在条件变为 true 时显示的内容。

在很多情况下这不是问题,但可能会出现问题,你应该注意这一点。

LiveSvelte 开发

本地设置

示例项目

你可以使用 /example_project 来本地测试 live_svelte

自定义项目

你也可以使用自己的项目。

live_svelte 克隆到要测试的项目的父目录中。

mix.exs 中:

{:live_svelte, path: "../live_svelte"},

assets/package.json 中:

"live_svelte": "file:../../live_svelte",

构建静态文件

/assets/js 中进行更改,然后运行:

mix assets.build

或运行监视器:

mix assets.build --watch

发布

  • 确保你已构建最新的资源
  • 更新 README.md 中的版本
  • 更新 package.json 中的版本
  • 更新 mix.exs 中的版本
  • 更新更新日志

运行:

mix hex.publish
  • 为最新版本发布一个标签

部署

部署 LiveSvelte 应用与部署常规 Phoenix 应用相同,只是你需要确保在生产环境中安装了 nodejs(19 版或更高版本)。

以下指南展示了如何将 LiveSvelte 应用部署到 Fly.io,但也可以采取类似的步骤部署到其他托管提供商。 你可以在这里找到有关如何部署 Phoenix 应用的更多信息。

在 Fly.io 上部署

以下步骤用于部署到 Fly.io。本指南假设你将使用 Fly Postgres 作为数据库。有关如何部署到 Fly.io 的更多指导可以在这里找到。

  1. 生成 Dockerfile:
mix phx.gen.release --docker
  1. 修改生成的 Dockerfile 以安装 curl(用于安装 nodejs 19 版或更高版本),并添加一个步骤来安装我们的 npm 依赖:
# ./Dockerfile

...

# 安装构建依赖
- RUN apt-get update -y && apt-get install -y build-essential git \
+ RUN apt-get update -y && apt-get install -y build-essential git curl \
    && apt-get clean && rm -f /var/lib/apt/lists/*_*

+ # 为构建阶段安装 nodejs
+ RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && apt-get install -y nodejs

...

COPY assets assets

+ # 在 assets 目录中安装所有 npm 包
+ WORKDIR /app/assets
+ RUN npm install

+ # 切回构建目录
+ WORKDIR /app

...

# 开始一个新的构建阶段,以便最终镜像只包含
# 编译后的发布版本和其他运行时必需品
FROM ${RUNNER_IMAGE}

RUN apt-get update -y && \
-  apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates \
+  apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates curl \
   && apt-get clean && rm -f /var/lib/apt/lists/*_*

+ # 为生产环境安装 nodejs
+ RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && apt-get install -y nodejs

...

注意: nodejs 同时安装在构建阶段和最终镜像中。这是因为我们需要 nodejs 来安装 npm 依赖,同时在运行应用时也需要它。

  1. 使用 Fly.io CLI 启动你的应用:
fly launch
  1. 当提示调整设置时,选择 y:
? Do you want to tweak these settings before proceeding? (y/N) y

这将打开一个新窗口,你可以在其中调整启动设置。在数据库部分,选择 Fly Postgres 并为数据库输入一个名称。你可能还想将数据库更改为开发配置以避免额外费用。除非你想更改其他设置,否则可以保留其余设置不变。

确认后部署将继续。

  1. 部署完成后,运行以下命令查看已部署的应用!
fly apps open

致谢

Vue 替代方案

更喜欢 Vue? 有 LiveVue,它的功能与 LiveSvelte 完全相同,但使用 Vue。

LiveSvelte 项目

在公开项目中使用 LiveSvelte? 告诉我,我会将其添加到此列表中!

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号