Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Pedal to the metal: introducing Symfony Turbo

Pedal to the metal: introducing Symfony Turbo

Hotwire Turbo is a tiny library recently introduced by DHH (the creator of Ruby on Rails) allowing to have the speed of Single-Page Apps without having to write any JavaScript!

As part of the Symfony UX initiative, I created an official integration between Turbo and Symfony. With Symfony Turbo, you can get rid of JavaScript and enjoy using Twig again!

During this talk, we'll discover how the library works, how to leverage it to enhance your Twig templates, how to add real-time features to your websites…

Screen recordings:

Live Edit: https://www.youtube.com/watch?v=d2N71fnVU-c
Live Comments: https://www.youtube.com/watch?v=WcEynac4tlE

Kévin Dunglas

April 09, 2021
Tweet

More Decks by Kévin Dunglas

Other Decks in Programming

Transcript

  1. Turbo
    Photo by Ye Massa

    View full-size slide

  2. @dunglas
    Kévin Dunglas
    ❏ Founder of Les-Tilleuls.coo
    p

    ❏ Creator of Mercure.rocks and API Platform
    ❏ Symfony Core Team Member
    @dunglas
    .social

    View full-size slide

  3. @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]

    View full-size slide

  4. @dunglas
    Do You Need
    JavaScript?

    View full-size slide

  5. @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)

    View full-size slide

  6. @dunglas
    Symfony UX Turbo
    :

    Getting Started

    View full-size slide

  7. @dunglas
    The Symfony UX Initiative

    View full-size slide

  8. @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

    View full-size slide

  9. @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

    View full-size slide

  10. @dunglas
    Enable the Webpack Encore Integration
    {# templates/base.html.twig #}


    {# ... #}


    {% block javascripts %}


    {{ encore_entry_script_tags('app') }}


    {% endblock %}

    View full-size slide

  11. @dunglas
    Turbo Drive

    View full-size slide

  12. @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

    View full-size slide

  13. @dunglas
    Turbo Drive Is Automatically Enabled
    {# templates/conference/show.html.twig #}


    {# … #}


    back to list




    href="{{ path('conference_edit', {'id': conference.id}) }}"


    data-turbo-action="replace"


    >


    edit





    Our standalone blog

    View full-size slide

  14. @dunglas
    Click on a link

    View full-size slide

  15. @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],


    );


    }




    View full-size slide

  16. @dunglas
    Turbo Frames

    View full-size slide

  17. @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

    View full-size slide

  18. @dunglas
    Turbo Frames
    {# templates/base.html.twig #}





    {# … #}


    Navbar displayed on every pages








    Frame content, replaced on click


    {% block body %}{% endblock %}








    View full-size slide

  19. @dunglas
    Lazy Loading Frames

    View full-size slide

  20. @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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  24. @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)

    View full-size slide

  25. @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');


    }

    View full-size slide

  26. @dunglas
    Lazy Loading Frame: Templates
    {# templates/base.html.twig #}





    Navbar displayed on every pages



    id="cart"

    src="{{ fragment_uri(controller('App\\Controller\\MyController::cart')) }}"


    >



    {# templates/cart.html.twig #}





    Cart content



    View full-size slide

  27. @dunglas
    Lazy Loading Frame: Cache Per Block

    View full-size slide

  28. @dunglas
    Turbo Streams

    View full-size slide

  29. @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

    View full-size slide

  30. @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)

    View full-size slide

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





    {{ conference.name }}


    {{ conference.description }}



    View full-size slide

  32. @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]


    );


    }

    View full-size slide

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








    {{ conference.name }}














    {{ conference.description }}






    View full-size slide

  34. @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)

    View full-size slide

  35. @dunglas
    Broadcast:

    Turbo Streams X Doctrine

    View full-size slide

  36. @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

    View full-size slide

  37. @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


    {


    // ...


    }

    View full-size slide

  38. @dunglas
    Update the Generated Template
    {# templates/broadcast/Comment.stream.html.twig #}


    {% block create %}











    {{ entity.content }}











    {% endblock %}


    {% block update %}








    {{ entity.content }}








    {% endblock %}


    {% block remove %}





    {% endblock %}


    View full-size slide

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


    Live Comments





    {% for comment in comments %}





    {{ comment.content }}





    {% endfor %}



    View full-size slide

  40. @dunglas
    0 lines of JS!

    View full-size slide

  41. @dunglas
    Going Further

    View full-size slide

  42. @dunglas
    Turbo Native
    ❏ Wraps Turbo websites in native iOS and Android
    app
    s

    ❏ Webview-based

    View full-size slide

  43. Testing: Panther already supports
    Turbo and Mercure!

    View full-size slide

  44. @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.

    View full-size slide

  45. @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!

    View full-size slide

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

    View full-size slide