108

I am using webpack with HtmlWebpackPlugin, html-loader and file-loader. I have a simple project structure in which I use no frameworks, but only typescript. Thus, I write my HTML code directly to index.html. I also use this HTML file as my template in HtmlWebpackPlugin.

As all websites do I need to put an image which refers to a PNG in my assets folder. file-loader should load the file correctly put the new filename inside the src tag but that is not what is happening. Instead, as the value of src tag, I have [object Module]. I assume the file-loader emits some object and it is represented like this when its .toString() method is run. However, I can see that file-loader has processed the file successfully and emitted with new name to the output path. I get no errors. Here is my webpack configuration and index.html.

const projectRoot = path.resolve(__dirname, '..');

{
  entry: path.resolve(projectRoot, 'src', 'app.ts'),
  mode: 'production',
  output: {
    path: path.resolve(projectRoot, 'dist'),
    filename: 'app.bundle.js'
  },
  resolve: {
    extensions: ['.ts', '.js']
  },
  module: {
    rules: [
      {
        test: /\.html$/i,
        use: 'html-loader'
      },
      {
        test: /\.(eot|ttf|woff|woff2|svg|png)$/i,
        use: 'file-loader'
      },
      {
        test: /\.scss$/i,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              hmr: false
            }
          },
          {
            loader: 'css-loader',
            options: {
              sourceMap: false
            }
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: false
            }
          }
        ]
      },
      {
        exclude: /node_modules/,
        test: /\.ts$/,
        use: 'ts-loader'
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: path.resolve(projectRoot, 'src', 'index.html')
    }),
    new MiniCssExtractPlugin({
      filename: '[name].[hash].css',
      chunkFilename: '[id].[hash].css',
      ignoreOrder: false
    })
  ]
};

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title></title>
  </head>
  <body class="dark">
    <header>
      <nav class="navigation">
        <div class="left">
          <img src="assets/logo.png" class="logo"> <!-- This logo is output as [object Module] -->
        </div>
        <div class="right">

        </div>
      </nav>
    </header>
  </body>
</html>

Project structure:

config/
    webpack.config.js
dist/
src/
    styles/
    assets/
        logo.png
    index.html
    app.ts

Edit My package.json dependencies:

"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.2.0",
"file-loader": "^5.0.2",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.8.0",
"node-sass": "^4.13.0",
"sass-loader": "^8.0.0",
"style-loader": "^1.0.0",
"ts-loader": "^6.2.1",
"typescript": "^3.7.2",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.9.0"
Bora
  • 1,778
  • 4
  • 17
  • 28

11 Answers11

242

Per the file-loader docs:

By default, file-loader generates JS modules that use the ES modules syntax. There are some cases in which using ES modules is beneficial, like in the case of module concatenation and tree shaking.

It seems that webpack resolves ES module require() calls to an object that looks like this: {default: module}, instead of to the flattened module itself. This behavior is somewhat controversial and is discussed in this issue.

Therefore, to get your src attribute to resolve correctly, you need to be able to access the default property of the exported module. If you're using a framework, you should be able to do something like this:

<img src={require('assets/logo.png').default}/> <!-- React -->
<!-- OR -->
<img src="require('assets/logo.png').default"/> <!-- Vue -->

Alternatively, you can enable file-loader's CommonJS module syntax, which webpack will resolve directly to the module itself. Set esModule:false in your webpack config.

webpack.config.js:

 {
        test: /\.(png|jpe?g|gif)$/i,
        use: [
          {
            loader: 'file-loader',
            options: {
              esModule: false,
            },
          },
        ],
      },
