javascriptwebpackstatic-fileswebpack-file-loader

file-loader creating extra file in root of dist folder


I am using file loader 6.2 to copy static image files into an images folder in my dist destination and it all works fine using the following config (ie the svg is copied from my src folder to the dist/images):

    test: /\.(png|jpe?g|gif|svg)$/i,
    loader: 'file-loader',
    options: {
      name: '[name].[ext]',
      outputPath: 'images',
      publicPath: 'images',
    },

However, as well as the static file being copied over, it also creates another file in the root of the dist folder that references the static - eg, when I copy a file called loading.svg, it creates that file as well as a file with a random hash (eg 817e56ed349aea52edfa.svg)

enter image description here

and inside this file, it just references the other file:

export default "images/loading.svg";

If I then check my compiled sass, I can see it references the hash file instead of the asset that was copied into the images folder.

background: url(../817e56ed349aea52edfa.svg) center center no-repeat; 

How do I stop the extra file being created so that it only creates and references the image in the images folder directly?

On a side note, I tried swapping file loader for copy webpack plugin, and that also copied the files into the images folder, but on that occasion, instead of referencing that file, it just also created a hash version of the svg (but with the actual svg contents rather than a webpack export) in the root directory and referenced that instead - so it may be something else causing this issue? I include full webpack config below in case anyone can spot anything obvious or something else is causing the issue

Full webpack config

const paths = require("./webpack.paths");
const { VueLoaderPlugin } = require('vue-loader');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require("terser-webpack-plugin");

const isProduction = (process.env.NODE_ENV === 'production');
if (isProduction) {
  console.log("Bundling in PRODUCTION mode")
} else {
  console.log("Bundling in DEVELOPMENT mode")
}

module.exports = {
  entry: {
    styles: paths.styles,
    home: './vue/templates/home/main.js',
  },
  mode: isProduction ? 'production' : 'development',
  output: {
    path: paths.dist,
    filename: 'js/[name].js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
      {
        test: /\.(css|scss)$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            // Use the css-loader to parse and minify CSS imports.
            loader: 'css-loader',
            options: { sourceMap: true }
          },
          {
            // Use the postcss-loader to add vendor prefixes via autoprefixer.
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                config: paths.postcssConfig,
              },
              sourceMap: true
            }
          },
          {
            // Use the sass-loader to parse and minify CSS imports.
            loader: 'sass-loader',
            options: { sourceMap: true }
          },
        ],
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images',
          publicPath: 'images',
        },
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              ['@babel/preset-env', { targets: "defaults" }],
            ]
          }
        },
      },
    ]
  },
  devServer: {
    hot: true,
    noInfo: true,
    overlay: true,
    contentBase: [paths.root],
  },
  plugins: [
    new VueLoaderPlugin(),
    new MiniCssExtractPlugin({
      filename: 'css/[name].css'
    }),
    new CleanWebpackPlugin({
      cleanOnceBeforeBuildPatterns: [
        `${paths.dist}/css/*`,
        `${paths.dist}/images/*`,
        `${paths.dist}/js/*`,
      ],
    }),
    new ESLintPlugin({
      extensions: ['vue', 'js'],
    })
  ],
  optimization: {
    minimize: isProduction,
    minimizer: [
      new TerserPlugin(),
    ],
    runtimeChunk: 'single',
    splitChunks: {
      minSize: 0,
      cacheGroups: {
        vendor: {
          name: 'vendor',
          chunks: 'all',
          test: /[\\/]node_modules[\\/]/,
          priority: 10,
          enforce: true
        }
      }
    }
  }
}

if (isProduction) {
  module.exports.plugins = (module.exports.plugins || []).concat([
    new CompressionWebpackPlugin(),
  ])
}


Solution

  • In the end it looks as if it was the css loader that was causing the issue - so I configured it to ignore urls and then changed from the file-loader plugin to the copy-webpack-plugin to copy the images:

    Ignore urls with css loader

          MiniCssExtractPlugin.loader,
          {
            // Use the css-loader to parse and minify CSS imports.
            loader: 'css-loader',
            options: {
              sourceMap: true,
              url: false,
            }
          },
    

    Copy webpack plugin to copy images

    const CopyPlugin = require("copy-webpack-plugin");
    
    plugins: [
      new CopyPlugin({
        patterns: [
          {
            from: 'vue/images', // src location
            to: 'images',       // destination location in dist folder
          },
        ],
        options: {
          concurrency: 100,
        },
      }),
    ]
    

    Then in my css I could just reference the image as relative from the dist folders css to the dist folder images folders:

    background-image: url(../images/loading.svg);
    

    Seems a bit odd to me that I had to do it this way in order not to get the extra svg file so if anyone comes up with a better solution or knows how not to create the extra file, please feel free to answer