Search code examples
webpackcss-loadersass-loaderwebpack-5

How do you selectively inline an SVG in CSS with Webpack 5?


I'm compiling my SCSS to CSS using Webpack 5. I want to selectively inline some of the SVGs. Ideally, I'd like to achieve this either by file name, path, or some query parameter in its URL.

Say I have the following SCSS:

/* src/scss/main.scss */
.logo {
    background-image: url('/images/logo.svg');
}

And this webpack.config.js:

const path = require('path');

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const src_dir = path.resolve(__dirname, "src");

module.exports = {
    entry: {
        "styles": path.resolve(__dirname, "src/scss/main.scss")
    },
    output: {
        clean: true,
        path: path.resolve(__dirname, "dist"),
        publicPath: "",
    },
    module: {
        rules: [ ... ]
    },
    plugins: [
        // Extract the generated CSS from the JS-CSS into actual CSS.
        new MiniCssExtractPlugin({
            filename: "[name].[contenthash].css"
        }),
    ]
}

This is my rule compiling SCSS to CSS if it's relevant:

{
    test: /\.scss$/,
    use: [
        // Extract the generated CSS from the JS-CSS into actual CSS.
        {
            loader: MiniCssExtractPlugin.loader,
            options: {
                esModule: false,
            }
        },
        {
            loader: 'css-loader',
        },
        // Compile SCSS to JS-CSS.
        {
            loader: 'sass-loader',
            options: {
                sassOptions: {},
                sourceMap: true
            }
        }
    ]
},

This is my current asset rule:

{
    test: /\.(jpg|png|svg)$/,
    type: "asset/resource",
    generator: {
        // Do not copy the asset to the output directory.
        emit: false,
        // Rewrite asset filesystem path to be relative to the asset public path.
        filename: (pathData) => {
            const file = pathData.module.resource;
            if (file.startsWith(`${src_dir}/`)) {
                return path.relative(src_dir, file);
            }
            throw new Error(`Unexpected asset path: ${file}`);
        },
        // The public path (prefix) for assets.
        publicPath: "/a/",
    }
}

How do I configure a new rule to inline only logo.svg? From what I gather, I need something along the lines of:

{
    test: /\.svg$/,
    type: "asset/inline",
    // ... some unknown webpack-fu.
},

But what do I do from here? The webpack documentation is sorely lacking in any useful examples for CSS. This isn't some JavaScript application where every asset is imported using require(). I'm only using SCSS/CSS.


Solution

  • I was close. What you can do is add ?inline as a query parameter to the SVG URL:

    /* src/scss/main.scss */
    .logo {
        background-image: url('/images/logo.svg?inline');
    }
    

    Add /inline/ as the resourceQuery on the inline rule:

    {
        test: /\.svg$/,
        type: "asset/inline",
        // Inline assets with the "inline" query parameter.
        resourceQuery: /inline/,
    },
    

    And wrap the asset rules with oneOf to prioritize inlining marked SVGs before the general resource rule:

    module.exports = {
        // ...
        module: {
            rules: [
                // ...
                {oneOf: [
                    {
                        test: /\.svg$/,
                        type: "asset/inline",
                        // ...
                    },
                    {
                        test: /\.(jpg|png|svg)$/,
                        type: "asset/resource",
                        // ...
                    },
                ]},
                // ...
            ]
        },
        // ...
    };