Skip to content
Draft
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
49f3ad6
Merge branch 'main' into feat/server-components
Jun 14, 2024
2b9bbfd
add server components setup
Jun 14, 2024
497460f
finish server components
Jun 29, 2024
9ad1735
update server_components experiment
Jul 1, 2024
842b897
Merge branch 'main' into feat/server-components
Jul 3, 2024
0d13a83
Merge branch 'main' into feat/server-components
schultek Feb 25, 2025
35ce3d8
fix merge issues
schultek Feb 25, 2025
0f2d046
fixes
schultek Feb 25, 2025
227648b
Merge branch 'main' into feat/server-components
schultek Sep 25, 2025
a732436
fix server comp experiment
schultek Sep 25, 2025
bb2e0d2
fix nested client components
schultek Sep 28, 2025
4230c82
extend server component handling
schultek Nov 2, 2025
6e499de
add stateful reload feature
schultek Nov 7, 2025
9619db0
Merge branch 'main' into feat/server-components
schultek Dec 8, 2025
1f9cc50
Merge branch 'main' into feat/server-components
schultek Dec 11, 2025
0c0cbe2
Merge branch 'main' into feat/server-components
schultek Jan 5, 2026
c08515a
add reload event handling
schultek Jan 7, 2026
f0073ce
add onReload feature
schultek Jan 8, 2026
7bb7ed6
update experiment
schultek Jan 8, 2026
7053147
Merge branch 'main' into feat/server-components
schultek Feb 15, 2026
2034181
make reload private
schultek Mar 16, 2026
e46ec38
apply review
schultek Mar 18, 2026
95719cd
Feat: Component.apply with target (#777)
schultek Apr 20, 2026
f5bbd92
Merge branch 'main' into feat/server-components
schultek Apr 20, 2026
8f4ba5b
apply review
schultek May 4, 2026
4b6b491
Merge branch 'main' into feat/server-components
schultek May 4, 2026
222ba09
add testcases for server components
schultek May 26, 2026
32afb3e
fix formatting
schultek May 26, 2026
01e018c
add reload test
schultek May 26, 2026
1ad049e
add server component docs
schultek May 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/dart_quotes/lib/main.client.options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ ClientOptions get defaultClientOptions => ClientOptions(
clients: {
'quote_like_button': ClientLoader(
(p) => _quote_like_button.QuoteLikeButton(
id: p['id'] as String,
initialCount: p['initialCount'] as int,
id: p.get<String>('id'),
initialCount: p.get<int>('initialCount'),
),
loader: _quote_like_button.loadLibrary,
),
Expand Down
4 changes: 2 additions & 2 deletions apps/dart_quotes_server/lib/main.client.options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ ClientOptions get defaultClientOptions => ClientOptions(
clients: {
'quote_like_button': ClientLoader(
(p) => _quote_like_button.QuoteLikeButton(
id: p['id'] as int,
initialCount: p['initialCount'] as int,
id: p.get<int>('id'),
initialCount: p.get<int>('initialCount'),
),
loader: _quote_like_button.loadLibrary,
),
Expand Down
2 changes: 1 addition & 1 deletion apps/fluttercon/lib/main.client.options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ ClientOptions get defaultClientOptions => ClientOptions(
clients: {
'like_button': ClientLoader(
(p) => _like_button.LikeButton(
session: _session.SessionCodex.decode(p['session'] as String),
session: _session.SessionCodex.decode(p.get<String>('session')),
),
loader: _like_button.loadLibrary,
),
Expand Down
2 changes: 1 addition & 1 deletion apps/website/lib/main.client.options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import 'package:website/pages/home/5_community/components/sponsors_list.dart'
ClientOptions get defaultClientOptions => ClientOptions(
clients: {
'header': ClientLoader(
(p) => _header.Header(showHome: p['showHome'] as bool),
(p) => _header.Header(showHome: p.get<bool>('showHome')),
loader: _header.loadLibrary,
),
'install_command': ClientLoader(
Expand Down
2 changes: 1 addition & 1 deletion examples/backend_dart_frog/lib/main.client.options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ ClientOptions get defaultClientOptions => ClientOptions(
loader: _counter.loadLibrary,
),
'hello': ClientLoader(
(p) => _hello.Hello(name: p['name'] as String),
(p) => _hello.Hello(name: p.get<String>('name')),
loader: _hello.loadLibrary,
),
},
Expand Down
6 changes: 6 additions & 0 deletions experiments/server-components/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Files and directories created by pub.
.dart_tool/
.packages

# Conventional directory for build output.
build/
3 changes: 3 additions & 0 deletions experiments/server-components/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# server_components

A basic pure-dart web app with ssr & automatic client hydration.
1 change: 1 addition & 0 deletions experiments/server-components/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:lints/recommended.yaml
51 changes: 51 additions & 0 deletions experiments/server-components/lib/counter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';

@client
class Counter extends StatefulComponent {
const Counter({this.step = 1, this.child, super.key});

final int step;
final Component? child;

@override
State<StatefulComponent> createState() => CounterState();
}

class CounterState extends State<Counter>
with SyncStateMixin<Counter, Map<String, dynamic>> {
int count = 0;

@override
Map<String, dynamic> getState() {
return {'stepDouble': component.step * 2};
}

@override
void updateState(Map<String, dynamic> value) {
print("GOT SYNC: $value");
}

@override
Component build(BuildContext context) {
print("Building Counter with count=$count and step=${component.step}");
return div(classes: 'client', [
.text('$count '),
button(
onClick: () {
setState(() {
count += component.step;
});
},
[.text("Increase by ${component.step}")],
),
if (component.child != null) component.child!,
button(
onClick: () {
context.reload();
},
[.text('Reload Page')],
),
]);
}
}
26 changes: 26 additions & 0 deletions experiments/server-components/lib/main.client.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// The entrypoint for the **client** environment.
//
// The [main] method will only be executed on the client after loading the page.

// Client-specific Jaspr import.
import 'package:jaspr/client.dart';

// This file is generated automatically by Jaspr, do not remove or edit.
import 'main.client.options.dart';

void main() {
// Initializes the client environment with the generated default options.
Jaspr.initializeApp(
options: defaultClientOptions,
);

// Starts the app.
//
// [ClientApp] automatically loads and renders all components annotated with @client.
//
// You can wrap this with additional [InheritedComponent]s to share state across multiple
// @client components if needed.
runApp(
const ClientApp(),
);
}
42 changes: 42 additions & 0 deletions experiments/server-components/lib/main.client.options.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// dart format off
// ignore_for_file: type=lint

// GENERATED FILE, DO NOT MODIFY
// Generated with jaspr_builder

import 'package:jaspr/client.dart';

import 'package:server_components/counter.dart' deferred as _counter;
import 'package:server_components/minicounter.dart' deferred as _minicounter;

/// Default [ClientOptions] for use with your Jaspr project.
///
/// Use this to initialize Jaspr **before** calling [runApp].
///
/// Example:
/// ```dart
/// import 'main.client.options.dart';
///
/// void main() {
/// Jaspr.initializeApp(
/// options: defaultClientOptions,
/// );
///
/// runApp(...);
/// }
/// ```
ClientOptions get defaultClientOptions => ClientOptions(
clients: {
'counter': ClientLoader(
(p) => _counter.Counter(
step: p.get<int>('step'),
child: p.mount(p.get<String>('child')),
),
loader: _counter.loadLibrary,
),
'minicounter': ClientLoader(
(p) => _minicounter.MiniCounter(),
loader: _minicounter.loadLibrary,
),
},
);
43 changes: 43 additions & 0 deletions experiments/server-components/lib/main.server.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'package:jaspr/dom.dart';
import 'package:jaspr/server.dart';
import 'main.server.options.dart';

import 'counter.dart';
import 'minicounter.dart';

void main() {
Jaspr.initializeApp(options: defaultServerOptions);

runApp(
Document(
title: 'server_components',
body: Builder(
builder: (context) {
var time = DateTime.now();
return div(classes: 'server', [
p([.text('Server Time: $time')]),
Counter(
step: time.second,
child: p(classes: 'server', [
.text('This is a server component, rendered at $time.'),
MiniCounter(),
]),
),
]);
},
),
),
);
}

@css
List<StyleRule> get styles => [
css('.server').styles(
padding: Padding.all(2.px),
border: Border.all(color: Colors.red),
),
css('.client').styles(
padding: Padding.all(2.px),
border: Border.all(color: Colors.green),
),
];
45 changes: 45 additions & 0 deletions experiments/server-components/lib/main.server.options.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// dart format off
// ignore_for_file: type=lint

// GENERATED FILE, DO NOT MODIFY
// Generated with jaspr_builder

import 'package:jaspr/server.dart';
import 'package:server_components/counter.dart' as _counter;
import 'package:server_components/main.server.dart' as _main$server;
import 'package:server_components/minicounter.dart' as _minicounter;

/// Default [ServerOptions] for use with your Jaspr project.
///
/// Use this to initialize Jaspr **before** calling [runApp].
///
/// Example:
/// ```dart
/// import 'main.server.options.dart';
///
/// void main() {
/// Jaspr.initializeApp(
/// options: defaultServerOptions,
/// );
///
/// runApp(...);
/// }
/// ```
ServerOptions get defaultServerOptions => ServerOptions(
clientId: 'main.client.dart.js',
clients: {
_counter.Counter: ClientTarget<_counter.Counter>(
'counter',
params: __counterCounter,
),
_minicounter.MiniCounter: ClientTarget<_minicounter.MiniCounter>(
'minicounter',
),
},
styles: () => [..._main$server.styles],
);

Map<String, Object?> __counterCounter(_counter.Counter c) => {
'step': c.step,
'child': c.child,
};
29 changes: 29 additions & 0 deletions experiments/server-components/lib/minicounter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';

@client
class MiniCounter extends StatefulComponent {
const MiniCounter({super.key});

@override
State<StatefulComponent> createState() => MiniCounterState();
}

class MiniCounterState extends State<MiniCounter> {
int count = 0;

@override
Component build(BuildContext context) {
return span(classes: 'client', [
button(

onClick: () {
setState(() {
count++;
});
},
[.text("Count: $count")],
),
]);
}
}
Loading
Loading