Slide 1

Slide 1 text

Jaspr :Dart Web Framework Incheon / Songdo 박제창 1

Slide 2

Slide 2 text

박제창 Flutter Seoul GDG Golang Korea Incheon / Songdo 2

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

4 Dart Web

Slide 5

Slide 5 text

5

Slide 6

Slide 6 text

6 @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); html, body { width: 100%; height: 100%; margin: 0; padding: 0; font-family: 'Roboto', sans-serif; } #output { padding: 20px; text-align: left; }

Slide 7

Slide 7 text

7 dart:html dart:indexed_db dart:js dart:js_util dart:svg dart:web_audio dart:web_gl

Slide 8

Slide 8 text

8 dart:html dart:indexed_db dart:js dart:js_util dart:svg dart:web_audio dart:web_gl 🪦 in 2025, the following dart: libraries will be deprecated

Slide 9

Slide 9 text

9 package : web

Slide 10

Slide 10 text

10 import 'package:web/web.dart' as web; final element = web.document.querySelector('#output') as web.HTMLDivElement; for (final item in thingsTodo()) { element.appendChild(newLI(item)); }

Slide 11

Slide 11 text

11

Slide 12

Slide 12 text

Progressive Web Apps Single Page Apps Existing Flutter mobile apps Flutter Web 12

Slide 13

Slide 13 text

SEO (Search Engine Optimization) Renderer (CanvasKit & HTML) Refresh / Update Flutter Web 13 Text Input & Text Field

Slide 14

Slide 14 text

2024 Flutter Roadmap 14 Web platform We'll continue to focus on performance and quality, including investigating reducing the overall application size, better use of multi-threading, supporting platform views, improving app load times, making CanvasKit the default renderer, improving text input, and investigating options for supporting SEO for Flutter web. We expect to complete the effort to compile Dart to WasmGC, and with that support Wasm compilation of Flutter web apps. This also includes a new JS interop mechanism for Dart that supports both JS and Wasm compilation. We also plan to resume work to support hot reload on the web.

Slide 15

Slide 15 text

Incheon / Songdo We'll continue to focus on performance and quality, including … investigating options for supporting SEO for Flutter web. 15

Slide 16

Slide 16 text

SEO 16

Slide 17

Slide 17 text

👍 Admin & CMS 🤔 Blog & Articles (static websites) Flutter Web 17

Slide 18

Slide 18 text

18 Web Renderer CanvasKit Skia + WebAssembly

Slide 19

Slide 19 text

19 CJK

Slide 20

Slide 20 text

20

Slide 21

Slide 21 text

21

Slide 22

Slide 22 text

22

Slide 23

Slide 23 text

23 flutter run -d chrome --web-renderer html

Slide 24

Slide 24 text

24 Web Renderer

Slide 25

Slide 25 text

25 Web Renderer

Slide 26

Slide 26 text

26

Slide 27

Slide 27 text

27 Web Renderer

Slide 28

Slide 28 text

28

Slide 29

Slide 29 text

29 flutter run -d chrome --wasm

Slide 30

Slide 30 text

30 How?

Slide 31

Slide 31 text

31 @Kilian Schulte

Slide 32

Slide 32 text

1.Open Source 2.Documentation 3.Playground 4.CLI Tool 5.Examples 6.Test Jaspr Jaspr is an modern web framework for building websites in Dart with support for both client-side and server-side rendering. 32

Slide 33

Slide 33 text

33

Slide 34

Slide 34 text

34

Slide 35

Slide 35 text

35 Directories ● /apps: Production apps built with jaspr ○ /jaspr_pad: Online Editor and Playground inspired by DartPad, built with jaspr. ● /docs: Documentation hosted at docs.page/schultek/jaspr ● /examples: Well-maintained and documented examples ● /packages: ○ /jaspr: The core framework package. ○ /jaspr_builder: Code-generation builders for jaspr. ○ /jaspr_cli: The command line interface of jaspr. ○ /jaspr_flutter_embed: Flutter element embedding bindings for jaspr. ○ /jaspr_lints: A collection of lints and assists for jaspr projects. ○ /jaspr_riverpod: Riverpod implementation for jaspr. ○ /jaspr_router: A router implementation for jaspr. ○ /jaspr_test: A testing package for jaspr. ○ /jaspr_tailwind: A tailwind integration for jaspr.

Slide 36

Slide 36 text

