webpack resolve error with packaged css file referencing an image

Viewed 141

I'm working on a Vue component library built via VueCLI (and using Storybook Js, Bulma, and Buefy) and I am having issues consuming the CSS downstream. Specifically when I import the CSS file from my package, I am getting Webpack errors with referenced images.

For example, in my upstream src scss files I have a file called "notice-badge.scss" and am referencing background images like so:

.notice-badge img {
    background-image: url('@/assets/img/warning-dark.svg');
} 

and my src directory structure looks like:

my-app/
|--src/
|--assets/
  |--scss/
    |-- notice-badge.scss
  |--img/
    |--warning-dark.svg
  |--fonts/
|--vue-components/

and I build the packages with this command which produces no errors.

vue-cli-service build --target lib --name my-ui-components ./src/index.ts

This outputs my JS, a CSS file, and 2 directories (img and fonts) into my "dist" directory. The images listed in my errors are infact inthere.

So over in another Vue cli app (and later Nuxt) I will be importing the CSS file and Vue components but I am getting a "can't resolve" error on that warning-dark.svg file:

Can't resolve /img/warning-dark.a45b259b.svg in /Users/myname/sites/my-app/ui-components/dist. My package also contains font awesome font files too (a business decision to include this all up stream)

So how can I get my downstream Vue CLI app to resolve the images and fonts referenced inside my node_modules dir?

1 Answers

You have (at least) 3 options:

  1. Inline the images/fonts as data URLs.
  2. Use a relative path in the output and require apps that install your package to move the image directory to the same path as the built CSS file.
  3. Don't ship built CSS, but instead source SCSS files. That way file loading/moving can be handled with WebPack configuration in the app that uses it (using file_loader. You can include example configuration in your package to make this easier.

If you're writing a Vue component library, it probably makes most sense to use method 3. However from your description it seems like this may not be an option (the business decision you mention). Method 2 might be viable but I didn't try it nor seen someone else suggest it.

Inline

This method probably is easiest and has best performance. If your other SVGs are similar to the examples, it seems like they should all be relatively small files. There's few reasons for a component library to ship big images, so this might be sufficient for your use case.

If you're using WebPack 5, you can inline assets using "Asset Modules".

  module: {
    rules: [
      {
       test: /\.svg/,
       type: 'asset/inline'
     }
    ]
  }

If you tried this, you may have run into the following problem.

Since Sass implementations don't provide url rewriting, all linked assets must be relative to the output.

  • If you pass the generated CSS on to the css-loader, all urls must be relative to the entry-file (e.g. main.scss).
  • If you're just generating CSS without passing it to the css-loader, it must be relative to your web root.

You can try replacing url('@/assets/img/warning-dark.svg') with url('../img/warning-dark.svg') (or whatever the path relative to the entrypoint is). Does it now properly inline them?

Related