Get object style parsing working without the insanity

This commit is contained in:
Adam Wathan 2018-03-06 14:08:30 -05:00
parent bc05473b37
commit 10be05a167
2 changed files with 184 additions and 98 deletions

View File

@ -1,6 +1,4 @@
import _ from 'lodash'
import parseObjectStyles from '../src/util/parseObjectStyles'
import cxs from 'cxs'
import postcss from 'postcss'
function css(nodes) {
@ -16,7 +14,7 @@ test('it parses simple single class definitions', () => {
},
})
expect(result).toMatchCss(`
expect(css(result)).toMatchCss(`
.foobar {
background-color: red;
color: white;
@ -38,7 +36,7 @@ test('it parses multiple class definitions', () => {
},
})
expect(result).toMatchCss(`
expect(css(result)).toMatchCss(`
.foo {
background-color: red;
color: white;
@ -66,17 +64,35 @@ test('it parses nested pseudo-selectors', () => {
},
})
expect(result).toMatchCss(`
expect(css(result)).toMatchCss(`
.foo {
background-color: red;
color: white;
padding: 1rem
padding: 1rem;
}
.foo:hover {
background-color: orange
background-color: orange;
}
.foo:focus {
background-color: blue
background-color: blue;
}
`)
})
test('it parses top-level media queries', () => {
const result = parseObjectStyles({
'@media (min-width: 200px)': {
'.foo': {
backgroundColor: 'orange',
},
},
})
expect(css(result)).toMatchCss(`
@media (min-width: 200px) {
.foo {
background-color: orange
}
}
`)
})
@ -93,44 +109,168 @@ test('it parses nested media queries', () => {
},
})
expect(result).toMatchCss(`
expect(css(result)).toMatchCss(`
.foo {
background-color: red;
color: white;
padding: 1rem
padding: 1rem;
}
@media (min-width: 200px) {
.foo {
background-color: orange
background-color: orange;
}
}
`)
})
// test('it parses pseudo-selectors in nested media queries', () => {
// const result = parseObjectStyles({
// '.foo': {
// backgroundColor: 'red',
// color: 'white',
// padding: '1rem',
// ':hover': {
// '@media (min-width: 200px)': {
// backgroundColor: 'orange',
// }
// },
// },
// })
test('it parses pseudo-selectors in nested media queries', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
':hover': {
'@media (min-width: 200px)': {
backgroundColor: 'orange',
},
},
},
})
// expect(result).toMatchCss(`
// .foo {
// background-color: red;
// color: white;
// padding: 1rem
// }
// @media (min-width: 200px) {
// .foo:hover {
// background-color: orange
// }
// }
// `)
// })
expect(css(result)).toMatchCss(`
.foo {
background-color: red;
color: white;
padding: 1rem;
}
@media (min-width: 200px) {
.foo:hover {
background-color: orange;
}
}
`)
})
test('it parses descendant selectors', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'.bar': {
backgroundColor: 'orange',
},
},
})
expect(css(result)).toMatchCss(`
.foo {
background-color: red;
color: white;
padding: 1rem;
}
.foo .bar {
background-color: orange;
}
`)
})
test('it parses nested multi-class selectors', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'&.bar': {
backgroundColor: 'orange',
},
},
})
expect(css(result)).toMatchCss(`
.foo {
background-color: red;
color: white;
padding: 1rem;
}
.foo.bar {
background-color: orange;
}
`)
})
test('it parses nested multi-class selectors in media queries', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'@media (min-width: 200px)': {
'&.bar': {
backgroundColor: 'orange',
},
},
},
})
expect(css(result)).toMatchCss(`
.foo {
background-color: red;
color: white;
padding: 1rem;
}
@media (min-width: 200px) {
.foo.bar {
background-color: orange;
}
}
`)
})
test('it strips empty selectors when nesting', () => {
const result = parseObjectStyles({
'.foo': {
'.bar': {
backgroundColor: 'orange',
},
},
})
expect(css(result)).toMatchCss(`
.foo .bar {
background-color: orange
}
`)
})
test('it can parse an array of styles', () => {
const result = parseObjectStyles([
{
'.foo': {
backgroundColor: 'orange',
},
},
{
'.bar': {
backgroundColor: 'red',
},
},
{
'.foo': {
backgroundColor: 'blue',
},
},
])
expect(css(result)).toMatchCss(`
.foo {
background-color: orange
}
.bar {
background-color: red
}
.foo {
background-color: blue
}
`)
})

View File

@ -1,69 +1,15 @@
import _ from 'lodash'
import postcss from 'postcss'
import postcssNested from 'postcss-nested'
import postcssJs from 'postcss-js'
const hyph = s => s.replace(/[A-Z]|^ms/g, '-$&').toLowerCase()
const mx = (rule, media) => media ? `${media}{${rule}}` : rule
const noAnd = s => s.replace(/&/g, '')
const createDeclaration = (key, value) => hyph(key) + ':' + value
const createRule = ({
className,
child,
media,
declarations
}) => mx(`${className + child}{${declarations.join(';')}}`, media)
const parseRules = (obj, child = '', media) => {
const rules = []
const declarations = []
for (let key in obj) {
const value = obj[key]
if (value === null) continue
if (typeof value === 'object') {
const _media = /^@/.test(key) ? key : null
const _child = _media ? child : child + noAnd(key)
parseRules(value, _child, _media)
.forEach(r => rules.push(r))
continue
}
const dec = createDeclaration(key, value)
declarations.push(dec)
}
rules.unshift({
media,
child,
declarations
})
return rules
}
const parse = (selector, obj) => {
const rules = parseRules(obj)
console.log(rules)
return rules.map(rule => {
const className = selector
const ruleset = createRule(Object.assign(rule, { className }))
return ruleset
})
}
function parseObjectStyles(styles) {
export default function parseObjectStyles(styles) {
if (!Array.isArray(styles)) {
return parseObjectStyles([styles])
}
return _.flatMap(styles, (style) => {
return _.flatMap(style, (declarations, selector) => {
return parse(selector, declarations)
})
}).join('')
return _.flatMap(
styles,
style => postcss([postcssNested]).process(style, { parser: postcssJs }).root.nodes
)
}
export default parseObjectStyles