Skip to content

Instantly share code, notes, and snippets.

@ianmcnally
Last active June 5, 2018 14:56
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ianmcnally/a1727d96cab1f42d8a55ab9afa2fc0fc to your computer and use it in GitHub Desktop.
Save ianmcnally/a1727d96cab1f42d8a55ab9afa2fc0fc to your computer and use it in GitHub Desktop.
Mocks a directory of css files with a css-modules like object
const { parse } = require('css')
const { readdirSync, readFileSync } = require('fs')
const { basename, extname, resolve } = require('path')
const camel = require('lodash.camelcase')
const baseNameForCSSFile = filename => basename(filename, '.css')
const isAClassSelector = selector => /^\./.test(selector)
const removeLeadingDotAndTrailingSelectors = selector =>
/* assumes dash-separated classNames */
selector.replace(/^\.((\w|-)+)(\b.*$)/, '$1')
const getClassNamesForRule = rule => {
let selectors = []
switch (rule.type) {
case 'rule':
selectors = rule.selectors
break
case 'media':
selectors = rule.rules
? rule.rules.reduce((out, subRule) => out.concat(subRule.selectors), [])
: []
break
}
return selectors
.filter(isAClassSelector)
.map(removeLeadingDotAndTrailingSelectors)
}
const getClassNamesFromStylesheet = stylesheetAsString =>
parse(stylesheetAsString).stylesheet.rules.reduce(
(classNames, rule) => classNames.concat(getClassNamesForRule(rule)),
[],
)
const mapClassNamesToFilenameBasedSelector = (classNames, filename) =>
classNames.reduce((map, className) => {
map[camel(className)] = `${filename}__${className}`
return map
}, {})
const getMapOfStylesForFile = (stylesheet, filename) =>
mapClassNamesToFilenameBasedSelector(
getClassNamesFromStylesheet(stylesheet),
filename,
)
const parseStylesheetInFiles = filenames =>
filenames.reduce((map, filename) => {
const stylesheetAsString = readFileSync(filename, 'utf8')
const baseFilename = baseNameForCSSFile(filename)
map[camel(baseFilename)] = getMapOfStylesForFile(
stylesheetAsString,
baseFilename,
)
return map
}, {})
const getCSSFilesInPath = inPath =>
readdirSync(resolve(inPath))
.filter(f => extname(f) === '.css')
.map(f => resolve(inPath, f))
const mockStylesheetsInPath = inPath => {
const cssFilenames = getCSSFilesInPath(inPath)
return parseStylesheetInFiles(cssFilenames)
}
module.exports = mockStylesheetsInPath(
'./path/to/css',
)
@ianmcnally
Copy link
Author

ianmcnally commented Aug 2, 2017

example

given a directory of:

src/
  - font-size.css

and the css:

/* src/font-size.css */
.hello {
  font-size: 100%;
}

running mockStylesheetsInPath('./src')

would build:

{
  fontSize: { 
    hello: 'font-size__hello'
  }
}

@ianmcnally
Copy link
Author

Supports media queries with nested rules, pseudo selectors, dash-separated class names

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment