Encore -> AssetMapper Part 2
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 SubscribeGetting 3rd party CSS files working is one of the trickier things to do in AssetMapper. Importing them like this isn't going to work.
Installing Bootstrap CSS
Let's focus on Bootstrap first. This is a third-party package, and we install third-party packages by saying bin/console importmap:require the package name:
php bin/console importmap:require bootstrap
Bootstrap is especially interesting because it grabs the JavaScript package, a dependency of the JavaScript package, and it also noticed that this package commonly has a CSS file... so it grabbed that too. All three things were added to importmap.php.
| // ... lines 1 - 15 | |
| return [ | |
| // ... lines 17 - 29 | |
| 'bootstrap' => [ | |
| 'version' => '5.3.2', | |
| ], | |
| '@popperjs/core' => [ | |
| 'version' => '2.11.8', | |
| ], | |
| 'bootstrap/dist/css/bootstrap.min.css' => [ | |
| 'version' => '5.3.2', | |
| 'type' => 'css', | |
| ], | |
| ]; |
We're not using the bootstrap JavaScript in this project. So we could delete this. But I'll leave it because it's not hurting anything. The real star, however, is this CSS file. Copy its path. And in app.css, remove the top line.
You can import third party CSS with AssetMapper, but can't do it from inside another CSS file. Well, you technically can, but life is easier if we do it from app.js. Say import, then paste.
| // ... lines 1 - 8 | |
| import 'bootstrap/dist/css/bootstrap.min.css'; | |
| import './styles/app.css'; | |
| // ... lines 11 - 16 |
And now... Bootstrap springs to life!
Adding FontAwesome
Next up is FontAwesome. Notice that we're grabbing a specific CSS file from the package. One big difference between Encore and AssetMapper is that if you need to import a specific file from a package, you need to importmap:require that file, not just the package in general. Watch bin/console importmap:require and paste:
php bin/console importmap:require @fortawesome/fontawesome-free/css/all.css
That grabs this one CSS file, downloads it into the project and adds it to importmap.php right here. If you're curious, these files are downloaded into an assets/vendor/ directory.
Head into app.css, remove that line and add another import for that path.
| // ... lines 1 - 8 | |
| import 'bootstrap/dist/css/bootstrap.min.css'; | |
| import '@fortawesome/fontawesome-free/css/all.css'; | |
| import './styles/app.css'; | |
| // ... lines 12 - 17 |
And that works! Though, on the topic of FontAwesome, I don't recommend using FontAwesome like this anymore. Instead, use FontAwesome kits. Or, better, render an inline SVG. Hopefully we'll have an icon package soon from Symfony UX to make that easier.
Adding CSS Fonts
The last item in app.css is a font. This is trickier. If we run importmap:require followed by only a package name - no path - it will always download the package's main JavaScript file. You only get a CSS file if you importmap:require a path to a CSS file, like we just did.
Ok, I know earlier we ran import:require bootstrap and that did give us a CSS file. So, let me be more clear. If you run importmap:require packageName, you'll get the JavaScript for that package. In some cases, like Bootstrap, the package advertises that it has a CSS file. When that happens, AssetMapper sees that and, effectively, runs importmap:require bootstrap/dist/css/bootstrap.min.css automatically... just to be helpful.
Anyway, I know we need a CSS file. With Encore, if you imported a package from inside a CSS file, Encore would try to find the CSS file in the package and import that. This doesn't happen with AssetMapper: we need to figure out what the path is to the CSS file then require that.
I like to do this at jsDelivr.com. This is the CDN that AssetMapper uses behind the scenes to fetch packages. Search for the package. It shows up, but there's one below from @fontsource-variable. Variable fonts can be a bit more efficient, so let's change to that. Inside, hey! It advertises the main CSS file! If you wanted a different file, you could click the Files tab and navigate to find what you need.
Copy this path all the way down to the package name, then spin over and run importmap:require and paste. But we don't need the version: just the package, then the path:
php bin/console importmap:require @fontsource-variable/roboto-condensed/index.min.css
Copy that and hit enter. It downloads the CSS file and adds an entry to importmap.php.
| // ... lines 1 - 15 | |
| return [ | |
| // ... lines 17 - 43 | |
| '@fontsource-variable/roboto-condensed/index.min.css' => [ | |
| 'version' => '5.0.1', | |
| 'type' => 'css', | |
| ], | |
| ]; |
Finally, remove the import from app.css and import it from app.js.
| // ... lines 1 - 8 | |
| import 'bootstrap/dist/css/bootstrap.min.css'; | |
| import '@fortawesome/fontawesome-free/css/all.css'; | |
| import '@fontsource-variable/roboto-condensed/index.min.css'; | |
| import './styles/app.css'; | |
| // ... lines 13 - 18 |
Oh, and because we changed to the variable font, in app.css, update the font family to Roboto Condensed Variable.
Over on the site, watch the font when I refresh. Got it! Grabbing those third party CSS files might be the trickiest thing you'll do in AssetMapper.
Oh, and if you're using Sass or Tailwind, there are Symfonycasts bundles to support both of those in AssetMapper.
Adding the .js Extension
Now that styling is working, let's look into our JavaScript. In the console, we have an error: a 404 for something called bootstrap. That's coming from app.js: from this import line. To fix this, open app.js and add .js to the end.
| // ... lines 1 - 13 | |
| // start the Stimulus application | |
| import './bootstrap.js'; | |
| // ... lines 16 - 18 |
With Webpack Encore, we're running inside a Node environment. And Node lets you cheat: if the file you're importing ends in .js, you don't need to include the .js. But in a real JavaScript environment, like your browser, you can't do that: the .js is needed.
This is probably the biggest change you'll need to make when converting.
stimulus-bridge -> stimulus-bundle
Try the page now. Next error! And it's important:
Failed to resolve module specifier
@symfony/stimulus-bridge.
This means that, somewhere, we're importing this package... but the package doesn't exist in importmap.php.
There are two types of imports. First, if an import starts with ./ or ../, it's a relative import. Those are simple: you're importing a file next to this file. The second type is called a bare import. This is when you're importing a package or a file in a package. For these, the string inside the import must exactly exist in importmap.php. If it doesn't, you'll see this error.
The source of our error is bootstrap.js. See this @symfony/stimulus-bridge? That does not exist in importmap.php. The solution, usually, is to install this.
But in this case, the package is specific to Webpack Encore and the fix is related to our migration. Change this to @symfony/stimulus-bundle.
| import { startStimulusApp } from '@symfony/stimulus-bundle'; | |
| // ... lines 2 - 8 |
And lo and behold: that string does live inside importmap.php! Below, the next line simplifies.
| import { startStimulusApp } from '@symfony/stimulus-bundle'; | |
| // Registers Stimulus controllers from controllers.json and in the controllers/ directory | |
| export const app = startStimulusApp(); | |
| // ... lines 5 - 8 |
But it does the same thing as before: starts the Stimulus app and load our controllers. If you start a new Symfony app, you get all this with the recipe. But since we're converting, we need to do a bit more work.
Installing Missing Packages
Refresh now. We get the exact same error but with a different package: axios. You know the drill: somewhere, we're importing this... but it doesn't live in importmap.php. In this case, it's coming from song-controls_controller.js.
And this time, the fix is to install this package! Spin over and run
php bin/console importmap:require axios
That adds axios to importmap.php and now... our app is alive! This is powered by AssetMapper! We have a performant, modern frontend all with no build system.
Downgrading a Dependency
Oh, but look at the footer: the text is darker than it used to be. Before, I was using bootstrap 5.1. But when we installed bootstrap with AssetMapper, it grabbed the latest 5.3. And apparently something changed!
I could figure out what changed and fix this... But we can also downgrade. Update the version in importmap.php to 5.1.3.
| // ... lines 1 - 15 | |
| return [ | |
| // ... lines 17 - 29 | |
| 'bootstrap' => [ | |
| 'version' => '5.1.3', | |
| ], | |
| // ... lines 33 - 35 | |
| 'bootstrap/dist/css/bootstrap.min.css' => [ | |
| 'version' => '5.1.3', | |
| 'type' => 'css', | |
| ], | |
| // ... lines 40 - 50 | |
| ]; |
If we just did that and refreshed, nothing would change: the newer version is still downloaded into assets/vendor/. To sync that directory with importmap.php, run:
php bin/console importmap:install
Think of this is as the composer install of the AssetMapper world. It noticed that we changed two packages and downloaded those. And just like that, we've crossed the finish line! We're running AssetMapper!
Next up, let's take three minutes to modernize & simplify our JavaScript.
11 Comments
Hello!
I am using premium plugins from GSAP. They are not available on CDN and so, can not be installed with importmap:require. How can I continue to use them?
Thx!
Hey @be_tnt
That's a very good question, sadly I don't know the answer, I've never had that problem before. Have you tried opening an issue on the Asset Mapper GitHub repository?
I will. Thx!
If I have the following custom function call on certain pages (not all):
How can I convert this to use AssetMapper, given that
'time-entries-summary-style'is a Webpack entry mapped to an SCSS file like this in the Webpack configuration?What steps are necessary to replace
encore_entry_link_tagswith the AssetMapper equivalent while ensuring it works correctly only on specific pages?Hey @ahmedbhs ,
I think this blog post might be interested for you: https://symfony.com/blog/upgrading-symfony-websites-to-assetmapper - it highlights some migration steps from Encore to AssetMapper.
But in particular, to replace
{{ encore_entry_link_tags('time-entries-summary-style') }}with the AssetMapper I think you should follow these steps:The Webpack Encore entries are so called AssetMapper entrypoints. You need to convert your entries into entrypoints1-to-1. You need to do this manually by editing the importmap.php file. See the blog post I linked above for more examples.
Fix imports in your assets. Now, every time you import files, you must include the file extension. Once again see the example from the blog post.
Replace
encore_entry_link_tags()calls in Twig templates withimportmap()ones.Enable SCSS Compilation in AssetMapper. By default, AssetMapper doesn't process SCSS files. You’ll need to configure it to use the symfonycasts/sass-bundle SCSS processor for this. Though think well if you still need Sass, because native CSS supports a lot of things and most probably you can finally convert your SCSS files into plain CSS files. Then you will simplify your setup a lot.
So. once again, I think that blog post will answer most of your questions, so make sure to read it in full to make the smooth migration. And sorry for the long reply on it!
I hope it helps!
Cheers!
Hi Ryan, I'm following this course to upgrade my app from 6.2 to 7.1. I have been able to follow all the steps discussed in the video without any problem. Unfortunately when I try to import jquery-ui (it's not mentioned in the video, I know) I can't get it to work. I have searched for a solution on the internet without good results. Is it possible that this is too new to find any solution? If not, how could I find a solution to my problem?
the error I'm getting in the console is:
Instead, if I can use jQuery without difficulties in the project, for example, if a
console.log($(".myclass");and of course, everything works correctly in webpackencore, but I want to be up to date with symfony and assetmapper.Any information on this is welcome, thank you very much.
Hey @Juan-D
Are you using Webpack or Asset Mapper?
Cheers!
As I say in my query, I am upgrading from Symfony 6.4 to 7.1 and changing webpack to activemapper. I have gone from 6.2 to 6.4 and from 6.4 to 7.1. I have been able to follow the tutorial without problem except for jquery-ui.
Regards!
Hey Juan,
Your upgrading strategy looks good to me. However, probably would be better to finish the upgrade first, and then, in a new PR, migrate from WebpackEncore to AssetMapper. It would simplify things for upgrading for you, and also it will be more obvious for you if you could easily migrate your project to AssetMapper or it requires more changes in your code.
Ideally, I would recommend you to get rid of jQuery completely in your project. jQuery is not recommended anymore as most of things can be easily done natively in the modern JS now. You can reference this website to see how you can replace jQuery calls with modern native JS: https://youmightnotneedjquery.com/
But if your code is tied very much to jQuery - I understand you, and it would require you some time to refactor. But it's difficult to say what's wrong in your code, because it depends on how (and where) you use jQuery. I don remember for sure, but it might be so that you can't use jQuery globally anymore. But in modern JS you should be able to import it like this:
Or in case of the AssetMapper, you will need to install the package with
bin/console importmap:install jqueryand then in the import specify the exact path to the jquery file to import it correctly, something likeimport $ from './path/to/vendor/path/to/jquery.js'This should be the first step, and you need to make sure jQuery works for you this way. When it works for you, you can try to do the same with
jquery-ui, i.e. install it and import from the specific path where it was installed (I'm not sure if you can import everything at once, probably you would need to be more specific about what to import, e.g.import './path/to/vendor/jquery-ui/ui/widgets/datepicker.js'). It might be so that you may need to import both jquery and jquery-ui, i.e. import jquery first because jquery-ui may need that already. And in theory it should work, but if not - probably because jquery-ui requires the jquery to be globally available, not only for this file scope. And if so, this is a bad practice, but you may try to make the imported jquery global withwindow.$ = window.jQuery = $;. Once again, it's a bad practice, but it might work as a temporary solution before you get rid of jQuery completely.Also, please take a look at the official docs: https://symfony.com/doc/current/frontend/asset_mapper.html#global-variables-like-jquery - this should help as well.
I hope that helps!
Cheers!
Hello Victor,
Of course what you mention is the first thing I did. I have not given many explanations before because I wanted to get to the crux of the question directly. But I have carried out the entire update process in a new branch of my project called update. First I updated the framework completely, then the recipes and then I made the necessary adjustments so that everything worked. Now I'm with the js part, and I haven't had any problems until getting to jQuery-ui. As I mentioned, jqyery is imported without problems but as soon as I do "... importmap:require jquery-ui" when I do the import in my js file, the error mentioned appears, "jQuery is not defined". The first thing I did before anything was to consult the official documentation that you mention, to include "window.$=window.jQuery=$" but this does not work for me. And I have verified that I can use jquery in other ways, although in my case, it is with datepicker, but with autocomplete, although I suppose it does not matter which component to use since it is also part of jquery-ui. I had thought that perhaps importing the component I need explicitly could work, but it is something else that still fails, I imported "jquery-ui/ui/widgets/autocomplete" but this has not worked either.
On the other hand, you are right, I have a fairly strong dependency on jquery since I use other components through jquery, such as owl.carousel, but the import does not work either, but I will try to solve the problem first with jquery-ui and Then I will go to owl.carousel. I think it is related, and if I solve one problem the other will be solved.
Thanks for the help.
Greetings.
Hey @Juan-D
Have you tried to define the
jQueryas a global variable? It should happen before loading any other dependency. My guess is thejquery-uiplugin it's badly written, as many jquery plugins are, they assume that a globaljQueryvariable already existsCheers!
"Houston: no signs of life"
Start the conversation!