Skip to content

AssetMapper: Simple, Modern CSS & JS Management

Warning: You are browsing the documentation for Symfony 6.3, which is no longer maintained.

Read the updated version of this page for Symfony 7.2 (the current stable version).

6.3

The AssetMapper component was introduced as an experimental feature in Symfony 6.3.

The AssetMapper component lets you write modern JavaScript and CSS without the complexity of using a bundler. Browsers already support many modern JavaScript features like the import statement and ES6 classes. And the HTTP/2 protocol means that combining your assets to reduce HTTP connections is no longer urgent. This component is a light layer that helps serve your files directly to the browser.

The AssetMapper component has two main features:

  • Mapping & Versioning Assets: All files inside of assets/ are made available publicly and versioned. For example, you can reference assets/styles/app.css in a template with {{ asset('styles/app.css') }}. The final URL will include a version hash, like /assets/styles/app-3c16d9220694c0e56d8648f25e6035e9.css.
  • Importmaps: A native browser feature that makes it easier to use the JavaScript import statement (e.g. import { Modal } from 'bootstrap') without a build system. It's supported in all browsers (thanks to a shim) and is part of the HTML5 standard.

Installation

To install the AssetMapper component, run:

1
$ composer require symfony/asset-mapper symfony/asset symfony/twig-pack

In addition to symfony/asset-mapper, this also makes sure that you have the Asset Component and Twig available.

If you're using Symfony Flex, you're done! The recipe just added a number of files:

  • assets/app.js Your main JavaScript file;
  • assets/styles/app.css Your main CSS file;
  • config/packages/asset_mapper.yaml Where you define your asset "paths";
  • importmap.php Your importmap config file.

It also updated the templates/base.html.twig file:

1
2
3
4
5
6
7
{% block stylesheets %}
+    <link rel="stylesheet" href="{{ asset('styles/app.css') }}">
{% endblock %}

{% block javascripts %}
+    {{ importmap() }}
{% endblock %}

If you're not using Flex, you'll need to create & update these files manually. See the latest asset-mapper recipe for the exact content of these files.

Mapping and Referencing Assets

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. Thanks to the asset_mapper.yaml file, your app starts with one mapped path: the assets/ directory.

If you create an assets/images/duck.png file, you can reference it in a template with:

1
<img src="{{ asset('images/duck.png') }}">

The path - images/duck.png - is relative to your mapped directory (assets/). This is known as the logical path to your asset.

If you look at the HTML in your page, the URL will be something like: /assets/images/duck-3c16d9220694c0e56d8648f25e6035e9.png. If you update the file, the version part of the URL will change automatically!

Serving Assets in dev vs prod

In the dev environment, the URL /assets/images/duck-3c16d9220694c0e56d8648f25e6035e9.png is handled and returned by your Symfony app.

For the prod environment, before deploy, you should run:

1
$ php bin/console asset-map:compile

This will physically copy all the files from your mapped directories to public/assets/ so that they're served directly by your web server. See Deployment for more details.

Paths Inside of CSS Files

From inside CSS, you can reference other files using the normal CSS url() function and a relative path to the target file:

1
2
3
4
5
/* assets/styles/app.css */
.quack {
    /* file lives at assets/images/duck.png */
    background-image: url('../images/duck.png');
}

The path in the final app.css file will automatically include the versioned URL for duck.png:

1
2
3
4
/* public/assets/styles/app-3c16d9220694c0e56d8648f25e6035e9.css */
.quack {
    background-image: url('../images/duck-3c16d9220694c0e56d8648f25e6035e9.png');
}

Debugging: Seeing All Mapped Assets

To see all of the mapped assets in your app, run:

1
$ php bin/console debug:asset-map

This will show you all the mapped paths and the assets inside of each:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
AssetMapper Paths
------------------

--------- ------------------
 Path      Namespace prefix
--------- ------------------
assets

Mapped Assets
-------------

------------------ ----------------------------------------------------
 Logical Path       Filesystem Path
