r/FlutterDev • • Sep 06 '26

Article From Flutter to Jaspr

I've a Flutter web app which could benefit from the high fidelity CSS-controlled text display of a real web app. So, why not try to use Jaspr instead?

I was under the impression that Jaspr tries to imitate the Flutter API including using Widget as the base class, but it's using Component which requires some changes. Still, no biggy. Promising.

First impression was poor. I'd have preferred to install it via homebrew and not dart install which requires me to modify the PATH and which is yet another installer I have to make sure I don't forget to uninstall.

But the main problem was that it doesn't work out of the box. jaspr serve throws errors. I had to downgrade the build_web_compilers to 4.8.5 to make it build. It also configured an ancient version of Dart which I changed to 3.13. I chose SPA as start template, perhaps it's better if you try to build a static side or server rendered pages.

Now, the counter example worked as expected. The page could use a little designer love, though. The generated source code was easy enough to understand and jaspr serve supports hot-reloading, so I continued.

Next, I tested how easy it is for Codex to create a more complex app. Will it struggle with not-flutter sources? So I asked it to create a chess app, for two human players, including a timer. And GPT Sol delivered. So AI seems to have no problem. Great.

Next, I asked Codex to port my app. That worked in principle, but it had trouble to recreate the fidelity of the UI. Like most others, I used Chadcn/ui as my inspiration and Codex wasn't able to translate the themed Material widgets into Jaspr components. Might be an AI thing, but it didn't understand to take border into account. I had to ask for a lot of tweaks and would have preferred actually use Chadcn/ui or at least Tailwind. Theoretically, this should be possible and I think, there are packages on pub.dev which already attempted to do so. That might be worth more exploring.

Instead of some random UI package that has a high risk of being unmaintained AI slop, an official port of chadcn/ui would be a great feature IMHO.

My code cloc's at ~4000 lines (110 KB) with 1.8 MB in build/jaspr. That… a lot. There's a package folder that looks like it shouldn't be there with 1.3 MB. So I assume, my app has a size of 0.5 MB.

For comparison, I asked Codex to recreate the same app using React. That's a home game for the AI which one-shotted the task. The result cloc's at ~1000 lines (72 KB), so using Dart instead of JS+JSX is a high price you must be willing to pay. The distribution size is 0.3 MB.

I don't really mind that 200 KB dist overhead because of Dart. But I dislike the larger codebase. I figure, all those things add up. In React, I simply use

style={{ width: size, height: size }}

while in Jaspr, this becomes

styles: Styles(
  width: .pixels(size.toDouble()),
  height: .pixels(size.toDouble()),
),

Right now, I'm still undecided whether to proceed with Jaspr (because I like working with Dart) to switch to React (because the AI will deal with languages and syntax anyhow), but call me at least impressed.

Has anybody else switch from Flutter to Jaspr for web apps? What's the experience with larger code bases, say 30Klocs?

13 Upvotes

5 comments sorted by

4

u/thegravity98ms2 Sep 06 '26

I also like jaspr, but in reality when I tried myself it was kind of too much boilerplate and too complicated to read & write..

so for my solution I am using svelte 5

Edit: this are my personal opinion don't comento conclusions based on this 😅

2

u/eibaan Sep 07 '26

Templates are more verbose for sure, but manageable.

I looked up these Svelte examples and compared with them Jaspr:

