224 lines
6.6 KiB
JavaScript
224 lines
6.6 KiB
JavaScript
var esutils = require('esutils')
|
|
var groupProps = require('./lib/group-props')
|
|
var mustUseProp = require('./lib/must-use-prop')
|
|
|
|
var isInsideJsxExpression = function (t, path) {
|
|
if (!path.parentPath) {
|
|
return false
|
|
}
|
|
if (t.isJSXExpressionContainer(path.parentPath)) {
|
|
return true
|
|
}
|
|
return isInsideJsxExpression(t, path.parentPath)
|
|
}
|
|
|
|
module.exports = function (babel) {
|
|
var t = babel.types
|
|
|
|
return {
|
|
inherits: require('babel-plugin-syntax-jsx'),
|
|
visitor: {
|
|
JSXNamespacedName (path) {
|
|
throw path.buildCodeFrameError(
|
|
'Namespaced tags/attributes are not supported. JSX is not XML.\n' +
|
|
'For attributes like xlink:href, use xlinkHref instead.'
|
|
)
|
|
},
|
|
JSXElement: {
|
|
exit (path, file) {
|
|
// turn tag into createElement call
|
|
var callExpr = buildElementCall(path.get('openingElement'), file)
|
|
if (path.node.children.length) {
|
|
// add children array as 3rd arg
|
|
callExpr.arguments.push(t.arrayExpression(path.node.children))
|
|
if (callExpr.arguments.length >= 3) {
|
|
callExpr._prettyCall = true
|
|
}
|
|
}
|
|
path.replaceWith(t.inherits(callExpr, path.node))
|
|
}
|
|
},
|
|
'Program' (path) {
|
|
path.traverse({
|
|
'ObjectMethod|ClassMethod' (path) {
|
|
const params = path.get('params')
|
|
// do nothing if there is (h) param
|
|
if (params.length && params[0].node.name === 'h') {
|
|
return
|
|
}
|
|
// do nothing if there is no JSX inside
|
|
const jsxChecker = {
|
|
hasJsx: false
|
|
}
|
|
path.traverse({
|
|
JSXElement () {
|
|
this.hasJsx = true
|
|
}
|
|
}, jsxChecker)
|
|
if (!jsxChecker.hasJsx) {
|
|
return
|
|
}
|
|
// do nothing if this method is a part of JSX expression
|
|
if (isInsideJsxExpression(t, path)) {
|
|
return
|
|
}
|
|
const isRender = path.node.key.name === 'render'
|
|
// inject h otherwise
|
|
path.get('body').unshiftContainer('body', t.variableDeclaration('const', [
|
|
t.variableDeclarator(
|
|
t.identifier('h'),
|
|
(
|
|
isRender
|
|
? t.memberExpression(
|
|
t.identifier('arguments'),
|
|
t.numericLiteral(0),
|
|
true
|
|
)
|
|
: t.memberExpression(
|
|
t.thisExpression(),
|
|
t.identifier('$createElement')
|
|
)
|
|
)
|
|
)
|
|
]))
|
|
},
|
|
JSXOpeningElement (path) {
|
|
const tag = path.get('name').node.name
|
|
const attributes = path.get('attributes')
|
|
const typeAttribute = attributes.find(attributePath => attributePath.node.name && attributePath.node.name.name === 'type')
|
|
const type = typeAttribute && t.isStringLiteral(typeAttribute.node.value) ? typeAttribute.node.value.value : null
|
|
|
|
attributes.forEach(attributePath => {
|
|
const attribute = attributePath.get('name')
|
|
|
|
if (!attribute.node) {
|
|
return
|
|
}
|
|
|
|
const attr = attribute.node.name
|
|
|
|
if (mustUseProp(tag, type, attr) && t.isJSXExpressionContainer(attributePath.node.value)) {
|
|
attribute.replaceWith(t.JSXIdentifier(`domProps-${attr}`))
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
function buildElementCall (path, file) {
|
|
path.parent.children = t.react.buildChildren(path.parent)
|
|
var tagExpr = convertJSXIdentifier(path.node.name, path.node)
|
|
var args = []
|
|
|
|
var tagName
|
|
if (t.isIdentifier(tagExpr)) {
|
|
tagName = tagExpr.name
|
|
} else if (t.isLiteral(tagExpr)) {
|
|
tagName = tagExpr.value
|
|
}
|
|
|
|
if (t.react.isCompatTag(tagName)) {
|
|
args.push(t.stringLiteral(tagName))
|
|
} else {
|
|
args.push(tagExpr)
|
|
}
|
|
|
|
var attribs = path.node.attributes
|
|
if (attribs.length) {
|
|
attribs = buildOpeningElementAttributes(attribs, file)
|
|
args.push(attribs)
|
|
}
|
|
return t.callExpression(t.identifier('h'), args)
|
|
}
|
|
|
|
function convertJSXIdentifier (node, parent) {
|
|
if (t.isJSXIdentifier(node)) {
|
|
if (node.name === 'this' && t.isReferenced(node, parent)) {
|
|
return t.thisExpression()
|
|
} else if (esutils.keyword.isIdentifierNameES6(node.name)) {
|
|
node.type = 'Identifier'
|
|
} else {
|
|
return t.stringLiteral(node.name)
|
|
}
|
|
} else if (t.isJSXMemberExpression(node)) {
|
|
return t.memberExpression(
|
|
convertJSXIdentifier(node.object, node),
|
|
convertJSXIdentifier(node.property, node)
|
|
)
|
|
}
|
|
return node
|
|
}
|
|
|
|
/**
|
|
* The logic for this is quite terse. It's because we need to
|
|
* support spread elements. We loop over all attributes,
|
|
* breaking on spreads, we then push a new object containing
|
|
* all prior attributes to an array for later processing.
|
|
*/
|
|
|
|
function buildOpeningElementAttributes (attribs, file) {
|
|
var _props = []
|
|
var objs = []
|
|
|
|
function pushProps () {
|
|
if (!_props.length) return
|
|
objs.push(t.objectExpression(_props))
|
|
_props = []
|
|
}
|
|
|
|
while (attribs.length) {
|
|
var prop = attribs.shift()
|
|
if (t.isJSXSpreadAttribute(prop)) {
|
|
pushProps()
|
|
prop.argument._isSpread = true
|
|
objs.push(prop.argument)
|
|
} else {
|
|
_props.push(convertAttribute(prop))
|
|
}
|
|
}
|
|
|
|
pushProps()
|
|
|
|
objs = objs.map(function (o) {
|
|
return o._isSpread ? o : groupProps(o.properties, t)
|
|
})
|
|
|
|
if (objs.length === 1) {
|
|
// only one object
|
|
attribs = objs[0]
|
|
} else if (objs.length) {
|
|
// add prop merging helper
|
|
var helper = file.addImport('babel-helper-vue-jsx-merge-props', 'default', '_mergeJSXProps')
|
|
// spread it
|
|
attribs = t.callExpression(
|
|
helper,
|
|
[t.arrayExpression(objs)]
|
|
)
|
|
}
|
|
return attribs
|
|
}
|
|
|
|
function convertAttribute (node) {
|
|
var value = convertAttributeValue(node.value || t.booleanLiteral(true))
|
|
if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) {
|
|
value.value = value.value.replace(/\n\s+/g, ' ')
|
|
}
|
|
if (t.isValidIdentifier(node.name.name)) {
|
|
node.name.type = 'Identifier'
|
|
} else {
|
|
node.name = t.stringLiteral(node.name.name)
|
|
}
|
|
return t.inherits(t.objectProperty(node.name, value), node)
|
|
}
|
|
|
|
function convertAttributeValue (node) {
|
|
if (t.isJSXExpressionContainer(node)) {
|
|
return node.expression
|
|
} else {
|
|
return node
|
|
}
|
|
}
|
|
}
|