external-svg-sprite-loader icon indicating copy to clipboard operation
external-svg-sprite-loader copied to clipboard

Error when defining Rule.use as a function

Open markplewis opened this issue 6 years ago • 2 comments

I'm trying to define Rule.use as a function instead of an array, so that I can gain access to the issuer string and use that information to generate my sprite names:

{
  test: /\.svg$/,
  issuer: {
    include: /sprite\.js$/
  },
  use: (info) => ([
    {
      loader: "file-loader",
      options: { name: "[name].[contenthash].[ext]" }
    },
    {
      loader: SvgStorePlugin.loader,
      options: {
        name: `sprites/${info.issuer.split("/").pop()}.sprite.svg`,
        iconName: "[name]"
      }
    }
  ])
}

Unfortunately, this results in the following error:

Module build failed (from ./node_modules/external-svg-sprite-loader/lib/loader.js):
TypeError: Cannot read property 'addIcon' of undefined

I tried hacking the SvgStorePlugin.js file to add support for the function syntax:

injectSpritesIntoRules(rules) {
    for (const rule of rules) {
        const { oneOf: oneOfRules, rules: subRules, use: ruleUse } = rule;

        let loaders = ruleUse || [rule];
        
        // Check to see if ruleUse is a function
        if (ruleUse && {}.toString.call(ruleUse) === '[object Function]') {
          loaders = ruleUse();
        }

But sprite still ends up being undefined in loader.js:

function loader(content) {
    const { addDependency, resource, resourcePath } = this;

    // Get callback because the SVG is going to be optimized and that is an async operation
    const callback = this.async();

    // Parse the loader query and apply the default values in case no values are provided
    const { iconName, publicPath, sprite, svgoOptions } = Object.assign({}, DEFAULT_LOADER_OPTIONS, loaderUtils.getOptions(this));

    console.log(sprite); // undefined :(

I'd be willing to submit a pull request if I can figure this out, but I'm stuck. Any ideas?

markplewis avatar Apr 05 '19 16:04 markplewis

Hmm I guess this is a new feature... I don't think it was possible to pass a function to use when I came up with this idea. This was necessary to fix several complex issues but I don't think it will work for this case...

I would like to tell you how to go forward but I can't really come up with any idea right now. Although, I can tell you that what you are trying to do won't work. Unfortunately, I am quite busy at the moment so I don't think I'll have time to look into this any time soon 😞

Side note: You can detect functions in JS using ruleUse instanceof Function. No need for toString trickery :p

bensampaio avatar Apr 08 '19 15:04 bensampaio

Thanks @bensampaio! I was able to satisfy the requirements of my project by doing the following, so I no longer need to use Rule.use as a function. It would still be a nice feature to have though.

const spriteManifests = glob.sync(path.resolve(__dirname, "src/**/*sprite.js"));

spriteManifests.forEach(issuer => {
  const fileName = issuer.split("/").pop().replace(".js", "");

  rules.push({
    test: /\.svg$/,
    issuer: {
      include: issuer
    },
    use: [{
      loader: SvgStorePlugin.loader,
      options: {
        name: `dist/sprites/${fileName}.[contenthash].svg`,
        iconName: "[name]"
      }
    }]
  });
});

markplewis avatar Apr 08 '19 20:04 markplewis