Join us as we explore the strange new world of Symfony’s AssetMapper via SymfonyCast

reubenwalker64

Reuben Walker, Jr.

Posted on August 3, 2023

Join us as we explore the strange new world of Symfony’s AssetMapper via SymfonyCast

This communiqué originally appeared on Symfony Station.

This article could also be titled AssetMapper: Modern JS without BS (either Build System or Bullshit). But we want to boldly go where no one has gone before in the Symfony universe. 🛸

In any event, it will explore Symfony’s new and currently experimental AssetMapper component.

While my JavaScript skills are currently better than my Symfony ones, I am by no means an expert. Coding book camp only gets a content creator so far. Plus, as you just read, AssetMapper is at the experimental stage and too new for anyone to have developed expertise by using it.

So, I am mainly going to paraphrase/plagiarise/quote and summarize general points from the SymfonyCasts course - AssetMapper: Modern JS with Zero Build System. All credit for the outstanding content goes to the esteemed Ryan Weaver. At least I won’t use any mofoing “AI”.

I will also emphasize the things it does to make your Symfony apps even more outstanding. Like being best friends with Symfony UX and its Stimulus and Turbo goodies.

Ryan is my favorite Symfony developer. And it’s because I’m a frontend guy and he’s contributed fantastic things to the frontend aspects of Symfony. Not because he’s super, super awesome. And funny. And intelligent. And handsome.

So please don’t sue me Ryan. 😜

I will supplement as needed from the official documentation.

To take the course you have to and should subscribe to SymfonyCasts. It is an outstanding value. I know because I obviously use it personally. Send some love and cash Ryan’s way.

And if you want to see how all this works from a technical standpoint, you will need to watch the AssetMapper SymfonyCast after subscribing. You will also need it if you want to make sense of the directories and files mentioned in this article.

You can check out the official Symfony docs for the AssetMapper component for more details.

My opinions and comments will be in bold. And believe me, I have them.

Engage Symfony Trekkers.

JavaScript Build Systems

JavaScript build systems were created because browsers didn't support modern ES6 features. Ones like the import statement, const, the class syntax, etc. If you tried to run this kind of JavaScript in a browser, you would have been greeted with error messages.

So, a JS build system was needed to transpile (convert) new-looking JavaScript to old-looking JavaScript to run in the browser. It would also combine JavaScript and CSS files so that we would have fewer requests, and it could create versioned filenames, process TypeScript and JSX, Sass, and more.

These systems are super powerful. But they also add complexity and slow down coding.

Note: I experienced all these horrors in coding boot camp.

With AssetMapper you can write the modern JavaScript that you know and love but with no build system, and no Node JS. Fuck yeah.

It’s just you and the browser: the way the Gods of the Internet intended it.

And your Symfony app is going to be as performant and fast as one built with a build system.

And if you're wondering about things like Sass preprocessors, or Tailwind, you can use those if you are lazy.

Note: Please do not use Failwind in your projects. Learn CSS peeps. It’s fucking easier in the long run without any horseshit hacks. Bootcrap is almost as bad.

Modern JavaScript

Much of modern JS was introduced in ES6, or ECMAScript 6. You'll hear ES6 a lot because most modern features you're used to came from this version - released back in 2015.

Unfortunately, this was the kind of code that browsers historically choked on!

So, usually, we would need a build system like Symfony Encore that would read this modern code and rewrite it to old JavaScript so it would work in our browser.

But that’s the past. Today, ES6 and newer versions work in our browser.

Note that if you want your JavaScript to be able to use import and export, you need to load the original file "as a module" in your Twig templates.

You can also import third-party packages like lodash.

However, using import can cause a variety of problems (versioning, caching, etc.) Problems that Symfony's new AssetMapper component will help us solve.


SymfonyCasts homepage Screenshot

Now’s a great time to subscribe to SymfonyCasts. 😉


AssetMapper

For now, AssetMapper is experimental in Symfony 6.3, so there will likely be backwards compatibility breaks before 6.4. But, the concepts in the SymfonyCast are solid, and you can deploy a super-performant site with AssetMapper today.

The Symfony Flex recipe for installing AssetMapper adds several things to your app. First, it gives you an assets/ directory... which looks pretty much identical to what you would get if you installed WebpackEncore. You have an app.js file - this will be the main, one file that's executed - and also app.css, the main CSS file.

In templates/base.html.twig, the recipe also adds a link tag to point to app.css.

Mapping

