Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prefix objects css props #197

Merged
merged 14 commits into from
Jul 29, 2017
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 204 additions & 40 deletions src/ast-object.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,99 @@
import { expandCSSFallbacks, prefixer } from './parser'
import { forEach, reduce } from './utils'

export default class ASTObject {
obj: { [string]: any }
expressions: Array<any>
composesCount: number
t: any
function prefixAst (args, t) {
function isLiteral (value) {
return (
t.isStringLiteral(value) ||
t.isNumericLiteral(value) ||
t.isBooleanLiteral(value)
)
}

constructor (obj, expressions, composesCount, t) {
this.obj = obj
this.expressions = expressions
this.composesCount = composesCount
this.t = t
if (Array.isArray(args)) {
return args.map(element => prefixAst(element, t))
}

toAST () {
const { obj, t } = this
if (t.isObjectExpression(args)) {
let properties = []
args.properties.forEach(property => {
// nested objects
if (t.isObjectExpression(property.value)) {
const key = property.computed
? property.key
: t.isStringLiteral(property.key)
? t.stringLiteral(property.key.value)
: t.identifier(property.key.name)

const props = []
for (let key in obj) {
const rawValue = obj[key]
const { computed, composes, ast: keyAST } = this.objKeyToAst(key)

let valueAST
if (composes) {
// valueAST = t.arrayExpression(expressions.slice(0, composesCount))
continue
return properties.push(
t.objectProperty(key, prefixAst(property.value, t), property.computed)
)

// literal value or array of literal values
} else if (
isLiteral(property.value) ||
(t.isArrayExpression(property.value) &&
property.value.elements.every(isLiteral))
) {
// bail on computed properties
if (property.computed) {
properties.push(property)
return
}

// handle array values: { display: ['flex', 'block'] }
const propertyValue = t.isArrayExpression(property.value)
? property.value.elements.map(element => element.value)
: property.value.value

const style = { [property.key.name]: propertyValue }
const prefixedStyle = expandCSSFallbacks(prefixer(style))

for (let k in prefixedStyle) {
const key = t.isStringLiteral(property.key)
? t.stringLiteral(k)
: t.identifier(k)
const val = prefixedStyle[k]
let value

if (typeof val === 'number') {
value = t.numericLiteral(val)
} else if (typeof val === 'string') {
value = t.stringLiteral(val)
} else if (Array.isArray(val)) {
value = t.arrayExpression(val.map(i => t.stringLiteral(i)))
}

properties.push(t.objectProperty(key, value))
}

// expressions
} else {
valueAST = this.objValueToAst(rawValue)
properties.push(property)
}
})

props.push(t.objectProperty(keyAST, valueAST, computed))
}
return t.objectExpression(props)
return t.objectExpression(properties)
}

if (t.isArrayExpression(args)) {
return t.arrayExpression(prefixAst(args.elements, t))
}

return args
}

export default class ASTObject {
props: Array<any>
expressions: Array<any>
composesCount: number
t: any

constructor (props, expressions, composesCount, t) {
this.props = props
this.expressions = expressions || []
this.composesCount = composesCount
this.t = t
}

objKeyToAst (key): { computed: boolean, ast: any, composes: boolean } {
Expand All @@ -54,21 +116,26 @@ export default class ASTObject {
}

objValueToAst (value) {
const { expressions, composesCount, t } = this

const { composesCount, t } = this
if (typeof value === 'string') {
const matches = this.getDynamicMatches(value)
if (matches.length) {
return this.replacePlaceholdersWithExpressions(matches, value)
}
return t.stringLiteral(value)
} else if (typeof value === 'number') {
return t.numericLiteral(value)
} else if (Array.isArray(value)) {
// should never hit here

if (value[0] && (value[0].key || value[0].value || value[0].spread)) {
return this.toAST(value)
}

return t.arrayExpression(value.map(v => this.objValueToAst(v)))
}

const obj = new this.constructor(value, expressions, composesCount, t)
return obj.toAST()
return ASTObject.fromJS(value, composesCount, t).toAST()
}

getDynamicMatches (str) {
Expand Down Expand Up @@ -109,7 +176,7 @@ export default class ASTObject {
// }

templateExpressions.push(
expressions
expressions.length
? expressions[p1 - composesCount]
: t.identifier(`x${p1 - composesCount}`)
)
Expand All @@ -131,6 +198,73 @@ export default class ASTObject {
return t.templateLiteral(templateElements, templateExpressions)
}

toAST (props = this.props) {
return this.t.objectExpression(
props.map(prop => {
if (this.t.isObjectProperty(prop)) {
return prop
}

// console.log(JSON.stringify(prop, null, 2))

const { property, key, value, spread, shorthand } = prop

if (spread || shorthand) {
return property
}

const { computed, ast: keyAST } = this.objKeyToAst(key)
const valueAST = this.objValueToAst(value)

return this.t.objectProperty(keyAST, valueAST, computed)
})
)
}

toJS (props = this.props) {
return props.reduce(
(
accum,
{ property, key, value, computed: isComputedProperty, spread }
) => {
if (spread) {
return accum
}

accum[key] = value
return accum
},
{}
)
}

static fromJS (jsObj, composesCount, t) {
const props = []
for (let key in jsObj) {
// console.log(key)
if (jsObj.hasOwnProperty(key)) {
let value
if (Object.prototype.toString.call(jsObj[key]) === '[object Object]') {
// console.log("what the fuck", jsObj[key])
// value = ASTObject.fromJS(jsObj[key], composesCount, t)
value = jsObj[key]
} else {
value = jsObj[key]
}

props.push({
key: key,
value: value,
computed: false,
spread: false,
property: null
})
}
}

return new ASTObject(props, [], composesCount, t)
}

static fromAST (astObj, t) {
function isLiteral (value) {
return (
Expand Down Expand Up @@ -170,32 +304,62 @@ export default class ASTObject {
return `xxx${expressions.length - 1}xxx`
}

function toObj (astObj) {
let obj = {}
function convertAstToObj (astObj) {
const props = []

forEach(astObj.properties, property => {
// nested objects
forEach(astObj.properties, (property, i) => {
let key
if (t.isSpreadProperty(property)) {
props.push({
key: null,
value: null,
computed: false,
shorthand: false,
spread: true,
property
})
return
}

if (property.shorthand) {
return property
}

if (property.computed) {
key = replaceExpressionsWithPlaceholders(property.key)
} else {
key = t.isIdentifier(property.key)
? property.key.name
: property.key.value
}

// nested objects
if (t.isObjectExpression(property.value)) {
obj[key] = toObj(property.value)
props.push({
key,
value: convertAstToObj(property.value),
computed: property.computed,
spread: false,
shorthand: false,
property
})
} else {
obj[key] = replaceExpressionsWithPlaceholders(property.value)
props.push({
key,
value: replaceExpressionsWithPlaceholders(property.value),
computed: property.computed,
spread: false,
shorthand: false,
property
})
}
})

return obj
return props
}

const obj = toObj(astObj)
const objectProperties = convertAstToObj(prefixAst(astObj, t))
return new ASTObject(
obj,
objectProperties,
expressions,
0, // composesCount: we should support this,
t
Expand Down
15 changes: 9 additions & 6 deletions src/babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import { map } from './utils'
import cssProps from './css-prop'
import ASTObject from './ast-object'

function getFilename (path) {
return path.hub.file.opts.filename === 'unknown'
? ''
: path.hub.file.opts.filename
}

function getFilename (path) {
return path.hub.file.opts.filename === 'unknown' ? '' : path.hub.file.opts.filename
}
Expand Down Expand Up @@ -61,7 +67,7 @@ export function replaceCssWithCallExpression (
t.blockStatement([
t.returnStatement(
t.arrayExpression([
new ASTObject(styles, false, composesCount, t).toAST()
ASTObject.fromJS(styles, composesCount, t).toAST()
])
)
])
Expand Down Expand Up @@ -115,7 +121,7 @@ export function buildStyledCallExpression (identifier, tag, path, state, t) {
t.blockStatement([
t.returnStatement(
t.arrayExpression([
new ASTObject(styles, false, composesCount, t).toAST()
ASTObject.fromJS(styles, composesCount, t).toAST()
])
)
])
Expand All @@ -137,10 +143,7 @@ export function buildStyledObjectCallExpression (path, identifier, t) {

function buildProcessedStylesFromObjectAST (objectAST, path, t) {
if (t.isObjectExpression(objectAST)) {
const astObject = ASTObject.fromAST(objectAST, t)
const { styles } = parseCSS(astObject.obj, false, getFilename(path))
astObject.obj = styles
return astObject.toAST()
return ASTObject.fromAST(objectAST, t).toAST()
}
if (t.isArrayExpression(objectAST)) {
return t.arrayExpression(
Expand Down
6 changes: 3 additions & 3 deletions src/css-prop.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ export default function (path, t) {
)
)
} else {
throw path.buildCodeFrameError(
`${cssPropValue.value} is not a string or template literal`
)
cssTemplateExpression = t.callExpression(t.identifier('css'), [
cssPropValue
])
}

if (!classNamesValue) {
Expand Down
Loading