Skip to content

Instantly share code, notes, and snippets.

@avanderhoorn
Last active December 4, 2017 14:46
Show Gist options
  • Save avanderhoorn/1fed982b1bfe5ac016cc2fdcc9d2faeb to your computer and use it in GitHub Desktop.
Save avanderhoorn/1fed982b1bfe5ac016cc2fdcc9d2faeb to your computer and use it in GitHub Desktop.
CSS Modules Themes using CSS Variables
:root {
--alt-color: #111;
--main-color: #000;
--status-bar-background-color: #222;
--status-bar-font-color: #333;
}
:root {
--alt-color: #ccc;
--main-color: #fff;
--status-bar-background-color: #bbb;
--status-bar-font-color: #aaa;
}
.bar {
display: flex;
flex-direction: row;
justify-content: space-between;
color: --alt-color;
background-color: --main-color;
}
var fs = require('fs');
var postcss = require('postcss');
var extractionThemeTargetsPlugin = postcss.plugin('postcss-theme-target-extraction', () => {
return (css, result) => {
var targets = {};
css.walkRules(rule => {
for (var i = 0; i < rule.nodes.length; i++) {
var decl = rule.nodes[i];
if (decl.prop && decl.prop.indexOf('//') == -1 && decl.value && decl.value.indexOf('--') > -1) {
var item = targets[rule.selector] || (targets[rule.selector] = {});
item[decl.prop] = decl.value;
decl.remove();
i--;
}
}
});
result.targets = targets;
};
});
var extractionThemeVariablesPlugin = postcss.plugin('postcss-theme-variables-extraction', () => {
return (css, result) => {
var variables = {};
css.walkRules(rule => {
if (rule.selector == ':root') {
for (var i = 0; i < rule.nodes.length; i++) {
var decl = rule.nodes[i];
if (decl.prop.indexOf('--') == 0) {
variables[decl.prop] = decl.value;
}
}
}
});
result.variables = variables;
};
});
function VariableBasedThemesPlugin(options) {
this.options = options || {};
this.cache = null;
}
VariableBasedThemesPlugin.prototype.apply = function apply(compiler) {
var that = this;
function prefixSelector(selector, prefix) {
// this is rather nieve but should be fine for the moment
var parts = selector.split(',');
for (var i = 0; i < parts.length; i++) {
parts[i] = '.' + prefix + ' ' + parts[i].trim();
}
return parts.join(', ');
}
function generateTheme(targets, variables, theme) {
var result = '';
// run through each targeted selector and replace each styles var with the actual value
for (var selector in targets) {
var resultInner = '';
var selectorData = targets[selector];
for (var prop in selectorData) {
resultInner += prop + ': ' + variables[selectorData[prop]] + '; ';
}
// update the scope of the selector with the theme, currently the logic here
selector = prefixSelector(selector, theme);
result += selector + ' {' + resultInner + '} ';
}
return result;
}
function process(targetsSource, done) {
var promiseChain = [];
// processes source to strip variable usage and identify targets
var targetsPromise = postcss([ extractionThemeTargetsPlugin ])
.process(targetsSource)
.then(result => {
return { css: result.css, targets: result.targets };
});
promiseChain.push(targetsPromise);
// process themes to identify variables
for (var themeKey in that.options.themes) {
var theme = that.options.themes[themeKey];
var variableSource = fs.readFileSync(theme).toString();
var themePromise = postcss([ extractionThemeVariablesPlugin ])
.process(variableSource, { themeKey: themeKey })
.then(result => {
return { themeKey: result.opts.themeKey, variables: result.variables };
});
promiseChain.push(themePromise);
}
// once all processing is done generate themes
Promise.all(promiseChain)
.then(results => {
var targetsResult = results[0];
var themeCss = targetsResult.css;
for (var i = 1; i < results.length; i++) {
var themeResult = results[i];
themeCss += generateTheme(targetsResult.targets, themeResult.variables, themeResult.themeKey);
}
that.cache = themeCss;
done();
}).catch(reason => {
console.log(reason);
done();
});
}
compiler.plugin('emit', (compilation, done) => {
var target = that.options.target;
if (compilation.assets[target]) {
var asset = compilation.assets[target];
var origFn = asset.source();
process(origFn, done);
asset.source = () => that.cache;
asset.size = () => that.cache.length;
}
else {
done();
}
});
};
module.exports = VariableBasedThemesPlugin;
var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var VariableBasedThemesPlugin = require('./variable-based-themes-plugin');
module.exports = {
entry: [
'./src/client/index.js'
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
pathinfo: true,
publicPath: '/'
},
resolve: {
extensions: [ '', '.js', '.jsx' ],
},
plugins: [
new ExtractTextPlugin('[name].css'),
new VariableBasedThemesPlugin({ target: 'main.css', themes: { 'dark': require.resolve('./src/client/shell/dark.tcss'), 'light': require.resolve('./src/client/shell/light.tcss') } })
],
module: {
loaders: [
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style', 'css-loader?sourceMap&importLoaders=1') },
{ test: /\.scss$/, loader: ExtractTextPlugin.extract('style', 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', 'sass') },
]
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment