Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Añadir Stimulus

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Podemos escribir JavaScript moderno en este archivo, podemos importar paquetes de terceros: somos libres de idear el código que queramos. Pero, si eres como yo, probablemente quieras utilizar Stimulus. Así que vamos a instalarlo.

Stimulus no es más que una biblioteca de JavaScript, así que podríamos decir

php bin/console importmap:require '@hotwired/stimulus'

Luego sigue su documentación sobre cómo configurar las cosas.

Instalar StimulusBundle

Pero Symfony tiene una integración especial con Stimulus. Así que en su lugar, ejecuta:

composer require symfony/stimulus-bundle

StimulusBundle es un paquete relativamente nuevo que contiene algunos atajos de Twig que utilizaremos, como stimulus_controller(). Pero, lo que es más delicioso, tiene una receta que configurará nuestra aplicación para cargar controladores Stimulus sin esfuerzo.

Compruébalo: gracias a la receta, ahora tenemos un directorio assets/controllers/con hello_controller.js dentro.

import { Controller } from '@hotwired/stimulus';
... lines 2 - 11
export default class extends Controller {
connect() {
this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js';
}
}

Sin tocar nada más, abre templates/vinyl/homepage.html.twig y, justo después de <h1>, añade un nuevo <div>. Vamos a adjuntar el nuevo controlador hello a este elemento. Hazlo con stimulus_controller() - que es una de las nuevas funciones que vienen de StimulusBundle - pasando hello.

... lines 1 - 4
{% block body %}
<div class="px-4">
... line 7
<div {{ stimulus_controller('hello') }}></div>
... lines 9 - 33
</div>
{% endblock %}

Eso no puede funcionar ya... ¿verdad? Actualiza. Y funciona. Y abajo, en la consola, vemos registros sobre la inicialización de Stimulus y la conexión de nuestro controladorhello. Con sólo una línea composer require, ¡Stimulus está vivo!

Cómo se carga Stimulus

Pongámonos nuestros sombreros de detective y profundicemos un poco más en cómo funciona esto y qué hizo realmente la receta. En templates/base.html.twig, éste es probablemente el cambio menos importante: añadió ux_controller_link_tags(). Hablaremos de ello en el próximo capítulo, cuando exploremos los paquetes UX. Pero, en resumen, si un paquete UX viene con su propio CSS, esto lo produce. Ahora mismo, no hace nada.

... lines 1 - 2
<head>
... lines 4 - 11
{% block stylesheets %}
{{ ux_controller_link_tags() }}
... lines 14 - 15
{% endblock %}
... lines 17 - 21
</head>
... lines 23 - 72

Y lo que es más importante, la receta añadió un nuevo archivo assets/bootstrap.js. Y, enassets/app.js, espolvoreó algo de código para importar ese archivo. Así, se carga app.js, que importa bootstrap.js, y luego que importa @symfony/stimulus-bundle.

import { startStimulusApp } from '@symfony/stimulus-bundle';
const app = startStimulusApp();
... lines 4 - 6

Ooh, ¡eso es una importación desnuda! ¡No empieza por "../" ni "./"! Eso significa que nuestro navegador lo buscará en importmap para averiguar qué archivo debe cargar.

¡De acuerdo! Ve a abrir importmap.php. ¡Sorpresa! La receta ha añadido dos entradas nuevas: Una para la propia biblioteca @hotwired/stimulus y otra para @symfony/stimulus-bundle, que apunta a este path de aspecto extraño.

31 lines importmap.php
... lines 1 - 15
return [
... lines 17 - 23
'@hotwired/stimulus' => [
'url' => 'https://cdn.jsdelivr.net/npm/@hotwired/stimulus@3.2.1/+esm',
],
'@symfony/stimulus-bundle' => [
'path' => '@symfony/stimulus-bundle/loader.js',
],
];

Aquí arriba, cuando se utilice una CDN, la entrada tendrá una clave url. Cuando apunte a un archivo local, la entrada tendrá una clave path, que será la ruta lógica a un archivo en AssetMapper.

Pero, ¿a qué apunta esta extraña ruta? Gira hasta tu terminal y ejecuta:

php bin/console debug:asset

Si coges un ascensor hasta arriba... ¡voilà! Cuando instalamos StimulusBundle, añadió una nueva "ruta de activos" a nuestro sistema, que apunta avendor/symfony/stimulus-bundle/assets/dist y tiene un "prefijo de espacio de nombres". Esto significa que, para apuntar a un archivo de este directorio, la ruta lógica empezará por @symfony/stimulus-bundle.

Así que aquí, cuando decimos @symfony/stimulus-bundle/loader.js, nos estamos refiriendo a este archivo de aquí: vendor/symfony/stimulus-bundle/assets/dist/loader.js. Es una forma larga de decir que cuando importamos @symfony/stimulus-bundle, en realidad estamos importando este archivo vendor/symfony/stimulus-bundle/assets/dist/loader.js. El bundle expone ese archivo añadiendo la "ruta de activos" de AssetMapper, que permite a la receta añadir una entrada a importmap.php que apunte a él.

