New in Symfony 6.3: AssetMapper Component
Contributed by
Ryan Weaver
and Kévin Dunglas
in #50112.
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:
<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:
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:
# 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):
your-project/
assets/
app.js
styles/
app.css
images/
logo.png
(3) Refer to those assets with the normal asset() function that you know:
<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:
<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 new assets:mapper:compile command,
which copies all of the assets into public/assets/ so that the real files
are returned. This command also dumps a public/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:
// 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:
$ php bin/console importmap:require ‚@hotwired/stimulus‘;
This command will create or update an importmap.php at the root of your project:
return [
‚app‘ => [
‚path‘ => ‚app.js‘,
‚preload‘ => true,
],
‚@hotwired/stimulus‘ => [
‚url‘ => ‚https://ga.jspm.io/npm:@hotwired/[email protected]/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:
<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/[email protected]/dist/stimulus.js“,
}}
</script>
<link rel=„modulepreload“ href=„/assets/app-cf9cfe84e945a554b2f1f64342d542bc.js“>
<link rel=„modulepreload“ href=„/assets/cool_stuff-10b27bd6986c75a1e69c8658294bf22c.js“>
<script type=„module“></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.
Symfony Blog