Slide 1

Slide 1 text

Turbo Photo by Ye Massa

Slide 2

Slide 2 text

@dunglas Kévin Dunglas ❏ Founder of Les-Tilleuls.coo p ❏ Creator of Mercure.rocks and API Platform ❏ Symfony Core Team Member @dunglas .social

Slide 3

Slide 3 text

@dunglas Les-Tilleuls.coop Symfony, JavaScript and cloud expert s ✊ Self-managed, 100% employee-owne d 🦄 50 people, 1,000% growth in 6 year s 👷 [email protected]

Slide 4

Slide 4 text

@dunglas Do You Need JavaScript?

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

@dunglas Hotwire aka DHH’s “NEW MAGIC” ❏ Turbo: the heart of Hotwire, the topic of this tal k ❏ Stimulus: when you really need JS (~20% use cases ) ❏ Strada: mobile hybrid apps, not released ye t ❏ Created by David Heinemeier Hansson et al.
 (Ruby on Rails, Basecamp, Hey, racing driver)

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

@dunglas Symfony UX Turbo : Getting Started

Slide 11

Slide 11 text

@dunglas The Symfony UX Initiative

Slide 12

Slide 12 text

@dunglas Tooling ❏ Symfony CLI: bootstrap a project, run a local web server and… a surprise ! ❏ Webpack Encore: process and compile assets, Twig integratio n ❏ Stimulus Bridge: automatically loads JS files in your Symfony apps

Slide 13

Slide 13 text

@dunglas Create a Symfony UX Turbo Project # Create a new Symfony 5.3 project symfony new sf—turbo --full cd sf-turbo # Install Symfony UX Turbo composer require symfony/ux-turbo-mercure # Install and compile the JS dependencies yarn install yarn encore dev # Start the local web server symfony serve -d

Slide 14

Slide 14 text

