Browse Source

Remove nested conditions in babel.js (#40)

* Remove nested conditions in babel.js

* Add makeStyledJsxTag

* Avoid work when skipTransform is true
add-plugins-support
Giuseppe 8 years ago
committed by Guillermo Rauch
parent
commit
692baffa56
  1. 274
      src/babel.js

274
src/babel.js

@ -31,161 +31,177 @@ export default function ({types: t}) {
expr.value
)
const makeStyledJsxTag = transformedCss => (
t.JSXElement(
t.JSXOpeningElement(
t.JSXIdentifier(STYLE_COMPONENT),
[t.JSXAttribute(
t.JSXIdentifier(STYLE_COMPONENT_CSS),
t.JSXExpressionContainer(t.stringLiteral(transformedCss))
)],
true
),
null,
[]
)
)
return {
inherits: jsx,
visitor: {
JSXOpeningElement(path, state) {
if (state.hasJSXStyle) {
if (state.ignoreClosing == null) {
// we keep a counter of elements inside so that we
// can keep track of when we exit the parent to reset state
// note: if we wished to add an option to turn off
// selectors to reach parent elements, it would suffice to
// set this to `1` and do an early return instead
state.ignoreClosing = 0
}
if (!state.hasJSXStyle) {
return
}
if (state.ignoreClosing === null) {
// we keep a counter of elements inside so that we
// can keep track of when we exit the parent to reset state
// note: if we wished to add an option to turn off
// selectors to reach parent elements, it would suffice to
// set this to `1` and do an early return instead
state.ignoreClosing = 0
}
const el = path.node
const el = path.node
if (el.name && el.name.name !== 'style') {
for (const attr of el.attributes) {
if (attr.name === MARKUP_ATTRIBUTE) {
// avoid double attributes
return
}
if (el.name && el.name.name !== 'style') {
for (const attr of el.attributes) {
if (attr.name === MARKUP_ATTRIBUTE) {
// avoid double attributes
return
}
const attr = t.jSXAttribute(
t.JSXIdentifier(MARKUP_ATTRIBUTE),
t.JSXExpressionContainer(t.stringLiteral(state.jsxId))
)
el.attributes.push(attr)
}
state.ignoreClosing++
// next visit will be: JSXElement exit()
const attr = t.jSXAttribute(
t.JSXIdentifier(MARKUP_ATTRIBUTE),
t.JSXExpressionContainer(t.stringLiteral(state.jsxId))
)
el.attributes.push(attr)
}
state.ignoreClosing++
// next visit will be: JSXElement exit()
},
JSXElement: {
enter(path, state) {
if (state.hasJSXStyle == null) {
const styles = findStyles(path.node.children)
if (styles.length > 0) {
state.jsxId = ''
state.styles = []
for (const style of styles) {
// compute children excluding whitespace
const children = style.children.filter(c => (
t.isJSXExpressionContainer(c) ||
// ignore whitespace around the expression container
(t.isJSXText(c) && c.value.trim() !== '')
))
if (children.length !== 1) {
throw path.buildCodeFrameError(`Expected one child under ` +
`JSX Style tag, but got ${style.children.length} ` +
`(eg: <style jsx>{\`hi\`}</style>)`)
}
const child = children[0]
if (!t.isJSXExpressionContainer(child)) {
throw path.buildCodeFrameError(`Expected a child of ` +
`type JSXExpressionContainer under JSX Style tag ` +
`(eg: <style jsx>{\`hi\`}</style>), got ${child.type}`)
}
const expression = child.expression
if (!t.isTemplateLiteral(child.expression) &&
!t.isStringLiteral(child.expression)) {
throw path.buildCodeFrameError(`Expected a template ` +
`literal or String literal as the child of the ` +
`JSX Style tag (eg: <style jsx>{\`some css\`}</style>),` +
` but got ${expression.type}`)
}
const styleText = getExpressionText(expression)
const styleId = String(hash(styleText))
state.styles.push([
styleId,
styleText,
expression.loc
])
}
state.jsxId += hash(state.styles.map(s => s[1]).join(''))
state.hasJSXStyle = true
state.file.hasJSXStyle = true
// next visit will be: JSXOpeningElement
} else {
state.hasJSXStyle = false
if (state.hasJSXStyle !== null) {
return
}
const styles = findStyles(path.node.children)
if (styles.length === 0) {
state.hasJSXStyle = false
return
}
state.jsxId = ''
state.styles = []
for (const style of styles) {
// compute children excluding whitespace
const children = style.children.filter(c => (
t.isJSXExpressionContainer(c) ||
// ignore whitespace around the expression container
(t.isJSXText(c) && c.value.trim() !== '')
))
if (children.length !== 1) {
throw path.buildCodeFrameError(`Expected one child under ` +
`JSX Style tag, but got ${style.children.length} ` +
`(eg: <style jsx>{\`hi\`}</style>)`)
}
} else if (state.hasJSXStyle) {
const el = path.node.openingElement
if (el.name && el.name.name === 'style') {
// we replace styles with the function call
const [id, css, loc] = state.styles.shift()
const skipTransform = el.attributes.some(attr => (
attr.name.name === GLOBAL_ATTRIBUTE
))
const useSourceMaps = Boolean(state.file.opts.sourceMaps)
let transformedCss = css
if (!skipTransform) {
if (useSourceMaps) {
const filename = state.file.opts.sourceFileName
const generator = new SourceMapGenerator({
file: filename,
sourceRoot: state.file.opts.sourceRoot
})
generator.setSourceContent(filename, state.file.code)
transformedCss = [
transform(id, css, generator, loc.start, filename),
convert
.fromObject(generator)
.toComment({multiline: true}),
`/*@ sourceURL=${filename} */`
].join('\n')
} else {
transformedCss = transform(id, css)
}
}
path.replaceWith(
t.JSXElement(
t.JSXOpeningElement(
t.JSXIdentifier(STYLE_COMPONENT),
[
t.JSXAttribute(
t.JSXIdentifier(STYLE_COMPONENT_CSS),
t.JSXExpressionContainer(t.stringLiteral(transformedCss))
)
],
true
),
null,
[]
)
)
const child = children[0]
if (!t.isJSXExpressionContainer(child)) {
throw path.buildCodeFrameError(`Expected a child of ` +
`type JSXExpressionContainer under JSX Style tag ` +
`(eg: <style jsx>{\`hi\`}</style>), got ${child.type}`)
}
const expression = child.expression
if (!t.isTemplateLiteral(child.expression) &&
!t.isStringLiteral(child.expression)) {
throw path.buildCodeFrameError(`Expected a template ` +
`literal or String literal as the child of the ` +
`JSX Style tag (eg: <style jsx>{\`some css\`}</style>),` +
` but got ${expression.type}`)
}
const styleText = getExpressionText(expression)
const styleId = String(hash(styleText))
state.styles.push([
styleId,
styleText,
expression.loc
])
}
state.jsxId += hash(state.styles.map(s => s[1]).join(''))
state.hasJSXStyle = true
state.file.hasJSXStyle = true
// next visit will be: JSXOpeningElement
},
exit(path, state) {
if (state.hasJSXStyle && !--state.ignoreClosing) {
state.hasJSXStyle = null
state.skipTransform = false
}
if (!state.hasJSXStyle) {
return
}
const el = path.node.openingElement
if (!el.name || el.name.name !== 'style') {
return
}
// we replace styles with the function call
const [id, css, loc] = state.styles.shift()
const skipTransform = el.attributes.some(attr => (
attr.name.name === GLOBAL_ATTRIBUTE
))
if (skipTransform) {
path.replaceWith(makeStyledJsxTag(css))
return
}
const useSourceMaps = Boolean(state.file.opts.sourceMaps)
let transformedCss
if (useSourceMaps) {
const filename = state.file.opts.sourceFileName
const generator = new SourceMapGenerator({
file: filename,
sourceRoot: state.file.opts.sourceRoot
})
generator.setSourceContent(filename, state.file.code)
transformedCss = [
transform(id, css, generator, loc.start, filename),
convert
.fromObject(generator)
.toComment({multiline: true}),
`/*@ sourceURL=${filename} */`
].join('\n')
} else {
transformedCss = transform(id, css)
}
path.replaceWith(makeStyledJsxTag(transformedCss))
}
},
Program: {
enter(path, state) {
state.hasJSXStyle = null
state.ignoreClosing = null
state.file.hasJSXStyle = false
},
exit({node, scope}, state) {

Loading…
Cancel
Save