index.js 6.6 KB

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