AssetMapper is really quite simple. It has two main things (and they’re not deuterium and anti-deuterium):

  • Mapping & Versioning Assets - All files inside of assets/ are made available publicly and versioned.
  • Importmaps - A native browser feature that makes it easier to use the JavaScript import statement without a build system.

AssetMapper configures "paths" - like the assets/ directory - and it makes the files inside available publicly.

Because we've pointed AssetMapper at the assets/ directory, we can refer to things inside of that via their path relative to that root. This is known as the "logical path" to the asset.

Symfony bundles can add also their own AssetMapper paths to make their files publicly available.

Again, the AssetMapper component works by defining directories/paths of assets that you want to expose publicly. These assets are then versioned and easy to reference.

CSS

When we're talking about the frontend of a site, we're mostly talking about two things, CSS and JavaScript (in addition to the HTML handled by Twig templates).

The CSS side of things is dead simple with AssetMapper. You create a CSS file inside the assets/ directory then include it with a good old-fashioned link tag in your base.twig file that uses the CSS file's logical path. That's it. Zero magic.

The point is: you get to code CSS like normal and everything just works.

3rd Party CSS

In AssetMapper, because there's no Node, we don't have an easy system for grabbing CSS packages. But we can still get them via a CDN.

If you want to avoid using the CDN, you can download the package file directly into your project. Again, because there's no package system like NPM, so create an assets/vendor/ directory and put the file inside of that. Then commit that assets/vendor/ directory to Git to keep it in your project and versioned. Committing vendor files into your project isn't amazing, but it's not a huge deal and is your best option right now if you want to avoid a CDN.

So, you can use 3rd party CSS, but don’t. Because its 90% horseshit.

I will allow that custom and variable fonts are in the 10%. Although system fonts are always more performant.

You can use Tailwind but it requires a build step. But, fuck Failwind anyway, because it’s about 65% of the 90% BS above. Don’t be a lazy programmer. Go vanilla or go home. Or hire a frontend developer.

But, let’s get off the soapbox and more on to JavaScript.

Importing JS

Imported JavaScript is generated from an importmap.php file inside your Symfony project. The file isn't super-interesting but it'll be more useful with third party JavaScript. It has an app key that points to our assets/app.js file using its logical path.

Thanks to that, this <script type="importmap"> dumps onto the page. When you import something that doesn't start with a ".", "/", or "../", that's called a bare import. You will usually see this for third-party libraries.

In the browser environment, when it sees a "bare import", your browser looks for an importmap on the page to find a matching entry.

The browser sees import 'app', finds the key, and that's the path it downloads. It effectively copies this path here and pastes it down there. That's why our app.js file is being executed: it's team work between the importmap and the extra <script type="module"> that bootstraps your app.

The best thing about importmap is that it's not a Symfony thing: it's just an internet thing. It's how your browser works. We have this importmap.php file, which is a Symfony thing. But once its is on the page, your browser is doing the work.

And importmap works in most browsers. About 81% of browsers at this time. That could be a problem, except that the importmap() function also dumps a shim.

Thanks to the shim, if a browser does not support importmap, it adds that functionality. So, it just works.

Thanks to the {{ importmap() }} Twig function, the assets/app.js file is loaded and executed by the browser.

The importmap is constructed from two sources. The first source is the importmap.phpfile. The second source is more subtle. Whenever our JavaScript imports another JavaScript file using a relative path, that imported file is automatically added.

This is powerful. It means that our final code can look like it originally does: ./lib/vinyl.js. But thanks to the importmap, our browser will smartly download the real file with the long version part in the name.

3rd Party JS

IMHO with AssetMapper we're thankfully not using yarn or npm, but we can achieve the same functionality.

Over in your terminal, open a new tab, and run php bin/console importmap:require followed by the name of the NPM package you want: for example lodash:

php bin/console importmap:require lodash

It adds 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.

How? When we ran the command, it made one change: it added a section to importmap.php. And as hip 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.

When our browser sees import _ from 'lodash', it looks inside the importmap for lodash, finds this URL, and downloads it from there.

But, 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.

If we ran code through Symfony Encore, Encore would do something called "tree shaking". This is where it would see that we're only importing camelCase from lodash. And so, in the final JavaScript, it would only give us the code for camelCase, not the complete lodash package.

In a browser environment, if you import from lodash, you're going to get all of lodash even if you're only importing one part of it. Now, that might not be that big of a deal. The complete build of lodash is still only 24 kilobytes. But what if you were using a big package but only need to import one specific thing?

