Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class MeetJasprButtonState extends State<MeetJasprButton> {
Component build(BuildContext context) {
if (notifier.done) {
return div(id: 'meet-jaspr-button', [
.wrapElement(
.apply(
events: {
'click': (event) {
event.preventDefault();
Expand All @@ -77,7 +77,7 @@ class MeetJasprButtonState extends State<MeetJasprButton> {
}

return div(id: 'meet-jaspr-button', [
.wrapElement(
.apply(
classes: touchTimer != null ? 'active' : null,
events: {
'mousemove': (event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class CounterButtonState extends State<CounterButton> {
Component build(BuildContext context) {
return div(classes: 'counter-container', [
Particles(particles: particles),
.wrapElement(
.apply(
events: {
'click': (event) {
event.preventDefault();
Expand Down
4 changes: 4 additions & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@
"title": "💙 Flutter Embedding",
"href": "/going_further/flutter_embedding"
},
{
"title": "🖥️ Server Components",
"href": "/going_further/server_components"
},
{
"title": "🪐 Custom Backend",
"href": "/going_further/backend"
Expand Down
21 changes: 17 additions & 4 deletions docs/api/utils/at_client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,20 @@ Learn more about how to set up serialization for custom data types using the [`@

With this setup you can use any class as the parameter of a `@client` component.

### Server Components (Passing Components)

Parameters can also be other components (type `Component`, including `List`s and `Map`s thereof). When you pass a component as a parameter to a `@client` component, it is treated as a **Server Component**:

- It is rendered **only on the server**.
- Its Dart code is **not** compiled to JavaScript and **never** executes on the client.
- Its pre-rendered HTML structure is embedded directly into the `@client` component's output and is automatically mounted on the client during hydration.

This is extremely useful when building layout components (like modals, sidebars, or tabs) that wrap large parts of your website that don't need client-side interactivity, thereby keeping your client bundle small.

<Info>
For a detailed explanation of this design pattern, including advanced examples like Tab bars and component scope transitions, see the [Server Components Guide](/going_further/server_components).
</Info>

## How it works

The following happens when you use a `@client` component:
Expand All @@ -114,11 +128,10 @@ The following happens when you use a `@client` component:

2. The component is built and pre-rendered normally.
3. Jaspr adds a html marker (`<!--$<name> data=<serialized-parameters>-->`) around your components output.
4. Jaspr adds the components js target as a `<script>` tag to the documents `<head>`.

*on the client:*

5. The browser loads the pre-rendered html and compiled js scripts.
6. The used component is located based on the html marker.
7. The parameters are deserialized and the component is mounted to the target element.
4. The browser loads the pre-rendered html and compiled js scripts.
5. The used component is located based on the html marker.
6. The parameters are deserialized and the component is mounted to the target element.

32 changes: 27 additions & 5 deletions docs/concepts/components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ In every aspect, this component behaves the same as Flutter's `InheritedWidget`.

## Foundation Components

Jaspr has three foundational component types, which are accessible through factory constructors on the `Component` class: `Component.element()`, `Component.text()` and `Component.fragment()`. Additionally, there are two more `Component.empty()` and `Component.wrapElement()` which are all explained below.
Jaspr has three foundational component types, which are accessible through factory constructors on the `Component` class: `Component.element()`, `Component.text()` and `Component.fragment()`. Additionally, there are two more `Component.empty()` and `Component.apply()` which are all explained below.

<Info>
Unlike Flutter, Jaspr has a fixed number of foundational components which make up all other components and represent the core building blocks of HTML: element nodes and text nodes. Flutter is different because it allows you to write custom render objects with your own layouting and painting logic. This isn't possible in Jaspr because with HTML, the browser handles both layouting and painting.
Expand Down Expand Up @@ -210,14 +210,14 @@ This is useful when you want to return "nothing" from a build method.
final component = Component.empty();
```

### Component.wrapElement()
### Component.apply()

A component which applies its attributes and parameters (like `classes`, `styles`,) etc.) to its direct child element(s).
A component which applies its attributes and parameters (like `classes`, `styles`,) etc.) to its target element(s).

This does not create a HTML element itself. All properties are merged with the respective child element's properties, with the child's properties taking precedence where there are conflicts.
This does not create a HTML element itself. All properties are merged with the respective target element's properties, with the target's properties taking precedence where there are conflicts.

```dart
final component = Component.wrapElement(
final component = Component.apply(
classes: 'wrapping-class',
styles: Styles(backgroundColor: Colors.blue, padding: Padding.all(8.px)),
child: div(
Expand All @@ -238,6 +238,28 @@ The above component renders the following HTML:
</div>
```

By default, `Component.apply()` targets all of its direct children. You can change this by using the `ApplyTarget target` parameter to target elements, either direct children or descendants, by tag, id or class names.

```dart
final component = Component.apply(
target: ApplyTarget.descendantWith(tag: 'p'),
styles: Styles(color: Colors.blue),
child: div([
p([
.text('Hello World'),
]),
]),
);
```

The above component renders the following HTML:

```html
<div>
<p style="color: blue;">Hello World</p>
</div>
```

## Formatting Whitespace

When pre-rendering your components in **server** and **static** mode, Jaspr will output cleanly formatted html on a best-effort basis. This means it will add newlines and indentations to your html element, while trying to not affect the way the html is rendered.
Expand Down
Loading
Loading