Cómo se registran nuestros controladores

Bien, estamos cargando este archivo loader.js, y podemos verlo aquí. En tu navegador, actualiza... ve a tus herramientas de Red, y busca "cargador". Ahí lo tienes Ábrelo en una pestaña nueva.

Este código tiene funciones para iniciar la aplicación Stimulus y registrar los controladores de nuestra aplicación, como hello_controller.js. Pero... espera. Esto es sólo un archivo codificado. ¿Cómo es capaz de encontrar y cargar dinámicamente los archivos que viven dentro de nuestro directorio assets/controllers/?

La clave está arriba: import, isApplicationDebug, eagerControllers,lazyControllers de ./controllers.js. Esto... es un poco de magia. Vuelve a las herramientas de Red y busca "controladores"... ahí está - controllers.js. Abre esta nueva pestaña. ¡Woh! Tiene import controller_0 de../../controllers/hello_controller.js, que luego exporta a una variable llamadaeagerControllers.

Este archivo es creado dinámicamente por el bundle. Si miramos en el directorio vendor/, loader.js es un bonito archivo estático. Pero si nos fijamos en controllers.js, ¡no se parece en nada a lo que tenemos en el navegador! Cuando se sirve este archivo, AssetMapper lo intercepta, mira dentro de nuestro directorio assets/controllers/, encuentra allí todos los controladores y devuelve contenidos dinámicos basados en ellos.

Observa. Crea otro archivo llamado goodbye-controller.js (puedes utilizar guiones o guiones bajos). Cambia el texto a Goodbye controller!.

import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
connect() {
this.element.textContent = 'Goodbye controller!';
}
}

Es de esperar que, cuando actualicemos el archivo, veamos aparecer aquí el nuevo controlador. Y casi tienes razón. Lo que realmente ocurre es... ¡nada! Ningún cambio! O incluso podrías obtener un error 404. Eso es porque el contenido de este archivo acaba de cambiar y, por tanto, el hash también cambiará. ¡Estamos ante una versión desactualizada del archivo!

De vuelta al sitio, si actualizamos, deberíamos ver un nuevo archivo con un nuevo hash. No lo vemos... debido a un error de almacenamiento en caché que ya se ha solucionado. Para solucionarlo, voy a ejecutar:

php bin/console cache:clear

Y luego actualizar. ¡Ahora veo que tiene un nombre de archivo diferente, y que el contenido ha cambiado dinámicamente para incluir goodbye-controller.js!

Así que ahí lo tienes, el emocionante viaje al corazón de cómo Stimulus y AssetMapper se hicieron mejores amigos. bootstrap.js carga un archivo que inicia Stimulus... y que carga automáticamente todo lo que hay dentro del directorio assets/controllers/... así como cualquier paquete UX de terceros en assets/controllers.json. Hablemos ahora de esos paquetes de terceros.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^4.0", // v4.2.0
        "doctrine/doctrine-bundle": "^2.7", // 2.10.0
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.4
        "doctrine/orm": "^2.12", // 2.15.2
        "knplabs/knp-time-bundle": "^1.18", // v1.20.0
        "pagerfanta/doctrine-orm-adapter": "^4.0", // v4.1.0
        "pagerfanta/twig": "^4.0", // v4.1.0
        "stof/doctrine-extensions-bundle": "^1.7", // v1.7.1
        "symfony/asset": "6.3.*", // v6.3.0
        "symfony/asset-mapper": "6.3.*", // v6.3.0
        "symfony/console": "6.3.*", // v6.3.0
        "symfony/dotenv": "6.3.*", // v6.3.0
        "symfony/flex": "^2", // v2.3.1
        "symfony/framework-bundle": "6.3.*", // v6.3.0
        "symfony/http-client": "6.3.*", // v6.3.0
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/proxy-manager-bridge": "6.3.*", // v6.3.0
        "symfony/runtime": "6.3.*", // v6.3.0
        "symfony/stimulus-bundle": "^2.9", // v2.9.1
        "symfony/twig-bundle": "6.3.*", // v6.3.0
        "symfony/ux-turbo": "^2.9", // v2.9.1
        "symfony/web-link": "6.3.*", // v6.3.0
        "symfony/yaml": "6.3.*", // v6.3.0
        "twig/extra-bundle": "^2.12|^3.0", // v3.6.1
        "twig/twig": "^2.12|^3.0" // v3.6.1
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.4
        "symfony/debug-bundle": "6.3.*", // v6.3.0
        "symfony/maker-bundle": "^1.41", // v1.49.0
        "symfony/stopwatch": "6.3.*", // v6.3.0
        "symfony/web-profiler-bundle": "6.3.*", // v6.3.0
        "zenstruck/foundry": "^1.21" // v1.33.0
    }
}
userVoice