36 Directories ● /apps: Production apps built with jaspr ○ /jaspr_pad: Online Editor and Playground inspired by DartPad, built with jaspr. ● /docs: Documentation hosted at docs.page/schultek/jaspr ● /examples: Well-maintained and documented examples ● /packages: ○ /jaspr: The core framework package. ○ /jaspr_builder: Code-generation builders for jaspr. ○ /jaspr_cli: The command line interface of jaspr. ○ /jaspr_flutter_embed: Flutter element embedding bindings for jaspr. ○ /jaspr_lints: A collection of lints and assists for jaspr projects. ○ /jaspr_riverpod: Riverpod implementation for jaspr. ○ /jaspr_router: A router implementation for jaspr. ○ /jaspr_test: A testing package for jaspr. ○ /jaspr_tailwind: A tailwind integration for jaspr.

Slide 37

Slide 37 text

37

Slide 38

Slide 38 text

38

Slide 39

Slide 39 text

39 Differences to Flutter Web Flutter Web is for building Web-Apps, not Web-Sites. Jaspr is an alternative to Flutter Web, when you want to build any kind of website with Dart. This includes (but is not limited to): ● Static Sites ● Server-Rendered Sites ● Single-Page Applications Jaspr works by giving you the familiar look and feel of the Flutter framework, while using native web technologies, like HTML, the DOM and CSS to enable you building all kinds of websites using Dart.

Slide 40

Slide 40 text

