Webpack url-loader or file-loader not working react app
Asked Answered
F

2

6

Images are not loading in the browser using Webpack 4 with either of url-loader, or file-loader. Small images are not in data url(or if they are the browser isn't showing them) and file network requests are not being made with file loader.

Nginx is serving the images properly at https://{server}/images/image_name.png, but not https://{server} and no network calls for images are being made in web inspector network panel.

Best guess so far is Webpack url-loader or file-loader must not be generating the right URL. Cannot find the host when searching app.bundle.js for the url. I've tried for days every combination of publicPath, outputPath, etc.. from all the other stackoverflow posts, nothing works.

Is there any way to view the url's that webpack generates besides searching the js? Is the webpack config not proper? Troubleshooting advice?

Here is how I handle images in code:

import nav_logo from "Images/white_nav_logo.svg";

<img src={nav_logo} />

Here is my webpack.common.js:

module.exports = {
  mode: mode,
  entry: {
    app: ["./src/js/app.js"]
  },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: '[name].bundle.js',
    publicPath: '/',
    chunkFilename: '[name].bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.(sc|c|)ss$/,
        issuer: {
          exclude: /\.less$/,
        },
        use: [
          {
            loader:  'style-loader',
            options: {
            },
          },
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              localIdentName: '[name]-[local]-[hash:base64:5]',
            },
          },
        ],
      },

      {
        test: /\.less$/,
        use: [
          {
            loader:  'style-loader',
          },
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
            },
          },
        ],
      },
      {
        test: /\.(jsx?)/,
        exclude: ["/node_modules", "/src/js/elm"],
        use: [
          { loader: "babel-loader?cacheDirectory=true",
          }
        ]
      },
      {
        test: /\.scss$/,
        issuer: /\.less$/,
        use: {
          loader: './src/js/sassVarsToLess.js'
        }
      },
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: 'images/[name].[ext]',
            }
          },
          {
            loader: "image-webpack-loader",
            options: {
              disable: true,
              mozjpeg: {
               progressive: true,
               quality: 65
              },
              // optipng.enabled: false will disable optipng
              optipng: {
               enabled: true,
              },
              pngquant: {
               quality: '65-90',
               speed: 4
              },
              gifsicle: {
               interlaced: false,
              },
              // the webp option will enable WEBP
              webp: {
               quality: 75
              }
            }
          },
        ],
      },
      {
        test: /\.(ttf|otf|eot|woff2?)$/,
        loader: "file-loader",
        options: {
          name: 'fonts/[name].[ext]',
        }
      }
    ],
    noParse: [/\.elm$/]
  },
  node: {
    fs: 'empty'
  },
  plugins: [
    new Dotenv(),
    new CopyWebpackPlugin([{
      from: "./src/assets/css",
      to: "css"
    },
  ]),
  ]
};

and webpack.prod.js

module.exports = merge(common, {
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.(sc|c|)ss$/,
        issuer: {
          exclude: /\.less$/,
        },
        use: [
          {
            loader:  MiniCssExtractPlugin.loader,
          },
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              localIdentName: '[name]-[local]-[hash:base64:5]',
            },
          },
        ],
      },
      {
        test: /\.less$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
            }
          },
        ],
      },
      {
        test: /\.(jsx?)/,
        exclude: ["/node_modules", "/src/js/elm"],
        use: [
          { loader: "babel-loader?cacheDirectory=true",
          }
        ]
      },
      {
        test: /\.scss$/,
        issuer: /\.less$/,
        use: {
          loader: './src/js/sassVarsToLess.js' // Change path if necessary
        }
      },
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: 'images/[name]-[hash:8].[ext]'
            }
          },
          {
            loader: "image-webpack-loader",
            options: {
              disable: false,
              mozjpeg: {
               progressive: true,
               quality: 65
              },
              // optipng.enabled: false will disable optipng
              optipng: {
               enabled: true,
              },
              pngquant: {
               quality: '65-90',
               speed: 4
              },
              gifsicle: {
               interlaced: false,
              },
              // the webp option will enable WEBP
              webp: {
               quality: 75
              }
            }
          },
        ],
      },
      {
        test: /\.(ttf|otf|eot|woff2?)$/,
        loader: "file-loader",
        options: {
          name: 'fonts/[name].[ext]',
        }
      }
    ],
    noParse: [/\.elm$/]
  },
  optimization: {
    minimizer: [new TerserJSPlugin(), new OptimizeCSSAssetsPlugin({})]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/assets/prod.index.html'
    }),
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css',
    }),
  ],
})

and here is nginx default.conf

