Search code examples
javascripttypescriptsvgwebpackwebpack-config

SVG Icons don't show after compiling / bundling with Webpack


This is my first time building a web-application, and I'm in my final steps. I'm trying to bundle the whole project with webpack, and everything seems to work, except for my SVG-Icons.

I think the problem is how webpack references the svg's in the compiled index.html.

The original index.html references the svg's like this:

<svg role="img">
   <use href="images/icons.svg#settings-icon"></use>
</svg>

The compiled index.html references the svg's like this:

<svg role="img" style="margin-bottom:-3px">
   <use href="aaf2f945f2b181c45647.svg#settings-icon"></use>
</svg>

This is my dist/ folder after compiling:

dist/
├── images/
│   └── icons.svg
├── aaf2f945f2b181c45647.svg
├── bundle.js
└── index.html

aaf2f945f2b181c45647.svg contains this: export default "images/icons.svg";

icons.svg contains all my svg's as it should.

When I replace all "aaf2f945f2b181c45647.svg" with "images/icons.svg" in the compiled index.html, everything works fine.

The problem is that webpack seems to be trying to navigate to icons.svg via aaf2f945f2b181c45647.svg. Why does it do that? Can I somehow tell it to directly write "images/icons.svg" into the html file?

This is my webpack.config.cjs:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
    entry: './frontend/src/scripts/index.ts', // Entry point of your application
    output: {
        filename: 'bundle.js', // Output bundle file name
        path: path.resolve(__dirname, 'frontend/dist'), // Output directory
        clean: true,
    },
    module: {
        rules: [
            {
                test: /\.ts$/,
                use: 'ts-loader',
                exclude: /node_modules/,
            },
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader'],
            },
            {
                test: /\.svg$/,
                use: {
                    loader: 'file-loader',
                    options: {
                        name: '[name].[ext]',
                        outputPath: 'images/',
                        publicPath: 'images/',
                    },
                },
            },
            {
                test: /\.html$/,
                use: 'html-loader',
            },
        ],
    },
    resolve: {
        extensions: ['.ts', '.js'],
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: './frontend/public/index.html', // Template HTML file
            filename: 'index.html', // Output HTML file name
        }),
    ],
};

This is my complete folder structure:

dist/
├── images/
│   └── icons.svg
├── aaf2f945f2b181c45647.svg
├── bundle.js
└── index.html

public/
├── images/
│   └── icons.svg
└── index.html

src/
├── scripts/
│   └── ...
└── styles/
    └── ...

I already tried using url-loader, svg-url-loader and svg-sprite-loader instead. None of them was able to display my svg's.


Solution

  • Why not use html-loader for handling HTML files to ensure that use tags are processed correctly and also update file-loader settings to handle SVGs correctly within HTML as below:

    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    
    module.exports = {
        entry: './frontend/src/scripts/index.ts',
        output: {
            filename: 'bundle.js', 
            path: path.resolve(__dirname, 'frontend/dist'), 
            clean: true,
        },
        module: {
            rules: [
                {
                    test: /\.ts$/,
                    use: 'ts-loader',
                    exclude: /node_modules/,
                },
                {
                    test: /\.css$/,
                    use: ['style-loader', 'css-loader'],
                },
                {
                    test: /\.svg$/,
                    type: 'asset/resource',
                    generator: {
                    filename: 'images/[name][ext]', 
           },
                },
                {
                    test: /\.html$/,
                    use: [
                        {
                            loader: 'html-loader',
                            options: {
                                sources: {
                                    list: [
                                        '...',
                                        {
                                            tag: 'use',
                                            attribute: 'href',
                                            type: 'src',
                                        },
                                    ],
                                },
                            },
                        },
                    ],
                },
            ],
        },
        resolve: {
            extensions: ['.ts', '.js'],
        },
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './frontend/public/index.html',
                filename: 'index.html', // Output HTML file name
            }),
         ],
    };