40 class Counter extends StatefulComponent { const Counter({super.key}); @override State createState() => CounterState(); } class CounterState extends State { int count = 0; @override Iterable build(BuildContext context) sync* { yield div(classes: 'counter', [ Jaspr

Slide 41

Slide 41 text

41 class MainApp extends StatelessWidget { const MainApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( home: Scaffold( body: Center( child: Text('Hello World!'), ), ), ); } } class Header extends StatelessComponent { const Header({super.key}); @override Iterable build(BuildContext context) sync* { yield header([ nav([ for (var route in [ (label: 'Home', path: '/'), ]) ]), ]); } } Flutter Jaspr Differences to Flutter Web

Slide 42

Slide 42 text

42 void main() { runApp(const MainApp()); } Flutter

Slide 43

Slide 43 text

43 void main() { runApp(const MainApp()); } Flutter MaterialApp CupertinoApp

Slide 44

Slide 44 text

44 void main() { Jaspr.initializeApp(options: defaultJasprOptions); runApp(Document( title: "Flutter Plugin Interop", head: [ link(rel: "stylesheet", href: "styles.css"), ], body: App(), )); } Jaspr

Slide 45

Slide 45 text

45 const factory Document({ String? title, String? lang, String? base, String? charset, String? viewport, Map? meta, List? styles, List head, required Component body, }) = BaseDocument; Jaspr

Slide 46

Slide 46 text

46 Jaspr My Site

Slide 47

Slide 47 text

47 Jaspr - Installation > dart pub global activate jaspr_cli

Slide 48

Slide 48 text

Jaspr CLI 48 Project (Jaspr comes with a cli tool to create, serve and build your website.) ● jaspr create: Create a new jaspr project. ● jaspr serve: Serve the project and automatically refresh when you make changes. ● jaspr build: Build the project according to the selected rendering mode. Tooling (Additionally, there are the following service commands available) ● jaspr doctor: Show information about the environment and current project. ● jaspr clean: Delete the build/ and .dart_tool/ directories. ● jaspr update: Update the jaspr cli. ● jaspr analyze

Slide 49

Slide 49 text

Jaspr Create 49 > jaspr create my_website

Slide 50

Slide 50 text

Jaspr Create 50 1 Select a rendering mode:ely client-rendered site. ❯ ◉ static: Build a statically pre-rendered site. ◯ server: Build a server-rendered site. ◯ client: Build a purely client-rendered site. Select a rendering mode: 2 (Recommended) Enable automatic hydration on the client? (Y/n) 3 Setup routing for different pages of your site? (Y/n) 4 (Recommended) Use multi-page (server-side) routing? 5 Setup Flutter web embedding? (y/N) 6 Enable support for using Flutter web plugins in your project? (Y/n)

Slide 51

Slide 51 text

Jaspr Create 51 1 Select a rendering mode:ely client-rendered site. ❯ ◉ static: Build a statically pre-rendered site. ◯ server: Build a server-rendered site. ◯ client: Build a purely client-rendered site. Select a rendering mode: static: Build a statically pre-rendered site. 2 (Recommended) Enable automatic hydration on the client? (Y/n) Yes 3 Setup routing for different pages of your site? (Y/n) Yes 4 (Recommended) Use multi-page (server-side) routing? Yes 5 Setup Flutter web embedding? (y/N) No 6 Enable support for using Flutter web plugins in your project? (Y/n) Yes

Slide 52

Slide 52 text

Static Pre-rendering at build time Static Site No server Server Pre-rendering at request time Dynamic site Server Client No pre-rendering Single page site No server Jaspr Rendering Mode 52

Slide 53

Slide 53 text

Jaspr Project Structure (Static) 53 📦lib ┣ 📂components ┃ ┣ 📜counter.dart ┃ ┣ 📜header.dart ┃ ┗ 📜sample.dart ┣ 📂constants ┃ ┗ 📜theme.dart ┣ 📂pages ┃ ┣ 📜about.dart ┃ ┗ 📜home.dart ┣ 📜app.dart ┣ 📜jaspr_options.dart ┗ 📜main.dart 📦web ┣ 📂images ┃ ┗ 📜logo.png ┗ 📜favicon.ico

Slide 54

Slide 54 text

Jaspr Project Structure (Client) 54 📦lib ┣ 📂components ┃ ┣ 📜counter.dart ┃ ┗ 📜header.dart ┣ 📂pages ┃ ┣ 📜about.dart ┃ ┗ 📜home.dart ┗ 📜app.dart 📦web ┣ 📂images ┃ ┗ 📜logo.png ┣ 📜favicon.ico ┣ 📜index.html ┣ 📜main.dart ┗ 📜styles.css

Slide 55

Slide 55 text

Jaspr Routing (jaspr_router) 55 import 'package:jaspr_router/jaspr_router.dart'; import 'pages/home.dart'; import 'pages/about.dart' ; class App extends StatelessComponent { @override Iterable build(BuildContext context) sync* { yield Router(routes: [ Route(path: '/', builder: (context, state) => Home()), Route(path: '/about', builder: (context, state) => About()), ]); } }

Slide 56

Slide 56 text

56 📦pages ┣ 📂admin ┃ ┗ 📜admin.dart ┣ 📜about.dart ┣ 📜home.dart ┗ 📜profile.dart

Slide 57

Slide 57 text

Jaspr Routing (jaspr_router) 57 class App extends StatelessComponent { @override Iterable build(BuildContext context) sync* { yield Router(routes: [ Route(path: '/', builder: (context, state) => Home()), Route(path: '/about', builder: (context, state) => About()), Route(path: "/profile", title: "Profile", builder: (context,state){ return Profile(); }), Route(path: "/admin", title: "Admin", builder: (context,state){ return Admin(); }) ]); } }

Slide 58

Slide 58 text

Jaspr Component 58

This is heading 1

  1. Coffee
  2. Tea
  3. Milk

Slide 59

Slide 59 text

Jaspr Component 59

This is heading 1

  1. Coffee
  2. Tea
  3. Milk
@override Iterable build(BuildContext context) sync* { yield h1([text("This is heading 1")]); yield ol([ li([text("Coffee")]), li([text("Tea")]), li([text("Milk")]), ]);

Slide 60

Slide 60 text

Jaspr Component 60

This is heading 1

  1. Coffee
  2. Tea
  3. Milk
@override Iterable build(BuildContext context) sync* { yield h1([text("This is heading 1")]); yield ol([ li([text("Coffee")]), li([text("Tea")]), li([text("Milk")]), ]);

Slide 61

Slide 61 text

Jaspr Component 61 @override Iterable build(BuildContext context) sync* { yield h1([text("This is heading 1")]); yield ol([ li([text("Coffee")]), li([text("Tea")]), li([text("Milk")]), ]);

Slide 62

Slide 62 text

Jaspr Component 62 Component h1(List children, {Key? key, String? id, String? classes, Styles? styles, Map? attributes, Map? events}) { return DomComponent( tag: 'h1', key: key, id: id, classes: classes, styles: styles, attributes: attributes, events: events, children: children, ); } Component ProxyComponent DomComponent

Slide 63

Slide 63 text

Jaspr Styling 63 Global Stylesheet Inline Styles Responsive styles Third-Party CSS Frameworks

Slide 64

Slide 64 text

64
div(styles: const Styles.background(color: Colors.red), []); Jaspr Styling

Slide 65

Slide 65 text

65

Slide 66

Slide 66 text

66 yield div( styles: const Styles.background(color: Colors.red), [text("Devfest 2024")]); yield div( styles: const Styles.raw({ 'background-color': 'red', }), [text("Devfest 2024")]); yield div( styles: const Styles.background(color: Colors.red) .text(fontSize: 24.px), [text("Devfest 2024")]); yield div( styles: Styles.combine([ Styles.background(color: Colors.red), Styles.text(color: Colors.blue, fontSize: 23.px), ]), [text("Devfest 2024")]); Jaspr Styling

Slide 67

Slide 67 text

67 const redText = Styles.text(color: Colors.red); //Reusing Styles const redBlueTest = Styles.combine([ redText, Styles.background(color: Colors.blue), ]); const redGreenTest = Styles.combine([ redText, Styles.background(color: Colors.green), ]); Jaspr Styling

Slide 68

Slide 68 text

68 #content { width: 100%; height: 100%; } #content a { color: green; } .red-text { color: red; } Jaspr Styling

Slide 69

Slide 69 text

69 Style(styles: [ css('#content', [ css('&').box(width: 100.percent, height: 100.percent), css('a').text(color: Colors.green), ]), css('.red-text').text(color: Colors.red), ]); Jaspr Styling

Slide 70

Slide 70 text

70 Style(styles: [ css('#content', [ css('&').box(width: 100.percent, height: 100.percent), css('a').text(color: Colors.green), ]), css('.red-text').text(color: Colors.red), ]); Jaspr Styling #content { width: 100%; height: 100%; } #content a { color: green; } .red-text { color: red; }

Slide 71

Slide 71 text

71 Jaspr Styling @override Iterable build(BuildContext context) sync* { yield div(classes: 'main', [ p([text('Hello World')]), ]); } @css static final styles = [ css('.main', [ css('&').box(width: 100.px), css('p').text(color: Colors.blue), ]), ];

Slide 72

Slide 72 text

72 Jaspr Styling @override Iterable build(BuildContext context) sync* { yield div(classes: 'main', [ p([text('Hello World')]), ]); } @css static final styles = [ css('.main', [ css('&').box(width: 100.px), css('p').text(color: Colors.blue), ]), ]; 컴포넌트에서 정의되고 @css를 사용하여 등록된 스타일은 해당 컴포넌트에 범위가 지정되지 않습니다. 다른 컴포넌트에 원치 않는 영향을 주거나 스타일 정의가 충돌하는 것을 방지하려면 ID 또는 고유한 클래스 이름을 선택자로 사용해야 합니다

Slide 73

Slide 73 text

73 @css final styles = [ css('.main').flexbox(direction: FlexDirection.row), css.media(MediaQuery.screen(maxWidth: 600.px), [ css('.main').flexbox(direction: FlexDirection.column) ]), ]; Jaspr Styling

Slide 74

Slide 74 text

74 Jaspr Styling

Slide 75

Slide 75 text

75 @RhysSullivan Rapidly build modern websites without ever leaving your HTML. A utility-first CSS framework packed with classes like flex, pt-4, text-center and rotate-90 that can be composed to build any design, directly in your markup.

“Tailwind CSS”

Slide 76

Slide 76 text

76 Jaspr tailwindcss > curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/downloa d/tailwindcss-macos-arm64 > chmod +x tailwindcss-macos-arm64 > mv tailwindcss-macos-arm64 /usr/local/bin/tailwindcss Tailwind CSS Standalone CLI

Slide 77

Slide 77 text

77 Jaspr tailwindcss > curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/downloa d/tailwindcss-macos-arm64 > chmod +x tailwindcss-macos-arm64 > mv tailwindcss-macos-arm64 /usr/local/bin/tailwindcss Tailwind CSS Standalone CLI

Slide 78

Slide 78 text

78 Jaspr tailwindcss dev_dependencies: build_runner: ^2.4.0 jaspr_web_compilers: ^4.0.10+1 jaspr_builder: ^0.16.2 jaspr_lints: ^0.1.2 lints: ^3.0.0 jaspr_tailwind: ^0.3.0 pubspec.yaml

Slide 79

Slide 79 text

79 Jaspr tailwindcss styles.tw.css 📦web ┣ 📂images ┃ ┗ 📜logo.png ┣ 📜favicon.ico ┗ 📜styles.tw.css @tailwind base; @tailwind components; @tailwind utilities;

Slide 80

Slide 80 text

80 Jaspr tailwindcss ● BULMA ● daisyUI

Slide 81

Slide 81 text

81 State Management setState(() { _counter++; });

Slide 82

Slide 82 text

82 State Management

Slide 83

Slide 83 text

83 Jaspr State Management class FabButtonComponent extends StatefulComponent { State createState() => FabButtonComponentState(); } class FabButtonComponentState extends State { int count = 0; int count1 = 0; @override Iterable build(BuildContext context) sync* { // code } }

Slide 84

Slide 84 text

84 yield div( classes: "fixed bottom-4 right-4 flex flex-col items-center", [ span( classes: "mb-2 text-gray-700 text-sm", [text("count: ${count.toString()}"),],), span( classes: "mb-2 text-gray-700 text-sm", [text("count1: ${count1.toString()}"),],), button( classes: "bg-blue-500 hover:bg-blue-600 text-white rounded-full p-4 shadow-lg", [text("클릭 ${count}")], onClick: () { setState(() { count += 1; }); }, )], );

Slide 85

Slide 85 text

85 yield div( classes: "fixed bottom-4 right-4 flex flex-col items-center", [ span( classes: "mb-2 text-gray-700 text-sm", [text("count: ${count.toString()}"),],), span( classes: "mb-2 text-gray-700 text-sm", [text("count1: ${count1.toString()}"),],), button( classes: "bg-blue-500 hover:bg-blue-600 text-white rounded-full p-4 shadow-lg", [text("클릭 ${count}")], onClick: () { setState(() { count += 1; }); }, )], );

Slide 86

Slide 86 text

86

Slide 87

Slide 87 text

87 Jaspr Riverpod Jaspr comes with a Riverpod package that ports over flutter_riverpod to Jaspr. It is based on Riverpod 2 and supports all providers and modifiers. ● No Consumer / ConsumerComponent ● Just use context.read / context.watch ● Additional SyncProvider to sync values between server and client.

Slide 88

Slide 88 text

88 Jaspr Riverpod ● context.read(), ● context.watch(), ● context.refresh(), ● context.invalidate(), ● context.listen()

Slide 89

Slide 89 text

89 @client class App extends StatelessComponent { @override Iterable build(BuildContext context) sync* { yield ProviderScope(child: Counter()); } }

Slide 90

Slide 90 text

90 final count = context.watch(countProvider); yield button([text("click2")], onClick: (){ print("click"); context.read(countProvider.notifier).state = count + 1; } );

Slide 91

Slide 91 text

91 final count = context.watch(countProvider); yield button([text("click2")], onClick: (){ print("click"); context.read(countProvider.notifier).state = count + 1; } ); ♂

Slide 92

Slide 92 text

92 class Button extends StatelessComponent { Button({required this.label, required this.onPressed}); final String label; final void Function() onPressed; @override Iterable build(BuildContext context) sync* { yield DomComponent( tag: 'button', events: {'click': (e) => onPressed()}, child: Text(label), ); } }

Slide 93

Slide 93 text

93 Preloading and Syncing Provider ● Server-Side Rendering

Slide 94

Slide 94 text

94 final initialTokenProvider = SyncProvider((ref) async { final res = await http.get( Uri.parse( "https://us.api.blizzard.com/data/wow/token/index?namespace=dynamic-us&lo cale=en_US", ), headers: {'Authorization': 'Bearer '}); final token = Token.fromJson(json.decode(res.body)); return token.price ?? 0; }, id: 'init_token');

Slide 95

Slide 95 text

95 Jaspr Deploying ● Github Page ● Firebase ● Docker

Slide 96

Slide 96 text

96 jobs: deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Pages uses: actions/configure-pages@v5 - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: # Upload entire repository path: '.' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 Github Page & Action

Slide 97

Slide 97 text

97 Github Page & Action jobs: # Single deploy job since we're just deploying deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Pages uses: actions/configure-pages@v5 - name: "Setup Dart" uses: dart-lang/[email protected] - name: "Setup Flutter" uses: subosito/flutter-action@v2 - name: "Build Jaspr" run: | dart pub global activate jaspr_cli jaspr build --verbose - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: # Upload entire repository path: 'build/jaspr' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4

Slide 98

Slide 98 text

98 Github Page & Action jobs: # Single deploy job since we're just deploying deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Pages uses: actions/configure-pages@v5 - name: "Setup Dart" uses: dart-lang/[email protected] - name: "Setup Flutter" uses: subosito/flutter-action@v2 - name: "Build Jaspr" run: | dart pub global activate jaspr_cli jaspr build --verbose - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: # Upload entire repository path: 'build/jaspr' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4

Slide 99

Slide 99 text

99

Slide 100

Slide 100 text

Summary 100 ● Flutter Web & Dart Web ○ Renderer, WASM ● Jaspr ○ Open-Source ○ Documents ○ Differences to Flutter Web ○ CLI ○ Rendering Modes ○ Project Structure ○ Component & Styling (CSS) ○ State Management ○ Deploy

Slide 101

Slide 101 text

Thank You. Incheon / Songdo 101