Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to require generated sprite in JS file #147

Closed
czhDavid opened this issue Jan 27, 2021 · 8 comments
Closed

How to require generated sprite in JS file #147

czhDavid opened this issue Jan 27, 2021 · 8 comments

Comments

@czhDavid
Copy link

Hi i have a quiestion and can't find solution anywhere. We want to add hash to the generated sprite, so when we add or change an icon, the browser will download newer spritefile.
This can be easily achieved with
.addPlugin(createSvgSpritemapPluginInstance('images/icons.[contenthash].svg'))
And this will generate the file and line in manifest.json
"dist/images/icons.svg": "/dist/images/icons.68e91515460ebaae.svg",.
The problem is how to use this file in a React component or in any JS file? I am trying to do an import but can't seem to find the right combination:
/import icons from '/dist/images/icons.svg';
The problem is that the file physicaly doesn't exist and is generated during compilation.
Webpack will exit with
`This dependency was not found:

  • dist/images/icons.svg in ./js/utilities/icon.js
    `

is there a way how to use file with hash in JS file?

@cascornelissen
Copy link
Owner

cascornelissen commented Jan 28, 2021

You probably don't want to import the SVG spritemap file as that will download the entire spritemap every time you try to import/use a single sprite. There's a couple of approaches:

  • Switch to something like react-inlinesvg and import each icon separately, skipping the use of the spritemap
  • Import the SVG spritemap in the HTML template instead and reference it from your JavaScript, skipping the JS import
  • Include something like webpack-manifest-plugin, import the generated manifest json (which will include the filename with the hash) and do a dynamic import from there

All approaches have pros and cons and the "best" option depends on your setup. If you have any further questions feel free to comment ✌🏼

@HassanZahirnia
Copy link

@cascornelissen Sorry for pinging on an old closed issue. Didn't want to create a new issue for asking this question.
I'm using laravel mix and in my vue files I'm doing something like :

<template>
    <svg>
        <use :xlink:href="'/svg/sprite.svg#' + icon"/>
    </svg>
</template>

Which directly references the generated file at the public path ( public/svg/sprite.svg ).
As you may have guessed, this causes caching issue in the browser because the filename has no unique hash attached to it and I don't want to manually add a random string to it everytime I add or remove a svg file.

Do you know anyway I can solve this issue ? You mentioned 3 solutions, and I don't quite know how to do the second and the third ones.
Using a sprite file not only causes caching issues in production for the clients, but also it's really hard to develop locally because everytime I make change to the sprite file, I have to stop my npm run hot and run npm run dev to recompile the svg sprite then I have to run npm run hot again which takes lot of time.

Any helps would be appreciated! Thanks in advance!

@cascornelissen
Copy link
Owner

No problem, the main issue is that you want to bust the cache when the contents of the spritemap changes. The best way to do this is to use [contenthash] in the filename, this will insert a hash of the content in the filename which only changes when the content of the file changes. For example: sprite.95f18a53.svg where 95f18a53 is this contenthash that is injected by the plugin.

Then the next, and probably harder-to-solve issue is that you now have a filename with a dynamic value. The solution to this really depends on your stack, which is why it's hard to answer this question as I haven't worked with Laravel (mix) in a few years. I expect that both the second and third option in #147 (comment) are possible in your case.

The second option describes a solution where you use the power of the templating engine that's used by the project. The templating engine probably has access to the filesystem and should be able to figure out what the filename of the spritemap is. Then this templating engine injects the SVG directly into the HTML that it's generating, this way you don't have to reference an external file but you can reference an element that's available in the DOM already. The main downside of this approach is that you're forcing users to download the entire spritemap because it's part of the HTML document, kind of defeating one of the main points on why you'd want to use a spritemap in the first place.

