mirror of
				https://github.com/vuejs/babel-plugin-jsx.git
				synced 2025-10-31 09:22:19 +08:00 
			
		
		
		
	refactor: directives
This commit is contained in:
		
							
								
								
									
										18
									
								
								.jest.js
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								.jest.js
									
									
									
									
									
								
							| @@ -1,17 +1,9 @@ | ||||
| const path = require('path') | ||||
| const jsxInjectionPATH = 'PACKAGE/lib/jsxInjection'; | ||||
| const { jsxRender,jsxMergeProps } = require("./lib/jsxInjection"); | ||||
| const { h, mergeProps } = require('vue'); | ||||
|  | ||||
| module.exports = { | ||||
|   moduleNameMapper:{ | ||||
|     [jsxInjectionPATH]:path.resolve(__dirname,'./lib/jsxInjection') | ||||
|   }, | ||||
|   "transform": { | ||||
|     "^.+\\.[t|j]sx?$": "babel-jest" | ||||
|   }, | ||||
|   modulePaths :["<rootDir>/lib/"], | ||||
|   globals: { | ||||
|     _jsxRender:jsxRender, | ||||
|     _jsxMergeProps:jsxMergeProps | ||||
|   } | ||||
|     '_h': h, | ||||
|     '_mergeProps': mergeProps | ||||
|   }, | ||||
|   setupFiles: ['./test/setup.js'], | ||||
| } | ||||
|   | ||||
| @@ -1,69 +0,0 @@ | ||||
| const { | ||||
|   h, resolveDirective, withDirectives, mergeProps, | ||||
| } = require('vue'); | ||||
|  | ||||
| function isObject(val) { | ||||
|   return val !== null && typeof val === 'object'; | ||||
| } | ||||
| function isVNode(value) { | ||||
|   // eslint-disable-next-line no-underscore-dangle | ||||
|   return value ? value._isVNode === true : false; | ||||
| } | ||||
|  | ||||
| function handleDirective(directives) { | ||||
|   return directives.map((item) => { | ||||
|     // handle situation: <a v-cust={{value,modifiers,arg}} /> | ||||
|     // eslint-disable-next-line no-underscore-dangle | ||||
|     if (item._internal_directive_flag && item.value && item.value && !item.modifiers && !item.arg) { | ||||
|       const directiveOption = item.value; | ||||
|       item = { | ||||
|         value: directiveOption.value, | ||||
|         modifiers: directiveOption.modifiers, | ||||
|         arg: directiveOption.arg, | ||||
|         name: item.name, | ||||
|       }; | ||||
|     } | ||||
|     if (typeof item.dir === 'string') { | ||||
|       item.name = item.dir; | ||||
|     } | ||||
|     if (typeof item.name === 'string') { | ||||
|       item.dir = resolveDirective(item.name); | ||||
|     } | ||||
|     return [item.dir, item.value, item.arg, item.modifiers]; | ||||
|   }); | ||||
| } | ||||
| function jsxRender(type, propsOrChildren, children) { | ||||
|   if ( | ||||
|     arguments.length > 1 | ||||
|         && isObject(propsOrChildren) | ||||
|         && !Array.isArray(propsOrChildren) | ||||
|         && !isVNode(propsOrChildren) | ||||
|   ) { | ||||
|     const { directives } = propsOrChildren; | ||||
|     if (directives && directives.length > 0) { | ||||
|       const directivesArr = handleDirective(directives); | ||||
|       delete propsOrChildren.directives; | ||||
|       return withDirectives(h.call(this, type, propsOrChildren, children), directivesArr); | ||||
|     } | ||||
|   } | ||||
|   return h.call(this, type, propsOrChildren, children); | ||||
| } | ||||
| function jsxMergeProps(...arg) { | ||||
|   arg = arg.map((props) => { | ||||
|     props.onJSXTEMPDirectives = props.directives; | ||||
|     return props; | ||||
|   }); | ||||
|   // 'directives' should be merged like 'on' | ||||
|   const result = mergeProps(...arg); | ||||
|   result.directives = result.onJSXTEMPDirectives; | ||||
|   delete result.onJSXTEMPDirectives; | ||||
|   if (!result.directives || result.directives.length === 0) { | ||||
|     delete result.directives; | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   jsxRender, | ||||
|   jsxMergeProps, | ||||
| }; | ||||
| @@ -2,13 +2,11 @@ const syntaxJsx = require('@babel/plugin-syntax-jsx').default; | ||||
| const t = require('@babel/types'); | ||||
| const htmlTags = require('html-tags'); | ||||
| const svgTags = require('svg-tags'); | ||||
| const helperModuleImports = require('@babel/helper-module-imports'); | ||||
| const { addNamed } = require('@babel/helper-module-imports'); | ||||
|  | ||||
| const jsxInjectionPATH = 'PACKAGE/lib/jsxInjection'; | ||||
| const xlinkRE = /^xlink([A-Z])/; | ||||
| const eventRE = /^on[A-Z][a-z]+$/; | ||||
| const rootAttributes = ['class', 'style']; | ||||
| const dirRE = /^v-/; | ||||
|  | ||||
| /** | ||||
|  * click --> onClick | ||||
| @@ -22,6 +20,13 @@ const filterEmpty = (value) => ( | ||||
|     && !t.isJSXEmptyExpression(value) | ||||
| ); | ||||
|  | ||||
| /** | ||||
|  * Checks if string is describing a directive | ||||
|  * @param src string | ||||
|  */ | ||||
| const isDirective = (src) => src.startsWith('v-') | ||||
|   || (src.startsWith('v') && src.length >= 2 && src[1] >= 'A' && src[1] <= 'Z'); | ||||
|  | ||||
| /** | ||||
|  * Transform JSXMemberExpression to MemberExpression | ||||
|  * @param path JSXMemberExpression | ||||
| @@ -86,7 +91,7 @@ const getJSXAttributeValue = (path, injected) => { | ||||
|   return null; | ||||
| }; | ||||
|  | ||||
| const transformJSXAttribute = (path, attributesToMerge, injected, directives) => { | ||||
| const transformJSXAttribute = (path, attributesToMerge, directives, injected) => { | ||||
|   let name = getJSXAttributeName(path); | ||||
|   if (name === 'on') { | ||||
|     const { properties = [] } = getJSXAttributeValue(path); | ||||
| @@ -100,11 +105,15 @@ const transformJSXAttribute = (path, attributesToMerge, injected, directives) => | ||||
|     }); | ||||
|     return null; | ||||
|   } | ||||
|   if (dirRE.test(name)) { | ||||
|     directives.push(t.objectExpression([ | ||||
|       t.objectProperty(t.identifier('name'), t.stringLiteral(name.replace(dirRE, ''))), | ||||
|       t.objectProperty(t.identifier('value'), getJSXAttributeValue(path)), | ||||
|       t.objectProperty(t.identifier('_internal_directive_flag'), t.booleanLiteral(true)), | ||||
|   if (isDirective(name)) { | ||||
|     const directiveName = name.startsWith('v-') | ||||
|       ? name.replace('v-', '') | ||||
|       : name.replace(`v${name[1]}`, name[1].toLowerCase()); | ||||
|     directives.push(t.arrayExpression([ | ||||
|       t.callExpression(injected.resolveDirective, [ | ||||
|         t.stringLiteral(directiveName), | ||||
|       ]), | ||||
|       getJSXAttributeValue(path), | ||||
|     ])); | ||||
|     return null; | ||||
|   } | ||||
| @@ -157,11 +166,11 @@ const transformJSXSpreadAttribute = (path, attributesToMerge) => { | ||||
|   }))); | ||||
| }; | ||||
|  | ||||
| const transformAttribute = (path, attributesToMerge, injected, directives) => (path.isJSXAttribute() | ||||
|   ? transformJSXAttribute(path, attributesToMerge, injected, directives) | ||||
| const transformAttribute = (path, attributesToMerge, directives, injected) => (path.isJSXAttribute() | ||||
|   ? transformJSXAttribute(path, attributesToMerge, directives, injected) | ||||
|   : transformJSXSpreadAttribute(path, attributesToMerge)); | ||||
|  | ||||
| const getAttributes = (path, injected) => { | ||||
| const getAttributes = (path, directives, injected) => { | ||||
|   const attributes = path.get('openingElement').get('attributes'); | ||||
|   if (attributes.length === 0) { | ||||
|     return t.nullLiteral(); | ||||
| @@ -169,10 +178,9 @@ const getAttributes = (path, injected) => { | ||||
|  | ||||
|   const attributesToMerge = []; | ||||
|   const attributeArray = []; | ||||
|   const directives = []; | ||||
|   attributes | ||||
|     .forEach((attribute) => { | ||||
|       const attr = transformAttribute(attribute, attributesToMerge, injected, directives); | ||||
|       const attr = transformAttribute(attribute, attributesToMerge, directives, injected); | ||||
|       if (attr) { | ||||
|         attributeArray.push(attr); | ||||
|       } | ||||
| @@ -182,7 +190,6 @@ const getAttributes = (path, injected) => { | ||||
|     [ | ||||
|       ...attributesToMerge, | ||||
|       t.objectExpression(attributeArray), | ||||
|       t.objectExpression([t.objectProperty(t.identifier('directives'), t.arrayExpression(directives))]), | ||||
|     ], | ||||
|   ); | ||||
| }; | ||||
| @@ -278,11 +285,21 @@ const getChildren = (paths, injected) => paths | ||||
|     throw new Error(`getChildren: ${path.type} is not supported`); | ||||
|   }).filter(filterEmpty); | ||||
|  | ||||
| const transformJSXElement = (path, injected) => t.callExpression(injected.h, [ | ||||
|   getTag(path), | ||||
|   getAttributes(path, injected), | ||||
|   t.arrayExpression(getChildren(path.get('children'), injected)), | ||||
| ]); | ||||
| const transformJSXElement = (path, injected) => { | ||||
|   const directives = []; | ||||
|   const h = t.callExpression(injected.h, [ | ||||
|     getTag(path), | ||||
|     getAttributes(path, directives, injected), | ||||
|     t.arrayExpression(getChildren(path.get('children'), injected)), | ||||
|   ]); | ||||
|   if (!directives.length) { | ||||
|     return h; | ||||
|   } | ||||
|   return t.callExpression(injected.withDirectives, [ | ||||
|     h, | ||||
|     t.arrayExpression(directives), | ||||
|   ]); | ||||
| }; | ||||
|  | ||||
| module.exports = () => ({ | ||||
|   name: 'babel-plugin-transform-vue-jsx', | ||||
| @@ -291,15 +308,23 @@ module.exports = () => ({ | ||||
|     JSXElement: { | ||||
|       exit(path, state) { | ||||
|         if (!state.vueCreateElementInjected) { | ||||
|           state.vueCreateElementInjected = helperModuleImports.addNamed(path, 'jsxRender', jsxInjectionPATH); | ||||
|           state.vueCreateElementInjected = addNamed(path, 'h', 'vue'); | ||||
|         } | ||||
|         if (!state.vueMergePropsInjected) { | ||||
|           state.vueMergePropsInjected = helperModuleImports.addNamed(path, 'jsxMergeProps', jsxInjectionPATH); | ||||
|           state.vueMergePropsInjected = addNamed(path, 'mergeProps', 'vue'); | ||||
|         } | ||||
|         if (!state.vueWithDirectivesInjected) { | ||||
|           state.vueWithDirectivesInjected = addNamed(path, 'withDirectives', 'vue'); | ||||
|         } | ||||
|         if (!state.vueResolveDirectiveInjected) { | ||||
|           state.vueResolveDirectiveInjected = addNamed(path, 'resolveDirective', 'vue'); | ||||
|         } | ||||
|         path.replaceWith( | ||||
|           transformJSXElement(path, { | ||||
|             h: state.vueCreateElementInjected, | ||||
|             mergeProps: state.vueMergePropsInjected, | ||||
|             withDirectives: state.vueWithDirectivesInjected, | ||||
|             resolveDirective: state.vueResolveDirectiveInjected, | ||||
|           }), | ||||
|         ); | ||||
|       }, | ||||
|   | ||||
| @@ -1,64 +0,0 @@ | ||||
| import { shallowMount } from '@vue/test-utils'; | ||||
| import { defineComponent } from 'vue'; | ||||
|  | ||||
| test('directive', () => { | ||||
|   const calls = []; | ||||
|   const customDirective = { | ||||
|     mounted() { | ||||
|       calls.push(1); | ||||
|     }, | ||||
|   }; | ||||
|   const compoentA = defineComponent({ | ||||
|     directives: { custom: customDirective }, | ||||
|     render: () => ( | ||||
|       <a | ||||
|         v-custom={{ | ||||
|           value: 123, | ||||
|           modifiers: { modifier: true }, | ||||
|           arg: 'arg', | ||||
|         }} | ||||
|       /> | ||||
|     ), | ||||
|   }); | ||||
|   const wrapper = shallowMount(compoentA); | ||||
|   const node = wrapper.vm.$.subTree; | ||||
|   expect(calls).toEqual(expect.arrayContaining([1])); | ||||
|   expect(node.dirs).toHaveLength(1); | ||||
|   expect(node.dirs[0]).toMatchObject({ | ||||
|     modifiers: { modifier: true }, | ||||
|     dir: customDirective, | ||||
|     arg: 'arg', | ||||
|     value: 123, | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| test('directive in spread object property', () => { | ||||
|   const calls = []; | ||||
|   const customDirective = { | ||||
|     mounted() { | ||||
|       calls.push(1); | ||||
|     }, | ||||
|   }; | ||||
|   const directives = [ | ||||
|     { | ||||
|       name: 'custom-directive', | ||||
|       value: 123, | ||||
|       modifiers: { modifier: true }, | ||||
|       arg: 'arg', | ||||
|     }, | ||||
|   ]; | ||||
|   const compoentA = defineComponent({ | ||||
|     directives: { customDirective }, | ||||
|     render: () => <a {...{ directives }}>123</a>, | ||||
|   }); | ||||
|   const wrapper = shallowMount(compoentA); | ||||
|   const node = wrapper.vm.$.subTree; | ||||
|   expect(calls).toEqual(expect.arrayContaining([1])); | ||||
|   expect(node.dirs).toHaveLength(1); | ||||
|   expect(node.dirs[0]).toMatchObject({ | ||||
|     modifiers: { modifier: true }, | ||||
|     dir: customDirective, | ||||
|     arg: 'arg', | ||||
|     value: 123, | ||||
|   }); | ||||
| }); | ||||
| @@ -145,7 +145,7 @@ test('domProps input[checked]', () => { | ||||
|     }, | ||||
|   }); | ||||
|  | ||||
|   expect(wrapper.componentVM); | ||||
|   expect(wrapper.vm.$.subTree.props.checked).toBe(val); | ||||
| }); | ||||
|  | ||||
| test('domProps option[selected]', () => { | ||||
| @@ -155,7 +155,18 @@ test('domProps option[selected]', () => { | ||||
|       return <option selected={val} />; | ||||
|     }, | ||||
|   }); | ||||
|   expect(wrapper); | ||||
|   expect(wrapper.vm.$.subTree.props.selected).toBe(val); | ||||
| }); | ||||
|  | ||||
| test('domProps video[muted]', () => { | ||||
|   const val = 'foo'; | ||||
|   const wrapper = shallowMount({ | ||||
|     render() { | ||||
|       return <video muted={val} />; | ||||
|     }, | ||||
|   }); | ||||
|  | ||||
|   expect(wrapper.vm.$.subTree.props.muted).toBe(val); | ||||
| }); | ||||
|  | ||||
| test('Spread (single object expression)', () => { | ||||
| @@ -190,6 +201,7 @@ test('Spread (mixed)', async () => { | ||||
|           {...data} | ||||
|           class={{ c: true }} | ||||
|           onClick={() => calls.push(4)} | ||||
|           hook-insert={() => calls.push(2)} | ||||
|         /> | ||||
|       ); | ||||
|     }, | ||||
| @@ -204,3 +216,29 @@ test('Spread (mixed)', async () => { | ||||
|  | ||||
|   expect(calls).toEqual(expect.arrayContaining([3, 4])); | ||||
| }); | ||||
|  | ||||
| test('directive', () => { | ||||
|   const calls = []; | ||||
|   const customDirective = { | ||||
|     mounted() { | ||||
|       calls.push(1); | ||||
|     }, | ||||
|   }; | ||||
|   const wrapper = shallowMount(({ | ||||
|     directives: { custom: customDirective }, | ||||
|     setup() { | ||||
|       return () => ( | ||||
|         <a | ||||
|           v-custom={{ | ||||
|             value: 123, | ||||
|             modifiers: { modifier: true }, | ||||
|             arg: 'arg', | ||||
|           }} | ||||
|         /> | ||||
|       ); | ||||
|     }, | ||||
|   })); | ||||
|   const node = wrapper.vm.$.subTree; | ||||
|   expect(calls).toEqual(expect.arrayContaining([1])); | ||||
|   expect(node.dirs).toHaveLength(1); | ||||
| }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user