Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

importmap:require - 3rd Party JS Libs

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

In our code, we get to use import statements with relative paths, ES6 classes: everything we're used to. It's business as usual. Except, how can we use third-party packages?

As we saw earlier, we could import things via a full URL, like import _ from, and I'll paste in the CDN URL that we used earlier. With that done, the rest is normal: add _.camelCase() to the log.

6 lines assets/app.js
... line 1
import _ from 'https://cdn.jsdelivr.net/npm/lodash@4.17.21/+esm';
... lines 3 - 4
console.log(_.camelCase(mix.describe()));

If we refresh and check the console... that works. But I don't like it! I don't want to have to include this crazy URL everywhere I use lodash. And what happens if we upgrade lodash... and I need to change the URL in 10 different files? Lame!

importmap:require to Fetch Node Packages

If we were using a build system, like Webpack, we could just say:

yarn add lodash

or

npm install lodash

We're not using yarn or npm, but we can do nearly the same thing. Over in the terminal, open a new tab, and run php bin/console importmap:require followed by the name of the NPM package we want: lodash:

php bin/console importmap:require lodash

Done! It added lodash to importmap.php and tells us we can use the package as usual. This means we can say import _ from 'lodash'... and everything will work fine.

6 lines assets/app.js
... line 1
import _ from 'lodash';
... lines 3 - 6

How? When we ran the command, it made one tiny change: it added this section to importmap.php. And as cool as this is, it's not magic. Behind the scenes, the command went to the JSDelivr CDN, found the latest version of lodash, then added the lodash key set to that URL.

If you head over and look at the page source... no surprise! We have a new lodash entry inside the importmap! When our browser sees import _ from 'lodash', it looks inside the importmap for lodash, finds this URL, and downloads it from there. Our browser is the hero!

Telling your Editor about the Packages

One bummer is that we don't get autocompletion in our editor. It says "Module not installed". And if I say _.... it doesn't really work. It's autocompleting camelCase... but only because I'm using that down here.

I hope this will be better-supported in PhpStorm soon. There is a workaround, but it's a bit manual. Copy the package, go into base.html.twig and add a temporary <script> tag that points to this. Hit "alt" + "enter" and select "Download library". This downloads that into the "External Libraries" section down here: /lodash.

Ok, remove that script tag. Back in app.js, it's still going to underline the import as if it doesn't know what it is, but it does autocomplete when we use _. something. For example, tail() is from lodash.

Updating Packages

What about updating the versions of packages inside importmap? Whelp, there's a command for that!

php bin/console importmap:update

That will loop through every package and update its URL to the latest version. This is already the latest version... but if we change it to .19... then run the update command... it moves back up to .21. The command could be more flexible - like by allowing you to update just one package, or by having some version constraints - and those things may be added in the future.

25 lines importmap.php
... lines 1 - 15
return [
... lines 17 - 20
'lodash' => [
'url' => 'https://cdn.jsdelivr.net/npm/lodash@4.17.21/+esm',
],
];

Downloading Packages Locally

Finally, if you don't want to rely on the CDN, you don't have to. To avoid it, when you require the package - or any time later - pass the --download option:

php bin/console importmap:require lodash --download

In importmap.php, this still shows the source URL to the CDN, but it downloaded that file into an assets/vendor/ directory. This downloaded_to points to the logical path for that file.

26 lines importmap.php
... lines 1 - 15
return [
... lines 17 - 20
'lodash' => [
'downloaded_to' => 'vendor/lodash.js',
... line 23
],
];

The result? When we go over and refresh.... and "View Page Source"... the importmap now points to the local file! We're no longer relying on the CDN.

But... now what? Do we commit this vendor/lodash.js file? The answer is... yes. At least at this moment, that's the only way to version that file and keep it in your repository.

So even without npm or yarn, we can use any npm package we want. Woo! But sometimes, instead of importing an entire package, we may only want to import a specific file. Let's talk about how we can do that next.

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