index.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. var esutils = require('esutils')
  2. var groupProps = require('./lib/group-props')
  3. module.exports = function (babel) {
  4. var t = babel.types
  5. return {
  6. inherits: require('babel-plugin-syntax-jsx'),
  7. visitor: {
  8. JSXNamespacedName (path) {
  9. throw path.buildCodeFrameError(
  10. 'Namespaced tags/attributes are not supported. JSX is not XML.\n' +
  11. 'For attributes like xlink:href, use xlinkHref instead.'
  12. )
  13. },
  14. JSXElement: {
  15. exit (path, file) {
  16. // turn tag into createElement call
  17. var callExpr = buildElementCall(path.get('openingElement'), file)
  18. // add children array as 3rd arg
  19. callExpr.arguments.push(t.arrayExpression(path.node.children))
  20. if (callExpr.arguments.length >= 3) {
  21. callExpr._prettyCall = true
  22. }
  23. path.replaceWith(t.inherits(callExpr, path.node))
  24. }
  25. },
  26. 'Program' (path) {
  27. path.traverse({
  28. 'ObjectMethod|ClassMethod' (path) {
  29. const params = path.get('params')
  30. // do nothing if there is (h) param
  31. if (params.length && params[0].node.name === 'h') {
  32. return
  33. }
  34. // do nothing if there is no JSX inside
  35. const jsxChecker = {
  36. hasJsx: false
  37. }
  38. path.traverse({
  39. JSXElement () {
  40. this.hasJsx = true
  41. }
  42. }, jsxChecker)
  43. if (!jsxChecker.hasJsx) {
  44. return
  45. }
  46. const isRender = path.node.key.name === 'render'
  47. // inject h otherwise
  48. path.get('body').unshiftContainer('body', t.variableDeclaration('const', [
  49. t.variableDeclarator(
  50. t.identifier('h'),
  51. (
  52. isRender
  53. ? t.memberExpression(
  54. t.identifier('arguments'),
  55. t.numericLiteral(0),
  56. true
  57. )
  58. : t.memberExpression(
  59. t.thisExpression(),
  60. t.identifier('$createElement')
  61. )
  62. )
  63. )
  64. ]))
  65. }
  66. })
  67. }
  68. }
  69. }
  70. function buildElementCall (path, file) {
  71. path.parent.children = t.react.buildChildren(path.parent)
  72. var tagExpr = convertJSXIdentifier(path.node.name, path.node)
  73. var args = []
  74. var tagName
  75. if (t.isIdentifier(tagExpr)) {
  76. tagName = tagExpr.name
  77. } else if (t.isLiteral(tagExpr)) {
  78. tagName = tagExpr.value
  79. }
  80. if (t.react.isCompatTag(tagName)) {
  81. args.push(t.stringLiteral(tagName))
  82. } else {
  83. args.push(tagExpr)
  84. }
  85. var attribs = path.node.attributes
  86. if (attribs.length) {
  87. attribs = buildOpeningElementAttributes(attribs, file)
  88. } else {
  89. attribs = t.nullLiteral()
  90. }
  91. args.push(attribs)
  92. return t.callExpression(t.identifier('h'), args)
  93. }
  94. function convertJSXIdentifier (node, parent) {
  95. if (t.isJSXIdentifier(node)) {
  96. if (node.name === 'this' && t.isReferenced(node, parent)) {
  97. return t.thisExpression()
  98. } else if (esutils.keyword.isIdentifierNameES6(node.name)) {
  99. node.type = 'Identifier'
  100. } else {
  101. return t.stringLiteral(node.name)
  102. }
  103. } else if (t.isJSXMemberExpression(node)) {
  104. return t.memberExpression(
  105. convertJSXIdentifier(node.object, node),
  106. convertJSXIdentifier(node.property, node)
  107. )
  108. }
  109. return node
  110. }
  111. /**
  112. * The logic for this is quite terse. It's because we need to
  113. * support spread elements. We loop over all attributes,
  114. * breaking on spreads, we then push a new object containing
  115. * all prior attributes to an array for later processing.
  116. */
  117. function buildOpeningElementAttributes (attribs, file) {
  118. var _props = []
  119. var objs = []
  120. function pushProps () {
  121. if (!_props.length) return
  122. objs.push(t.objectExpression(_props))
  123. _props = []
  124. }
  125. while (attribs.length) {
  126. var prop = attribs.shift()
  127. if (t.isJSXSpreadAttribute(prop)) {
  128. pushProps()
  129. prop.argument._isSpread = true
  130. objs.push(prop.argument)
  131. } else {
  132. _props.push(convertAttribute(prop))
  133. }
  134. }
  135. pushProps()
  136. objs = objs.map(function (o) {
  137. return o._isSpread ? o : groupProps(o.properties, t)
  138. })
  139. if (objs.length === 1) {
  140. // only one object
  141. attribs = objs[0]
  142. } else if (objs.length) {
  143. // add prop merging helper
  144. var helper = file.addImport('babel-helper-vue-jsx-merge-props', 'default', '_mergeJSXProps')
  145. // spread it
  146. attribs = t.callExpression(
  147. helper,
  148. [t.arrayExpression(objs)]
  149. )
  150. }
  151. return attribs
  152. }
  153. function convertAttribute (node) {
  154. var value = convertAttributeValue(node.value || t.booleanLiteral(true))
  155. if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) {
  156. value.value = value.value.replace(/\n\s+/g, ' ')
  157. }
  158. if (t.isValidIdentifier(node.name.name)) {
  159. node.name.type = 'Identifier'
  160. } else {
  161. node.name = t.stringLiteral(node.name.name)
  162. }
  163. return t.inherits(t.objectProperty(node.name, value), node)
  164. }
  165. function convertAttributeValue (node) {
  166. if (t.isJSXExpressionContainer(node)) {
  167. return node.expression
  168. } else {
  169. return node
  170. }
  171. }
  172. }