3

I have in my index.ejs file (you can consider this the .html file) a classic tag <img> that I'm copying in the dist folder with copy-webpack-plugin.

My problem is that in 'production' mode I add an hash to my image instead in the index.ejs file the attribute src of the <img> still will point to the image without the hash. Thus my index.html in the dist folder doesn't display the image.

This is my project :

https://github.com/cuccagna/Webpack28

For convenience, I put here the package.json, the webpack.production.config.js and the index.ejs files:

webpack.production.config.js:

const pathM = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
/* const { CleanWebpackPlugin } = require('clean-webpack-plugin');
 */
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
   entry: './src/js/index.js',
   output: {
      filename: 'js/bundle.[contenthash].js',
      path: pathM.resolve(__dirname, './dist'),
      assetModuleFilename: '[path][name].[contenthash][ext]', 
      publicPath: './',
      clean: true /* {
         dry: false,
         keep:/\.css$/ 
      } */   //Serve per cancellare la cartella dist dalla precedente esecuzione
   },
   mode: 'production',
   module: {
      rules:[
         {
            test: /\.(png|jpe?g|webp|avif|gif|svg)$/,
            type: 'asset',
            parser: {
               dataUrlCondition: {
                  maxSize: 3 * 1024 //3 Kilobytes QUI CAMBIA LA SOGLIA A 3 KByte
                
               }
            }
         },
        
         {
            test: /\.css$/,
            use: [MiniCssExtractPlugin.loader,'css-loader'] 
         },
         {
            test: /\.scss$/,
            use: [MiniCssExtractPlugin.loader,'css-loader','sass-loader'] 
         },
         {
            test: /\.js$/,
            exclude: /node_modules/,
            use: {
               loader: 'babel-loader',
               options: {
                  presets: [['@babel/env', {
                      targets: "> 0.1%, not dead",
                     debug:true, 
                     useBuiltIns: 'usage',
                     //Puoi mettere anche solo version:3
                     //La versione la puoi prelevare da package.json
                     corejs:{version:3.26 , proposals:true}
                  }]],
                  //plugins: ['@babel/plugin-proposal-class-properties']
               }
            }
         }
      ]
   },
   plugins: [
       new CopyWebpackPlugin({
         patterns: [
             { from: './assets/img', to: './assets/img/[name].[contenthash][ext]' },
            
          ],
          options: {
        concurrency: 100,
      }
      }),
      new MiniCssExtractPlugin({
      filename:"css/main.[contenthash].css"
      }),
      new HtmlWebpackPlugin({
        
         inject: false,
         template: "./index.ejs", //Puoi mettere anche un file html
         minify:true 
      })
      
   ]

}

package.json

{
  "name": "project5",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack --progress --profile --config webpack.production.config.js",
    "dev":"webpack --progress --profile --config webpack.dev.config.js",
    "watch": "webpack --progress --profile --watch"
  },
  "watch": {
    "build": "./src"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.20.5",
    "@babel/preset-env": "^7.20.2",
    "babel-loader": "^9.1.0",
    "copy-webpack-plugin": "^11.0.0",
    "css-loader": "^6.7.3",
    "html-webpack-plugin": "^5.5.0",
    "mini-css-extract-plugin": "^2.7.2",
    "npm-watch": "^0.11.0",
    "sass": "^1.56.2",
    "sass-loader": "^13.2.0",
    "style-loader": "^3.3.1",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.1"
  },
  "dependencies": {
    "core-js": "^3.26.1"
  }
}

index.ejs

<!DOCTYPE html>
<html>
   <head>
      <title>Custom insertion example</title>
      <!-- prettier-ignore -->
      <% if (process.env.NODE_ENV === 'production'){%> 
       <%  for(var i=0; i < htmlWebpackPlugin.files.css.length; i++) {%>
      <link
         type="text/css"
         rel="stylesheet"
         href="<%= htmlWebpackPlugin.files.css[i] %>"
      />
      <% } }%>
   </head>
   <body>
      <img src="./assets/img/natura.jpg" alt="Natura.jpg" />
      <img src="./assets/img/natale.jpg" alt="Natale.jpg" />
      <button class="hello-world-button">Ciao</button>
      <img id="asset-resource" />

      <% for(var i=0; i < htmlWebpackPlugin.files.js.length; i++) {%>
      <script
         type="text/javascript"
         src="<%= htmlWebpackPlugin.files.js[i] %>"
      ></script>
      <% } %>
   </body>
</html>

A similar problem is described here:

https://github.com/webpack-contrib/copy-webpack-plugin/issues/279

I tried to fix the problem from myself but I can't figure how to proceed.

Lin Du
  • 88,126
  • 95
  • 281
  • 483
Nick
  • 1,439
  • 2
  • 15
  • 28

1 Answers1

1

A solution without any additional webpack loaders and plugins. We can use compilation, the webpack compilation object to get the assets by compilation.assets property.

This can be used, for example, to get the contents of processed assets and inline them directly in the page, through compilation.assets[...].source()

The loadAsset function can be used in HTML template to load the bundled asset by its filename.

For example:

loadAsset('./assets/img/icons8-apple-50.png') will return ./asserts/img/icons8-apple-50.be319f9e64e51eba6ea0.png.

E.g.

project structure:

$ tree -L 4 -I node_modules
.
├── assets
│   └── img
│       ├── icons8-apple-50.png
│       └── icons8-iphone-64.png
├── dist
│   ├── assets
│   │   └── img
│   │       ├── icons8-apple-50.be319f9e64e51eba6ea0.png
│   │       └── icons8-iphone-64.49b2d0705a1347a44580.png
│   ├── index.html
│   └── js
│       └── bundle.acd85cb0428d8156db50.js
├── package-lock.json
├── package.json
├── src
│   ├── index.html
│   └── index.js
└── webpack.config.js

src/index.html:

<!DOCTYPE html>
<html>
    <head>
        <title>Custom insertion example</title>
    </head>
    <body>
        <img
            src="<%= loadAsset('./assets/img/icons8-apple-50.png') %>"
            alt="icons8-apple-50.png"
        />
        <img
            src="<%= loadAsset('./assets/img/icons8-iphone-64.png') %>"
            alt="icons8-iphone-64.png"
        />
    </body>
</html>

webpack.config.js:

const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'js/bundle.[contenthash].js',
        path: path.resolve(__dirname, './dist'),
        clean: true,
        publicPath: './',
    },
    mode: 'production',
    plugins: [
        new CopyWebpackPlugin({
            patterns: [{ from: './assets/img', to: './assets/img/[name].[contenthash][ext]' }],
            options: {
                concurrency: 100,
            },
        }),
        new HtmlWebpackPlugin({
            template: './src/index.html',
            templateParameters: (compilation, assets, assetTags, options) => {
                console.log('compilation.assets: ', compilation.assets);
                return {
                    compilation,
                    webpackConfig: compilation.options,
                    htmlWebpackPlugin: {
                        tags: assetTags,
                        files: assets,
                        options,
                    },
                    loadAsset: (filename) => {
                        const parsedFilepath = path.parse(filename);
                        const assetNames = Object.keys(compilation.assets)
                            .map((k) => compilation.options.output.publicPath + k);
                        for (let i = 0; i < assetNames.length; i++) {
                            const assetName = assetNames[i];
                            const parsedAssetPath = path.parse(assetName);
                            const parsedAssetNameWithoutContentHash = parsedAssetPath.name.split('.')[0];
                            if (
                                parsedAssetNameWithoutContentHash === parsedFilepath.name &&
                                parsedAssetPath.ext === parsedFilepath.ext
                            ) {
                                return assetName;
                            }
                        }
                    },
                };
            },
        }),
    ],
};

We also use templateParameters option of html-webpack-plugin to expose the loadAsset function to HTML template.

Build logs:

> webpack

compilation.assets:  {
  'js/bundle.7c72cbf332a585c75092.js': RawSource {
    _valueIsBuffer: false,
    _value: 'console.log("webpack mini template");',
    _valueAsBuffer: undefined,
    _valueAsString: 'console.log("webpack mini template");'
  },
  'assets/img/icons8-apple-50.be319f9e64e51eba6ea0.png': RawSource {
    _valueIsBuffer: true,
    _value: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 00 32 00 00 00 32 08 06 00 00 00 1e 3f 88 b1 00 00 00 09 70 48 59 73 00 00 0b 13 00 00 0b 13 01 ... 1836 more bytes>,
    _valueAsBuffer: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 00 32 00 00 00 32 08 06 00 00 00 1e 3f 88 b1 00 00 00 09 70 48 59 73 00 00 0b 13 00 00 0b 13 01 ... 1836 more bytes>,
    _valueAsString: undefined
  },
  'assets/img/icons8-iphone-64.49b2d0705a1347a44580.png': RawSource {
    _valueIsBuffer: true,
    _value: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 00 40 00 00 00 40 08 06 00 00 00 aa 69 71 de 00 00 00 09 70 48 59 73 00 00 0b 13 00 00 0b 13 01 ... 2409 more bytes>,
    _valueAsBuffer: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 00 40 00 00 00 40 08 06 00 00 00 aa 69 71 de 00 00 00 09 70 48 59 73 00 00 0b 13 00 00 0b 13 01 ... 2409 more bytes>,
    _valueAsString: undefined
  }
}
assets by status 4.28 KiB [cached] 3 assets
asset index.html 353 bytes [compared for emit]
./src/index.js 37 bytes [built] [code generated]
webpack 5.88.2 compiled successfully in 273 ms

As you can see, the key of compilation.assets is the filename of assets.

Output:

dist/index.html:

<!DOCTYPE html>
<html>
    <head>
        <title>Custom insertion example</title>
        <script
            defer="defer"
            src="./js/bundle.acd85cb0428d8156db50.js"
        ></script>
    </head>
    <body>
        <img
            src="./assets/img/icons8-apple-50.be319f9e64e51eba6ea0.png"
            alt="icons8-apple-50.png"
        />
        <img
            src="./assets/img/icons8-iphone-64.49b2d0705a1347a44580.png"
            alt="icons8-iphone-64.png"
        />
    </body>
</html>

package.json:

{
    "version": "1.0.0",
    "scripts": {
        "build": "webpack",
        "start": "http-server ./dist"
    },
    "devDependencies": {
        "copy-webpack-plugin": "^11.0.0",
        "html-webpack-plugin": "^5.5.3",
        "http-server": "^14.1.1",
        "webpack": "^5.80.0",
        "webpack-cli": "^5.0.2"
    }
}

After building, run npm start to start a HTTP server to verify:

enter image description here

Lin Du
  • 88,126
  • 95
  • 281
  • 483