Last active
December 4, 2017 14:46
-
-
Save avanderhoorn/1fed982b1bfe5ac016cc2fdcc9d2faeb to your computer and use it in GitHub Desktop.
CSS Modules Themes using CSS Variables
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
:root { | |
--alt-color: #111; | |
--main-color: #000; | |
--status-bar-background-color: #222; | |
--status-bar-font-color: #333; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
:root { | |
--alt-color: #ccc; | |
--main-color: #fff; | |
--status-bar-background-color: #bbb; | |
--status-bar-font-color: #aaa; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.bar { | |
display: flex; | |
flex-direction: row; | |
justify-content: space-between; | |
color: --alt-color; | |
background-color: --main-color; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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