功能特性
- ⚡ 与LiveView实现端到端响应式
- 🔋 服务器端渲染(SSR)Svelte
- 🪄 Sigil作为替代LiveView DSL
- ⭐ 支持使用svelte-preprocess进行Svelte预处理
- 🦄 支持Tailwind
- 💀 支持Dead View
- 🤏 支持live_json
- 🦥 插槽互操作性(实验性)
资源
演示
完整介绍和演示请查看YouTube介绍视频
/examples/advanced_chat
Svelte处理聊天的外观和感觉,而LiveView负责同步。对Svelte组件的端到端响应式意味着我们不需要真正获取任何东西!输入名字的"登录"是一个简单的LiveView表单。混合使用!
/examples/breaking_news
新闻项目与服务器同步,而速度仅在客户端控制。
为什么选择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
以了解破坏性变更。
- 在
mix.exs
中将live_svelte
添加到Phoenix应用的依赖列表中:
defp deps do
[
{:live_svelte, "~> 0.13.3"}
]
end
- 调整
mix.exs
中的setup
和assets.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,可以从列表中移除它。
- 在终端中运行以下命令
mix deps.get
mix live_svelte.setup
- 在
/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
- 对于Tailwind支持,在
tailwind.config.js
文件中将"./svelte/**/*.svelte"
添加到content
中
...
content: [
...
"./svelte/**/*.svelte"
],
...
- 最后,从
config/config.exs
中移除esbuild
配置,并从mix.exs
的deps
函数中移除依赖,这样就完成了!
我们做了什么?
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-click
和 phx-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 模板。
安装
- 如果在
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
- 在你的
.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
,但这是需要根据你的用例进行实验的。
用法
-
安装 live_json
-
在你的项目中使用
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 的更多指导可以在这里找到。
- 生成
Dockerfile
:
mix phx.gen.release --docker
- 修改生成的
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 依赖,同时在运行应用时也需要它。
- 使用 Fly.io CLI 启动你的应用:
fly launch
- 当提示调整设置时,选择
y
:
? Do you want to tweak these settings before proceeding? (y/N) y
这将打开一个新窗口,你可以在其中调整启动设置。在数据库部分,选择 Fly Postgres
并为数据库输入一个名称。你可能还想将数据库更改为开发配置以避免额外费用。除非你想更改其他设置,否则可以保留其余设置不变。
确认后部署将继续。
- 部署完成后,运行以下命令查看已部署的应用!
fly apps open
致谢
Vue 替代方案
更喜欢 Vue? 有 LiveVue,它的功能与 LiveSvelte 完全相同,但使用 Vue。
LiveSvelte 项目
在公开项目中使用 LiveSvelte? 告诉我,我会将其添加到此列表中!