judocss
Version:
The functional CSS toolkit designed for minimal effort and maximum efficiency.
177 lines (130 loc) • 3.88 kB
JavaScript
(function() {
const glob = require('glob-fs')({ gitignore: true });
const htmlparser = require("htmlparser2")
const fs = require('fs')
const dir = require('node-dir');
const ruleset = require('./judo.ruleset.js')
const classRegex = require('./judo.classRegex.js')
const units = "%,cm,em,ex,in,mm,pc,pt,px,vh,vw,vmin".split(",")
const parts = (value, fn) => {
let i = units.length;
let index = -1
let unit = ""
while(i--) {
unit = units[i]
index = value.indexOf(unit)
if(index > -1) {
let n = value.substr(0, value.length - unit.length)
if(isNaN(n)) { break }
return fn(n, unit)
}
}
throw "Baseline value must consist of a number and a unit. E.g., 7px"
}
const base = (baseline) => {
return parts(baseline, (n, unit) => {
return (x) => {
return isNaN(x) ? x : (x * n) + unit
}
})
}
function trim(key) {
var i = key.indexOf('$')
return i === -1 ? key : key.substr(0, i)
}
function createMap(config) {
let res = {}
let i
let key
for(i in config){
key = trim(i)
res[key] = res[key] || []
res[key].push({
regex: classRegex(i),
value: config[i]
})
}
for(i in res){
res[i].sort((a, b) => a.value.length - b.value.length)
}
return res
}
//=====================
function getStylesGroupedByQuery(classes, classMap){
let cache = {}
Object.keys(classes).forEach(function(className){
ruleset(classMap, className, function(value, query){
cache[query] = cache[query] || {}
cache[query][className] = cache[query][className] || value
})
})
return cache
}
//=====================
const queryBlockOpen = {
"all": () => "",
"small": (breakpoint) => `@media screen and (min-width: ${breakpoint.small}) {`,
"medium": (breakpoint) => `@media screen and (min-width: ${breakpoint.medium}) {`,
"large": (breakpoint) => `@media screen and (min-width: ${breakpoint.large}) {`
}
const queryBlockClose = (key) => key === "all" ? "" : "}"
function expandMediaQueries(o, breakpoint) {
let q
let res = []
for(q in o){
//start query
res.push(queryBlockOpen[q](breakpoint))
let items = o[q]
let i
for(i in items){
res.push(items[i])
}
//end query
res.push(queryBlockClose(q))
}
return res.join(' ')
}
//=====================
function onComplete(classes, classMap, config, callback) {
const stylesGroupedByQuery = getStylesGroupedByQuery(classes, classMap)
const css = expandMediaQueries(stylesGroupedByQuery, config.breakpoint)
if(config.out){
fs.writeFile(config.out, css, (err) => {
if(err) throw err;
console.log('Saved.')
callback(css)
})
}else {
callback(css)
}
}
let classes = {}
const parser = new htmlparser.Parser({
onopentag: function(name, attrs){
if(attrs.class){
attrs.class.split(' ').forEach(function(name){
classes[name] = true;
})
}
}
}, {decodeEntities: true})
module.exports = function(config, callback) {
classes = {}
callback = callback || function(){}
//load the path relative to the cwd or use the default
const mapPath = config.map ? process.cwd() + '/' + config.map : "./judo.map.js"
const confMap = require(mapPath);
const baselineFn = base(config.baseline)
const classMap = createMap(confMap(baselineFn))
glob.readdirStream(config.in)
.on('data', function (file) {
const content = fs.readFileSync(file.path)
parser.write(content)
})
.on('error', console.error)
.on('end', () => {
onComplete(classes, classMap, config, callback)
});
;
}
})()