------------------ ----------------------------------------------------
 app.js             assets/app.js
 styles/app.css     assets/styles/app.css
 images/duck.png    assets/images/duck.png

The "Logical Path" is the path to use when referencing the asset, like from a template.

Importmaps & Writing JavaScript

All modern browsers support the JavaScript import statement and modern ES6 features like classes. So this code "just works":

1
2
3
4
5
// assets/app.js
import Duck from './duck.js';

const duck = new Duck('Waddles');
duck.quack();
1
2
3
4
5
6
7
8
9
// assets/duck.js
export default class {
    constructor(name) {
        this.name = name;
    }
    quack() {
        console.log(`${this.name} says: Quack!`);
    }
}

Thanks to the {{ importmap() }} Twig function, which you'll learn all about in this section, the assets/app.js file is loaded & executed by the browser.

Tip

When importing relative files, be sure to include the .js extension. Unlike in Node, the extension is required in the browser environment.

Importing 3rd Party JavaScript Packages

Suppose you want to use an npm package, like bootstrap. Technically, this can be done by importing its full URL, like from a CDN:

1
import { Alert } from 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/+esm';

But yikes! Needing to include that URL is a pain! Instead, we can add this to our "importmap" via the importmap:require command. This command can be used to download any npm package:

1
$ php bin/console importmap:require bootstrap

This adds the bootstrap package to your importmap.php file:

1
2
3
4
5
6
7
8
// importmap.php
return [
    // ...

    'bootstrap' => [
        'url' => 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/+esm',
    ],
];

Now you can import the bootstrap package like normal:

1
2
import { Alert } from 'bootstrap';
// ...

If you want to download the package locally, use the --download option:

1
$ php bin/console importmap:require bootstrap --download

This will download the package into an assets/vendor/ directory and update the importmap.php file to point to it. You should commit this file to your repository.

Note

Sometimes, a package - like bootstrap - will have one or more dependencies, such as @popperjs/core. The download option will download both the main package and its dependencies.

To update all 3rd party packages in your importmap.php file, run:

1
$ php bin/console importmap:update

How does the importmap Work?

How does this importmap.php file allow you to import bootstrap? That's thanks to the {{ importmap() }} Twig function in base.html.twig, which outputs an importmap:

1
2
3
4
5
6
7
<script type="importmap">{
    "imports": {
        "app": "/assets/app-4e986c1a2318dd050b1d47db8d856278.js",
        "/assets/duck.js": "/assets/duck-1b7a64b3b3d31219c262cf72521a5267.js",
        "bootstrap": "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/+esm"
    }
}</script>

Import maps are a native browser feature. They work in all browsers thanks to a "shim" file that's included automatically by the AssetMapper component (all modern browsers support them natively).

When you import bootstrap from your JavaScript, the browser will look at the importmap and see that it should fetch the package from the URL.

But where did the /assets/duck.js import entry come from? Great question!

The assets/app.js file above imports ./duck.js. When you import a file using a relative path, your browser looks for that file relative to the one importing it. So, it would look for /assets/duck.js. That URL would be correct, except that the duck.js file is versioned. Fortunately, the AssetMapper component sees that import and adds a mapping from /assets/duck.js to the correct, versioned filename. The result: importing ./duck.js just works!

Preloading and Initializing "app.js"

In addition to the importmap, the {{ importmap() }} Twig function also renders an ES module shim (see the polyfill config) and a few other things, like a set of "preloads":

1
2
<link rel="modulepreload" href="/assets/app-4e986c1a2318dd050b1d47db8d856278.js">
<link rel="modulepreload" href="/assets/duck-1b7a64b3b3d31219c262cf72521a5267.js">

In importmap.php, each entry can have a preload option. If set to true, a <link rel="modulepreload"> tag is rendered for that entry as well as for any JavaScript files it imports (this happens for "relative" - ./ or ../ - imports only). This is a performance optimization and you can learn more about below in Performance: Add Preloading.

The importmap() function also renders one more line:

