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 >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
CodePudding user response:
You don't need WebpackManifestPlugin
nor CopyWebpackPlugin
, HtmlWebpackPlugin
with .ejs
template is fully capable to complete the task on its own.
You just need to use the correct template syntax:
<img src="<%= require('./assets/img/natura.jpg') %>" alt="Natura.jpg" />
<img src="<%= require('./assets/img/natale.jpg') %>" alt="Natale.jpg" />
Looks like you need some explanation besides a solution. So here we go.
The idea about webpack is that, it's a bundler. It finds assets by following all the require/import
hints in your code, starting from the "entry points" that you supply. This process is very akin to how web crawler works.
How the image got included even without CopyWebpackPlugin
? It's because <%= require('./assets/img/natura.jpg') %>
is a legit require
hint, webpack sees that and just includes the image into its compilation process.
Next step normally involves loader, but in webpack v5 they introduce asset-modules, I see you're using it in your webpack config. Basically it's a built-in loader for static assets.
output: {
assetModuleFilename: '[path][name].[contenthash][ext]',
// ...
module: {
rules:[{
test: /\.(png|jpe?g|webp|avif|gif|svg)$/,
type: 'asset',
To answer your questions in comment:
I can't figure how is automatically added the hash to images.
Well, it's because you have specified assetModuleFilename
to include [contenthash]
. Basically asset modules has already done the job of CopyWebpackPlugin
for you.
how can I optimize images?
You can chain the ImageMinimizerPlugin.loader
with asset modules. Please read the doc for more details. The idea be like:
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/i,
type: "asset",
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
use: [
{
loader: ImageMinimizerPlugin.loader,
And finally, you might wonder when will CopyWebpackPlugin
be useful?
Remember the web-crawler-like behavior I mentioned above? It also implies, if some assets is not reachable by following require/import
hints, then webpack is completely unaware of them, and they won't be included in /dist
. This is where CopyWebpackPlugin
comes to rescue by simply copying these forgotten assets over to the other side.
Now you know what's what, feel free to use CopyWebpackPlugin
where you see fit.
Update
So I looked into ManifestPlugin's source code. And I found a hack:
- webpack.production.config.js
//