7

In my application, I have multiple theme styles (you can think of them as different, separate CSS styles files). I would like to start using the CSS modules, but I don't know even how to import my first file.

Lets assume the following (simple) directory structure:

layouts/
    themeA/
        myComponent.css
    themeB/
        myComponent.css
    themeC/
        myComponent.css
components/
    myComponent.js

Depending on the user settings, I would like to pick a different CSS. That's easy to do in the browser (or on the server). But how can I include myComponent.css into myComponent.js?

According to CSS modules, I should import the file I'm using. So import styles from 'theme/myComponent.css. The problem is that I don't have one true theme, but 3 different, parallel themes.

import styles from '' // <<<< from what?

return `<div class=${styles.caption></div>`

Is it even possible to work with multiple layouts/themes when using CSS modules?

3
  • 1
    Do you want do download one of the styles in runtime or you want to bundle all 3 styles (all styles will be always downloaded) and user will simply pick one of them? PS. Do you use ReactJS? Commented Jul 11, 2016 at 17:00
  • I want to have one script bundle (containing only JS) and three separate themes bundles that user can switch between. I'm using React and Webpack. Commented Jul 12, 2016 at 8:52
  • I've edited my answer to split code on 1 chunk with main js code and 3 with styles. Styles are loaded in runtime. Commented Jul 13, 2016 at 9:28

3 Answers 3

4

If you bundle all 3 themes in one file. You can easily pick one of them and render component with it. You must have the same schema for all .css themes, for example:

.wrapper {
  // example content
}

.image {
  // example content
} 

In myComponent.js you will import all themes and assign to object (it will be easier to pick one of them):

import themeA from './themeA.css';
import themeB from './themeB.css';
import themeC from './themeC.css';

const themes = {
  light: themeA,
  dark: themeB,
  pink: themeC
}

Your themes will look something like this:

{ 
    light: {
        wrapper: "themeA---wrapper---2IVWH",
        image: "themeA---image---3omJ7"
    },
    dark: {
        wrapper: "themeB---wrapper---fHfAZ",
        image: "themeB---image---17erf"
    },
    pink: {
        wrapper: "themeC---wrapper---2i9L2",
        image: "themeC---image---3OKIG"
    }
}

Since css-modules are simple object with pointer to new class names you can dynamically pick one of them:

const render = themeName => {
  const theme = themes[themeName];
  return $(`
    <div class="${theme.wrapper}">
      <img 
          class="${theme.image}"
          src="http://exmoorpet.com/wp-content/uploads/2012/08/cat.png" 
      />

      <p>Lorem ipsum </p>
    </div>
  `);
};

I used jQuery only for simplicity of mockups. You can see all working code here: webpackbin


Load styles asynchronously in runtime (edit)

If you use require.ensure (great explanation here) you can download style in runtime. Change myComponent.js to async require:

import $ from 'jquery';

const render = (wrapper, theme) => {
  const template = $(`
    <div class="${theme.wrapper}">
      <img 
          class="${theme.image}"
          src="http://exmoorpet.com/wp-content/uploads/2012/08/cat.png" 
      />

      <p>Lorem ipsum </p> 
    </div>
  `);
  wrapper.html(template);
};

export default (wrapper, themeName) => {
  switch(themeName) { // this will produce 3 chunks with styles
    case 'light':
      require.ensure([], () => {
        render(wrapper, require('./themeA.css'));
      });
      break;
    case 'dark':
      require.ensure([], () => {
        render(wrapper, require('./themeB.css'));
      });
      break;
    case 'pink':
      require.ensure([], () => {
        render(wrapper, require('./themeC.css'));
      });
      break;
  }
};

Webpack will produce this chunks (1 main and 3 with styles):

chunk    {0} main.js (main) 267 kB [rendered]
    [0] ./src/main.js 827 bytes {0} [built]
    [1] ./~/jquery/dist/jquery.js 264 kB {0} [built]
    [2] ./src/select.js 440 bytes {0} [built]
    [3] ./src/myComponent.js 1.82 kB {0} [built]
chunk    {1} 1.1.js 10.2 kB {0} [rendered]
    [4] ./src/themeA.css 1.08 kB {1} [built]
    [5] ./~/css-loader?modules&localIdentName=[name]---[local]---[hash:base64:5]!./src/themeA.css 428 bytes {1} [built]
    [6] ./~/css-loader/lib/css-base.js 1.51 kB {1} {2} {3} [built]
    [7] ./~/style-loader/addStyles.js 7.21 kB {1} {2} {3} [built]