1
<script type="module">import 'app';</script>

So far, the snippets shown export an importmap and even hinted to the browser that it should preload some files. But the browser hasn't yet been told to actually parse and execute any JavaScript. This line does that: it imports the app entry, which causes the code in assets/app.js to be executed.

Importing Specific Files From a 3rd Party Package

Sometimes you'll need to import a specific file from a package. For example, suppose you're integrating highlight.js and want to import just the core and a specific language:

1
2
3
4
5
import hljs from 'highlight.js/lib/core';
import javascript from 'highlight.js/lib/languages/javascript';

hljs.registerLanguage('javascript', javascript);
hljs.highlightAll();

In this case, adding the highlight.js package to your importmap.php file won't work: whatever your importing - e.g. highlight.js/lib/core - needs to exactly match an entry in the importmap.php file.

Instead, use importmap:require and pass it the exact paths you need. This also shows how you can require multiple packages at once:

1
$ php bin/console importmap:require highlight.js/lib/core highlight.js/lib/languages/javascript

Global Variables like jQuery

You might be accustomed to relying on global variables - like jQuery's $ variable:

1
2
3
4
5
// assets/app.js
import 'jquery';

// app.js or any other file
$('.something').hide(); // WILL NOT WORK!

But in a module environment (like with AssetMapper), when you import a library like jquery, it does not create a global variable. Instead, you should import it and set it to a variable in every file you need it:

1
2
import $ from 'jquery';
$('.something').hide();

You can even do this from an inline script tag:

1
2
3
4
<script type="module">
    import $ from 'jquery';
    $('.something').hide();
</script>

If you do need something to become a global variable, you do it manually from inside app.js:

1
2
3
import $ from 'jquery';
// things on "window" become global variables
window.$ = $;

Handling 3rd-Party CSS

With the importmap:require command, you can quickly use any JavaScript package. But what about CSS? For example, the bootstrap package also contains a CSS file.

