index.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. 'use strict'
  2. var allowedModifiers = ['trim', 'number', 'lazy']
  3. var RANGE_TOKEN = '__r'
  4. var CHECKBOX_RADIO_TOKEN = '__c'
  5. var htmlTags = require('html-tags')
  6. var svgTags = require('svg-tags')
  7. var isReservedTag = function isReservedTag(tag) {
  8. return ~htmlTags.indexOf(tag) || ~svgTags.indexOf(tag)
  9. }
  10. var getExpression = function getExpression(t, path) {
  11. return t.isStringLiteral(path.node.value) ? path.node.value : path.node.value.expression
  12. }
  13. var genValue = function genValue(t, model) {
  14. return t.jSXAttribute(t.jSXIdentifier('domPropsValue'), t.jSXExpressionContainer(model))
  15. }
  16. var genAssignmentCode = function genAssignmentCode(t, model, assignment) {
  17. if (model.computed) {
  18. var object = model.object,
  19. property = model.property
  20. return t.ExpressionStatement(
  21. t.CallExpression(t.MemberExpression(t.ThisExpression(), t.Identifier('$set')), [object, property, assignment])
  22. )
  23. } else {
  24. return t.ExpressionStatement(t.AssignmentExpression('=', model, assignment))
  25. }
  26. }
  27. var genListener = function genListener(t, event, body) {
  28. return t.jSXAttribute(
  29. t.jSXIdentifier('on' + event),
  30. t.jSXExpressionContainer(t.ArrowFunctionExpression([t.Identifier('$event')], t.BlockStatement(body)))
  31. )
  32. }
  33. var genSelectModel = function genSelectModel(t, modelAttribute, model, modifier) {
  34. if (modifier && modifier !== 'number') {
  35. throw modelAttribute.get('name').get('name').buildCodeFrameError('you can only use number modifier with <select/ >')
  36. }
  37. var number = modifier === 'number'
  38. var valueGetter = t.ConditionalExpression(
  39. t.BinaryExpression('in', t.StringLiteral('_value'), t.Identifier('o')),
  40. t.MemberExpression(t.Identifier('o'), t.Identifier('_value')),
  41. t.MemberExpression(t.Identifier('o'), t.Identifier('value'))
  42. )
  43. var selectedVal = t.CallExpression(
  44. t.MemberExpression(
  45. t.CallExpression(
  46. t.MemberExpression(
  47. t.MemberExpression(
  48. t.MemberExpression(t.Identifier('Array'), t.Identifier('prototype')),
  49. t.Identifier('filter')
  50. ),
  51. t.Identifier('call')
  52. ),
  53. [
  54. t.MemberExpression(
  55. t.MemberExpression(t.Identifier('$event'), t.Identifier('target')),
  56. t.Identifier('options')
  57. ),
  58. t.ArrowFunctionExpression(
  59. [t.Identifier('o')],
  60. t.MemberExpression(t.Identifier('o'), t.Identifier('selected'))
  61. )
  62. ]
  63. ),
  64. t.Identifier('map')
  65. ),
  66. [
  67. t.ArrowFunctionExpression(
  68. [t.Identifier('o')],
  69. number
  70. ? t.CallExpression(t.MemberExpression(t.ThisExpression(), t.Identifier('_n')), [valueGetter])
  71. : valueGetter
  72. )
  73. ]
  74. )
  75. var assignment = t.ConditionalExpression(
  76. t.MemberExpression(t.MemberExpression(t.Identifier('$event'), t.Identifier('target')), t.Identifier('multiple')),
  77. t.Identifier('$$selectedVal'),
  78. t.MemberExpression(t.Identifier('$$selectedVal'), t.NumericLiteral(0), true)
  79. )
  80. var code = t.VariableDeclaration('const', [t.VariableDeclarator(t.Identifier('$$selectedVal'), selectedVal)])
  81. return [genValue(t, model), genListener(t, 'Change', [code, genAssignmentCode(t, model, assignment)])]
  82. }
  83. var genCheckboxModel = function genCheckboxModel(t, modelAttribute, model, modifier, path) {
  84. if (modifier && modifier !== 'number') {
  85. throw modelAttribute
  86. .get('name')
  87. .get('name')
  88. .buildCodeFrameError('you can only use number modifier with input[type="checkbox"]')
  89. }
  90. var value = t.NullLiteral()
  91. var trueValue = t.BooleanLiteral(true)
  92. var falseValue = t.BooleanLiteral(false)
  93. path.get('attributes').forEach(function(path) {
  94. if (!path.node.name) {
  95. return
  96. }
  97. if (path.node.name.name === 'value') {
  98. value = getExpression(t, path)
  99. path.remove()
  100. } else if (path.node.name.name === 'true-value') {
  101. trueValue = getExpression(t, path)
  102. path.remove()
  103. } else if (path.node.name.name === 'false-value') {
  104. falseValue = getExpression(t, path)
  105. path.remove()
  106. }
  107. })
  108. var checkedProp = t.JSXAttribute(
  109. t.JSXIdentifier('checked'),
  110. t.JSXExpressionContainer(
  111. t.ConditionalExpression(
  112. t.CallExpression(t.MemberExpression(t.Identifier('Array'), t.Identifier('isArray')), [model]),
  113. t.BinaryExpression(
  114. '>',
  115. t.CallExpression(t.MemberExpression(t.ThisExpression(), t.Identifier('_i')), [model, value]),
  116. t.UnaryExpression('-', t.NumericLiteral(1))
  117. ),
  118. t.isBooleanLiteral(trueValue) && trueValue.value === true
  119. ? model
  120. : t.CallExpression(t.MemberExpression(t.ThisExpression(), t.Identifier('_q')), [model, trueValue])
  121. )
  122. )
  123. )
  124. var handlerProp = t.JSXAttribute(
  125. t.JSXIdentifier('on' + CHECKBOX_RADIO_TOKEN),
  126. t.JSXExpressionContainer(
  127. t.ArrowFunctionExpression(
  128. [t.Identifier('$event')],
  129. t.BlockStatement([
  130. t.VariableDeclaration('const', [
  131. t.VariableDeclarator(t.Identifier('$$a'), model),
  132. t.VariableDeclarator(
  133. t.Identifier('$$el'),
  134. t.MemberExpression(t.Identifier('$event'), t.Identifier('target'))
  135. ),
  136. t.VariableDeclarator(
  137. t.Identifier('$$c'),
  138. t.ConditionalExpression(
  139. t.MemberExpression(t.Identifier('$$el'), t.Identifier('checked')),
  140. trueValue,
  141. falseValue
  142. )
  143. )
  144. ]),
  145. t.IfStatement(
  146. t.CallExpression(t.MemberExpression(t.Identifier('Array'), t.Identifier('isArray')), [t.Identifier('$$a')]),
  147. t.BlockStatement([
  148. t.VariableDeclaration('const', [
  149. t.VariableDeclarator(
  150. t.Identifier('$$v'),
  151. modifier === 'number'
  152. ? t.CallExpression(t.MemberExpression(t.ThisExpression(), t.Identifier('_n')), [value])
  153. : value
  154. ),
  155. t.VariableDeclarator(
  156. t.Identifier('$$i'),
  157. t.CallExpression(t.MemberExpression(t.ThisExpression(), t.Identifier('_i')), [
  158. t.Identifier('$$a'),
  159. t.Identifier('$$v')
  160. ])
  161. )
  162. ]),
  163. t.IfStatement(
  164. t.MemberExpression(t.Identifier('$$el'), t.Identifier('checked')),
  165. t.BlockStatement([
  166. t.ExpressionStatement(
  167. t.LogicalExpression(
  168. '&&',
  169. t.BinaryExpression('<', t.Identifier('$$i'), t.NumericLiteral(0)),
  170. t.AssignmentExpression(
  171. '=',
  172. model,
  173. t.CallExpression(t.MemberExpression(t.Identifier('$$a'), t.Identifier('concat')), [
  174. t.Identifier('$$v')
  175. ])
  176. )
  177. )
  178. )
  179. ]),
  180. t.BlockStatement([
  181. t.ExpressionStatement(
  182. t.LogicalExpression(
  183. '&&',
  184. t.BinaryExpression('>', t.Identifier('$$i'), t.UnaryExpression('-', t.NumericLiteral(1))),
  185. t.AssignmentExpression(
  186. '=',
  187. model,
  188. t.CallExpression(
  189. t.MemberExpression(
  190. t.CallExpression(t.MemberExpression(t.Identifier('$$a'), t.Identifier('slice')), [
  191. t.NumericLiteral(0),
  192. t.Identifier('$$i')
  193. ]),
  194. t.Identifier('concat')
  195. ),
  196. [
  197. t.CallExpression(t.MemberExpression(t.Identifier('$$a'), t.Identifier('slice')), [
  198. t.BinaryExpression('+', t.Identifier('$$i'), t.NumericLiteral(1))
  199. ])
  200. ]
  201. )
  202. )
  203. )
  204. )
  205. ])
  206. )
  207. ]),
  208. t.BlockStatement([genAssignmentCode(t, model, t.Identifier('$$c'))])
  209. )
  210. ])
  211. )
  212. )
  213. )
  214. return [checkedProp, handlerProp]
  215. }
  216. var genRadioModel = function genRadioModel(t, modelAttribute, model, modifier, path) {
  217. if (modifier && modifier !== 'number') {
  218. throw modelAttribute
  219. .get('name')
  220. .get('name')
  221. .buildCodeFrameError('you can only use number modifier with input[type="radio"]')
  222. }
  223. var value = t.BooleanLiteral(true)
  224. path.get('attributes').forEach(function(path) {
  225. if (!path.node.name) {
  226. return
  227. }
  228. if (path.node.name.name === 'value') {
  229. value = getExpression(t, path)
  230. path.remove()
  231. }
  232. })
  233. var checkedProp = t.JSXAttribute(
  234. t.JSXIdentifier('checked'),
  235. t.JSXExpressionContainer(
  236. t.CallExpression(t.MemberExpression(t.ThisExpression(), t.Identifier('_q')), [model, value])
  237. )
  238. )
  239. var handlerProp = t.JSXAttribute(
  240. t.JSXIdentifier('on' + CHECKBOX_RADIO_TOKEN),
  241. t.JSXExpressionContainer(
  242. t.ArrowFunctionExpression(
  243. [t.Identifier('$event')],
  244. t.BlockStatement([
  245. genAssignmentCode(
  246. t,
  247. model,
  248. modifier === 'number'
  249. ? t.CallExpression(t.MemberExpression(t.ThisExpression(), t.Identifier('_n')), [value])
  250. : value
  251. )
  252. ])
  253. )
  254. )
  255. )
  256. return [checkedProp, handlerProp]
  257. }
  258. var genDefaultModel = function genDefaultModel(t, modelAttribute, model, modifier, path, type) {
  259. var needCompositionGuard = modifier !== 'lazy' && type !== 'range'
  260. var event = modifier === 'lazy' ? 'Change' : type === 'range' ? RANGE_TOKEN : 'Input'
  261. var valueExpression = t.MemberExpression(
  262. t.MemberExpression(t.Identifier('$event'), t.Identifier('target')),
  263. t.Identifier('value')
  264. )
  265. if (modifier === 'trim') {
  266. valueExpression = t.CallExpression(t.MemberExpression(valueExpression, t.Identifier('trim')), [])
  267. } else if (modifier === 'number') {
  268. valueExpression = t.CallExpression(t.MemberExpression(t.ThisExpression(), t.Identifier('_n')), [valueExpression])
  269. }
  270. var code = genAssignmentCode(t, model, valueExpression)
  271. if (needCompositionGuard) {
  272. code = t.BlockStatement([
  273. t.IfStatement(
  274. t.MemberExpression(
  275. t.MemberExpression(t.Identifier('$event'), t.Identifier('target')),
  276. t.Identifier('composing')
  277. ),
  278. t.ReturnStatement(null)
  279. ),
  280. code
  281. ])
  282. } else {
  283. code = t.BlockStatement([code])
  284. }
  285. var valueProp = t.JSXAttribute(t.jSXIdentifier('domPropsValue'), t.JSXExpressionContainer(model))
  286. var handlerProp = t.JSXAttribute(
  287. t.JSXIdentifier('on' + event),
  288. t.JSXExpressionContainer(t.ArrowFunctionExpression([t.Identifier('$event')], code))
  289. )
  290. var props = [valueProp, handlerProp]
  291. if (modifier === 'trim' || modifier === 'number') {
  292. props.push(
  293. t.JSXAttribute(
  294. t.JSXIdentifier('onBlur'),
  295. t.JSXExpressionContainer(
  296. t.ArrowFunctionExpression(
  297. [],
  298. t.CallExpression(t.MemberExpression(t.ThisExpression(), t.Identifier('$forceUpdate')), [])
  299. )
  300. )
  301. )
  302. )
  303. }
  304. return props
  305. }
  306. var genComponentModel = function genComponentModel(t, modelAttribute, model, modifier, path, type) {
  307. var baseValueExpression = t.Identifier('$$v')
  308. var valueExpression = baseValueExpression
  309. if (modifier === 'trim') {
  310. valueExpression = t.CallExpression(t.MemberExpression(valueExpression, t.Identifier('trim')), [])
  311. } else if (modifier === 'number') {
  312. valueExpression = t.CallExpression(t.MemberExpression(t.ThisExpression(), t.Identifier('_n')), [valueExpression])
  313. }
  314. var assignment = genAssignmentCode(t, model, valueExpression)
  315. var valueProp = t.JSXAttribute(t.jSXIdentifier('value'), t.JSXExpressionContainer(model))
  316. var handlerProp = t.JSXAttribute(
  317. t.JSXIdentifier('onInput'),
  318. t.JSXExpressionContainer(t.ArrowFunctionExpression([baseValueExpression], t.BlockStatement([assignment])))
  319. )
  320. return [valueProp, handlerProp]
  321. }
  322. module.exports = function(babel) {
  323. var t = babel.types
  324. return {
  325. inherits: require('babel-plugin-syntax-jsx'),
  326. visitor: {
  327. JSXOpeningElement: function JSXOpeningElement(path) {
  328. var model = null
  329. var modifier = null
  330. var modelAttribute = null
  331. var type = null
  332. var typePath = null
  333. path.get('attributes').forEach(function(path) {
  334. if (!path.node.name) {
  335. return
  336. }
  337. if (path.node.name.name === 'type') {
  338. type = path.node.value.value
  339. typePath = path.get('value')
  340. }
  341. /* istanbul ignore else */
  342. if (t.isJSXIdentifier(path.node.name)) {
  343. if (path.node.name.name !== 'v-model') {
  344. return
  345. }
  346. } else if (t.isJSXNamespacedName(path.node.name)) {
  347. if (path.node.name.namespace.name !== 'v-model') {
  348. return
  349. }
  350. if (!~allowedModifiers.indexOf(path.node.name.name.name)) {
  351. throw path
  352. .get('name')
  353. .get('name')
  354. .buildCodeFrameError('v-model does not support "' + path.node.name.name.name + '" modifier')
  355. }
  356. modifier = path.node.name.name.name
  357. } else {
  358. return
  359. }
  360. modelAttribute = path
  361. if (model) {
  362. throw path.buildCodeFrameError('you can not have multiple v-model directives on a single element')
  363. }
  364. if (!t.isJSXExpressionContainer(path.node.value)) {
  365. throw path.get('value').buildCodeFrameError('you should use JSX Expression with v-model')
  366. }
  367. if (!t.isMemberExpression(path.node.value.expression)) {
  368. throw path
  369. .get('value')
  370. .get('expression')
  371. .buildCodeFrameError('you should use MemberExpression with v-model')
  372. }
  373. model = path.node.value.expression
  374. })
  375. if (!model) {
  376. return
  377. }
  378. var tag = path.node.name.name
  379. if (tag === 'input' && typePath && t.isJSXExpressionContainer(typePath.node)) {
  380. throw typePath.buildCodeFrameError('you can not use dynamic type with v-model')
  381. }
  382. if (tag === 'input' && type === 'file') {
  383. throw typePath.buildCodeFrameError('you can not use "file" type with v-model')
  384. }
  385. var replacement = null
  386. if (tag === 'select') {
  387. replacement = genSelectModel(t, modelAttribute, model, modifier)
  388. } else if (tag === 'input' && type === 'checkbox') {
  389. replacement = genCheckboxModel(t, modelAttribute, model, modifier, path)
  390. } else if (tag === 'input' && type === 'radio') {
  391. replacement = genRadioModel(t, modelAttribute, model, modifier, path)
  392. } else if (tag === 'input' || tag === 'textarea') {
  393. replacement = genDefaultModel(t, modelAttribute, model, modifier, path, type)
  394. } else if (!isReservedTag(tag)) {
  395. replacement = genComponentModel(t, modelAttribute, model, modifier, path)
  396. } else {
  397. throw path.buildCodeFrameError('you can not use "' + tag + '" with v-model')
  398. }
  399. modelAttribute.replaceWithMultiple([
  400. ...replacement,
  401. t.JSXSpreadAttribute(
  402. t.ObjectExpression([
  403. t.ObjectProperty(
  404. t.Identifier('directives'),
  405. t.ArrayExpression([
  406. t.ObjectExpression([
  407. t.ObjectProperty(t.Identifier('name'), t.StringLiteral('model')),
  408. t.ObjectProperty(t.Identifier('value'), model)
  409. ])
  410. ])
  411. )
  412. ])
  413. )
  414. ])
  415. }
  416. }
  417. }
  418. }