简介
Jaspr 是一个使用 Dart 语言构建的现代化 Web 框架,专注于创建快速、动态且 SEO 友好的网站。它支持单页应用(SPA)、服务器端渲染(SSR)和静态站点生成(SSG)。
设计理念
Jaspr 的设计理念是为 Dart 开发者提供一个真正的 Web 开发框架,而不是简单地将应用移植到 Web 平台。它直接操作浏览器的 DOM(文档对象模型),渲染真实的 HTML 和 CSS,而不是在 Canvas 上绘制像素。
官方背书与实际应用
Jaspr 的能力得到了 Dart 和 Flutter 官方团队的认可,以下重要网站均使用 Jaspr 构建:
- dart.dev - Dart 编程语言官方网站
- docs.flutter.dev - Flutter 官方文档网站
这一事实充分证明了 Jaspr 的成熟度和可靠性。值得注意的是,虽然 Flutter Web 存在,但 Flutter 团队明确表示 Flutter Web 适合构建 Web 应用(Web Apps),而非 Web 网站(Websites)。因此,对于内容密集型的文档网站,官方团队选择了 Jaspr 而非 Flutter Web。
其他使用 Jaspr 构建的项目:
- jaspr.site - Jaspr 官网本身
- playground.jaspr.site - 在线代码编辑器(类似 DartPad)
官方资源
- 官网 : jaspr.site/
- GitHub : github.com/schultek/ja...
- 文档 : docs.page/schultek/ja...
- Pub.dev : pub.dev/packages/ja...
- 在线演示场 : playground.jaspr.site
- Discord 社区: 拥有超过 500 名开发者
核心特性
1. 熟悉的组件模型
Jaspr 的组件模型与 Flutter 的 Widget 系统非常相似,降低了 Flutter 开发者的学习曲线:
StatelessComponent对应StatelessWidgetStatefulComponent对应StatefulWidget- 类似的生命周期方法
- 声明式 UI 构建方式
2. 服务器端渲染(SSR)
- 开箱即用的 SSR 支持
- 自动在服务器和客户端之间同步组件状态
- 支持水合(Hydration)机制,提升首屏加载速度
3. 静态站点生成(SSG)
- 预渲染静态页面,实现最快的加载速度
- 适合博客、文档网站等内容驱动型站点
4. 真实的 HTML 和 CSS
- 渲染标准的 HTML 元素(div、p、span 等)
- 使用 CSS 进行样式控制
- 完美的 SEO 支持
- 优秀的可访问性(Accessibility)
5. 后端集成
与多种 Dart 后端框架无缝集成:
- Shelf: 轻量级 HTTP 服务器
- Serverpod: 全栈框架
- Dart Frog: 快速后端框架
6. 内置 Tailwind CSS 支持
- 原生支持 Tailwind CSS 工具类
- 简化样式开发流程
Jaspr vs Flutter Web 对比
渲染机制
| 特性 | Jaspr | Flutter Web |
|---|---|---|
| 渲染方式 | 直接操作 DOM,渲染真实的 HTML/CSS | 在 Canvas 上手动绘制像素(CanvasKit)或使用 HTML 渲染器 |
| 输出结果 | 标准 HTML 文档 | Canvas 元素或特殊的 HTML 结构 |
| SEO 友好度 | 优秀(搜索引擎可直接索引 HTML 内容) | 较差(CanvasKit 模式)到中等(HTML 模式) |
| 可访问性 | 原生支持(标准 HTML 语义) | 需要额外配置和优化 |
设计目标
| 方面 | Jaspr | Flutter Web |
|---|---|---|
| 主要用途 | 构建 Web 网站(Websites) | 构建 Web 应用(Web Apps) |
| 适用场景 | 内容驱动型网站、博客、文档、营销页面 | 跨平台应用的 Web 版本、复杂交互式应用 |
| 优先级 | SEO、加载速度、标准 Web 体验 | 跨平台代码复用、一致的 UI/UX |
组件与语法
dart
// Flutter Web
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Hello'),
Text('World'),
],
);
}
}
// Jaspr
class MyComponent extends StatelessComponent {
@override
Iterable<Component> build(BuildContext context) sync* {
yield div([
p([text('Hello')]),
p([text('World')]),
]);
}
}
主要差异:
- Jaspr 的
build方法返回Iterable<Component>,而不是单个Widget - Jaspr 使用 HTML 元素(div、p、span)而非 Flutter 布局组件(Column、Row、Stack)
- Jaspr 使用
sync*生成器语法(可选,也可返回列表)
布局与样式
| 特性 | Jaspr | Flutter Web |
|---|---|---|
| 布局系统 | CSS(Flexbox、Grid、传统布局) | Flutter 布局组件(Column、Row、Stack、Positioned) |
| 样式方式 | CSS 类、内联样式、Tailwind | Dart 代码中的样式属性 |
| 预置组件 | 无预置样式组件 | Material Design、Cupertino 组件库 |
| 响应式设计 | CSS 媒体查询 | MediaQuery + Flutter 布局 |
性能对比
根据实际测试数据(初始化时间):
| 框架 | 初始化时间 |
|---|---|
| Dart Web | ~0.79 秒 |
| Jaspr | ~1.10 秒 |
| Flutter Web | ~2.90 秒(CanvasKit) |
分析:
- Jaspr 的加载速度明显快于 Flutter Web
- Flutter Web 需要加载和初始化 CanvasKit 系统,增加了启动时间
- Jaspr 的首屏渲染更快,特别是使用 SSR 时
开发体验
| 方面 | Jaspr | Flutter Web |
|---|---|---|
| 学习曲线 | 需要了解 HTML/CSS(对 Flutter 开发者) | Flutter 开发者零学习成本 |
| 代码复用 | 无法与 Flutter 移动应用共享 UI 代码 | 可与移动应用共享大部分代码 |
| 调试工具 | 浏览器开发者工具 | Flutter DevTools + 浏览器工具 |
| 热重载 | 支持 | 支持 |
生态系统
| 方面 | Jaspr | Flutter Web |
|---|---|---|
| 包生态 | 较新,专用包较少,但可使用 Dart Web 包 | 成熟,可使用大部分 Flutter 包 |
| 社区规模 | 500+ Discord 成员,正在成长 | 数百万开发者,非常成熟 |
| 企业支持 | 社区驱动 | Google 官方支持 |
技术架构
渲染流程
css
Jaspr 组件树
↓
虚拟 DOM 构建
↓
差异计算(Diffing)
↓
真实 DOM 更新
↓
浏览器渲染 HTML/CSS
SSR 工作原理
-
服务器端:
- 执行组件的
build方法 - 生成完整的 HTML 字符串
- 包含序列化的状态数据
- 返回给客户端
- 执行组件的
-
客户端:
- 接收服务器渲染的 HTML
- 加载 Jaspr 客户端运行时
- 反序列化状态数据
- 执行水合(Hydration),将交互逻辑附加到静态 HTML
- 接管后续的客户端渲染
组件生命周期
dart
class MyComponent extends StatefulComponent {
@override
State<MyComponent> createState() => MyComponentState();
}
class MyComponentState extends State<MyComponent> {
@override
void initState() {
super.initState();
// 组件初始化,仅在服务器和客户端各执行一次
}
@override
Iterable<Component> build(BuildContext context) sync* {
// 构建 UI
}
@override
void dispose() {
super.dispose();
// 清理资源
}
}
组件模型
无状态组件(StatelessComponent)
dart
import 'package:jaspr/jaspr.dart';
class WelcomeMessage extends StatelessComponent {
const WelcomeMessage({required this.name, super.key});
final String name;
@override
Iterable<Component> build(BuildContext context) sync* {
yield div(classes: 'welcome-container', [
h1([text('Welcome, $name!')]),
p([text('Thanks for using Jaspr.')]),
]);
}
}
有状态组件(StatefulComponent)
dart
import 'package:jaspr/jaspr.dart';
class Counter extends StatefulComponent {
const Counter({super.key});
@override
State<Counter> createState() => CounterState();
}
class CounterState extends State<Counter> {
int count = 0;
void increment() {
setState(() {
count++;
});
}
@override
Iterable<Component> build(BuildContext context) sync* {
yield div([
p([text('Count: $count')]),
button(
onClick: increment,
[text('Increment')],
),
]);
}
}
HTML 元素
Jaspr 提供了所有标准 HTML 元素的 Dart 封装:
dart
// 结构元素
div([...])
section([...])
article([...])
header([...])
footer([...])
// 文本元素
h1([...])
p([...])
span([...])
a(href: '...', [...])
// 表单元素
form([...])
input(type: 'text', value: '...')
button([...])
textarea([...])
// 媒体元素
img(src: '...', alt: '...')
video([...])
audio([...])
CSS 样式
dart
// 使用 CSS 类
div(classes: 'container mx-auto p-4', [...])
// 使用内联样式
div(
styles: {
'display': 'flex',
'justify-content': 'center',
'background-color': '#f0f0f0',
},
[...],
)
// 使用 Tailwind CSS
div(classes: 'flex justify-center bg-gray-100 p-8', [...])
使用场景
Jaspr 最适合的场景
内容驱动型网站
- 博客和新闻网站
- 文档和知识库
- 营销和宣传页面
- 企业官网
SEO 关键型应用
- 电商产品页面
- 搜索引擎需要索引的内容
- 需要社交媒体分享预览的页面
静态站点
- 作品集网站
- 个人主页
- 会议和活动网站
Flutter Web 最适合的场景
跨平台应用
- 需要在移动、桌面和 Web 上共享代码的应用
- 复杂的交互式应用
- 游戏和图形密集型应用
企业内部工具
- 管理后台(不需要 SEO)
- 数据可视化仪表板
- 内部协作工具
创意应用
- 设计工具
- 绘图和编辑应用
- 自定义 UI 需求高的应用
快速开始
安装
bash
# 激活 Jaspr CLI
dart pub global activate jaspr_cli
# 创建新项目
jaspr create my_website
# 进入项目目录
cd my_website
# 运行开发服务器
jaspr serve
项目结构
bash
my_website/
├── lib/
│ ├── components/ # 可复用组件
│ ├── pages/ # 页面组件
│ └── app.dart # 应用入口
├── web/ # 静态资源
│ └── styles.css
├── pubspec.yaml # 依赖配置
└── jaspr_options.yaml # Jaspr 配置
代码示例
完整的页面示例
dart
import 'package:jaspr/jaspr.dart';
// 主应用组件
class App extends StatelessComponent {
@override
Iterable<Component> build(BuildContext context) sync* {
yield html([
head([
title([text('My Jaspr App')]),
meta(name: 'description', content: 'A website built with Jaspr'),
link(rel: 'stylesheet', href: '/styles.css'),
]),
body([
Header(),
MainContent(),
Footer(),
]),
]);
}
}
// 头部组件
class Header extends StatelessComponent {
@override
Iterable<Component> build(BuildContext context) sync* {
yield header(classes: 'bg-blue-600 text-white p-4', [
nav(classes: 'container mx-auto flex justify-between', [
h1(classes: 'text-2xl font-bold', [text('My Website')]),
ul(classes: 'flex gap-4', [
li([a(href: '/', [text('Home')])]),
li([a(href: '/about', [text('About')])]),
li([a(href: '/contact', [text('Contact')])]),
]),
]),
]);
}
}
// 主内容组件
class MainContent extends StatefulComponent {
@override
State<MainContent> createState() => MainContentState();
}
class MainContentState extends State<MainContent> {
List<String> items = ['Item 1', 'Item 2', 'Item 3'];
String newItem = '';
void addItem() {
if (newItem.isNotEmpty) {
setState(() {
items.add(newItem);
newItem = '';
});
}
}
@override
Iterable<Component> build(BuildContext context) sync* {
yield main(classes: 'container mx-auto p-8', [
section([
h2(classes: 'text-3xl mb-4', [text('Welcome to Jaspr')]),
p(classes: 'mb-8', [
text('This is a demo of a Jaspr application with interactive components.'),
]),
]),
section([
h3(classes: 'text-2xl mb-4', [text('Todo List')]),
div(classes: 'flex gap-2 mb-4', [
input(
type: 'text',
value: newItem,
onInput: (value) {
setState(() {
newItem = value;
});
},
classes: 'border p-2 flex-1',
placeholder: 'Enter new item',
),
button(
onClick: addItem,
classes: 'bg-blue-500 text-white px-4 py-2 rounded',
[text('Add')],
),
]),
ul(classes: 'list-disc pl-5', [
for (var item in items)
li([text(item)]),
]),
]),
]);
}
}
// 底部组件
class Footer extends StatelessComponent {
@override
Iterable<Component> build(BuildContext context) sync* {
yield footer(classes: 'bg-gray-800 text-white p-4 mt-8', [
div(classes: 'container mx-auto text-center', [
p([text('© 2025 My Website. Built with Jaspr.')]),
]),
]);
}
}
// 应用入口
void main() {
runApp(App());
}
服务器端渲染配置
dart
// server.dart
import 'package:jaspr/server.dart';
import 'app.dart';
void main() async {
// 启动 Jaspr 服务器
await serveApp(
(request, render) async {
// 渲染应用
return render(App());
},
port: 8080,
);
print('Server running on http://localhost:8080');
}
静态站点生成
bash
# 生成静态文件
jaspr build
# 输出位置:build/jaspr/
生态系统
推荐的集成
-
Tailwind CSS
yaml# jaspr_options.yaml styles: mode: tailwind -
路由管理
dart// 使用 jaspr_router 包 dependencies: jaspr_router: ^latest -
状态管理
- Provider 模式
- Riverpod(Dart 版本)
- 自定义状态管理
-
后端集成
dart// 与 Shelf 集成 import 'package:shelf/shelf.dart'; import 'package:jaspr/server.dart'; Handler createHandler() { return Pipeline() .addMiddleware(logRequests()) .addHandler(jasprHandler(App())); }
性能对比
包大小对比
| 框架 | 压缩后大小(gzip) |
|---|---|
| Jaspr | ~50-100 KB |
| Flutter Web (HTML renderer) | ~1-2 MB |
| Flutter Web (CanvasKit) | ~2-3 MB |
首屏加载时间(FCP - First Contentful Paint)
- Jaspr SSR: < 1 秒(优秀)
- Jaspr SPA: 1-2 秒(良好)
- Flutter Web: 2-4 秒(中等)
SEO 性能
- Jaspr: 100% 可索引(真实 HTML)
- Flutter Web HTML renderer: 部分可索引
- Flutter Web CanvasKit: 基本不可索引(需要额外配置)
总结与建议
何时选择 Jaspr
选择 Jaspr,如果你的项目:
- 需要优秀的 SEO 表现
- 是内容驱动型网站(博客、文档、营销页面)
- 需要快速的首屏加载
- 希望使用标准 Web 技术(HTML/CSS)
- 需要服务器端渲染
- 团队熟悉 Dart 但项目不需要跨平台移动应用
何时选择 Flutter Web
选择 Flutter Web,如果你的项目:
- 需要与移动应用共享大量代码
- 是复杂的交互式应用或工具
- SEO 不是主要考虑因素(如内部工具)
- 需要 Flutter 的丰富 UI 组件库
- 团队已经投入 Flutter 生态系统
- 需要高度自定义的 UI 和动画
技术选型决策树
markdown
是否需要 SEO?
├── 是 → 是否内容驱动型网站?
│ ├── 是 → Jaspr ✓
│ └── 否 → 考虑 Jaspr(支持 SSR)
└── 否 → 是否需要与移动应用共享代码?
├── 是 → Flutter Web ✓
└── 否 → 根据团队技能和项目复杂度选择