Including CSS is a bit more manual, but still easy enough. To find the CSS, we recommend using jsdelivr.com:

  1. Search for the package on jsdelivr.com.
  2. Once on the package page (e.g. https://www.jsdelivr.com/package/npm/bootstrap), sometimes the link tag to the CSS file will already be shown in the "Install" box.
  3. If not, click the "Files" tab and find the CSS file you need. For example, the bootstrap package has a dist/css/bootstrap.min.css file. If you're not sure which file to use, check the package.json file. Often this will have a main or style key that points to the CSS file.

Once you have the URL, include it in base.html.twig:

1
2
3
4
{% block stylesheets %}
+   <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="{{ asset('styles/app.css') }}">
{% endblock %}

If you'd rather download the CSS file and include it locally, you can do that. For example, you could manually download, save it to assets/vendor/bootstrap.min.css and then include it with:

1
<link rel="stylesheet" href="{{ asset('vendor/bootstrap.min.css') }}">

Lazily Importing CSS from a JavaScript File

When using a bundler like Encore, you can import CSS from a JavaScript file:

1
2
// this CAN work (keep reading), but will be loaded lazily
import 'swiper/swiper-bundle.min.css';

This can work with importmaps, but it should not be used for critical CSS that needs to be loaded before the page is rendered because the browser won't download the CSS until the JavaScript file executed.

However, if you do want to lazily-load a CSS file, you can make this work by using the importmap:require command and pointing it at a CSS file.

1
$ php bin/console importmap:require swiper/swiper-bundle.min.css

This works because jsdelivr returns a URL to a JavaScript file that, when executed, adds the CSS to your page.

Issues and Debugging

There are a few common errors and problems you might run into.

Missing importmap Entry

One of the most common errors will come from your browser's console, and will something like this:

Failed to resolve module specifier " bootstrap". Relative references must start with either "/", "./", or "../".

Or:

The specifier "bootstrap" was a bare specifier, but was not remapped to anything. Relative module specifiers must start with "./", "../" or "/".

This means that, somewhere in your JavaScript, you're importing a 3rd party package - e.g. import 'bootstrap'. The browser tries to find this package in your importmap file, but it's not there.

The fix is almost always to add it to your importmap:

1
$ php bin/console importmap:require bootstrap

Note

Some browsers, like Firefox, show where this "import" code lives, while others like Chrome currently do not.

404 Not Found for a JavaScript, CSS or Image File

Sometimes a JavaScript file you're importing (e.g. import './duck.js'), or a CSS/image file you're referencing won't be found, and you'll see a 404 error in your browser's console. You'll also notice that the 404 URL is missing the version hash in the filename (e.g. a 404 to /assets/duck.js instead of a path like /assets/duck.1b7a64b3b3d31219c262cf72521a5267.js).

This is usually because the path is wrong. If you're referencing the file directly in a Twig template:

1
<img src="{{ asset('images/duck.png') }}">

Then the path that you pass asset() should be the "logical path" to the file. Use the debug:asset-map command to see all valid logical paths in your app.

More likely, you're importing the failing asset from a CSS file (e.g. @import url('other.css')) or a JavaScript file:

1
2
// assets/controllers/farm-controller.js
import '../farm/chicken.js';

When doing this, the path should be relative to the file that's importing it (and, in JavaScript files, should start with ./ or ../). In this case, ../farm/chicken.js would point to assets/farm/chicken.js. To see a list of all invalid imports in your app, run:

1
2
$ php bin/console cache:clear
$ php bin/console debug:asset-map

Any invalid imports will show up as warnings on top of the screen (make sure you have symfony/monolog-bundle installed):

1
2
WARNING   [asset_mapper] Unable to find asset "../images/ducks.png" referenced in "assets/styles/app.css".
WARNING   [asset_mapper] Unable to find asset "./ducks.js" imported from "assets/app.js".

Missing Asset Warnings on Commented-out Code

The AssetMapper component looks in your JavaScript files for import lines so that it can automatically add them to your importmap. This is done via regex and works very well, though it isn't perfect. If you comment-out an import, it will still be found and added to your importmap. That doesn't harm anything, but could be surprising.

If the imported path cannot be found, you'll see warning log when that asset is being built, which you can ignore.

Deploying with the AssetMapper Component

When you're ready to deploy, "compile" your assets during deployment:

1
$ php bin/console asset-map:compile

That's it! This will write all your assets into the public/assets/ directory, along with a few JSON files so that the importmap can be rendered lightning fast.

But to make sure your site is performant, be sure that your web server (or a proxy) is running HTTP/2, is compressing your assets and setting long-lived Expires headers on them. See Optimization for more details.

Optimizing Performance

To make your AssetMapper-powered site fly, there are a few things you need to do. If you want to take a shortcut, you can use a service like Cloudflare, which will automatically do most of these things for you:

  • Use HTTP/2: Your web server must be running HTTP/2 (or HTTP/3) so the browser can download assets in parallel. HTTP/2 is automatically enabled in Caddy and can be activated in Nginx and Apache. Or, proxy your site through a service like Cloudflare, which will automatically enable HTTP/2 for you.
  • Compress your assets: Your web server should compress (e.g. using gzip) your assets (JavaScript, CSS, images) before sending them to the browser. This is automatically enabled in Caddy and can be activated in Nginx and Apache. Or, proxy your site through a service like Cloudflare, which will automatically compress your assets for you. In Cloudflare, you can also enable auto minify to further compress your assets (e.g. removing whitespace and comments from JavaScript and CSS files).
  • Set long-lived Expires headers: Your web server should set long-lived Expires headers on your assets. Because the AssetMapper component includes a version hash in the filename of each asset, you can safely set the Expires header to a very long time in the future (e.g. 1 year). This isn't automatic in any web server, but can be easily enabled.

Once you've done these things, you can use a tool like Lighthouse to validate the performance of your site!

Performance: Add Preloading

One common issue that LightHouse may report is:

Avoid Chaining Critical Requests

Some items in this list are fine. But if this list is long or some items are multiple-levels deep, that is something you should fix with "preloading". To understand the problem, imagine that you have this setup:

  • assets/app.js imports ./duck.js
  • assets/duck.js imports bootstrap

When the browser downloads the page, this happens:

  1. The browser downloads assets/app.js;
  2. It then sees the ./duck.js import and downloads assets/duck.js;
  3. It then sees the bootstrap import and downloads assets/bootstrap.js.

Instead of downloading all 3 files in parallel, the browser is forced to download them one-by-one as it discovers them. This hurts performance. To fix this, in importmap.php, add a preload key to the app entry, which points to the assets/app.js file. Actually, this should already be done for you:

1
2
3
4
5
6
7
8
// importmap.php
return [
    'app' => [
        'path' => 'app.js',
        'preload' => true,
    ],
    // ...
];

Thanks to this, the AssetMapper component will render a "preload" tag onto your page for assets/app.js and any other JavaScripts files that it imports using a relative path (i.e. starting with ./ or ../):

1
2
<link rel="preload" href="/assets/app.js" as="script">
<link rel="preload" href="/assets/duck.js" as="script">

This tells the browser to start downloading both of these files immediately, even though it hasn't yet seen the import statement for assets/duck.js

You'll also want to preload bootstrap as well, which you can do in the same way:

1
2
3
4
5
6
7
8
// importmap.php
return [
    // ...
    'bootstrap' => [
        'path' => '...',
        'preload' => true,
    ],
];

Note

As described above, when you preload assets/app.js, the AssetMapper component find all of the JavaScript files that it imports using a relative path and preloads those as well. However, it does not currently do this when you import "packages" (e.g. bootstrap). These packages will already live in your importmap.php file, so their preload setting is handled explicitly in that file.

Frequently Asked Questions

Does the AssetMapper Component Combine Assets?

Nope! But that's because this is no longer necessary!

In the past, it was common to combine assets to reduce the number of HTTP requests that were made. Thanks to advances in web servers like HTTP/2, it's typically not a problem to keep your assets separate and let the browser download them in parallel. In fact, by keeping them separate, when you update one asset, the browser can continue to use the cached version of all of your other assets.

See Optimization for more details.

Does the AssetMapper Component Minify Assets?

Nope! Minifying or compressing assets is important, but can be done by your web server or a service like Cloudflare. See Optimization for more details.

Is the AssetMapper Component Production Ready? Is it Performant?

Yes! Very! The AssetMapper component leverages advances in browser technology (like importmaps and native import support) and web servers (like HTTP/2, which allows assets to be downloaded in parallel). See the other questions about minimization and combination and Optimization for more details.

The https://ux.symfony.com site runs on the AssetMapper component and has a 99% Google Lighthouse score.

Does the AssetMapper Component work in All Browsers?

Yup! Features like importmaps and the import statement are supported in all modern browsers, but the AssetMapper component ships with an ES module shim to support importmap in old browsers. So, it works everywhere (see note below).

Inside your own code, if you're relying on modern ES6 JavaScript features like the class syntax, this is supported in all but the oldest browsers. If you do need to support very old browsers, you should use a tool like Encore instead of the AssetMapper component.

Note

The import statement can't be polyfilled or shimmed to work on every browser. However, only the oldest browsers don't support it - basically IE 11 (which is no longer supported by Microsoft and has less than .4% of global usage).

The importmap feature is shimmed to work in all browsers by the AssetMapper component. However, the shim doesn't work with "dynamic" imports:

1
2
3
4
5
6
7
// this works
import { add } from './math.js';

// this will not work in the oldest browsers
import('./math.js').then(({ add }) => {
    // ...
});

If you want to use dynamic imports and need to support certain older browsers (https://caniuse.com/import-maps), you can use an importShim() function from the shim: https://www.npmjs.com/package/es-module-shims#user-content-polyfill-edge-case-dynamic-import

Can I use with TypeScript, JSX or Vue?

Probably not.

TypeScript, by its very nature, requires a build step.

JSX can be compiled directly to a native JavaScript file but if you're using a lot of JSX, you'll probably want to use a tool like Encore. See the UX React Documentation for more details about using with the AssetMapper component.

Vue files can be written in native JavaScript, and those will work with the AssetMapper component. But you cannot write single-file components (i.e. .vue files) with component, as those must be used in a build system. See the UX Vue.js Documentation for more details about using with the AssetMapper component.

Using Tailwind CSS

To use the Tailwind CSS framework with the AssetMapper component, check out symfonycasts/tailwind-bundle.

Using Sass

To use Sass with AssetMapper component, check out symfonycasts/sass-bundle.

Third-Party Bundles & Custom Asset Paths

All bundles that have a Resources/public/ or public/ directory will automatically have that directory added as an "asset path", using the namespace: bundles/<BundleName>. For example, if you're using BabdevPagerfantaBundle and you run the debug:asset-map command, you'll see an asset whose logical path is bundles/babdevpagerfanta/css/pagerfanta.css.

This means you can render these assets in your templates using the asset() function:

1
<link rel="stylesheet" href="{{ asset('bundles/babdevpagerfanta/css/pagerfanta.css') }}">

Actually, this path - bundles/babdevpagerfanta/css/pagerfanta.css - already works in applications without the AssetMapper component, because the assets:install command copies the assets from bundles into public/bundles/. However, when the AssetMapper component is enabled, the pagerfanta.css file will automatically be versioned! It will output something like:

1
<link rel="stylesheet" href="/assets/bundles/babdevpagerfanta/css/pagerfanta-ea64fc9c55f8394e696554f8aeb81a8e.css">

Overriding 3rd-Party Assets

If you want to override a 3rd-party asset, you can do that by creating a file in your assets/ directory with the same name. For example, if you want to override the pagerfanta.css file, create a file at assets/bundles/babdevpagerfanta/css/pagerfanta.css. This file will be used instead of the original file.

Note

If a bundle renders their own assets, but they use a non-default asset package, then the AssetMapper component will not be used. This happens, for example, with EasyAdminBundle.

Importing Assets Outside of the assets/ Directory

You cannot currently import assets that live outside of your asset path (i.e. the assets/ directory). For example, this won't work:

1
2
3
4
5
6
/* assets/styles/app.css */

/* you cannot reach above assets/ */
@import url('../../vendor/babdev/pagerfanta-bundle/Resources/public/css/pagerfanta.css');
/* using a logical path won't work either */
@import url('bundles/babdevpagerfanta/css/pagerfanta.css');

This wouldn't work either:

1
2
3
4
5
6
7
8
// assets/app.js

// you cannot reach above assets/
import '../vendor/symfony/ux-live-component/assets/dist/live_controller.js';
// using a logical path won't work either (the "@symfony/ux-live-component" path is added by the LiveComponent library)
import '@symfony/ux-live-component/live_controller.js';
// importing like a JavaScript "package" won't work
import '@symfony/ux-live-component';

For CSS files, you can solve this by adding a link tag to your template instead of using the @import statement.

For JavaScript files, you can add an entry to your importmap file:

1
$ php bin/console importmap:require @symfony/ux-live-component --path=vendor/symfony/ux-live-component/assets/dist/live_controller.js

Then you can import '@symfony/ux-live-component' like normal. The --path option tells the command to point to a local file instead of a package. In this case, the @symfony/ux-live-component argument could be anything: whatever you use here will be the string that you can use in your import.

If you get an error like this:

The "some/package" importmap entry contains the path "vendor/some/package/assets/foo.js" but it does not appear to be in any of your asset paths.

It means that you're pointing to a valid file, but that file isn't in any of your asset paths. You can fix this by adding the path to your asset_mapper.yaml file:

1
2
3
4
5
6
# config/packages/asset_mapper.yaml
framework:
    asset_mapper:
        paths:
            - assets/
            - vendor/some/package/assets

Then try the command again.

Configuration Options

You can see every available configuration option and some info by running:

1
$ php bin/console config:dump framework asset_mapper

Some of the more important options are described below.

framework.asset_mapper.paths

This config holds all of the directories that will be scanned for assets. This can be a simple list:

1
2
3
4
5
framework:
    asset_mapper:
        paths:
            - assets/
            - vendor/some/package/assets

Of you can give each path a "namespace" that will be used in the asset map:

1
2
3
4
5
framework:
    asset_mapper:
        paths:
            assets/: ''
            vendor/some/package/assets/: 'some-package'

In this case, the "logical path" to all of the files in the vendor/some/package/assets/ directory will be prefixed with some-package - e.g. some-package/foo.js.

framework.asset_mapper.excluded_patterns

This is a list of glob patterns that will be excluded from the asset map:

1
2
3
4
framework:
    asset_mapper:
        excluded_patterns:
            - '*/*.scss'

You can use the debug:asset-map command to double-check that the files you expect are being included in the asset map.

framework.asset_mapper.importmap_polyfill

Configure the polyfill for older browsers. Default is ES module shim. You can pass any URL to be included, or false to disable the polyfill.

1
2
3
4
framework:
    asset_mapper:
        importmap_polyfill: false # disable the shim ...
        # importmap_polyfill: 'https://...' # ... or pass some custom URL

framework.asset_mapper.importmap_script_attributes

This is a list of attributes that will be added to the <script> tags rendered by the {{ importmap() }} Twig function:

1
2
3
4
framework:
    asset_mapper:
        importmap_script_attributes:
            crossorigin: 'anonymous'

Page-Specific CSS & JavaScript

Sometimes you may choose to include CSS or JavaScript files only on certain pages. To add a CSS file to a specific page, create the file, then add a link tag to it like normal:

1
2
3
4
5
6
{# templates/products/checkout.html.twig #}
{% block stylesheets %}
    {{ parent() }}

    <link rel="stylesheet" href="{{ asset('styles/checkout.css') }}">
{% endblock %}

For JavaScript, first create the new file (e.g. assets/checkout.js). Then, add a script tag that imports it:

1
2
3
4
5
6
7
8
{# templates/products/checkout.html.twig #}
{% block javascripts %}
    {{ parent() }}

    <script type="module">
        import '{{ asset('checkout.js') }}';
    </script>
{% endblock %}

This instructs your browser to download and execute the file.

In this setup, the normal app.js file will be executed first and then checkout.js. If, for some reason, you want to execute only checkout.js and not app.js, override the javascript block entirely and render checkout.js through the importmap() function:

1
2
3
4
5
6
{# templates/products/checkout.html.twig #}
{% block javascripts %}
    <script type="module">
        {{ importmap(asset('checkout.js')) }}
    </script>
{% endblock %}

The important thing is that the importmap() function must be called exactly one time on each page. It outputs the importmap and also adds a <script type="module"> tag that loads the app.js file or whatever path you pass to importmap().

Note

If you look at the source of your page, by default, the <script type="module"> from importmap() will contain import 'app'; - not something like import /assets/app-4e986c1a2318dd050b1d47.js. Both would work - but because app appears in your importmap.php, the browser will read app from the importmap on the page and ultimately load /assets/app-4e986c1a2318dd050b1d47.js

The AssetMapper Component Caching System in dev

When developing your app in debug mode, the AssetMapper component will calculate the content of each asset file and cache it. Whenever that file changes, the component will automatically re-calculate the content.

The system also accounts for "dependencies": If app.css contains @import url('other.css'), then the app.css file contents will also be re-calculated whenever other.css changes. This is because the version hash of other.css will change... which will cause the final content of app.css to change, since it includes the final other.css filename inside.

Mostly, this system just works. But if you have a file that is not being re-calculated when you expect it to, you can run:

1
$ php bin/console cache:clear

This will force the AssetMapper component to re-calculate the content of all files.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version