stellr42
  • 3,365
  • 2
  • 21
  • 33
  • That worked. However it is still little bit magic. If you have ideas about why this is the case could you also explain it in your answer? Thanks. – Bora Nov 27 '19 at 19:22
  • 1
    @Bora -- Did a little more research and updated answer. – stellr42 Nov 28 '19 at 16:05
  • 4
    This bit me during an update from `Angular 8` to `Angular 9` as that brought `file-loader` from version `4.2.0` to `6.0.0`. Using `require(...).default` fixed it for me. – ebhh2001 Apr 19 '20 at 04:44
  • I encountered this after upgrading file-loader. Looking more carefully at the changelog, I see the default value for esModule changed to true with version 5.0.0. – user650881 May 23 '20 at 17:38
  • With react 16.13.1 i faced the same issue where require was not working Using require(...).default fixed it for me – Photonic May 12 '21 at 14:29
  • 1
    Thank you soooo much for pointing out the esModule: false flag. I couldn't for the life of me figure out why my js was handling my image fine, but my scss was returning an object module instead. I spent hours on this! – sashaikevich Jul 26 '21 at 14:54
  • Using require(...).default is worked for me. Thanks – Muthulakshmi M Sep 23 '21 at 08:13
18

@stellr42's suggested fix of esModule: false in your file-loader configuration is the best workaround at the current time.

However, this is actually a bug in html-loader which is being tracked here: https://github.com/webpack-contrib/html-loader/issues/203

It looks like ES Module support was added to file-loader, css-loader, and other friends, but html-loader was missed.

Once this bug is fixed, it will be better to remove esModule: false and simply upgrade html-loader, as ES Modules offer some minor benefits (as mentioned in the docs)

Alternatively, if (like me), you found this issue because you were having trouble loading an image from CSS (instead of from HTML), then the fix is just to upgrade css-loader, no need to disable ES Modules.

brindyblitz
  • 281
  • 1
  • 5
  • 1
    Finally, someone who faced the same problem! But as of 07/2022, I still need to set `esModule: false` in my Webpack config in order to see my .svg displayed, even with `css-loader 6.7.0` in my dev dependencies. Any clue? – informaticienzero Jul 18 '22 at 09:58
  • as of today 04/2023, i still need this esModule: false :/ with file-loader:6.2.0 – jav974 Apr 06 '23 at 11:53
8

Just updated my file-loader to ^5.0.2 minutes ago.

I know esModule: false was the suggested fix but that did not work for me.

My fix was <img src={require('assets/logo.png').default}/> which was weird. First time using .default but it worked.

kerubim
  • 137
  • 1
  • 8
5

This happens on file-loader version 5.0.2 , earlier version works fine without calling default property

Jora
  • 191
  • 2
  • 13
3

Instead of this: <img src="require('assets/logo.png').default"/>

Use it like this: <img src={require('assets/logo.png').default}/>

Varun Kashyap
  • 31
  • 1
  • 2
2

Use "default" followed by require to display a dynamic image in react js

src={require('../images/'+image_name+'.png').default}

1

For Next.JS:

// require(...).default.src
<img src={require("../public/images/avatar.png").default.src} width={256} height={256} />

SchemeSonic
  • 376
  • 5
  • 12
1

I have had the same problem recently after upgrading Laravel Mix v4 to v6. This works fine with me in my Vue component.

<img :src="require('./assets/profile.png').default"/>
TRD-Warren
  • 357
  • 5
  • 16
0

I had the same problem in vuejs and esModule:false did not work for me. Instead, I used @kerubim solution and it fixed it, but only in production mode and in development mode I get some error.

So I wrote this function in util.js that solved my problem.

maybeDefault: (module) => {

  if (typeof module === "object") {

    module = module.default;
  }

  return module;
},

use example:

let logo = 'logo.svg';
util.maybeDefault(require(`img/svg/logos/${logo}`));
Dharman
  • 30,962
  • 25
  • 85
  • 135
Farshadi
  • 143
  • 1
  • 12
  • @Dharman sorry, I think it's clear, so yeah. I used this function and solved my problem. but in `vuejs`, and of course it's can used for other purposes. – Farshadi Mar 15 '21 at 17:48
  • This worked for me in Gatsby without having to rewrite the webpack config – lacy Jun 07 '21 at 16:53
0

This is a weird issue still unsure how mine got fixed.

So I deleted my node_module and package-lock.json and ran npm install --force

and it worked fine after

Ice_mank
  • 410
  • 3
  • 8
0

for webpack 5 you need to use, Type option:

{
   test: /\.(png|jpe?g|gif|eot|woff2|woff|ttf|svg)$/i,
   type: 'asset/resource',
},
Goaul
  • 943
  • 11
  • 13