How can I load webp images after these are generated by imagemin-webp-webpack-plugin?

3.7k views Asked by At

I was configuring imagemin-webp-webpack-plugin to convert all my .png and .jpg images in my src/assets/images to dist/assets/images. When I ran my build command, the conversion was successful. All images had been converted to webp and distributed to dist/assets/images. I thought "this is simple" and that it was time to create <picture> tags in my src/index.html file to start referencing .webp images:

src/index.html:

<picture>
    <source srcset="assets/images/img-hero-home-attorney.webp" type="image/webp">
    ...
    ... 
</picture>

When I npm run build again, this time I got:

ERROR in ./src/index.html (./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html)
    Module not found: Error: Can't resolve './assets/images/img-hero-home-attorney.webp' in '/Users/**/**/**/**/**/**/src'
     @ ./src/index.html (./node_modules/html-webpack-plugin/lib/loader.js!./src/index.html) 6:33-87

And it made perfect sense to me. These images don't exist in src/assets/images/ hence why Webpack can't resolve these.

So now I have hit a roadblock: How can I reference .webp images in my src/index.html when these images will only exist on dist/whateverpath after jpg's and png's have been processed by imagemin-webp-webpack-plugin?

This is my configuration file in case it could be helpful:

webpack.config.js

module.exports = {
    entry: {
        app: [
            './src/index.js'
        ]
    },

    output: {
        path: path.resolve(__dirname, 'dist/'),
        filename: 'assets/js/[name].bundle.js',
    },
    
    devtool: 'source-map',

    plugins: [
        new CleanWebpackPlugin({
            dry: false,
            cleanOnceBeforeBuildPatterns: ['!index.html']
        }),
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: './index.html',
            minify: false,
            chunks: ['app']
        }),
        new MiniCssExtractPlugin({
            filename: 'css/[name].css',
            chunkFilename: '[id].css'
        }),
        new HtmlCriticalWebpackPlugin({
            base: 'dist/',
            src: 'index.html',
            dest: 'index.html',
            inline: true,
            minify: true,
            extract: false,
            width: 1351,
            height: 1200,
            penthouse: {
                blockJSRequests: false,
            }
        }),
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery"
        }),
        new ImageminWebpWebpackPlugin({
            config: [{
                test: /\.(jpe?g|png)/,
                options: {
                    quality: 85
                }
            }],
            overrideExtension: true,
            detailedLogs: true,
            silent: true,
            strict: true
        })
    ],

    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader'
                }
            },
            {
                test: /\.html$/,
                loader: 'html-loader',
                query: {
                    minimize: false
                }
            },
            {
                test: /\.(scss)$/,
                use: [ 
                    {
                        loader: MiniCssExtractPlugin.loader,
                        options: {
                            publicPath: '../'
                        }
                    },
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: true,
                        }
                    }, 
                    {
                        loader: 'postcss-loader',
                        options: {
                            sourceMap: true,
                            plugins: function () {
                                return [
                                    require('autoprefixer')
                                ];
                            }
                        }
                    }, 
                    {
                        loader: 'sass-loader',
                        options: {
                            sourceMap: true
                        }
                    }
                ]
            },
            {
                test: /\.(png|svg|jpg|gif|webp)$/,
                use: {
                    loader: 'file-loader',
                    options: {
                        name: 'assets/images/[name].[ext]',
                    }
                }
            },
        ]
    },
    
};
3

There are 3 answers

0
Pong On

I have the same issue. After two day struggling, i decide to handle at runtime...

    import html from './content.html';
    
    const parser = new DOMParser();
    
    /**
      * @param {string} html
      */
    function replaceImgToPicture(html) {
      var dom = parser.parseFromString(html, 'text/html');
      var images = dom.querySelectorAll('img');
      function __setAttribute(source, img, attr, newAttr) {
        var value = img.getAttribute(attr);
        if (value) {
          source.setAttribute(
            newAttr || attr,
            value.replace(/\.(jpe?g|png|gif)/gi, '.webp')
          );
        }
      }
    
      for (var i = 0; i < images.length; i++) {
        var img = images[i];
        if (img.parentElement && img.parentElement.tagName === 'PICTURE') {
          continue;
        }
        var picture = document.createElement('picture');
        var source = document.createElement('source');
        source.setAttribute('type', 'image/webp');
        __setAttribute(source, img, 'sizes');
        __setAttribute(source, img, 'srcset');
        __setAttribute(source, img, 'media');
    
        if (!source.hasAttribute('srcset')) {
          __setAttribute(source, img, 'src', 'srcset');
        }
    
        img.parentElement.insertBefore(picture, img);
        picture.appendChild(source);
        picture.appendChild(img);
      }
    
      return dom.documentElement.outerHTML;
    }
0
ultimoTG On

You could use the filemanager-webpack-plugin to copy the converted images back to your assets/images folder - https://github.com/gregnb/filemanager-webpack-plugin/

const FileManagerPlugin = require('filemanager-webpack-plugin');

plugins: [
    new ImageminWebpWebpackPlugin({
        config: [{
            test: /\.(jpe?g|png)/,
            options: {
                quality: 85
            }
        }],
        overrideExtension: true,
        detailedLogs: true,
        silent: true,
        strict: true
    }),
    new FileManagerPlugin({
      onEnd: {
        copy: [
          {
            source: './dist/**/*.webp',
            destination: './src/assets/images/',
          },
        ],
      },
    }),
  ],
]
0
wontonman2 On

I have this same issue and it's pretty frustrating it never got resolved so here's my solution.

As you said you cannot reference your webp images in the element if they are only made by the ImageminWebpWebpackPlugin during the build process. Same as you my html-loader was throwing errors because the file did not exist. In my case I just added a pre-build step with to convert the images to webp first and just not use ImageminWebpWebpackPlugin in the build process but rather to just use the imagemin-webp plugin alone.

Here is the default configuration on their github page Site

const imagemin = require('imagemin');
const imageminWebp = require('imagemin-webp');
    
(async () => {
    await imagemin(['images/*.{jpg,png}'], {
        destination: 'build/images',
        plugins: [
            imageminWebp({quality: 50})
        ]
    });
    
    console.log('Images optimized');
})();

Of course I ran this on Windows 10 and it didn't work so another user on the issues tab solved this by converting the path to the unix style.

const path = require('path');
const imagemin = require('imagemin');
const imageminWebp = require('imagemin-webp');
    
(async () => {
    const img = await imagemin([path.resolve(__dirname, 'img/*.{jpg,png}').replace(/\\/g, '/')], {
    destination: path.resolve(__dirname, 'dist/webp').replace(/\\/g, '/'),
    plugins: [imageminWebp({ quality: 70 })],
    });
    
    console.log('Images optimized');
    console.log(img);
})();

Link to issues page here