==================================================================
django-js-asset -- JS, CSS and JSON support for django.forms.Media
==================================================================

.. image:: https://github.com/matthiask/django-js-asset/workflows/Tests/badge.svg
    :target: https://github.com/matthiask/django-js-asset

**Note!** `Django 5.2 adds its own support for JavaScript objects
<https://docs.djangoproject.com/en/dev/topics/forms/media/#script-objects>`__.
This library has a slightly different API and also supports much older versions
of Django, *and* it also supports CSS and JSON tags.

Usage
=====

Use this to insert a script tag via ``forms.Media`` containing additional
attributes (such as ``id`` and ``data-*`` for CSP-compatible data
injection.):

.. code-block:: python

    from js_asset import JS

    forms.Media(js=[
        JS("asset.js", {
            "id": "asset-script",
            "data-answer": "42",
        }),
    ])

The rendered media tag (via ``{{ media.js }}`` or ``{{ media }}`` will
now contain a script tag as follows, without line breaks:

.. code-block:: html

    <script type="text/javascript" src="/static/asset.js"
        data-answer="42" id="asset-script"></script>

The attributes are automatically escaped. The data attributes may now be
accessed inside ``asset.js``:

.. code-block:: javascript

    var answer = document.querySelector("#asset-script").dataset.answer;

Also, because the implementation of ``static`` differs between supported
Django versions (older do not take the presence of
``django.contrib.staticfiles`` in ``INSTALLED_APPS`` into account), a
``js_asset.static`` function is provided which does the right thing
automatically.


CSS and JSON support
====================

Since 3.0 django-js-asset also ships a ``CSS`` and ``JSON`` media object which
can be used to ship stylesheets, inline styles and JSON blobs to the frontend.
It's recommended to pass those through ``forms.Media(js=[])`` as well since
``js`` is a simple list while ``css`` uses a dictionary keyed with the media to
use for the stylesheet.

So, you can add everything at once:

.. code-block:: python

    from js_asset import CSS, JS, JSON

    forms.Media(js=[
        JSON({"configuration": 42}, id="widget-configuration"),
        CSS("widget/style.css"),
        CSS("p{color:red;}", inline=True),
        JS("widget/script.js", {"type": "module"}),
    ])

This produces:

.. code-block:: html

    <script id="widget-configuration" type="application/json">{"configuration": 42}</script>
    <link href="/static/widget/style.css" media="all" rel="stylesheet">
    <style media="all">p{color:red;}</style>
    <script src="/static/widget/script.js" type="module"></script>



Compatibility
=============

At the time of writing this app is compatible with Django 4.2 and better
(up to and including the Django main branch), but have a look at the
`tox configuration
<https://github.com/matthiask/django-js-asset/blob/main/tox.ini>`_ for
definitive answers.


Extremely experimental importmap support
========================================

django-js-asset ships an extremely experimental implementation adding support
for using `importmaps
<https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap>`_.

One of the reasons why importmaps are useful when used with Django is that this
easily allows us to use the file name mangling offered for example by Django
``ManifestStaticFilesStorage`` without having to rewrite import statements in
scripts themselves.

Browser support for multiple importmaps is not generally available; at the time
of writing (February 2025) it's not even clear if Mozilla wants to support them
ever, so merging importmaps is -- for now -- the only viable way to use them in
production. Because of this the implementation uses a global importmap variable
where new entries can be added to and a context processor to make the importmap
available to templates.

The ``importmap`` object can be imported from ``js_asset``. Usage is as follows:

.. code-block:: python

    # static is an alias for Django's static() function used in the
    # {% static %} template tag.
    from js_asset import JS, static, importmap

    # Run this during project initialization, e.g. in App.ready or whereever.
    importmap.update({
        "imports": {
            "my-library": static("my-library.js"),
        },
    })

You have to add ``js_asset.importmap.context_processor`` to the list of context
processors in your settings (or choose some other way of making the
``importmap`` object available in templates) and add ``{{ importmap }}``
somewhere in your base template, preferrably at the top before including any
scripts.

When you've done that you can start profiting from the importmap by adding
JavaScript modules:

.. code-block:: python

    # Example for adding a code.js JavaScript *module*
    forms.Media(js=[
        JS("code.js", {"type": "module"}),
    ])

The code in ``code.js`` can now use a JavaScript import to import assets from
the library, even though the library's filename may contain hashes not known at
programming time:

.. code-block:: javascript

    import { Stuff } from "my-library"
