0

I tried the solution for this question, and while it works great if I want my script on every page: How to import jQuery as a global variable with Symfony's AssetMapper?

However, I have dozens of scripts and I don't want to ask the browser to load them on every page if I don't need them.

I have imported two packages via importmap:install; one which depends on jQuery as a global variable. These two packages are:

  • jquery
  • jquery-counterup

I have imported both of them and add both $ and jQuery as global variables on the window object inside a script that I import in the main assets/app.js file:

// assets/provide_jquery.js
import $ from 'jquery';
window.$ = window.jQuery = $;
// assets/app.js
import './provide_jquery.js';
import 'jquery.counterup';

Which works great. However, I usually don't want jquery-counterup loaded; it's only on very specific pages that I want it.

But if I import it with the asset() function inside a Twig template like this after the importmap('app') call:

<script src="{{ asset('vendor/jquery.counterup/jquery.counterup.index.js') }}"></script>

It will give me this error:

Uncaught ReferenceError: jQuery is not defined

Of course, once the page loads, console.log(jQuery) works because the global variable is now defined. So the jquery-counterup script appears to be loading before the global variables are declared.

The only way I've been able to get this to work is to use the defer attribute on the script or make it type="module" so it loads just before the DOMContentLoaded event but after global variable declaration.

And this works...but is it the best way? It feels like I'm stacking hacks on top of hacks to get this to work and there's something fundamental I'm missing.

Is there an easier/better way to add scripts to only specific pages?

One issue I can foresee is that including scripts this way means I don't get the preloading benefit the docs mention.

1 Answer 1

1

It turns out I did a poor job of reading the docs.

The official docs for Asset Mapper explain exactly how to do this: https://symfony.com/doc/current/frontend/asset_mapper.html#page-specific-css-javascript

Sometimes you may choose to include CSS or JavaScript files only on certain pages. [...] [C]reate a separate entrypoint. For example, create a checkout.js file that contains whatever JavaScript and CSS you need:

// assets/checkout.js
import './checkout.css';

// ...

Next, add this to importmap.php and mark it as an entrypoint:

// importmap.php
return [
    // the 'app' entrypoint ...

    'checkout' => [
        'path' => './assets/checkout.js',
        'entrypoint' => true,
    ],
];

Finally, on the page that needs this JavaScript, call importmap() and pass both app and checkout:

{# templates/products/checkout.html.twig #}
{#
    Override an "importmap" block from base.html.twig.
    If you don't have that block, add it around the {{ importmap('app') }} call.
#}
{% block importmap %}
    {# do NOT call parent() #}

    {{ importmap(['app', 'checkout']) }}
{% endblock %}

The importmap() function always includes the full import map to ensure all module definitions are available on the page. It also adds a <script type="module"> tag to load the specific JavaScript entry files you pass to it (in the example above, the app.js file and the checkout.js file).

Do not call parent() inside the {% block importmap %} Twig block. Each page can include only one import map, so importmap() must be called exactly once.

If you want to execute only checkout.js (and not app.js), call {{ importmap('checkout') }}. In this case, the full import map will still be included in the page, but only the checkout.js file will actually be loaded.

This works perfectly for my use case.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.