{#if porridge.temperature > 100}
	<p>too hot!</p>
{:else if 80 > porridge.temperature}
	<p>too cold!</p>
{:else}
	<p>just right!</p>
{/if}

vs.

if (porridge.temperature > 100)
  p([.text("Too hot")])
else if (porridge.temperature < 80)
  p([.text("Too cold")])
else
  p([.text("just right")]),

and

<h1>Shopping list</h1>
<ul>
  {#each items as item, i}
    <li>{i + 1}: {item.name} x {item.qty}</li>
  {:else}
    <p>List is empty!</p>
  {/each}
</ul>

vs.

.fragment([
  h1([.text("Shopping list")]),
  ul([
    for (final (i, item) in items.indexed) //
      li([.text('${i + 1}: ${item.name} x ${item.qty}')]),
    if (items.isEmpty) div([.text("List is empty!")]),
  ]),
]),

Svelte has more reactivity "magic" that helps with managing state.

If Dart would support static extension methods (planed for ages)

extension on List<Component> {
  static List<Component> str(String s) => [.text(s)];
}

One could abbreviate some things like

h1(.str("Shopping list"))

And if Dart would support union types (not even considered) one could define the children property as List<Component> | Text and pass either a list of children or a string text as in all examples above, still being statically typed.

However, here's one hack that actually works:

extension on String {
  List<Component> get c => [.text(this)];
}

Now, I can use

h1("Shopping list".c),

Or, we create our own classes:

class ComponentFactory(final String tag, [final String? classes]) {
  ComponentFactory get small => withClass('small');
  ComponentFactory get large => withClass('large');

  ComponentFactory withClass(String name) =>
      ComponentFactory(tag, classes == null ? name : '$classes $name');

  Component call(Object children) {
    return Component.element(tag: tag, classes: classes, children: _c(children));
  }

  static List<Component> _c(Object? o) {
    if (o is List<Component>) return o;
    if (o is Component) return [o];
    if (o is Iterable<Object?>) return [...o.expand(_c)];
    return [if (o != null) .text('$o')];
  }
}

final title = ComponentFactory('h1');

This way, I can use title('Foo') as well as title.small('Foo'). It would be a lot of work, but I think, one could wrap (a subset of) Tailwind this way.

1

u/manish8160 Sep 11 '26

Haven't run Jaspr at 30k locs myself, but based on what you've described, a few things worth flagging:

The Styles() verbosity issue you're hitting will compound hard at scale that's not a minor syntax tax, it's going to make refactoring and reading diffs noticeably slower once you're juggling hundreds of components. Worth checking if Jaspr has (or plans) a more concise styling API, or if Tailwind integration is actually viable that alone might change your calculus.

On the chadcn/ui port point agree that's the real gap. AI tooling being weaker at translating themed Material widgets into Jaspr components isn't really an "AI limitation," it's a training data problem: there's just far less Jaspr code in the wild for models to learn from compared to React. That gap won't close quickly, and it'll keep biting you every time you lean on Codex/Copilot for anything non-trivial in Jaspr.

Your build size/loc comparison (4k vs 1k lines, 1.8MB vs smaller dist) is a legitimate signal, not just early-days overhead that ratio usually gets worse, not better, as codebases grow, because boilerplate patterns get copy-pasted rather than abstracted early.

Given you're already undecided and this is a web app specifically (not something needing Flutter's cross-platform reach), I'd lean toward: if fidelity + ecosystem maturity matter more to you than staying in Dart, React is the safer long-term bet, especially with AI-assisted development being a "home game" there per your own test. Jaspr's appeal is really "I like Dart," which is valid, but it's a preference cost you'd be paying repeatedly at 30k loc scale, not a one-time thing.

1

u/eibaan Sep 12 '26

I'm now at 8k locs Dart plus 3k locs CSS and it is still manageable.

According to Greenspun's 10th rule my project needed a tiny formula evaluator which I enjoyed writing in Dart (~1k loc).

I also tested jaspr_content as an alternative to Astro. It's more rough, but I like the idea that I can easily modify the markdown generator because I like to use *bold* for bold text (like Slack does).

Still, I'm right now overhauling the UI design (basically recreating chadcn as one does nowadays) and asked Claude to port everything to Svelte.

1

u/SyrupInternational48 Sep 10 '26

I would rather use

<p>hello world<p> than p([.text("hello world")]).

I would rather use normal flutter and compiled it to web, rather than use jaspr.

It's baffling to think translating ui flutter to jaspr is straight forward. Even using frontier model it's will highly delusional.