The third option describes a solution that makes use of another webpack plugin. This plugin generates a manifest JSON file with original filenames as keys and hashed filenames as values, allowing you to basically do manifest['sprite.svg'] to get to the value you need; sprite.95f18a53.svg. You can do this approach both on the server and on the client. Since you're using Laravel I'm guessing doing it on the server is the best way forward in your case, especially since doing it client-side also has some issues (e.g. #170). In your template (I'm guessing at this point), you can import this generated manifest JSON file, and do something like:

<use :xlink:href="'/svg/' + manifest['sprite.svg'] + '#' + icon"/>

I hope this clears up the approaches you can take, I'm sure there are other ways to accomplish this but this should at least give you some insights.

@HassanZahirnia
Copy link

HassanZahirnia commented Jul 23, 2021

@cascornelissen Thanks a ton for the quick answer and detailed explanation 😇
You're right about using the [contenthash] in the filename is a bit hard to figure out later cause the generated hash is not stored anywhere, plus you have to clean up the previously generated files and it seems lots of work.
The third option ( using manifest file ) seems to be the way to go. I've already implemented using the method you mentioned :

// main layout file
<script>
    window.sprite_path = "{!! mix('svg/sprite.svg') !!}";
</script>

and then

<use :xlink:href="'/svg/' + sprite_path + '#' + icon"/>

...

data(){
     return {
         sprite_path: window.sprite_path.split('/').pop(),
     }
},

The only ugly side of this approach was the mix webpack configuration because webpack doesn't pick up the sprite file generated by this plugin for some reason 🤔
So to tackle this issue I ended up generating the file at some unnecessary path like '../resources/pre/sprite.svg' and then use mix.copy to copy it over to the public directory. Doing all that just to let webpack know: "this is one of my assets you got to add to the manifest file and version it later in the production".

Thanks a lot again anyway, great stuff 😄👍

@philippdieter
Copy link

@cascornelissen

The third option describes a solution that makes use of another webpack plugin. This plugin generates a manifest JSON file with original filenames as keys and hashed filenames as values, allowing you to basically do manifest['sprite.svg'] to get to the value you need; sprite.95f18a53.svg. You can do this approach both on the server and on the client. Since you're using Laravel I'm guessing doing it on the server is the best way forward in your case, especially since doing it client-side also has some issues (e.g. #170). In your template (I'm guessing at this point), you can import this generated manifest JSON file, and do something like:

<use :xlink:href="'/svg/' + manifest['sprite.svg'] + '#' + icon"/>

This would be the exact way I want to go in a current project. But after spending time trying to figure out which plugin you may have in mind I still have no idea. I have found plugins which generate manifest files but none of them made a variable available in template files or allowed me to import the manifest. Can you give some more details on this?

@cascornelissen
Copy link
Owner

That comment seems to refer back to the comment I made earlier in this very same thread: #147 (comment). That earlier comment mentions webpack-manifest-plugin which will create a JSON file that looks like this, according to its documentation:

{
  "dist/batman.js": "dist/batman.1234567890.js",
  "dist/joker.js": "dist/joker.0987654321.js"
}

That's just the first step though, you'll need to import this JSON file and use the correct key to reach the hashed filename value.

none of them made a variable available in template files or allowed me to import the manifest

Most plug-ins aren't going to provide this as it fully depends on your setup. If you're using a templating engine you'll have to look into the documentation for that tool on how to import and interpret JSON files. If, for example, you're using Laravel's Blade something like this will probably work. If you're rendering things client-side it might be as "easy" as using a dynamic import to fetch the file.

@philippdieter
Copy link

In my projects I use the html-loader so the hashed paths are written into the resulting html files when linking images. My knowledge is a little limited here, but afaik the image paths are parsed into require functions.

I was hoping for a solution which works alike for spritemaps generated by this plugin, without having to use a template engine or client side scripts, just a static html file with the hashed filename for the sprite. But if I get you right now, the manifest will not help me here. Thank you for the explanation.

@cascornelissen
Copy link
Owner

I don't have much experience with html-loader specifically either but I quickly also wanted to highlight that there's an example using html-webpack-plugin that might be similar to what you're looking for: https://github.com/cascornelissen/svg-spritemap-webpack-plugin/tree/master/examples/inline-html.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants