How do I reference the hashed image (in the html page in the dist folder) after it has been created with copy-webpack-plugin? I tried to resolve this problem in this way.
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.
In order to resolve this problem I used WebpackManifestPlugin
to generate a manifest.json
that map my images and corresponding webpack output images (with hash) in a object like this:
{
"assets/img/natura.jpg": "./assets/img/natura.e1b203dd72abf2858773.jpg",
"assets/img/natale.jpg": "./assets/img/natale.5955e3731fd0538bb5ec.jpg",
"assets/img/logo-angular.svg": "./assets/img/logo-angular.e7d82ae6d37ff090ba95.svg",
"assets/img/manifest.json": "./assets/img/manifest.1473edc04cb44efe5ce6.json"
}
Later I have generated the manifest.json
I can read this file:
productsJSON = require('./assets/img/manifest.json');
and finally I can pass productsJson to my index.ejs in this way:
new HtmlWebpackPlugin({
inject: false,
template: "./index.ejs", //Puoi mettere anche un file html
templateParameters: {
'myJSON': productsJSON
},
minify:true
})
and in the index.ejs
I can do:
<img
src="<%= (process.env.NODE_ENV === 'production') ? myJSON['assets/img/natura.jpg'] : 'assets/img/natura.jpg' %>"
alt="Natura.jpg"
/>
<img
src="<%= (process.env.NODE_ENV === 'production') ? myJSON['assets/img/natale.jpg'] : 'assets/img/natale.jpg' %>"
alt="Natale.jpg"
/>
These are the whole files above: 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="<%= (process.env.NODE_ENV === 'production') ? myJSON['assets/img/natura.jpg'] : 'assets/img/natura.jpg' %>"
alt="Natura.jpg"
/>
<img
src="<%= (process.env.NODE_ENV === 'production') ? myJSON['assets/img/natale.jpg'] : '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>
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");
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
let productsJSON={};
try {
productsJSON = require('./assets/img/manifest.json');
}
catch (err) {
console.log(`Alla prima esecuzione il file manifest.json non esiste. Fare il build
due volte. Sarebbe meglio gestirlo in un altro modo. Ma non so
temporizzare l'esecuzione dei plugin. Cioè HTMLWebPackPlugin andrebbe
eseguito solo dopo che ManifestPlugin ha creato Manifest.json `)
}
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
/* Il logo Angular è 6, 5 Kbyte.Cambia la soglia per includere
nel bundle js il logo */
}
}
},
/*rules per quando provi ad importare un file css da javascript. Uso due loaders
css-loader legge il contenuto del css e ritorna il contenuto
style-loader prende il css e lo mette nella pagina, mette il css proprio nel
bundle.js Poi vedremo come generarli come file separati
*/
{
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]',
globOptions: {
ignore: [
// Ignore all `txt` files
"**/*.json",
],
}, },
],
options: {
concurrency: 100,
},
}),
new WebpackManifestPlugin({
//Percorso assoluto che serve per dire dove mettere il file manifest.json
fileName: pathM.resolve(__dirname, 'assets/img/manifest.json'),
/* publicPath: '/dist/' QUesto metterebbe /dist/ prima del percorso di sopra*/
filter: (file) => { const regEx = /img/;
return regEx.test(file.name);
},/*map: (file) => {
if ('production' === env.NODE_ENV) {
// Remove hash in manifest key
file.name = file.name.replace(/(\.[a-f0-9]{32})(\..*)$/, '$2');
}
return file;
},*/
}),
new MiniCssExtractPlugin({
filename:"css/main.[contenthash].css"
}),
new HtmlWebpackPlugin({
inject: false,
template: "./index.ejs", //Puoi mettere anche un file html
templateParameters: {
'myJSON': productsJSON
},
minify:true
})
/*Nella seguente configurazione di questo plugin eliminiamo tutti i file
e le cartelle e sottocartelle a partire dalla cartella .dist che è quella
specificata in ouput.path
asteriscoasterisco/asterisco vuol dire tutti i file e le sottocartelle
Inoltre specifico di ripulire anche tutti i file e le sottocartelle
dentro la cartella nomeCartella
Nota che devo fornire un percorso assoluto perchè di default parte
da ./dist (impostata in output.path)
*/
//new CleanWebpackPlugin({
// cleanOnceBeforeBuildPatterns: [
// '**/*',
// path.join(process.cwd(),'nomeCartella/**/*')
//]
//})
]
}
I have two problems related.
I have to exec the instruction productsJSON = require('./assets/img/manifest.json');
only when WebpackManifestPlugin
generated manifest.json
And I have to synchronize plugin execution, namely exec HtmlWebpackPlugin
only when the file manifest.json
is already ready
Here the whole project: https://github.com/cuccagna/Webpack28HandleHashWithManifest
How could I do?
I followed the a suggest in an answer. Using the done hook. (but in this way doesn't work because the html-webpack-plugin isn't executed after the hook,so this step is missing)
new WebpackManifestPlugin({
fileName: pathM.resolve(__dirname, 'assets/img/manifest.json'),
filter: (file) => { const regEx = /img/;
return regEx.test(file.name);
},
apply(webpackCompiler){
webpackCompiler.hooks.done.tap('WebpackManifestPlugin', (stats) => {
productsJSON = require('./assets/img/manifest.json');
})}
},
)
Here we are the documentation of manifest-webpack-plugin
https://www.npmjs.com/package/webpack-manifest-plugin/v/5.0.0
and in the hook
section you talk about to syncronize the order of execution of plugins but I don't understand how I can use it in an effective way