@dunglas Enable the Webpack Encore Integration {# templates/base.html.twig #} {# ... #} {% block javascripts %} {{ encore_entry_script_tags('app') }} {% endblock %}

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

@dunglas Turbo Drive

Slide 17

Slide 17 text

@dunglas Turbo Drive ❏ Enhances page level navigation:
 no more “white flicker ” ❏ Watches for clicks and form submissions ❏ Loads pages in the background using fetch( ) ❏ Replaces the , merges the ❏ Changes browser’s history using history.pushStat e ❏ Customizable progress ba r ❏ Programmatic API (Turbo.visit()) and event system

Slide 18

Slide 18 text

@dunglas Turbo Drive Is Automatically Enabled {# templates/conference/show.html.twig #} {# … #} back to list edit Our standalone blog

Slide 19

Slide 19 text

@dunglas Click on a link

Slide 20

Slide 20 text

@dunglas Turbo Drive and Forms #[Route('/conference')] class ConferenceController extends AbstractController { #[Route('/{id}/edit')] public function edit(Request $request, Conference $conference) { $form = $this->createForm(ConferenceType::class, $conference); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { // … return $this->redirectToRoute('conference_index', [], Response::HTTP_SEE_OTHER); } // New in Symfony 5.3: a 4(22) status code MUST be returned if the form is invalid return $this->renderForm( 'conference/edit.html.twig', ['conference' => $conference, 'form' => $form], ); }

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

@dunglas Turbo Frames

Slide 23

Slide 23 text

@dunglas Turbo Frames ❏ Functionally similar to old school HTML frame s ❏ Update parts of the page (blocks ) ❏ Capture links and forms in this fram e ❏ The content of the frame is extracted from the response, and the existing content is replace d ❏ The response can contain the full page, or only a fragmen t ❏ A Web Component is used to delimit frame s ❏ A page can contain multiple frames

Slide 24

Slide 24 text

@dunglas Turbo Frames {# templates/base.html.twig #} {# … #} Navbar displayed on every pages Frame content, replaced on click {% block body %}{% endblock %}

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

@dunglas Lazy Loading Frames

Slide 27

Slide 27 text

@dunglas Lazy Loading Frames ❏ Turbo can also lazy loads frames, client-side ❏ This allows to dramatically improve cache dynamic s ❏ The page can be divided in blocks, each block can have a different cache tim e ❏ Usage examples : ❏ cart: the main content is in cache, but not the cart which is user-specifi c ❏ breaking news: the main content is in cache with a TTL of a few hours, but not the “breaking news” block

Slide 28

Slide 28 text

@dunglas This looks like “ESI” isn’t it? Yes! But client-side.

Slide 29

Slide 29 text

@dunglas Trust me, with modern JS frameworks, ESI are a pain!

Slide 30

Slide 30 text

@dunglas But with Symfony, it’s easy and natively supported

Slide 31

Slide 31 text

@dunglas Lazy Loading Frames in Symfony ❏ Reuse the existing fragment subsystem (ESI, hinclude ) ❏ Routing is handled automaticall y ❏ You can pass variables to fragment s ❏ Generated URLs are signed (HMAC ) ❏ Best with a cache server (Varnish, Souin, Cloudflare… ) ❏ Require Symfony 5.3 + ❏ Can be mixed with server-side ESI (SEO)

Slide 32

Slide 32 text

@dunglas Lazy Loading Frame: Controllers #[Route('/')] #[Cache(public: true, maxage: 3600)] public function index() { // … } #[Cache(public: false, maxage: 0)] public function cart() { return $this->render('cart.html.twig'); }

Slide 33

Slide 33 text

@dunglas Lazy Loading Frame: Templates {# templates/base.html.twig #} Navbar displayed on every pages {# templates/cart.html.twig #} Cart content

Slide 34

Slide 34 text

@dunglas Lazy Loading Frame: Cache Per Block

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

@dunglas Turbo Streams

Slide 37

Slide 37 text

@dunglas Turbo Streams ❏ Add real-time capabilities to your websites ! ❏ Stream page changes as fragments of HTM L ❏ Wrap changes in a custom HTML elemen t ❏ The server can push the changes to all connected users using a real-time protocol such as Mercure or Websockets

Slide 38

Slide 38 text

@dunglas Turbo Streams in Symfony ❏ Natively supported 🎉 ❏ Use the Mercure protocol under the hoo d ❏ Developer-friendly API (new in MercureBundle 0.3, thanks @azjezz ) ❏ Native authorization support, aka private updates (new in MercureBundle 0.3, thanks @azjezz)

Slide 39

Slide 39 text

@dunglas

Slide 40

Slide 40 text

@dunglas Show Template {# templates/conference/show.html.twig #}

{{ conference.name }}

{{ conference.description }}

Slide 41

Slide 41 text

@dunglas Edit Controller #[Route('/{id}/edit')] public function edit(Request $request, Conference $conference, HubInterface $hub): Response { $form = $this->createForm(ConferenceType::class, $conference); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { // … $hub->publish( new Update( 'conference:'.$conference->getId(), $this->renderView( 'conference/edit.stream.html.twig', ['conference' => $conference] ) ) ); return $this->redirectToRoute('conference_index', [], Response::HTTP_SEE_OTHER); } return $this->renderForm( 'conference/edit.html.twig', ['conference' => $conference, 'form' => $form] ); }

Slide 42

Slide 42 text

@dunglas Stream Template {# templates/conference/edit.stream.html.twig #} {{ conference.name }} {{ conference.description }}

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

@dunglas Where Is The Mercure Hub?! ❏ Symfony CLI now includes a native Mercure hub! (thanks @tgalopin and @fabpot ) ❏ It is detected and used by Symfony automatically in development ❏ In production : ❏ Use the official hub (binary, Docker image… ) ❏ Use a managed versio n ❏ Write your own Hub (open protocol)

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

@dunglas Broadcast:
 Turbo Streams X Doctrine

Slide 47

Slide 47 text

@dunglas Turbo Streams X Doctrine ❏ If you use Doctrine, we can do better ! ❏ Symfony UX Turbo is shipped with
 an integration with Doctrine ORM ! ❏ The UI can always be up to date with changes made to database ! ❏ Supported by MakerBundle

Slide 48

Slide 48 text

@dunglas Create a Broadcasted Entity Using MakerBundle bin/console make:entity --broadcast Comment 
 bin/console make:crud Comment // src/Entity/Comment.php // ... use Symfony\UX\Turbo\Attribute\Broadcast; /** * @ORM\Entity */ #[Broadcast] class Comment { // ... }

Slide 49

Slide 49 text

@dunglas Update the Generated Template {# templates/broadcast/Comment.stream.html.twig #} {% block create %}
{{ entity.content }}
{% endblock %} {% block update %} {{ entity.content }} {% endblock %} {% block remove %} {% endblock %}

Slide 50

Slide 50 text

@dunglas Subscribe and List Existing Comments {# templates/comment/show.html.twig #}

Live Comments

{% for comment in comments %}
{{ comment.content }}
{% endfor %}

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

@dunglas 0 lines of JS!

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

@dunglas Going Further

Slide 55

Slide 55 text

@dunglas Turbo Native ❏ Wraps Turbo websites in native iOS and Android app s ❏ Webview-based

Slide 56

Slide 56 text

Testing: Panther already supports Turbo and Mercure!

Slide 57

Slide 57 text

@dunglas Hotwire or a “modern” JS framework? It depends of the use case ! ❏ For traditional websites (CMS, e-commerce…), Hotwire and Symfony UX dramatically reduce the complexity of your majestic monolith, without compromises regarding the user experienc e ❏ For (most) webapps (offline-first, Jamstack, real-time geolocation…) and microservices architectures using a JS framework such as Next, Nuxt or SvelteKit with a JSON API is better suited.

Slide 58

Slide 58 text

@dunglas Symfony gives you the choice:
 
 with API Platform build your API in minutes
 then scaffold a Next.js, Nuxt.js or React Native app!

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

dunglas Thanks! If you like this project, sponsor me on GitHub: