6

I am involved in an application that uses whitenoise to serve static files. It is configured to use CompressedManifestStaticFilesStorage which in turn uses ManifestStaticFilesStorage.

By static files, I mean static files we provide, and libraries such as leaflet from third parties.

The ManifestStaticFilesStorage class, provided by Django, will rename filenames to include a hash. For cachebusting. It filters resources and changes unhashed references to files to include the hash.

This in turn breaks a function in leaflet:

_detectIconPath: function () {
    var el = DomUtil.create('div',  'leaflet-default-icon-path', document.body);
    var path = DomUtil.getStyle(el, 'background-image') ||
               DomUtil.getStyle(el, 'backgroundImage'); // IE8

    document.body.removeChild(el);

    return path.indexOf('url') === 0 ?
        path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : '';
}

The problem being the value of path looks something like:

url("https://example.org/static/path/leaflet/dist/images/marker-icon.2273e3d8ad92.png")

As the filename does not match, the 2nd replace will not do anything. This in turn will return:

https://example.org/static/path/leaflet/dist/images/marker-icon.2273e3d8ad92.png")

When leaflet tries to append a filename to this "directory", we get:

https://example.org/static/path/leaflet/dist/images/marker-icon.2273e3d8ad92.png")marker-icon.png

Which it obviously wrong.

So what is the solution? I have been advised that we shouldn't be trying to hash filenames of third party packages, there could be other breakages. However I don't see any options for ManifestStaticFilesStorage to exclude certain directories from being hashed.

I created the following class to try to work around this. I reference this class in the settings instead of CompressedManifestStaticFilesStorage.

class MyStorage(CompressedManifestStaticFilesStorage):
    """ This class overrides the built in class and turns off file name hashing for selected directories. """

    def _nonhashed_name_func(self, name, hashed_files=None):
        name = posixpath.normpath(name)
        cleaned_name = self.clean_name(name)
        return cleaned_name

    def _url(self, hashed_name_func, name, force=False, hashed_files=None):
        split = name.split("/")
        if split[0] in ['bower_components', 'node_modules']:
            hashed_name_func = self._nonhashed_name_func
        return super()._url(hashed_name_func=hashed_name_func, name=name, force=force, hashed_files=hashed_files)

This works, however seems like a somewhat inelegant solution. Does anybody here have any better suggestions?

Penguin Brian
  • 1,991
  • 14
  • 25

2 Answers2

1

The Leaflet project endorses leaflet-defaulticon-compatibility as an official solution for this problem.

Retrieve all Leaflet Default Icon options from CSS, in particular all icon images URL's, to improve compatibility with bundlers and frameworks that modify URL's in CSS. In particular for webpack (with style-, css-, file- and url-loader's), Rails Asset Pipeline and Django pipeline. Should solve all use cases linked to issue #4968. (Source)

jnns
  • 5,148
  • 4
  • 47
  • 74
0

I found this page which describes similar problems, in the context of Angular2:

https://www.npmjs.com/package/@asymmetrik/angular2-leaflet#a-note-about-markers

In the specific case of leaflet, the solution seems to be to override the default icon path. As I am using vue2-leaflet this can be done using:

<v-icondefault :image-path="path"></v-icondefault>

As per the example supplied with the source code.

Penguin Brian
  • 1,991
  • 14
  • 25