7

I am developing a react app, that is now extended by several themes.

These themes are defined in .scss files in my themes folder:

- themes
- - theme-base.scss
- - theme.dark.scss
- - theme.light.scss

they contain basic color schemes with some variables, eg:

[theme-dark.scss]
$color-primary: #100534;
$color-primary-accent: #231454;

All components just use these color variables to theme them.

Now I want webpack to compile and concat my scss code to separate static css files for each theme.

I got this to work by using extractWebpackTextPlugin and some file loaders like this:

module.exports = env => {
  const config = require('./webpack.common')(env);
  const project = env.project;

  const postcss = {
    loader: 'postcss-loader',
    options: postcssConfig(settings.browsers),
  };

  config.watch = true;
  config.devtool = 'eval';

  const themes = ['theme-base'];

  themes.forEach(themeKey => {
    const themeInstance = new ExtractTextPlugin('css/' + themeKey + '.css');
    const themePath = path.resolve(__dirname, '../../react/app/css/_' + themeKey + '.scss');

    config.module.rules.push({
      test: /\.s?css$/,
      exclude: /node_modules/,
      use: themeInstance.extract({
        fallback: 'style-loader',
        use: [
          {
            loader: 'css-loader',
            options: {
              localIdentName: '[hash:base64:5]',
              modules: true,
              importLoaders: 1,
            }
          },
          postcss,
          {
            loader: 'sass-loader',
            options: {
              data: `@import "${themePath}";`,
            }
          }
        ]
      }),
    });

    config.plugins.push(themeInstance);
  });
}

But: I can not add more than one theme to my array themes! As soon as it contains more than one item, the css and sass loaders throw errors while reading the scss files.

Invalid CSS after "...load the styles": expected 1 selector or at-rule, was "var content = requi"

How would I setup webpack to compile one static css file for each theme?

ManuKaracho
  • 1,180
  • 1
  • 14
  • 32
  • I'm trying to do the same thing. Did you find a solution for it? – Stilltorik Apr 23 '18 at 15:31
  • I've answered a very similar question here: https://stackoverflow.com/questions/46021490/how-to-configure-webpack-to-generate-multiple-css-theme-files/50041851#50041851 – Stilltorik Apr 26 '18 at 11:31

2 Answers2

0

I'm dealing with the similar problem currently, I try to figure it out without any invasive of codes. Then I write a webpack plugin themes-switch to do that. It will extact themes depends on the theme directory. Maybe it can help you.

Sample config:

new ThemesGeneratorPlugin({
      srcDir: 'src',
      themesDir: 'src/assets/themes',
      outputDir: 'static/css',
      defaultStyleName: 'default.less',
      themesLoader: {
        test: /\.(less|css)$/,
        loaders: [
          { loader: require.resolve('css-loader') },
          { loader: require.resolve('less-loader') }
        ]
      }
    })

Now I just use variables to define different colors and dimensions, then webpack would generate themes for me, and the style formats can be less, postcss and sass. Then I can make a realtime-switch in my app. The plugin will clear unused files automatically.

Terence
  • 1
  • 1
0

I had a similar issue and realized that the error occurs because you can't have multiple Webpack rules with the same test: /\.s?css$/ condition. Anything after the first will error out.

To solve this instead create 1 rule for all of the SCSS, and the push the additional ExtractTextPlugin instances to the rules' oneOf array....

/SASS folder

-- app.scss
-- custom/
---- /blue/theme.scss
---- /red/theme.scss
---- /default/theme.scss
---- ...

The ./src/sass/app.scss?theme entry is the default/first theme.

var themeDefault = "default";
module.exports = {
  entry: ['./src/app.js','./src/sass/app.scss?theme'],
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/dist/',
    filename: 'app.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'css-loader'
        ],
      },
      {
        test: /\.scss$/,
        oneOf: [ // use oneOf since we'll need diff rules/handlers for each .scss
          {
            resourceQuery: /theme/, 
            use: extractSass.extract({
                use: [
                  {
                    loader: "css-loader"
                  },
                  {
                    loader: "sass-loader",
                    options: {
                      allChunks: true,
                      sourceMap: true,
                      includePaths: [
                        './src/sass/custom/'+themeDefault
                      ]
                    }
                }]
            })
          }
        ],
      },

      ...
    ]
  },
  plugins: [
    extractSass
  ],
  ...
}

Iterate each theme in the /custom folder.

  // iterate themes to add an entry point, rule and plugin instance for each
  fs.readdirSync('./src/sass/custom').filter(f => fs.statSync("./src/sass/custom/"+f).isDirectory()).forEach(themeKey => {
    const themeInstance = new ExtractTextPlugin('css/' + themeKey + '.css');

    module.exports.entry.push('./src/sass/app.scss?'+themeKey);

    // the SCSS rule is at index 1 in the rules array
    module.exports.module.rules[1].oneOf.push( 
    {
      resourceQuery: new RegExp(themeKey),  
      use: themeInstance.extract({
          use: [
            {
              loader: "css-loader"
            },
            {
              loader: "sass-loader",
              options: {
                allChunks: true,
                includePaths: [
                  './src/sass/custom/'+themeKey
                ]
              }
          }]
      })
    });

    module.exports.plugins.push(themeInstance);

  });

And then in the app.scss, the appropriate theme folder is loaded from the includePaths specified in Webpack sass-loader...

// load theme.scss from included path
@import "theme";
...

Therefore, each time the forEach above hits the next themeKey the @import "theme" in app.scss point to a different sub folder (blue,red,etc) resulting in different CSS files for each "theme".

Carol Skelly
  • 351,302
  • 90
  • 710
  • 624