Skip to content

Instantly share code, notes, and snippets.

@stevenkaspar
Last active December 20, 2017 15:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stevenkaspar/7f9ebc9845663febde2c000327e46335 to your computer and use it in GitHub Desktop.
Save stevenkaspar/7f9ebc9845663febde2c000327e46335 to your computer and use it in GitHub Desktop.
Gulpfile Atomic CSS Builder

You can use the above 2 files to create dynamically generated atomic CSS files

I only added pug and React support but it would be very easy to add more

Say you have this file structure. You can use the above config to get all CSS classes defined in your app.jsx file and views files that match a key in the config.yaml file and build a CSS file that has only the used classes

.
├── gulpfile.js
├── config.yaml
├── styles
    ├── atomic.css
├── views
    ├── layout.pug
    ├── index.pug
├── src
    ├── js
        ├── app.jsx

You can run it with

CONFIG_PATH=./config.yaml gulp

I have more instructions on a post I did for it

p: padding
pt: padding-top
pr: padding-right
pb: padding-bottom
pl: padding-left
px:
- padding-left
- padding-right
py:
- padding-top
- padding-bottom
m: margin
mt: margin-top
mr: margin-right
mb: margin-bottom
ml: margin-left
mx:
- margin-left
- margin-right
my:
- margin-top
- margin-bottom
bg: background
bgc: background-color
pos: position
d: display
td: text-decoration
ta: text-align
tc: color
jc: justify-content
ai: align-items
fd: flex-direction
f: flex
fs: font-size
fw: font-weight
lh: line-height
bdw: border-width
bdbw: border-bottom-width
bdtw: border-top-width
bdc: border-color
bds: border-style
bdbs: border-bottom-style
bdts: border-top-style
o: overflow
w: width
ws: white-space
c: cursor
actions:
hov: hover
breakpoints:
xs: '480px'
sm: '768px'
md: '1224px'
lg: '1824px'
values:
blue: '#5287de'
blue-dark: '#04213e'
gray-blue: '#5677a0'
gray-lighter: '#f0f3f7'
gray: '#e2e5ea'
gray-darker: '#b2b8c3'
gray-darkest: '#8a92a0'
100per: '100%'
sources:
- views/**/*
- src/js/**/*
dist: '/styles/atomic.css'
'use strict'
const gulp = require('gulp')
const fs = require('fs')
const yaml = require('js-yaml')
const glob = require('glob')
const path = require('path')
const JSX_CLASS_RX = /className=({?'|{?"|{?`)(-?[_a-zA-Z]+[_a-zA-Z0-9-]*( +)?)+('}?|"}?|`}?)+/g
const PUG_CLASS_RX = /(\.-?[_a-zA-Z]+[_a-zA-Z0-9-]*)+/g
const PARSER_RX = /([a-z]+_)/g
const getJSXClasses = jsxString => {
let matches = jsxString.match(JSX_CLASS_RX)
if(matches === null){
return []
}
let classSplit = []
for(let match of matches){
let classString = match.replace(/(className={?'?"?`?|'?"?`?}?)/g, '')
classSplit = classSplit.concat(classString.split(/ +/g))
}
return classSplit
}
const getPugClasses = pugString => {
let matches = pugString.match(PUG_CLASS_RX)
if(matches === null){
return []
}
let classes = []
for (let match of matches){
classes = classes.concat(match.split(/\./g))
}
return classes
}
const processClassNames = (classArray, config) => {
let cssString = ''
let seenClassNames = {}
for(let className of classArray){
if(typeof seenClassNames[className] !== 'undefined'){
continue
}
seenClassNames[className] = 1
let matches = className.match(PARSER_RX)
if (matches === null) {
continue
}
let rule = matches[0].replace(/_/, '')
let breakpoint = null, action = null
if (matches.length > 1){
for(let match of matches.slice(1)){
match = match.replace(/_/, '')
if (typeof config.breakpoints[match] !== 'undefined') {
breakpoint = match
}
else if (typeof config.actions[match] !== 'undefined') {
action = match
}
else {
console.warn(`\nModifier not defined: ${match} from ${className}\n`)
}
}
}
let value = className.replace(PARSER_RX, '')
// check if value is mapped to something
if (typeof config.values[value] !== 'undefined'){
value = config.values[value]
}
if (typeof config[rule] === 'undefined'){
console.warn(`\nRule not defined: ${rule} from ${className}\n`)
}
else {
// if rule is in config, build something like ".p_1px{padding: 1px}"
let cssRuleString = `.${className}`
if (action !== null) {
let actionValue = config.actions[action]
if(actionValue !== undefined){
cssRuleString += `:${actionValue}`
}
else {
console.warn(`\nAction not defined: ${action} from ${className}\n`)
}
}
cssRuleString += `{`
if (Array.isArray(config[rule])) {
for (let r of config[rule]) {
cssRuleString += `${r}: ${value};`
}
}
else {
cssRuleString += `${config[rule]}: ${value}`
}
cssRuleString += `}`
if (breakpoint !== null) {
let breakpointValue = config.breakpoints[breakpoint]
if(breakpointValue !== undefined){
cssString += `@media screen and (min-width: ${breakpointValue}) {\n ${cssRuleString}}\n`
}
else {
console.warn(`\nBreakpoint not defined: ${breakpoint} from ${className}\n`)
}
}
else {
cssString += cssRuleString
}
}
}
return cssString
}
const getClassesFromFiles = fileNames => {
let classArray = []
let missingParsers = {}
for (let fileName of fileNames){
let content = fs.readFileSync(`${__dirname}/${fileName}`, 'utf8')
let extension = path.extname(fileName)
switch(extension){
case '.jsx':
classArray = classArray.concat(getJSXClasses(content))
break
case '.pug':
classArray = classArray.concat(getPugClasses(content))
break
default:
missingParsers[extension] = 1
break;
}
}
console.log(`\nMissing Atomic CSS Parsers for ${Object.keys(missingParsers).join(', ')}`)
return classArray
}
const main = done => {
let config = yaml.safeLoad(fs.readFileSync(process.env.CONFIG_PATH, 'utf8'))
try {
let allFiles = []
for (let source of config.sources) {
let files = glob.sync(source, {nodir: true})
allFiles = allFiles.concat(files)
}
let classArray = getClassesFromFiles(allFiles)
let cssString = processClassNames(classArray, config)
fs.writeFileSync(`${__dirname}${config.dist}`, cssString, 'utf8')
}
catch(e){
console.log(e)
}
done()
}
gulp.task('default', main)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment