Bootstrap: Custom scss build fails on bootstrap function

Created on 2 Jun 2020  Β·  8Comments  Β·  Source: twbs/bootstrap

Thanks for the awesome work.
I created (like half a year ago) a theme for an application. The theme css is compiled using scss (node-sass). The content of the main scss file is like this:

// based on https://getbootstrap.com/docs/4.0/getting-started/theming/#importing
@import "~bootstrap/scss/_functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";
// setting variables like $theme-colors, etc
@import "~bootstrap/scss/bootstrap";

@import "customtheme"
// ...

So after I tried to recompile it recently, I got the following error message:

ERROR in ./theme.scss
Module build failed (from ./node_modules/mini-css-extract-plugin/dist/loader.js):
ModuleBuildError: Module build failed (from ./node_modules/sass-loader/dist/cjs.js):

  @if str-index($string, "data:image/svg+xml") {
     ^
      Argument `$string` of `str-index($string, $substring)` must be a string
      in /Users/d4rkmindz/code/node_modules/bootstrap/scss/_functions.scss (line 55, column 7)
    at runLoaders (/Users/d4rkmindz/code/node_modules/webpack/lib/NormalModule.js:316:20)
    at /Users/d4rkmindz/code/node_modules/loader-runner/lib/LoaderRunner.js:367:11
    at /Users/d4rkmindz/code/node_modules/loader-runner/lib/LoaderRunner.js:233:18
    at context.callback (/Users/d4rkmindz/code/node_modules/loader-runner/lib/LoaderRunner.js:111:13)
    at Object.render [as callback] (/Users/d4rkmindz/code/node_modules/sass-loader/dist/index.js:89:7)
    at Object.done [as callback] (/Users/d4rkmindz/code/node_modules/neo-async/async.js:8067:18)
    at options.error (/Users/d4rkmindz/code/node_modules/node-sass/lib/index.js:294:32)
 @ ./styles/theme.test.js 1:0-32

My webpack file looks like this:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  entry: {
    theme: path.join(__dirname, 'styles', 'theme.js'), // this file basically just imports the theme.scss file
  },
  module: {
    rules: [
      {
        test: /\.(scss|css)$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'sass-loader',
        ],
      },
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        use: 'babel-loader',
      },
    ],
  },
  optimization: {
    minimizer: [
      new OptimizeCSSAssetsPlugin({}),
      new TerserPlugin({
        parallel: true,
        terserOptions: {
          ecma: 6,
        },
      }),
    ],
    splitChunks: {
      cacheGroups: {
        common: {
          test: /node_modules/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },
  output: {
    // output stuff into code/webroot/ folder
    filename: path.join('js', '[name].min.js'),
    chunkFilename: path.join('js', '[name].min.js'),
    path: path.resolve(__dirname, 'webroot'),
  },
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
      'window.jQuery': 'jquery',
      Popper: ['popper.js', 'default'],
      Alert: 'exports-loader?Alert!bootstrap/js/dist/alert',
      Button: 'exports-loader?Button!bootstrap/js/dist/button',
      Carousel: 'exports-loader?Carousel!bootstrap/js/dist/carousel',
      Collapse: 'exports-loader?Collapse!bootstrap/js/dist/collapse',
      Dropdown: 'exports-loader?Dropdown!bootstrap/js/dist/dropdown',
      Modal: 'exports-loader?Modal!bootstrap/js/dist/modal',
      Popover: 'exports-loader?Popover!bootstrap/js/dist/popover',
      Scrollspy: 'exports-loader?Scrollspy!bootstrap/js/dist/scrollspy',
      Tab: 'exports-loader?Tab!bootstrap/js/dist/tab',
      Tooltip: 'exports-loader?Tooltip!bootstrap/js/dist/tooltip',
      Util: 'exports-loader?Util!bootstrap/js/dist/util',
    }),
    new MiniCssExtractPlugin({
      filename: path.join('css', '[name].min.css'),
    }),
  ],
};

