¿Ya es el 5º día? ¡Estamos volando! Es hora de añadir algo de CSS a nuestro sitio. ¿Y cómo funciona eso dentro de AssetMapper?
¿Incluyendo una Etiqueta de enlace manual?
Bueno, ya tenemos un archivo assets/styles/app.css. Y... nada nos impide entrar en base.html.twig, y añadir una etiqueta de enlace: link,rel="stylesheet", href luego asset() y la ruta lógica: styles/app.css.
¡Estupendo! Cuando actualizamos... y miramos la fuente de la página, ¡ahí está! Funciona de maravilla y es superaburrido. El tipo de aburrimiento que me gusta.
Sin embargo, si eliminamos esta línea... y vamos a actualizar la página. Huh, seguimos teniendo este fondo de blue: un fondo azul que viene de app.css:
| body { | |
| background-color: skyblue; | |
| } |
Echa otro vistazo a la fuente de la página. ¿Todavía hay una etiqueta link que apunta a ese archivo? Volvamos a base.html.twig, hmm, aquí no hay nada. ¿De dónde viene eso?
La respuesta -apuesto a que lo has adivinado- es la función importmap():
| // ... line 1 | |
| <html> | |
| <head> | |
| // ... lines 4 - 10 | |
| {% block javascripts %} | |
| {{ importmap('app') }} | |
| {% endblock %} | |
| </head> | |
| // ... lines 15 - 19 | |
| </html> |
Y es porque se está importando de app.js:
| // ... lines 1 - 6 | |
| import './styles/app.css'; | |
| // ... lines 8 - 15 |
Cómo funciona CSS
Importar un archivo CSS desde JavaScript es algo a lo que probablemente te hayas acostumbrado con Webpack Encore. Importas un archivo CSS... y en última instancia, se renderiza en la página como una etiqueta link. Sin embargo, esto no es algo que admitan realmente los módulos ECMAScript. Lo único que puedes importar son archivos JavaScript. Así que esto debería fallar estrepitosamente: como que debería descargar el archivo CSS e intentar parsearlo como JavaScript.
Sin embargo, como habrás notado, ¡no falla! ¡Me encantan los misterios!
Se trata de una función totalmente adicional que hemos añadido a AssetMapper. Funciona así En base.html.twig, decimos importmap('app'). El app se conoce como el punto de entrada: el único archivo que el navegador ejecutará directamente. Y sabemos que se refiere a assets/app.js.
Así que lo que hace AssetMapper es entrar en este archivo y encontrar todas las declaraciones importpara archivos JavaScript y CSS. Por cada importación CSS que encuentra, la añade como etiqueta link. Es... básicamente así de sencillo.
El truco del mapa de importación CSS
Bueno, hay una pequeña y fascinante complicación. Ve a la pestaña red de tu navegador y busca app. Este es el archivo app.js que está ejecutando el navegador. Fíjate: ¡todavía tiene la declaración de importación al archivo CSS! Si lo piensas, cuando nuestro navegador ejecuta esta línea, ¡debería fallar! Debería descargar el archivo CSS, intentar interpretarlo como JavaScript y encontrar un error de sintaxis, pero no lo hace.
La razón es un truco dentro de AssetMapper. Cuando importas un archivo CSS, AssetMapper añade una entrada importmap para él. Así que, aunque empiece por ./, nuestro navegador busca si hay una ruta coincidente dentro del mapa de importación, y la hay. Por eso, descarga este archivo.... que no es un archivo real. Es un archivo falso que no hace.... absolutamente nada. Así que hace que esa línea no de error y... no haga nada.
Importar CSS de otros archivos JavaScript
Para ver lo potente que es esto, vamos a crear un segundo archivo CSS para apoyar nuestro saludo alienígena. Llámalo alien-greeting.css y haz que el fondo del cuerpo sea darkgreen. Aunque, personalmente, espero que los extraterrestres tengan los colores del arco iris:
| body { | |
| background: darkgreen; | |
| } |
En alien-greeting.js, importa eso: ../styles/alien-greeting.css:
| import '../styles/alien-greeting.css'; | |
| // ... lines 2 - 6 |
¿Funcionará? ¡Pruébalo! Actualiza y... ¡fondo verde! En el código fuente, tenemos una segunda etiqueta link y un segundo elemento nuevo en importmap. ¡Estupendo! Como app.js importa alien-greeting.js, AssetMapper también encuentra los archivos CSS que importa.
Carga perezosa de CSS
Aquí es donde las cosas se ponen realmente espeluznantes. Los módulos JavaScript tienen una sintaxis de importación dinámica que te permite importar módulos de forma asíncrona. Esto nos permite cargar un archivo más tarde, cuando lo necesitemos, en lugar de al cargar la página. Y podemos utilizar este truco con CSS.
Copia esto. Imagina que sólo queremos cargar ese archivo CSS cuando inPeace sea igual a false. Así que diré, si no inPeace, entonces usa setTimeout() para esperar 4 segundos. Después de 4 segundos, importa el archivo CSS. Excepto que, en cuanto necesites que una importación no viva al principio de tu archivo, tendrás que llamarla como una función:
| export default function (message, inPeace = false) { | |
| if (!inPeace) { | |
| setTimeout(() => { | |
| import('../styles/alien-greeting.css'); | |
| }, 4000); | |
| } | |
| // ... lines 7 - 8 | |
| } |
Eso está muy bien. Pruébalo. Al principio, ¡fondo azul! 2, 3, 4, ¡fondo verde! El archivo CSS se cargó perezosamente. ¿Cómo? Bueno, ya no hay etiqueta de enlace alien-greeting.css en el código fuente de la página. En su lugar, esperamos a que el navegador ejecute esta línea JavaScript. Cuando lo hace, lo busca en el mapa de importación, lo encuentra y descarga este archivo falso. Pero esta vez, en lugar de ser una línea que no hace nada, este archivo falso añade una nueva etiqueta link a la sección head conrel="stylesheet" y href establecidas en alien-greeting.css.
Joder, ¡podemos ver esto en tiempo real! Aquí, bajo la etiqueta head, vemos la hoja de estilos. Si actualizo y abro rápidamente eso, no está ahí. Y... entonces se añade. Qué guay.
Ahora que ya sabemos cómo funciona el CSS, ¡mañana lo utilizaremos para dar vida a nuestro sitio web! Pero quiero hacerlo desde un ángulo extra divertido: Quiero utilizar Tailwind CSS.
9 Comments
Hi!
I have downloaded your project from github and am following all the instructions. However, I am getting the error: "styles/app.css" not found.
I tried to create a new symfony project and in this case it works fine and i can see the app.css file.
What could be the problem?
Hey @Daniele
That file is generated by Symfony Flex when you install the AssetMapper library. Perhaps you missed that step :)
Cheers!
Hello!
If I Import local files from my
app.js, exemple :They won't be set in the
<importmap>:Because I get a forbidden MIME type error ; in the (Firefox) console:
Is there anything I need to set in the configuration to manage mimetypes? I haven't found anything like this in the AssetMapper documentation.
Thanks for your help!
Hi @glioburd!
Sorry for my very slow reply! Let's see about our issue :)
My guess is that there is some other problem that has nothing to do with MIME types. My guess is that, if you try to go directly to the URL of either of these files, they won't be JS or CSS files at all! They are likely some sort of error page, which might help uncover the real issue.
However, it also looks weird to me that the files that are being requested do not contain the version hash (e.g.
alien-greeting.jsinstead of something likealien-greeting-1234abcd.js. Also,alien-greeting.jsshould be in your<importmap>.So, something IS wrong, but I don't see what. If you're still having this issue, my first recommendation would be to upgrade to the latest Symfony version and clear your Symfony cache. It's possible something just got "stuck".
Cheers!
That sounds really, really fun.
But: What about TypeScript? Besides being from MS it is quite cool, are there ways for AssetMapper to convert TS to JS? And TSX?
Kind regards and thx for the course.
Hey @tracer!
Ah, it is fun - I'm really enjoying this :).
Yea! There's a bundle for that! https://github.com/sensiolabs/AssetMapperTypeScriptBundle
So if you like TS, sure, why not :). This builds each .ts -> .js, and then that js is what is exposed by AssetMapper.
Nobody has built it yet, but sure? Why not :). The TypeScript bundle might be able to be extended to support this if it's not already. If you're interested, open an issue there. I know the developers and they're very good and helpful.
Cheers!
Thanx for your reply, Ryan.
Oh, there is already a bundle. Wow. I guess you believe that I searched for Asset Mapper and TS before asking here … But I didn't get any helpful results.
So, I'll take my xmas holiday to try converting a fun project with Symfony & React 18 to Asset Mapper.
Well, I'll give that a thought, I'm not bound to React, it was just fun to learn something new (at work my project is a 10 years old Cake2 Project …) I watched your Stimulus tutorials, maybe I'll give that a try.
Hey @tracer!
It's all pretty new still, so hopefully it'll be easier to find soon :).
The React part might be a problem. It's possible (heck, we do it in ux.symfony.com - https://github.com/symfony/ux/blob/2.x/ux.symfony.com/package.json#L12 ) but there isn't a bundle to go from .jsx -> .js. And, more broadly, my thinking is "If you're building an SPA in React, use React's tools & ecosystem". But if you're building a web app that returns HTML, AssetMapper + Stimulus + Turbo can give you a great UX & DX. So, there are 2 paths and both are valid.
Let me know if that makes sense :).
Cheers!
"Houston: no signs of life"
Start the conversation!