Leptos Axum Workspace项目分析

Leptos 是一个基于 Rust 的 Web 开发框架,使用细粒度更新的方式响应式地更新页面,提供较好的性能。这里对 Leptos 提供的 Leptos Axum Workspace 模版进行分析,了解其项目结构和开发流程。

项目启动

安装必要依赖

1
2
cargo install cargo-leptos cargo-generate
npm i -g sass

初始化 Leptos Axum Workspace 项目

1
cargo leptos new -g leptos-rs/start-axum-workspace -n leptos-axum-workspace-playground

在 end2end 目录中安装测试依赖

1
2
pnpm i
npx playwright install

端到端测试执行

1
cargo leptos end-to-end

启动支持热重载的开发服务器

1
cargo leptos watch

此时服务器将启动并监听 localhost:3000 地址,打开即可进入页面。

构建生产发布

1
cargo leptos build --release

完成后生成 target/release/ 目录包含服务器二进制文件、site 包含站点静态文件。配置环境变量 LEPTOS_SITE_ROOT 指向 site 目录并运行 server 服务器二进制文件即可启动服务器,并在 127.0.0.1:3000 上正常访问。

项目组织结构分析

项目的整体目录结构如下,通过顶层目录中的 Cargo.toml 组织为 Workspace 结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
leptos-axum-workspace/
├── Cargo.toml # Workspace 配置
├── Cargo.lock # 依赖锁定
├── README.md # 项目文档
├── app/ # 共享应用代码
│ ├── Cargo.toml
│ └── src/lib.rs
├── frontend/ # 前端 WASM 包
│ ├── Cargo.toml
│ └── src/lib.rs
├── server/ # 服务器包
│ ├── Cargo.toml
│ └── src/main.rs
├── style/ # 样式文件
│ └── main.scss
├── public/ # 静态资源
│ └── favicon.ico
├── end2end/ # E2E 测试
│ ├── package.json
│ ├── playwright.config.ts
│ └── tests/
└── target/ # 构建输出

Workspace 组织

顶层 Cargo.toml 的 workspace.depedencies 引入了项目中共用的依赖,统一这些依赖的版本,避免成员间的依赖版本冲突。workspace 部分指定了内部的 app、frontend、server 作为 workspace 的三个成员。

1
2
3
4
5
6
7
8
[workspace]
resolver = "2"
members = ["app", "frontend", "server"]

[workspace.dependencies]
leptos = { version = "0.8.2" }
leptos_meta = { version = "0.8.2" }
...

app 目录是 workspace 的成员之一,是项目中的共享库包。包含主要的应用逻辑和 UI 组件。Cargo.toml 使用 leptos.workspace = true 引入依赖包并使用 workspace 中指定的版本。leptos_axum = { workspace = true, optional = true } 将依赖定义为可选依赖。这类可选依赖的引入开关由 features 中定义的功能特性控制。这是因为 app 作为共享的库包,需要在 server 和 frontend 两个成员中同时使用,而两种使用环境提供的特性并不一致。hydrate 功能特性应用于 frontend ,不能直接使用后端数据库等能力。ssr 功能特性则应用于 server ,可使用后端环境中可用的组件等。

1
2
3
4
5
6
7
8
[dependencies]
leptos.workspace = true
...
leptos_axum = { workspace = true, optional = true }
[features]
default = []
hydrate = ["leptos/hydrate"]
ssr = ["leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", "dep:leptos_axum"]

app 目录中的 lib.rs 即为 app 作为共享库的导出点,应用的逻辑、Leptos 业务组件都在这里导出。

frontend 目录是 workspace 中的前端包,构建时生成 wasm 包。Cargo.toml 指定该 crate 作为库编译为 cdylib 和 rlib 两种类型,cdylib 导出的包适用于 FFI 场景,可供 Javascript 等其他语言调用。而 rlib 导出的包包含 Rust 语言专用的元数据,可用于其他 Rust 模块进行调用。导入依赖时,通过路径指定了对 app 的依赖,同时通过 features 指定在 frontend 使用该依赖时使用 hydrate 功能特性。

1
2
3
4
5
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
app = { path = "../app", default-features = false, features = ["hydrate"] }
leptos = { workspace = true, features = [ "hydrate" ] }

src/lib.rs 是 fronend 包作为库编译的导出点,内部定义了约定用于在页面中引入 app 逻辑的 hydrate 函数。函数内部将应用逻辑的总入口 App 装载到 HTML 页面框架上。

server 目录是 workspace 中的服务器二进制包。它构建一个二进制可执行文件,文件运行后提供 HTTP 服务。与 frontend 类似,server 依赖以路径形式指定依赖 app 库,但构建时使用 ssr 功能特性。其内部使用 Axum 作为 HTTP 服务器,而 Leptos 编写的业务逻辑则作为其中的部分路由处理器进行工作。leptos 支持服务器渲染能力,因此指定 leptos 依赖时指定 ssr 依赖可在 Javascript 和 WebAssembly 不可用的情况下提供基本的页面展示能力。

1
2
3
[dependencies]
app = { path = "../app", default-features = false, features = ["ssr"] }
leptos = { workspace = true, features = [ "ssr" ]}

全局配置

在 workspace 的三个构成成员之外,Cargo.toml 指定了一些额外的全局选项,并与 cargo-leptos 工具紧密结合。若简单地使用 cargo build 进行构建,生成的结果将不符合预期。

Web 应用正常工作的第一步就是从服务器端下载相应的页面、Javascript 脚本、WebAssembly 模块等。而这部分耗时与构建产物的大小紧密相关,因此在 Cargo.toml 中指定了 release 发布下的优化目标、编译选项等。将优化目标设置为产物大小,并限制并行编译、使用 lto 优化等手段以取得更好的优化效果。

1
2
3
4
[profile.release]
codegen-units = 1
lto = true
opt-level = 'z'

在 workspace 的三个成员模块之外,项目中还提供了 public、style 及 end2end 三个目录。public 中的文件会在构建时复制到构建目标的资源目录,作为静态资源在运行的服务器提供。style 为应用提供样式文件,在使用 sass、scss 等文件时会由 cargo-leptos 构建时先行编译为最终的样式文件。

这些能力由 cargo-leptos 提供,而这些功能的配置项就体现在 Cargo.toml 文件中的 [[workspace.metadata.leptos]] 部分。如: public 就是通过 assets-dir 指定为资源目录的;style 中的样式处理就是通过 style-file 配置的。

1
2
3
4
5
6
7
8
9
10
11
12
[[workspace.metadata.leptos]]
name = "leptos-axum-workspace-playground"
bin-package = "server"
lib-package = "frontend"
site-root = "target/site"
site-pkg-dir = "pkg"
style-file = "style/main.scss"
assets-dir = "public"
site-addr = "127.0.0.1:3000"
reload-port = 3001
end2end-cmd = "npx playwright test"
end2end-dir = "end2end"

端到端测试

Cargo.toml 的 [[workspace.metadata.leptos]] 部分对 end2end-cmd 和 end2end-dir 进行了配置。使用 cargo leptos end-to-end 可以在配置的目录中进行端到端测试。

与配置匹配,end2end 目录包含了项目的端到端测试环境。这个测试环境基于 node.js 构建,使用 playwright 作为测试工具。执行的测试 spec 在 tests 目录中。

package.json 定义了端到端测试的项目环境。tsconfig.json 对端到端测试项目中的 Typescript 进行配置。playwright.config.ts 对 playwright 进行配置。