So after having a look at the bootstrap code, i realized, that the method escape-svg (L54, _functions.scss)
uses the $escaped-characters variable which is not set by the moment of import (the recommended order is functions, then variables, where it is defined).

It might be a problem, that I always updated the dependencies, but missed out to recompile the theme. The last compilation might have been at version 4.2 or less (the escape-svg method didn't exist back then).

Now my question is: How do I get this to work again?

Used versions

  • macOS 10.15.4
  • Browser does not matter
  • Bootstrap 4.5
awaiting-reply

Most helpful comment

So I guess I was right: you're overriding $navbar-dark-toggler-icon-bg with a color, where Bootstrap uses a string that's passed through escape-svg() to be used as background-image.

This cannot work. I think if you drop this variable, everything should be fine.

@mdo @MartijnCuppens I understand this is quite confusing, we should probably find a way to mention this? Not sure though, thniking out loud.

All 8 comments

@twbs/css-review

You're importing the whole Bootstrap after importing functions, variables and mixins: they're imported twice.

Please read carefully our "Importing" documentation and try again, with either importing the whole Bootstrap or only part of itβ€”and share your result if that doesn't fix your issue :)

Closing per previous comment.

Please read carefully our "Importing" documentation and try again, with either importing the whole Bootstrap or only part of itβ€”and share your result if that doesn't fix your issue :)

I followed the documentation and removed all imports. Now the file looks (original file) something similar like this

// Your variable overrides
$body-bg: #000;
$body-color: #111;

// Bootstrap and its default variables
@import "../node_modules/bootstrap/scss/bootstrap";

I still receive the error as described above. Somehow, if I rearrange the code like below, it compiles, but (obviously) no changes are applied

// Bootstrap and its default variables
@import "../node_modules/bootstrap/scss/bootstrap";

// Your variable overrides
$body-bg: #000;
$body-color: #111;

@D4rkMindz Could you please share the exact variables you're trying to overwrite?

The error message is pretty clear, you're trying to replace a variable that's called in our escape-svg() function with something that the function cannot handle (it requires string, as the message says). Maybe you're dropping or nullifying a required variable, or using a color where Bootstrap expects a string.

@ffoodd I created a gist with all the used variables here.
I added a @debug "string value contains: #{$string}"; statement at scss/_functions.scss:55 and placed the output in the gist too.
The last debug output line contains the same value as set in the $body-bg variable. If I rename this variable, absolutely nothing changes.

I also checked all my scss files for following variables to be used/written somewhere else than in the bootstrap file.

Method escape-svg($string)
| Variable | Read | Write |
| --------- | ----- | ------ |
| $string | ❌ | ❌ |
| $char | ❌ | ❌ |
| $encoded | ❌ | ❌ |
| $escaped-characters | ❌ | ❌ |

Method str-replace($string, $search, $replace: "") used in escape-svg($string)
| Variable | Read | Write |
| --------- | ----- | ------ |
| $string | ❌ | ❌ |
| $search | ❌ | ❌ |
| $replace | ❌ | ❌ |
| $index | ❌ | ❌ |

So I guess I was right: you're overriding $navbar-dark-toggler-icon-bg with a color, where Bootstrap uses a string that's passed through escape-svg() to be used as background-image.

This cannot work. I think if you drop this variable, everything should be fine.

@mdo @MartijnCuppens I understand this is quite confusing, we should probably find a way to mention this? Not sure though, thniking out loud.

@ffoodd Thank you so much. This actually resolved my problem.

@mdo @MartijnCuppens The variables are pretty good named and really clear what they're standing for. But it still might be a good thing to document it. I mean most other framework have an (automatically generated) API documentation for all its methods/variables.
This might help. It's also better to have a documentation instead of just having to look through the code and "manually debug" it.

Still great work though and thank you for that.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

IamManchanda picture IamManchanda  Β·  3Comments

iklementiev picture iklementiev  Β·  3Comments

devfrey picture devfrey  Β·  3Comments

bellwood picture bellwood  Β·  3Comments

ghost picture ghost  Β·  3Comments