chunk    {2} 2.2.js 10.2 kB {0} [rendered]
    [6] ./~/css-loader/lib/css-base.js 1.51 kB {1} {2} {3} [built]
    [7] ./~/style-loader/addStyles.js 7.21 kB {1} {2} {3} [built]
    [8] ./src/themeB.css 1.08 kB {2} [built]
    [9] ./~/css-loader?modules&localIdentName=[name]---[local]---[hash:base64:5]!./src/themeB.css 429 bytes {2} [built]
chunk    {3} 3.3.js 10.2 kB {0} [rendered]
    [6] ./~/css-loader/lib/css-base.js 1.51 kB {1} {2} {3} [built]
    [7] ./~/style-loader/addStyles.js 7.21 kB {1} {2} {3} [built]
   [10] ./src/themeC.css 1.08 kB {3} [built]
   [11] ./~/css-loader?modules&localIdentName=[name]---[local]---[hash:base64:5]!./src/themeC.css 432 bytes {3} [built]

I will prove that 3 chunks with styles contain your theme styles. For example chunk 1 contains this code inside (I'm showing only important part of it):

/***/ },
/* 5 */
/***/ function(module, exports, __webpack_require__) {

    exports = module.exports = __webpack_require__(6)();
    // imports


    // module
    exports.push([module.id, ".themeA---wrapper---shnYu {\n  background-color: #eee;\n  color: #333;\n  padding: 20px;\n}\n\n.themeA---image---18Mgb {\n  float: left;\n  height: 100px;\n  margin: 20px;\n}\n", ""]);

    // exports
    exports.locals = {
        "wrapper": "themeA---wrapper---shnYu",
        "image": "themeA---image---18Mgb"
    };

How it looks in runtime

styles in runtime example

Here you can check new code it will even show ajax download chunks - you can try in console.

Sign up to request clarification or add additional context in comments.

Comments

1

You can dynamically import modules using commonjs syntax the following way.

config.js:
   export default function(key){
    var themes = ['themea','themeb','themec'];
    return themes[key] || theme[0];
   }

main.js:
   import getConfig from './config.js';
   var styles = require('../css/' + getConfig(2) + '.css');

Make sure to use babel-loader and style-loader/css-loader

I came up with the below code for dynamic file loading

var even = 0;
var themes = ['themea', 'themeb', 'themec'];
var currentConfig = 'themea';
require.context("../css", true, /.css$/)
var cache = Object.assign({}, require.cache);
require('../css/themea.css');
$('#small-button').click(function(){
  setConfig(themes[even]);
  even = (even+1) % 3;
});

function setConfig(config) {
  var modulename = '../css/' + currentConfig + '.css';
  Object.keys(require.cache).forEach(function(cacheKey) {
        Object.keys(cache).indexOf(cacheKey) < 0 ? delete require.cache[cacheKey] : null;
  });
  currentConfig = config;
  cache = Object.assign({}, require.cache);
  require('../css/' + currentConfig + '.css');
}

2 Comments

can you post how user can change theme in runtime?
You can use this to delete the previous require and require a new file.
-4

If you want to do it in JavaScript, you could do a if/else or a switch statement.

var node = document.createElement("link").setAttribute("rel", "stylesheet") setAttribute("type", "text/css");

if (setting === 'A') {
    node.setAttribute("href", "layouts/themeA/myComponent.css");
} else if (setting === 'B') {
    node.setAttribute("href", "layouts/themeB/myComponent.css");
} else if (setting === 'C') {
    node.setAttribute("href", "layouts/themeC/myComponent.css");
}

document.getElementsByTagName('head').appendChild(node);

Or in jQuery :

var href;

if (setting === 'A') {
    href = "layouts/themeA/myComponent.css";
} else if (setting === 'B') {
    href = "layouts/themeB/myComponent.css";
} else if (setting === 'C') {
    href = "layouts/themeC/myComponent.css";
}

$('head').append('<link href="' + href +'" rel="stylesheet" type="text/css" />');

1 Comment

Hi, thanks for answering. But that's not how CSS Modules works. They are build using webpack or gulp or grunt before reaching the client side.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.