Often, there's a specific file that we can import, like /camelCase. You'll usually find details about these files in the package documentation.

Documentation is your friend.

Text - Stimulus component

Stimulus

In a Symfony project’s UI, components are handled by a group of tools with the moniker of Symfony UX.

And it is one of the greatest things in Symfony. It’s 10,000% a better option than importing 3rd party JS.

Symfony describes Symfony UX as “JavaScript tools you can't live without. They’re a set of PHP and JavaScript packages to solve everyday frontend problems featuring Stimulus and Turbo.”

“Symfony UX is an initiative and set of libraries to seamlessly integrate JavaScript tools into your application.

Behind the scenes, the UX packages leverage Stimulus: a small but powerful library for binding JavaScript functionality to elements on your page.”

Thank you, abstraction.

StimulusBundle is a relatively new package that houses some Twig shortcuts that you can use, like stimulus_controller(). But, more deliciously, it has a recipe that will set your app up to load Stimulus controllers effortlessly.

Technically, Stimulus and AssetMapper are best friends. bootstrap.js loads a file that starts Stimulus and that automatically loads everything inside the assets/controllers/ directory as well as any 3rd party UX packages in assets/controllers.json.

Another part of StimulusBundle is the ability to get more Stimulus controllers by installing a UX package. For example Turbo.

There is a common pattern with UX packages: if a UX package depends on a third-party package, its recipe will add that package to your importmap automatically. The result is that, when that package is referenced - like import '@hotwired/turbo' - it just works.

When you install StimulusBundle, its recipe comes bearing gifts - one of which is ux_controller_link_tags(). Some UX packages come with CSS files. You'll find them under a key called autoimport, which the recipe will add under the controller. This ux_controller_link_tags() finds all the CSS files for all the controllers you have activated, and it outputs them.

You can also use lazy Stimulus controllers to keep your initial page lightweight if you have some heavy Stimulus controllers that are only used on certain pages.

Page-Specific CSS & JS

Suppose you have some CSS and JS that are only needed in certain areas like admin for instance. If you write that in the normal way and in the normal files, that code is going to be downloaded
everywhere, including the frontend of our site. That, at the very least, is wasteful. A better way is to only download the admin CSS and JS when you visit the admin area.

You can do it with lazy Stimulus controllers. But another option is to create an extra set of CSS and JavaScript that are explicitly loaded only on the admin pages via AssetMapper.

Let's start with CSS, which is simple. In the assets/styles/ directory, create an admin.css file. And add the CSS code you need. At this point, the new admin.css file is technically available publicly because it's in the assets/ directory. But, we're not using it yet. To do that, we need a link tag to the corresponding Twig template file.

But what about JavaScript? Create a new file maybe next to app.js called admin.js. Like with the CSS file, this file is now publicly available but nothing is loading it. In dashboard.html.twig, say {% block javascripts %}, {% endblock%}, then {{ parent() }}. Below that, add a <script> tag with type="module". Now we're going to code as if we're in a JavaScript file. Say import and then the path **to the JavaScript file. To get the real path we use the asset() function and pass the
logical path: admin.js. You’re good to go.

One of the things you’ve seen is that everything in the assets/ directory is exposed publicly which is the whole point of AssetMapper!

You can also exclude files with AssetMapper.


SymfonyCasts homepage Screenshot

It’s still a great time to subscribe to SymfonyCasts. 😉


Coming Soon

There are six more chapters to come in the course. But they are not critical to how Asset Mapper works locally. I have access to the course script and could write about them. But you can’t see the code at the moment, so I will update this article as they come out. And if their content is relevant in general.

They include:

  • Deploying to Platform.sh
  • Configuring the Platform.sh Deploy
  • Deploying the Assets
  • Long-Term Caching, Compression & File Combining
  • Optimizing & Profiling
  • Preloading

Hint: host your Symfony apps on Platform.sh.

Wrapping Up

Thanks for reading. You’ve explored how AssetMapper gives your Symfony app modern JS without a build system or bullshit. And it works beautifully with Symfony UX's StimulusBundle.

When AssetMapper is finalized in Symfony 6.4, be sure to put it to use. It will go a long way toward taking the BS in JS out of your applications.

And be sure to subscribe to SymfonyCasts. Send Ryan some love and cash. And thank him for the AssetMapper course. Again, I don’t want to get sued. 😉

Live long and prosper. 🖖

💖 💪 🙅 🚩
reubenwalker64
Reuben Walker, Jr.

Posted on August 3, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related