Svg-sprite-loader: Question: Generate SVG sprite without JS import

Created on 10 Nov 2017  Â·  12Comments  Â·  Source: JetBrains/svg-sprite-loader

Is there a way to generate the sprite without importing each icon in my JavaScript file?

This is my webpack config and it does not work unless I use import icon from './path/to/icon' although I set include.

module: {
    rules: [
      {
        test: /\.svg$/,
        loader: 'svg-sprite-loader',
        include: path.resolve(__dirname, 'source/assets/images/icons'),
        options: {
          extract: true,
          spriteFilename: 'assets/images/icons.svg',
        },
      },
    ],
  },
  plugins: [
    new SpriteLoaderPlugin(),
  ],
question

Most helpful comment

You can use require.context webpack feature. Somewhere in entrypoint of your application:

const svgModules = require.context('source/assets/images/icons', false, /\.svg$/);
svgModules.keys().forEach(svgModules);

All 12 comments

You can use require.context webpack feature. Somewhere in entrypoint of your application:

const svgModules = require.context('source/assets/images/icons', false, /\.svg$/);
svgModules.keys().forEach(svgModules);

@kisenka Thank you Stas for your quick reply and suggestion. But I wonder, is there a "webpack" only solution to it?
I would like to use webpack just as an build tool and not a module bundler when working with static sites. No React, Vue or any other larger framework.

Tell me more what you want from webpack. You want only to generate svg sprite?

Yes exactly. Currently I am using gulpand gulp-svgstore to create one svg sprite from many icons.
Because webpack has some more nice features, I would like to switch from gulp to webpack.
I would like to keep the approach from gulp to specify an input and an output.
Unfortunately in webpack you most often need to set the input in you main JavaScript entry.
Sure this would work but I am looking for a more cleaner way and not bring any server side transformation to the client. Just simple input – transformation – output all within my webpack.config.jsand NOT in my /path/to/entry.js file

You can create entrypoint dynamically:

const glob = require('glob').sync;

module.exports = {
  entry: {
    sprite: glob('./source/assets/images/icons/*.svg')
  },

  module: {
    rules: [
      {
        test: /\.svg$/,
        loader: 'svg-sprite-loader',
        include: path.resolve(__dirname, 'source/assets/images/icons'),
        options: {
          extract: true,
          spriteFilename: 'assets/images/icons.svg'
        },
      },
    ],
  },

  plugins: [
    new SpriteLoaderPlugin()
  ]
};

@kisenka
Thank you very much. You script works file. I din't know of the glob.sync method.
One last thing:
Because I now have two entries I also get two JS files in my output:

webpack.config.js

const SpriteLoaderPlugin = require('svg-sprite-loader/plugin');
const glob = require('glob');
const path = require('path');

module.exports = {
  entry: {
    main: path.resolve(__dirname, 'source/assets/js/index'),
    sprite: glob.sync(path.resolve(__dirname, 'source/assets/images/icons/**/*.svg')),
  },
  output: {
    filename: 'assets/js/[name].js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.svg$/,
        loader: 'svg-sprite-loader',
        include: path.resolve(__dirname, 'source/assets/images/icons'),
        options: {
          extract: true,
          spriteFilename: 'assets/images/icons.svg',
        },
      },
    ],
  },
  plugins: [
    new SpriteLoaderPlugin(),
  ],
};

output:

dist/assets/images/icons.svg
dist/assets/js/main.js
dist/assets/js/sprite.js

This is how webpack works. You can remove assets by creating a tiny plugin:

module.exports = {
  plugins: [
    {
      apply: (compiler) => {
        compiler.plugin('emit', (compilation, callback) => {
          delete compilation.assets['sprite.js'];
          callback();
        });
      }
    }
  ]
};

Hi @kisenka ,

I have a similar request which is related to the solution you have made up here
const svgModules = require.context('source/assets/images/icons', false, /\.svg$/); svgModules.keys().forEach(svgModules);

Basically,
I want to include all the icons I have (from a directory) in one page and render them using <use>tag.
And I don't want to include them manually.

I think I miss one part from the solution listed above as I can't see all those image added to the sprite in DOM.
Although when I use es6 import with a single file it works fine.

Thanks for you help.

@Memoyr approach above (with globbing) just works. You can create a demo repo where I can reproduce described behaviour.

Thanks @kisenka, no need for demo, I got it working.

Hi @kisenka ,

This seems a very interesting topic. Thanks for all the answers! I'm still new to Webpack and I can't achieve the same result as above. The basic setup with importing svg manually in the js works.

Setup
Webpack: v3.5.5
svg-sprite-loader: v3.6.2
Node: v8.9.3
Yarn: v1.2.1
NPM: v5.6.0

This is my original config, which works. Now i'm trying to add the automatic svg to it.
An example of my variable 'configSettings.css.input': ./src/_scss/main.scss

Original

entry: removeEmpty([
   configSettings.css.input,
   configSettings.js.input,
   ifDevelopment(...configHtmls.entry),
 ]),

 resolve: {
   extensions: [
     `.js`,
     `.jsx`,
     `.css`,
     `.scss`,
     `.svg`,
   ],
 },

 output: {
   path: path.join(__dirname, configSettings.general.output),
   filename: `js/[name].[hash].js`,
   publicPath,
 },

So now i'm trying to do the same as you guys, but this gives several errors.

New config

entry: {
   main: path.resolve(__dirname, `src/_js/script`),
   sprite: glob.sync(path.resolve(__dirname, `src/_assets/svg/**/*.svg`)),
 },

 resolve: {
   extensions: [
     `.js`,
     `.jsx`,
     `.css`,
     `.scss`,
     `.svg`,
   ],
 },

 output: {
   path: path.join(__dirname, configSettings.general.output),
   filename: `js/[name].[hash].js`,
   publicPath,
 },

error

webpack: wait until bundle finished: /
fs.js:897
  binding.readdir(pathModule._makeLong(path), options.encoding, req);
          ^

TypeError: path must be a string or Buffer
    at Object.fs.readdir (fs.js:897:11)

production run error

Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
 - configuration.entry should be one of these:
   object { <key>: non-empty string | [non-empty string] } | non-empty string | [non-empty string] | function
   -> The entry point(s) of the compilation.
   Details:
    * configuration.entry['sprite'] should be a string.
      -> The string is resolved to a module which is loaded upon startup.
    * configuration.entry['sprite'] should not be empty.
    * configuration.entry should be a string.
      -> An entry point without name. The string is resolved to a module which is loaded upon startup.
    * configuration.entry should be an array:
      [non-empty string]
    * configuration.entry should be an instance of function
      -> A Function returning an entry object, an entry string, an entry array or a promise to these things.

This production error also occurs when I use the wildcards plugin: https://github.com/8427003/wildcards-entry-webpack-plugin
Am I doing something wrong? What does this glob.sync returns? Is it an object, string or array?

If you have chunk with hash you can remove it by doing:

apply: (compiler) => {
  compiler.plugin('emit', (compilation, callback) => {
    const { assets } = compilation
    const chunkToRemove = Object.keys(assets).filter(chunk => chunk.match(/sprite.*\.js$/)).join('')
    delete compilation.assets[chunkToRemove]
    callback()
  })
}
Was this page helpful?
0 / 5 - 0 ratings