Topcoat: The full full-stack framework for Rust
摘要
Topcoat 是一个模块化、开箱即用的 Rust 全栈框架,强调简单性和生产力,支持服务器端渲染组件(可异步查询数据库)、$(...) 表达式(服务器执行并翻译为浏览器 JS,无 WASM 或客户端构建)、view! 宏的 HTML 模板(支持 Rust 控制流)、模块化路由、资产打包(扫描 asset! 调用)、内置 Tailwind 支持,以及 CLI 格式化工具。还提供路由、请求上下文、会话、字体图标等功能。项目处于早期实验阶段,预计会有破坏性变更。
荐读理由
它的 $(...) 表达式在服务器用 Rust 执行初始渲染并自动翻译成 JS 在浏览器重跑,组件标 #[shard] 后参数变化即服务器重渲染替换 HTML,能直接迁移到 Rust 全栈项目省掉 API 层和 WASM
原文
The full full-stack framework for Rust
Topcoat is a modular, batteries-included Rust framework for building fullstack apps. It prioritizes simplicity and productivity. See the Getting started guide to set up a new project.
Early-stage and experimental. Expect breaking changes.
use topcoat::{
Result,
router::{Router, RouterBuilderDiscoverExt, page},
view::{component, view},
};
#[tokio::main]
async fn main() {
topcoat::start(Router::builder().discover().build()).await.unwrap();
}
#[page("/")]
async fn home() -> Result {
view! {
<!DOCTYPE html>
<html>
<body>
hello(name: "World")
</body>
</html>
}
}
#[component]
async fn hello(name: &str) -> Result {
view! { <h1>"Hello, " (name) "!"</h1> }
}
What makes Topcoat different
Client reactivity without the boilerplate
Topcoat renders all markup on the server: components can be async and query the database directly, eliminating all the traditional boilerplate needed for a separate API layer. Interactivity does not have to cost a round-trip, though. A $(...) expression is ordinary type-checked Rust that Topcoat evaluates on the server for the initial render and also translates to JavaScript, so it re-runs instantly in the browser. No wasm bundle, no client build step:
view! {
signal open = false;
// Runs entirely in the browser; no server round-trip.
<button @click=$(|_e| open.set(!open.get()))>"What is Topcoat?"</button>
<p :hidden=$(!open.get())>"A fullstack Rust framework."</p>
}
When an update does need the server, like fresh search results, mark the component as a #[shard]. Topcoat re-renders it on the server whenever one of its $(...) arguments changes and swaps the new HTML in place:
#[component]
async fn search() -> Result {
view! {
signal query = String::new();
<input @input=$(|e: Event| query.set(e.target.value))>
// Updates as the user types.
search_results(query: $(query.get()))
}
}
#[shard]
async fn search_results(cx: &Cx, query: String) -> Result {
view! {
<ul>
// Your own server-side code, like a database query:
for product in search_products(cx, &query).await? {
<li>(product.name)</li>
}
</ul>
}
}
Powerful, unsurprising HTML templates
The view! macro stays true to HTML and Rust. Use familiar Rust control flow as part of your templates:
view! {
<nav>
for item in nav_items {
<a
href=(item.url)
if item.url == current_path {
aria-current="page"
class="active"
}
>
(item.label)
</a>
}
</nav>
}
Use the topcoat fmt CLI command to automatically format view! snippets (and other macros) across your codebase.
Module-based routing
Topcoat can optionally infer your route tree from your app's module structure (without a build step):
src/
|-- app.rs -> / (and the root <html> layout)
`-- app/
|-- about.rs -> /about
|-- _marketing.rs (layout, no URL segment)
|-- _marketing/
| `-- pricing.rs -> /pricing
|-- posts.rs -> /posts
|-- posts/
| `-- id.rs -> /posts/{post_id}
`-- api/
`-- health.rs -> GET /api/health
Asset bundling
The bundler scans your compiled binary for asset! calls, copies (or even downloads) every file into a local asset directory, and allows Topcoat to serve them efficiently with aggressive browser caching.
const FERRIS: Asset = asset!("./ferris.png");
view! { <img src=(FERRIS)> }
Topcoat also ships with utilities for web fonts and icons, as well as easy integrations for Fontsource (Google Fonts) and Iconify.
Built-in Tailwind support
Enabled the tailwind feature to integrate Tailwind into your project effortlessly:
view! { <link rel="stylesheet" href=(topcoat::tailwind::stylesheet!())> }
Learn Topcoat
Start here
Getting started: create a new project, install the CLI, run the dev server.
Source code formatting:
topcoat fmtfor macro bodies.
Rendering
The
view!macro: templating syntax, control flow, conditional attributes.The
#[component]macro: async functions as components, with child content.The
attributes!macro: reusable runtime attribute fragments.The
class!macro: space-separated class lists from static and conditional entries.
Routing
Router: pages, layouts, and API routes; manual and auto-discovered.
Module-based routing: derive the route table from your module tree.
Working with requests
Request context (
Cx): the value pages, layouts, and components read from.App context: share long-lived values across requests, keyed by type.
Memoization:
#[memoize]for per-request caching and fan-out dedup.Functions, not middlewares: the recommended way to model auth and other request-scoped concerns.
Cookies: read and write the request cookie jar, with signed, encrypted, and prefixed cookies.
Sessions: bring-your-own-storage session authentication: login/logout lifecycle, sliding expiration, and token rotation.
Asset system
Assets: declare assets in Rust, serve them with content-hashed URLs.
Fonts: bundle and serve web fonts.
Icons: download Iconify icon sets or declare your own.
Client reactivity
The runtime: signals,
$(...)expressions,@event handlers, and:bind attributes.Expressions: the dual Rust/JavaScript expression language and its vocabulary.
Procedures: async server functions callable from the browser.
Shards: components that re-render on the server when their arguments change.
Third-party integrations
这条对你有帮助吗?