I’ve been migrating Angular application from Angular 11/Webpack 3/Node14 to Angular 17/Webpack 5/Node20.
I got stuck with the problem that image references in Angular component do not get replaced to the references to bundled images (as it was before migration)
Thus, for example status-processed-icon16.svg is still injected in app.bunlde.js as it was in the component:
["src","../../../img/status-processed-icon-16.svg","width","16px","height","16px"]
but it must be injected as assets/status-processed-icon-169cf5bb34d4a87e23bf4f.svg like Webpack3 successfully did before migration. The .svg resource itself as you can see is bundled correctly to /assets folder with the hash. Moreover when the same icon is referenced from CSS, it is displayed correctly (probably because it gets processed by css-loader which is able to do this transformation without problems)
But when it referenced as
<img src="../../../img/status-processed-icon-16.svg" width="16px" height="16px" />
of Angular component, ‘src’ is still raw and therefore leads to 404(not found) error.
Having run dev server on the pre-migated instance (which works correctly) I could see that this file is referenced with a comment like HTML_loader__import__4 , then require() of the bundled asset goes. It led me to conclusion that HTML loader is the culprit.
I tried for several days many approaches found on SO – use different loaders with different options, use or do not use asset resources (recommended for Webpack5) – with no avail. The task is very simple – to replace raw references in app.bundle.js files to their analogs from /asset folder, but how to get it done – I am desperate to come up with any solution in terms of Webpack 5.
Of course the issue occurs for all images referenced in a similar way.
Webpack configs are below, any help is appreciated.
webpack.base.config.js
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
module.exports = {
resolve: {
extensions: ['.ts', '.js'],
alias: {
jquery: 'jquery/src/jquery'
}
},
module: {
rules: [
{
test: /.(scss)$/,
use: ['to-string-loader', 'css-loader', 'sass-loader']
},
{
test: /.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
type: 'asset/resource',
generator: {
filename: 'assets/[name][hash][ext][query]'
}
},
/*{
test: /.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file-loader',
options: {
name: 'assets/[name][hash][ext][query]',
}
}, -- if I enable this instead of asset/resource above I get weird files containing javascript 'requires' but having .svg or other image extensions, options/name is ignored, root folder anyway */
/*{
test: /.html$/,
type: 'asset/source',
exclude: /index.html/,
}
{
test: /.html$/,
loader: 'html-loader',
options: {
minimize: false,
esModule: false // read on SO that it helps to process images - really it affects nothing
}
}, -- this block does nothing in fact, I can switch on html-loader or asset/resource or disable both - no effect*/
]
},
plugins: [
new webpack.ContextReplacementPlugin(
/angular(\|/)core(\|/)@angular/,
'./ClientApp',
{}
),
new webpack.IgnorePlugin(
{ resourceRegExp: /^./locale$/,
contextRegExp: /moment$/ }),
new HtmlWebpackPlugin({template: 'index.html'}),
new FaviconsWebpackPlugin({outputPath: 'favicons', prefix: 'favicons'})
]
}
webpack.prod.config.js
const {merge} = require('webpack-merge');
const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const baseConfig = require('./webpack.base.config.js');
const publicPath = process.env.PUBLIC_PATH || '/';
const publicDir = 'wwwroot';
const outputDir = path.resolve(__dirname, publicDir);
const AngularWebpackPlugin = require('@ngtools/webpack').AngularWebpackPlugin;
const TerserPlugin = require('terser-webpack-plugin');
//const tsi = require('tsimportlib');
//const linkerPlugin = tsi.dynamicImport('@angular/compiler-cli/linker/babel', module);
console.log('diagn:process.env.PUBLIC_PATH = ', process.env.PUBLIC_PATH);
module.exports = merge(baseConfig, {
mode: 'production',
context: __dirname + '/ClientApp',
entry: {
app: './index-aot.ts'
},
output: {
filename: 'static/js/[name].[chunkhash:8].js',
chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
assetModuleFilename: "[name][ext]",
path: outputDir,
publicPath
},
module: {
rules: [
{
test: /(?:.ngfactory.js|.ngstyle.js|.ts)$/,
use: '@ngtools/webpack'
},
{
test: /.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader"
]
},
/*{
test: /.mjs$/,
loader: 'babel-loader',
options: {
compact: false,
plugins: [linkerPlugin.default],
},
resolve: {
fullySpecified: false
}
}*/
]
},
optimization: {
minimizer: [
new OptimizeCSSAssetsPlugin({
cssProcessorPluginOptions: {
preset: ['default', { discardComments: { removeAll: true } }],
}
}),
new TerserPlugin({
terserOptions: { format: { comments: false, }, },
extractComments: false,
})
],
splitChunks: {
cacheGroups: {
vendor: {
chunks: "all",
test: path.resolve(__dirname, "node_modules"),
name: "vendor",
enforce: true
}
}
}
},
plugins: [
new MiniCssExtractPlugin({
filename: 'static/css/style[name].[hash:8].css',
chunkFilename: 'static/css/[name].[hash:8].css',
}),
new AngularWebpackPlugin({
tsconfig: './tsconfig-aot.json'
})
]
});
Thanks in advance!