Handling web assets in web projects is a continuously changing feature. Browsers and front-end technologies evolve a lot and Symfony has to adapt to them. In the past, Symfony included Assetic as a web asset handling pipeline. It could combine, compile and filter assets before serving them in your application.
In 2017, we introduced Webpack Encore as a modern alternative to Assetic based on Webpack and with endless features to handle your web assets. It can be a bit overwhelming to newcomers, but once set up, this asset building pipeline is simple to manage and works great.
Although we're happy with the Webpack Encore based solution, browsers have recently added support for a game-changing feature called import maps. An import map is a JSON object that tells the browser how to resolve modules when importing JavaScript modules. It maps the module names to their locations (as relative paths or absolute URLs).
For example, if you add this to the HTML of your web pages:
1 2 3 4 5 6 7 8
<script type="importmap">
{
"imports": {
"square": "./module/shapes/square.js",
"circle": "https://example.com/shapes/circle.js"
}
}
</script>
You can use the following in your JavaScript code:
1 2 3 4
import { name as squareName, draw } from "square";
import { name as circleName } from "circle";
// ...
You don't need to build and compile the assets. The browser can find the built/compiled modules in the paths/URLs provided by the import map. In Symfony 6.3 we're introducing a new AssetMapper component which allows you to use import maps to handle your assets. This component makes unnecessary to use Webpack, Webpack Encore, Node.js, yarn/npm, etc.
The component is divided into two main features:
- A feature to map assets to publicly available and versioned paths;
- A feature to use import maps in your front-end code.
Mapping Assets to Paths
Here's a quick overview of how this component works when mapping assets:
(1) Activate the asset mapper by telling Symfony the path that will be used to serve them:
1 2 3 4
# config/packages/framework.yaml
framework:
asset_mapper:
paths: ['assets/']
(2) Put your built/compiled assets in the <your-project>/assets/
directory
(this is the same you do when using Webpack Encore):
1 2 3 4 5 6 7
your-project/
assets/
app.js
styles/
app.css
images/
logo.png
(3) Refer to those assets with the normal asset()
function that you know:
1 2 3 4
<link rel="stylesheet" href="{{ asset('styles/app.css') }}">
<script src="{{ asset('app.js') }}" defer></script>
<img src="{{ asset('images/logo.png') }}">
That's all. The final paths used by the browser will look like this:
1 2 3
<link rel="stylesheet" href="/assets/styles/app-b93e5de06d9459ec9c39f10d8f9ce5b2.css">
<script src="/assets/app-1fcc5be55ce4e002a3016a5f6e1d0174.js" defer type="module"></script>
<img src="/assets/images/logo-3f24cba25ce4e114a3116b5f6f1d2159.png">
How does it work behind the scenes?
- In the
dev
environment, a listener intercepts the requests to the path that you configured earlier (assets/
in this case), finds the file in the source<your-project>/assets/
directory, and returns it; - In the
prod
environment, you run a newasset-map:compile
command, which copies all of the assets intopublic/assets/
so that the real files are returned. This command also dumps apublic/assets/manifest.json
so that the source paths (e.g.styles/app.css
) can be exchanged for their final paths quickly.
Internally, this component provides a basic compiler to do things like updating
the value of url()
statements included in CSS files, to update the URLs in
the source maps, etc. We're not recreating Assetic, but we need to provide these
basic compilation features to make this component useful.
Working with Import Maps
The import maps feature included in AssetMapper component works as follows. In your JavaScript code, you import modules in the same way as before:
1 2 3 4 5
// assets/app.js
import { Application } from '@hotwired/stimulus';
import CoolStuff from './cool_stuff.js';
// ...
The difference is that now you don't have to use npm/yarn to install those
JavaScript dependencies. Instead, run the importmap:require
command to
"install" those dependencies:
1
$ php bin/console importmap:require '@hotwired/stimulus';
This command will create or update an importmap.php
at the root of your project:
1 2 3 4 5 6 7 8 9
return [
'app' => [
'path' => 'app.js',
'preload' => true,
],
'@hotwired/stimulus' => [
'url' => 'https://ga.jspm.io/npm:@hotwired/stimulus@3.2.1/dist/stimulus.js',
],
];
The final step is to add the new {{ importmap() }}
function inside the
<head>
tag of all your pages. The end result will be something like:
1 2 3 4 5 6 7 8 9 10 11
<script type="importmap">
{ "imports": {
"app": "/assets/app-cf9cfe84e945a554b2f1f64342d542bc.js",
"cool_stuff.js": "/assets/cool_stuff-10b27bd6986c75a1e69c8658294bf22c.js",
"@hotwired/stimulus": "https://ga.jspm.io/npm:@hotwired/stimulus@3.2.1/dist/stimulus.js",
}}
</script>
<link rel="modulepreload" href="/assets/app-cf9cfe84e945a554b2f1f64342d542bc.js">
<link rel="modulepreload" href="/assets/cool_stuff-10b27bd6986c75a1e69c8658294bf22c.js">
<script type="module">import 'app';</script>
There are many other great features provided by AssetMapper. We're still writing the docs for it and we hope to have them ready soon after the Symfony 6.3 release. Also, the upcoming SymfonyOnline June 2023 conference will include two different talks related to this component: Modern UIs with UX, a little JS & Zero Node (by Ryan Weaver) and AssetMapper: Manage Your JS Deps Without Node (by Kévin Dunglas).
Thank you so much guys for pushing towards simple solutions to simple problems.
This. Is. So. Cool ! Many thanks for this cool feature.
Thanks Ryan & Kevin for giving us another great feature <3
Will that be an alternative of the webpack encore
@Mina yes! You can still use Webpack Encore and not use this. Or you can only use this new feature and not use Webpack Encore.
However, if you use some technologies (e.g. TypeScript) then you need to still use Webpack Encore because you need the building and compilation phase to transform those files into JavaScript.
I like it :)
This is great thank you 🙏🙏
Awesome new feature ! How are CSS dependencies handled with Import Maps? Like Bootstrap / TailwindCSS, etc.?
This rocks! Are there any tutorials available yet on migrating from webpack encore to AssetMapper? Or a demo project on githhub that uses it, like the Symfony Demo or the Fast Track book?