Chapters
-
Course Code
Subscribe to download the code!
Subscribe to download the code!
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Okay, so how are we going to bring CSS and JavaScript into our app? Are we going to add a build system like Vite or Webpack? Heck no! That's one of the fun things about all of this! We're going to create something amazing with zero build system. To do that, let's install a new Symfony component called AssetMapper.
Installing AssetMapper
Spin over to our terminal and run:
composer require symfony/asset-mapper
This is the new alternative to Webpack Encore. It can do pretty much everything that Encore can do and more... but it's way simpler. You should definitely use it on new projects.
When I run:
git status
We see that its Flex recipe made a number of changes. For example, .gitignore
is ignoring a public/assets/
directory and assets/vendor/
:
Show Lines
|
// ... lines 1 - 11 |
###> symfony/asset-mapper ### | |
/public/assets/ | |
/assets/vendor | |
###< symfony/asset-mapper ### |
We'll talk more about those later. But on production, this is where your assets will be written to and, when we install third-party JavaScript libraries, they'll live in that vendor/
directory.
It also updated base.html.twig
and added an importmap.php
file. But put those on the back burner for now: we'll talk about them tomorrow.
The "Mapped Paths"
For today's adventure, pretend that, when we installed this, all it gave us was a new asset_mapper.yaml
file and an assets/
directory. Let's go check out that config file: config/packages/asset_mapper.yaml
:
framework: | |
asset_mapper: | |
# The paths to make available to the asset mapper. | |
paths: | |
- assets/ |
The idea behind AssetMapper couldn't be simpler: you define paths - like the assets/
directory - and AssetMapper makes every file inside available publicly... as if they lived in the public/
directory.
Referencing an Asset File
Let's see it in action you. If you downloaded the course code, you should have a tutorial/
directory, which I added so we can copy a few things out of it. Copy logo.png
. Inside assets/
, we can make this look however we want. So let's create a new directory called images/
and paste that in.
Since this new files lives inside the assets/
directory, we should be able to reference it publicly. Let's do that in our base layout: templates/base.html.twig
. Anywhere, say <img src="">
, {{
and then use the normal asset()
function. For the argument, pass the path relative to the assets/
directory. This is called the logical path: images/logo.png
:
<!DOCTYPE html> | |
<html> | |
Show Lines
|
// ... lines 3 - 14 |
<body> | |
<img src="{{ asset('images/logo.png') }}" alt="Space Inviters Logo" /> | |
Show Lines
|
// ... lines 17 - 18 |
</body> | |
</html> |
Before we try this, an easy way to see every asset that's available is via:
php bin/console debug:asset
Very simply: this looks through all of your mapped paths - just assets/
for us - finds every file then lists them with their logical path. So I can be lazy and copy that, paste it here.... and done.
Now, when we try this, it doesn't work! The asset()
function is still its own component, so let's get that installed:
composer require symfony/asset
And now.... cool logo!
Instant Asset Versioning
To see the really neat thing, inspect the image and look at the filename. It's /assets/images/logo-
and then this long hash. This hash comes from the file's contents. If we updated logo.png
, it would automatically generate a new hash. And that is super important for two, related, reasons. First, because when we deploy, the new filename will bust the browser cache for our users so that they see the new file immediately. And second, because of this, we can configure our production web server to serve all the assets with long-lived Expiration headers. That maximizes that caching & performance.
Serving Assets in Dev vs Prod
Now in the dev
environment, there is no physical file with this filename. Instead, the request for this asset is processed through Symfony and intercepted by a core listener. That listener looks at the URL, finds the matching logo.png
inside the assets/images/
directory and returns it.
But on production, that's not fast enough. So, when you deploy, you'll run:
php bin/console asset-map:compile
Very simply: this writes all the files into the public/assets/
directory. Look: in public/assets/
, we now have real, physical files! So when I go over and refresh, this file isn't being processed by Symfony, it's loading one of those real files.
Now, if you ever run this command locally, make sure to delete that directory after... so it stops using the compiled versions:
rm -rf public/assets/
Wow! Day 2 is already done! We now have a way to serve images, CSS or any file publicly with automatic file versioning. The second part of AssetMapper
is all about JavaScript modules. And that's tomorrow's topic.
12 Comments
Hey @Johann!
For example this beautyfull Space Inviters Logo img-tag. If i hover over {{ asset('images/logo.png') }} in my twig template and everywhere, Phpstorm just tells me "Missing asset".
💯 me too. I believe this is something that will need to be updated with the Symfony + PhpStorm plugin. It looks like there's an issue about it - https://github.com/Haehnchen/idea-php-symfony2-plugin/issues/2236 and the author has been pretty responsive and supportive for AssetMapper support. So hopefully it can be added :).
Cheers!
I also ran into an issue in dev where assets were returning a 404 due to nginx config.
The offender was
location ~* \.(js|css|png|jpg|jpeg|gif|webp|ico|eot|svg|ttf|woff)$ {
expires 1y;
}
A useful config in production, but in dev, it means assets are served directly, bypassing symfony's listener to rewrite the path.
Hey @Nicolas-S
Good catch, yes, that causes problems with the dev environment. From my point of view, it will be good to add some check for static file exist, and if it does not exist, then pass it to the listener.
Cheers!
I'm not sure what's going on but after running composer require symfony/asset, my browser is getting 404 for all assets. I can run asset-map:compile and then the assets load just fine.
At first I thought this meant I had APP_ENV set to prod, but that's not it. I have confirmed that it is set to dev.
I did set up a docker container to run this project instead of using symfony serve. Could that be a factor in this?
Hey @kinadian!
I might know what's going on :). If you open a 404 URL in your browser, what do you see? Is it a Symfony 404? Or a generic 404 from your web server? The key to loading assets in dev is that the asset URLs need to be processed through Symfony. In other words, when a request is made to /assets/app-abcd123.js
, that request needs to be processed by Symfony. Sometimes a web server will, by default, see the .js
and only look for that physical file (then trigger a 404 if it's not found). If I'm correct, check the rewrite rules on your web server. This won't be an issue on production, as you've seen, but you'll definitely want to get this working locally.
Cheers!
Hi @weaverryan!
Thank you. I asked a colleague to help. We ended up getting it fixed by installing symfony/apache-pack. This added an .htaccess file which changes apache's behaviour to let the request get processed by Symfony instead of returning a 404.
You were right, the 404 was a generic one from apache, not the Symfony 404 page.
Nice work! If you're using Apache, that is 100% the correct thing to do. I'm happy you got it sorted. Now, onward :)
With asset-mapper v.6.4 you may expect to not see the correct background color (i.e. skyblue).
In this case: try to remove comments in app.js (yes... the comments), remove public/asset directory and relaunch symfony serve command.
Thanks Ryan!
Thanks for sharing this! There was a but introduced in some version of AssetMapper related to the comment. I believe that should be fixed in 6.4.4 and 7.0.4, which will release very shortly after this comment.
Cheers!
Hey @weaverryan
The Asset Mapper looks really cool and I'm almost convinced to use it instead of Webpack Encore. The only thing that's holding me back, is that I do not see any examples or documentation on how to add integrity hashes for styles/scripts. Is this possible with the Asset Mapper?
Regards,
YT
Hey @Senet!
Ha! I like the challenge - let's see if we can get you the rest of the way there :).
The only thing that's holding me back, is that I do not see any examples or documentation on how to add integrity hashes for styles/scripts. Is this possible with the Asset Mapper?
You're right to catch this. It is not yet implemented - we did a LOT for 6.4 (and this was on my list), but it didn't quite happen. It became less of a priority because AssetMapper 6.4 (unlike 6.3) doesn't rely on serving content from CDNs. However, I recognize that some people may still want integrity hashes even if their assets are being served from the same hostname or perhaps if you use AssetMapper but still offload the serving of the files to your own CDN (am I listing one of your use-cases accurately)?
So right now, there is no hook in AssetMapper to add those, though it should be super easy to add. AssetMapper already knows the content of each asset and is responsible for rendering the script and link tags... so I can't really see a problem. However (and I'm not an expert at all on this topic), while we an add an integrity hash to any link or script tags on the page, if app.js
has import './foo.js'
, there is no mechanism currently to enforce an integrity hash on foo.js. You can read some discussion about it here - https://github.com/WICG/import-maps/issues/221. But even in that discussion, people are mainly talking about the importance of this when referencing assets on CDNs... and AssetMapper doesn't refer to CDN URLs. So, overall, I'm not sure HOW needed integrity hashes are... but I recognize that people (beyond you) will likely ask about this also.
Cheers!
"Houston: no signs of life"
Start the conversation!
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"babdev/pagerfanta-bundle": "4.x-dev", // 4.x-dev
"doctrine/doctrine-bundle": "^2.10", // 2.12.x-dev
"doctrine/doctrine-migrations-bundle": "^3.2", // 3.4.x-dev
"doctrine/orm": "^2.16", // 2.18.x-dev
"knplabs/knp-time-bundle": "dev-main", // dev-main
"pagerfanta/doctrine-orm-adapter": "4.x-dev", // 4.x-dev
"pagerfanta/twig": "4.x-dev", // 4.x-dev
"symfony/asset": "6.4.*", // 6.4.x-dev
"symfony/asset-mapper": "6.4.*", // 6.4.x-dev
"symfony/console": "6.4.x-dev", // 6.4.x-dev
"symfony/dotenv": "6.4.x-dev", // 6.4.x-dev
"symfony/flex": "^2", // 2.x-dev
"symfony/form": "6.4.x-dev", // 6.4.x-dev
"symfony/framework-bundle": "6.4.x-dev", // 6.4.x-dev
"symfony/monolog-bundle": "^3.0", // dev-master
"symfony/runtime": "6.4.x-dev", // 6.4.x-dev
"symfony/security-csrf": "6.4.x-dev", // 6.4.x-dev
"symfony/stimulus-bundle": "2.x-dev", // 2.x-dev
"symfony/twig-bundle": "6.4.x-dev", // 6.4.x-dev
"symfony/ux-autocomplete": "2.x-dev", // 2.x-dev
"symfony/ux-live-component": "2.x-dev", // 2.x-dev
"symfony/ux-turbo": "2.x-dev", // 2.x-dev
"symfony/ux-twig-component": "2.x-dev", // 2.x-dev
"symfony/validator": "6.4.x-dev", // 6.4.x-dev
"symfony/web-link": "6.4.*", // 6.4.x-dev
"symfony/yaml": "6.4.x-dev", // 6.4.x-dev
"symfonycasts/dynamic-forms": "dev-main", // dev-main
"symfonycasts/tailwind-bundle": "dev-main", // dev-main
"tales-from-a-dev/flowbite-bundle": "dev-main", // dev-main
"twig/extra-bundle": "^2.12|^3.0", // 3.x-dev
"twig/twig": "^2.12|^3.0" // 3.x-dev
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.6.x-dev
"phpunit/phpunit": "^9.5", // 9.6.x-dev
"symfony/browser-kit": "6.4.*", // 6.4.x-dev
"symfony/css-selector": "6.4.*", // 6.4.x-dev
"symfony/debug-bundle": "6.4.x-dev", // 6.4.x-dev
"symfony/maker-bundle": "^1.51", // dev-main
"symfony/panther": "^2.1", // v2.1.1
"symfony/phpunit-bridge": "7.1.x-dev", // 7.1.x-dev
"symfony/stopwatch": "6.4.x-dev", // 6.4.x-dev
"symfony/web-profiler-bundle": "6.4.x-dev", // 6.4.x-dev
"zenstruck/browser": "1.x-dev", // 1.x-dev
"zenstruck/foundry": "^1.36" // 1.x-dev
}
}
Good morning all,
because i really love asset mapper, i played around with it the last weeks and i hoped to get a solution for a little issue in my IDE with this LAST Stack.
I use PhpStorm, latest version but i never get autosuggestions/autocompletion for assets. For example this beautyfull Space Inviters Logo img-tag. If i hover over {{ asset('images/logo.png') }} in my twig template and everywhere, Phpstorm just tells me "Missing asset".
How can i fix this?
JD