Turbo Drive
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login Subscribe¡Es el día 9! Hermoso día 9 en el que empezamos a hacer brillar nuestra aplicación. Todos los fundamentos están en su sitio (AssetMapper, Tailwind y Stimulus), así que hoy es... casi una vuelta de la victoria. Estamos a punto de sacar un gran partido a nuestro dinero gracias a una biblioteca llamada Turbo.
En este momento, nuestro sitio, por supuesto, tiene actualizaciones de página completas. Fíjate en el logotipo de la barra de direcciones. Cuando hago clic, todo se hace con una actualización completa de la página. Eso está bien. No importa, ¡no está bien! Quiero que nuestro sitio tenga una experiencia de usuario devastadoramente buena.
Por suerte, tenemos a Turbo en nuestro equipo: una biblioteca JavaScript forjada en las profundidades de Internet, empeñada en destruir todas las actualizaciones de página completa. Mira en su sitio: no verás ninguna recarga de página completa mientras navegamos. Y comprueba lo rápido que se siente. Parece una aplicación de una sola página, porque, bueno, lo es, sólo que no es una que necesitemos construir con un framework frontend como React.
Instalando Turbo
Al igual que Stimulus, Symfony tiene un paquete que nos ayuda a trabajar con este Turbo. Busca tu terminal y ejecuta:
composer require symfony/ux-turbo
Cuando termine, haz:
git status
Al igual que el otro paquete UX, este modificó controllers.json y importmap.php. En assets/controllers.json, añadió dos nuevos controladores:
| { | |
| "controllers": { | |
| // ... lines 3 - 12 | |
| "@symfony/ux-turbo": { | |
| "turbo-core": { | |
| "enabled": true, | |
| "fetch": "eager" | |
| }, | |
| "mercure-turbo-stream": { | |
| "enabled": false, | |
| "fetch": "eager" | |
| } | |
| } | |
| }, | |
| // ... line 24 | |
| } |
El primero es... una especie de controlador falso. Carga y activa Turbo -verás lo que hace en un momento-, pero no es un controlador de Stimulus que vayamos a utilizar directamente. El segundo controlador es opcional -no vamos a hablar de él- y está desactivado por defecto.
El otro cambio, en importmap.php es, ninguna sorpresa: ha añadido @hotwired/turbo:
| // ... lines 1 - 15 | |
| return [ | |
| // ... lines 17 - 36 | |
| '@hotwired/turbo' => [ | |
| 'version' => '7.3.0', | |
| ], | |
| ]; |
El resultado de este único comando es asombroso. Cuando actualice, mira la barra de direcciones: ¡no vamos a ver más recargas de páginas completas! Y todo va superrápido. Me encanta. ¡Incluso los formularios! Haz clic en editar. Observa: se envía mediante AJAX. O, si creo uno nuevo y pulso enter, se envía vía AJAX. ¡Nuestro sitio se ha transformado en una aplicación de una sola página con un solo comando!
Turbo: ¿Cuál es el truco?
Puede que estés pensando
Esto es demasiado bueno para ser verdad, Ryan. ¿Cuál es el truco?
Vale, hay una pega, pero menor para los nuevos proyectos: tu JavaScript debe estar escrito para funcionar sin actualizaciones completas de la página. Históricamente, hemos escrito nuestro JavaScript para que se ejecute al cargar la página... o se ejecute en document.ready. Y esas cosas no ocurren después de la primera carga de la página. Pero siempre que tengas todo escrito en Stimulus, estarás bien.
Por ejemplo: nuestro controlador celebrate: no importa en cuántas páginas haga clic, eso sigue funcionando.
Si tu aplicación aún no está preparada para Turbo -por el problema del JavaScript-, puedes desactivarlo. En app.js, import * as Turbo from '@hotwired/turbo' . Abajo, pon Turbo.session.drive = false. No voy a hacerlo... así que lo comentaré:
| import * as Turbo from '@hotwired/turbo'; | |
| // ... lines 2 - 5 | |
| //Turbo.session.drive = false; | |
| // ... lines 7 - 8 |
Pero, ¿por qué iba a instalar Turbo... sólo para desactivarlo? Porque Turbo consta en realidad de tres partes. La primera se llama Turbo Drive. Es la parte que nos proporciona navegación AJAX gratuita en todos los clics de enlaces y envíos de formularios. Y eso es lo que esto desactiva.
Pero incluso si no estás preparado para Drive, puedes utilizar las otras dos partes: Turbo Frames y Turbo Streams. Son muy potentes y pasaremos mucho tiempo en este tutorial haciendo cosas increíbles con ellas.
Precargar enlaces
Turbo Drive en sí es bastante sencillo, pero tiene algunos trucos más en la manga. Y constantemente añaden cosas nuevas. Por ejemplo, una función se llama precarga. Para mostrar esto, entra en templates/base.html.twig. Si alguna vez estás en una página... y estás realmente seguro de saber qué enlace va a pulsar el usuario a continuación, puedes precargarlo.
Por ejemplo, en el enlace "viajes", añade data-turbo-preload:
| // ... lines 1 - 14 | |
| <body class="bg-black text-white font-mono"> | |
| <div class="container mx-auto min-h-screen flex flex-col"> | |
| <header class="my-8 px-4"> | |
| <nav class="flex items-center justify-between mb-4"> | |
| <div class="flex items-center"> | |
| // ... lines 20 - 27 | |
| <a href="{{ path('app_voyage_index') }}" data-turbo-preload class="ml-6 hover:text-gray-400">Voyages</a> | |
| // ... line 29 | |
| </div> | |
| // ... lines 31 - 36 | |
| </nav> | |
| </header> | |
| // ... lines 39 - 48 | |
| </div> | |
| </body> | |
| </html> |
Actualizar, inspeccionar elemento, luego ir a herramientas de red, XHR... y borrar el filtro. Cuando actualice, ¡veremos inmediatamente que se hace una petición AJAX para la página de los viajes! Por eso, cuando hagamos clic en este enlace, mira: va a ser instantáneo. ¡Boom!
Utiliza esto sólo cuando estés completamente seguro de cuál será la página siguiente. No queremos desencadenar un montón de tráfico innecesario a tu sitio que no se utilizará.
Ah, ¿y ves estos errores de JavaScript? Provienen de la barra de herramientas de depuración web y del perfilador de Symfony. No estoy seguro de por qué... pero no le gusta la precarga. Eso es algo que tenemos que arreglar, pero la precarga en sí funciona bien. Puedes ignorar esto.
De vuelta a la plantilla, elimina el data-turbo-preload... porque en realidad no sabemos en qué página hará clic el usuario a continuación.
Hoy ha ido genial. Con una biblioteca, hemos eliminado todas las recargas de página completa. ¿Qué podría ser lo siguiente? Mañana hablaremos de los Turbo Frames: una forma de crear "porciones" de nuestra página que se carguen con Ajax, sin escribir una sola línea de JavaScript.
16 Comments
I just did: composer require symfony/ux-turbo at my Easy Admin project.
My controllers.json was edited:
At my composer.json I have the "symfony/ux-turbo": "^2.16", version
My project does not have a importmap.php
But when I'm navigating through my admin I still get full-page reloads.
What did I miss?
Hey @Tim-V
Are you trying to use Turbo with EasyAdmin? I'm afraid EasyAdmin has not added support for Turbo yet, perhaps they will in the future.
Cheers!
Hi @MolloKhan,
Thanks for your reply. Yes I am trying to get Turbo Drive and Frames working at my Easy Admin project. So there is no way to get the Turbo Drive or Frames working?
I'm not saying it is impossible but basically, you'll have to modify EasyAdmin in a way that it can work with Turbo. That's something I've not done in the past, so I can't say how hard it might.
As a note, Turbo 8 (added in UX v2.15.0) includes "InstantClick" (https://turbo.hotwired.dev/handbook/drive#instantclick) which causes a pre-load like event (and additional XHR calls) on hover.
InstantClick also makes it impossible to click on session links in the XHR panel of the debug tool bar because it hovering over the link causes another XHR request, which then loads another line into list of XHR calls and moves the link out from under your mouse pointer.
To disable InstantClick globally by adding
<meta name="turbo-prefetch" content="false">to your<head>, but then you cannot enable prefetch at all. Instead, you can adddata-turbo-prefetch="false"to your<body>tag and then you can enable prefetch (as shown in this video) on a link-by-link basis.I believe this may be fixed now in the latest versions of Symfony - https://github.com/symfony/symfony/pull/54004
So, yay for the community!
Thank you for sharing it @Daryl
Hi !
If I click on the "previous" button of my browser (not the one coded in the app) it goes, well, to the real previous page, but not in the previous screen like expected. Is there any way to prevent the default behavior of the browser's previous button ?
Thank you :)
Hey @Laurent!
Can you tell me a bit more about the behavior you'd like? You said:
What do you mean by "previous page" vs "previous screen"?
In general, if the user clicks something (e.g. to open a modal or open a side drawer) and you want the "Back" button to "undo" that, then the click needs to "advance" the navigation. For example, if you're navigating a turbo frame, by default, those link clicks to not advance the navigation (and so they also do not change the URL). But in that case, you CAN make a frame "advance" the navigation if you want: https://symfonycasts.com/screencast/last-stack/data-tables#advancing-the-frame
Let me know if this helps!
Cheers!
Thank you Ryan, the "advance" feature was what I meant.
thank you for this tutorial, it's really nice :)
It's great that you can move between previously visited pages using browser navigation without downloading them again from the server,
but how to prevent this after logging out (for pages that should not be available to non-logged in users)?
Does Trubo provide any mechanisms for this?
Hey @Nataniel-Z
That's a great question. I believe a full page refresh may be necessary after login out (redirecting to somewhere else). Give it a try and let me know
Cheers!
When following this, only on the Voyages page (not the homepage or the Planets, I get this:
(link to image)
Seems to be somewhere in the ajax toolbar panel (in the symfony profiler bar)
Yo @Joris-Mak!
I'm not sure... other than I know that the web debug toolbar, sometimes, gets confused by Turbo. I mean, I know this happens when you add the preload attribute - but I haven't seen it without that preload (which I remove at the end of the chapter).
Either way - no big deal - just the WDT getting confused.
Cheers!
I don't get this - why when being on "voyage" page preload the same "voyage" page?
Hey @Nataniel-Z!
That's just me showing a bad example. I didn't even realize that I was already on the voyages page when I did this. The point is: if you know what the NEXT URL will be (which, if used correctly, would never be the current URL), you can preload it. Good example of this might be some linear process or documentation where users tend to go from one page to another - not something we really have in our app.
Cheers!
"Houston: no signs of life"
Start the conversation!