server {
    listen       80;
    server_name  <domain_name>;
    root /usr/share/nginx/html;
    access_log  /var/log/nginx/host.access.log  main;

    index index.html;

    location / {
      try_files $uri $uri/ =404;
    }

    location /images/ {
      alias /usr/share/nginx/html/images/;
      try_files $uri $uri/ =404;
      error_log /var/log/nginx/error.log debug;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
Fiction answered 3/10, 2019 at 2:38 Comment(0)
P
3

For loading images using url-loader

If you notice inside config/webpack.config.js There is a module object which has rules object inside it. For the rules or list of rules provided there is limit key limit key is very important

Significance of limit value -if the size of image to be loaded is greater than the limit value provided then by default file-loader is used. e.g if I have below webpack.config.js configuration

{
  test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
  loader: require.resolve('url-loader'),
  options: {
             limit: 10000,
             name: 'static/media/[name].[hash:8].[ext]',
           },
},

inside my moudules -> rules object

Above the limit value is 10000 bytes so webpack will load only those images using url-loader whose size is less than 10000 bytes if size of image is found equal to or greater than 10000 then file-loader is used by default until fallback loader is not specified.

So suppose if you are dynamically adding a image something like this inside your code.

import largeimage from '../static/images/largeimage.jpg' or whatever path and the size of largeimage is less than the limit value the image will not get loaded.

SOLUTION

For the webpack to load the image using url-loader your largeimage size should be less than limit value.

So either increase limit or decrease size of image.

Reference https://webpack.js.org/loaders/url-loader/#limit

Poster answered 28/10, 2020 at 15:2 Comment(0)
D
1

url-loader is not loading image as separate file, it encodes the file into base64 format and includes it into js bundle. Hence there will be no separate request to the image file. See this answer: Url-loader vs File-loader Webpack

Try to load images with file-loader instead. I usually load both fonts and images with file-loader and it is working correctly.

I am using this working configuration (development):

// this is configured outside of the exported webpack configuration code
const BASE_DIR = resolve(`${__dirname}`);

module.exports = {
    mode: 'development',
    devtool: 'eval-source-map',
    resolve: {
        modules: [
            resolve(BASE_DIR),
            'node_modules'
        ]
    },
    output: {
        // ...
        publicPath: '/'
    },

    module: {
        rules: [
            // ...
            {
                test: /\.(png|svg|jpg|jpeg|gif|tiff)$/,
                use: [
                    'file-loader?name=assets/[name].[ext]'
                ]
            },
            // ...
        ]
    }
    // ...
}

My image file is physically located at 'src/assets/logo_arc.png' and I am using it this way:

import logo from 'src/assets/logo_arc.png';
// ...
<img src={logo} alt={'company logo'} />

I can see my file located in development build directory under subdirectory assets as I would expect.

When running application on webopack dev server (on localhost, my custom port 9901) the image is served on address http://localhost:9901/assets/logo_arc.png.

In the development bundle I can see this involved parts:

// definition of webpack public path
/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "/";

// ...

// the image itself as a webpack module
/***/ "./src/assets/logo_arc.png":
/*!*********************************!*\
  !*** ./src/assets/logo_arc.png ***!
  \*********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

eval("module.exports = __webpack_require__.p + \"assets/logo_arc.png\";//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9zcmMvYXNzZXRzL2xvZ29fYXJjLnBuZz8wMmVlIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGlCQUFpQixxQkFBdUIiLCJmaWxlIjoiLi9zcmMvYXNzZXRzL2xvZ29fYXJjLnBuZy5qcyIsInNvdXJjZXNDb250ZW50IjpbIm1vZHVsZS5leHBvcnRzID0gX193ZWJwYWNrX3B1YmxpY19wYXRoX18gKyBcImFzc2V0cy9sb2dvX2FyYy5wbmdcIjsiXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///./src/assets/logo_arc.png\n");

/***/ }),
// importing webpack module into variable, it is used later in the img element
var src_assets_logo_arc_png__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! src/assets/logo_arc.png */ "./src/assets/logo_arc.png");

// ...

// usage in the img element
react__WEBPACK_IMPORTED_MODULE_0__["createElement"]("img", {
        src: src_assets_logo_arc_png__WEBPACK_IMPORTED_MODULE_7___default.a,
        alt: 'company logo'
      }))
Detachment answered 3/10, 2019 at 10:58 Comment(8)
I've tried both many, many times. Both seem to be doing the right thing, but still no loading of images. There is data:image/jpeg etc.. in the urls but still only chrome and safari placeholder images.Fiction
As I said the url-loader transform image to base64 format. Replace it for file-loader and search for image file name in the built bundle. The file name will be prefixed with the path, it can helps you to see what is going on.Detachment
Thanks for the tip, but one of my files is over the 10000 limit set for url-loader and it is not showing either, but there is data:image/jpeg showing where it should be.Fiction
I followed your advice and searched for image/ in the app.bundle.js. I see a bunch of places with n.p + 'images/image_file.jpeg' and when I search for n.p I see n.p="", so it must not be setting n.p ? Where does webpack pick up the url? By the way this is on localhostFiction
n.p should be the webpack publicPath (output.publicPath in a webpack config file). I ca see it in my bundle initilaized to my publicPath '/', are you sure that you have initialized to an empty string? Webpack does not require to know your server address. It serves your files relatively from location where the bundle is deployed (you can change it a little with the mentioned publicPath option). Therefore you cannot find anything about your "server_name" in the bundle.Detachment
I've edited my answer. I included all parts of mine webpack configuration, also with information from webpack bundle. Look at it, if helps you to solve your issue. I suggest to use simple file-loader without image-webpack-loader until you solve your problem.Detachment
Ok, after looking i see that I get two images: -rw-r--r-- 1 root root 183K Oct 5 03:29 hero-217ea6c8.jpeg -rw-r--r-- 1 root root 71 Oct 5 03:29 hero.jpeg and the one without the hash has this in it: module.exports = __webpack_public_path__ + "images/hero-217ea6c8.jpeg"; and this is why it's not showing up. Any way to get webpack to show the hashed version?Fiction
Thanks for heading me in the right direction. The problem was the hash in file-loader, taking that out seemed to workFiction

© 2022 - 2024 — McMap. All rights reserved.