mirror of
				https://github.com/vuejs/babel-plugin-jsx.git
				synced 2025-11-01 01:42:21 +08:00 
			
		
		
		
	feat: add directive feature
This commit is contained in:
		
							
								
								
									
										19
									
								
								.jest.js
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								.jest.js
									
									
									
									
									
								
							| @@ -1,8 +1,17 @@ | ||||
| const { h, mergeProps } = require('vue'); | ||||
| const path = require('path') | ||||
| const jsxInjectionPATH = 'PACKAGE/lib/jsxInjection'; | ||||
| const { jsxRender,jsxMergeProps } = require("./lib/jsxInjection"); | ||||
|  | ||||
| module.exports = { | ||||
|   globals: { | ||||
|     "_h": h, | ||||
|     "_mergeProps": mergeProps | ||||
|   moduleNameMapper:{ | ||||
|     [jsxInjectionPATH]:path.resolve(__dirname,'./lib/jsxInjection') | ||||
|   }, | ||||
|   setupFiles: ['./tests/setup.js'], | ||||
|   "transform": { | ||||
|     "^.+\\.[t|j]sx?$": "babel-jest" | ||||
|   }, | ||||
|   modulePaths :["<rootDir>/lib/"], | ||||
|   globals: { | ||||
|     _jsxRender:jsxRender, | ||||
|     _jsxMergeProps:jsxMergeProps | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										69
									
								
								lib/jsxInjection.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								lib/jsxInjection.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| 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, | ||||
| }; | ||||
| @@ -4,9 +4,11 @@ const htmlTags = require('html-tags'); | ||||
| const svgTags = require('svg-tags'); | ||||
| const helperModuleImports = 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 | ||||
| @@ -84,7 +86,7 @@ const getJSXAttributeValue = (path, injected) => { | ||||
|   return null; | ||||
| }; | ||||
|  | ||||
| const transformJSXAttribute = (path, attributesToMerge, injected) => { | ||||
| const transformJSXAttribute = (path, attributesToMerge, injected, directives) => { | ||||
|   let name = getJSXAttributeName(path); | ||||
|   if (name === 'on') { | ||||
|     const { properties = [] } = getJSXAttributeValue(path); | ||||
| @@ -98,6 +100,14 @@ const transformJSXAttribute = (path, attributesToMerge, injected) => { | ||||
|     }); | ||||
|     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)), | ||||
|     ])); | ||||
|     return null; | ||||
|   } | ||||
|   if (rootAttributes.includes(name) || eventRE.test(name)) { | ||||
|     attributesToMerge.push( | ||||
|       t.objectExpression([ | ||||
| @@ -147,8 +157,8 @@ const transformJSXSpreadAttribute = (path, attributesToMerge) => { | ||||
|   }))); | ||||
| }; | ||||
|  | ||||
| const transformAttribute = (path, attributesToMerge, injected) => (path.isJSXAttribute() | ||||
|   ? transformJSXAttribute(path, attributesToMerge, injected) | ||||
| const transformAttribute = (path, attributesToMerge, injected, directives) => (path.isJSXAttribute() | ||||
|   ? transformJSXAttribute(path, attributesToMerge, injected, directives) | ||||
|   : transformJSXSpreadAttribute(path, attributesToMerge)); | ||||
|  | ||||
| const getAttributes = (path, injected) => { | ||||
| @@ -159,9 +169,10 @@ const getAttributes = (path, injected) => { | ||||
|  | ||||
|   const attributesToMerge = []; | ||||
|   const attributeArray = []; | ||||
|   const directives = []; | ||||
|   attributes | ||||
|     .forEach((attribute) => { | ||||
|       const attr = transformAttribute(attribute, attributesToMerge, injected); | ||||
|       const attr = transformAttribute(attribute, attributesToMerge, injected, directives); | ||||
|       if (attr) { | ||||
|         attributeArray.push(attr); | ||||
|       } | ||||
| @@ -171,6 +182,7 @@ const getAttributes = (path, injected) => { | ||||
|     [ | ||||
|       ...attributesToMerge, | ||||
|       t.objectExpression(attributeArray), | ||||
|       t.objectExpression([t.objectProperty(t.identifier('directives'), t.arrayExpression(directives))]), | ||||
|     ], | ||||
|   ); | ||||
| }; | ||||
| @@ -279,10 +291,10 @@ module.exports = () => ({ | ||||
|     JSXElement: { | ||||
|       exit(path, state) { | ||||
|         if (!state.vueCreateElementInjected) { | ||||
|           state.vueCreateElementInjected = helperModuleImports.addNamed(path, 'h', 'vue'); | ||||
|           state.vueCreateElementInjected = helperModuleImports.addNamed(path, 'jsxRender', jsxInjectionPATH); | ||||
|         } | ||||
|         if (!state.vueMergePropsInjected) { | ||||
|           state.vueMergePropsInjected = helperModuleImports.addNamed(path, 'mergeProps', 'vue'); | ||||
|           state.vueMergePropsInjected = helperModuleImports.addNamed(path, 'jsxMergeProps', jsxInjectionPATH); | ||||
|         } | ||||
|         path.replaceWith( | ||||
|           transformJSXElement(path, { | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import { shallowMount } from '@vue/test-utils'; | ||||
| import { defineComponent } from 'vue'; | ||||
| import { createMountedApp } from './util'; | ||||
|  | ||||
| test('directive', () => { | ||||
|   const calls = []; | ||||
|   const customDirective = { | ||||
| @@ -7,30 +8,30 @@ test('directive', () => { | ||||
|       calls.push(1); | ||||
|     }, | ||||
|   }; | ||||
|   const compoentA = defineComponent( | ||||
|     { | ||||
|       directives: { customDirective }, | ||||
|   const compoentA = defineComponent({ | ||||
|     directives: { custom: customDirective }, | ||||
|     render: () => ( | ||||
|         <div | ||||
|           v-custom-directive={ | ||||
|             { | ||||
|       <a | ||||
|         v-custom={{ | ||||
|           value: 123, | ||||
|           modifiers: { modifier: true }, | ||||
|           arg: 'arg', | ||||
|             } | ||||
|           } /> | ||||
|         }} | ||||
|       /> | ||||
|     ), | ||||
|     }, | ||||
|   ); | ||||
|   const { node } = createMountedApp(compoentA); | ||||
|   }); | ||||
|   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, | ||||
|     modifiers: { modifier: true }, | ||||
|     dir: customDirective, | ||||
|     arg: 'arg', | ||||
|     value: 123, | ||||
|   }); | ||||
| }); | ||||
|  | ||||
|  | ||||
| test('directive in spread object property', () => { | ||||
|   const calls = []; | ||||
|   const customDirective = { | ||||
| @@ -40,19 +41,24 @@ test('directive in spread object property', () => { | ||||
|   }; | ||||
|   const directives = [ | ||||
|     { | ||||
|       name: 'custom-directive', value: 123, modifiers: { modifier: true }, arg: 'arg', | ||||
|       name: 'custom-directive', | ||||
|       value: 123, | ||||
|       modifiers: { modifier: true }, | ||||
|       arg: 'arg', | ||||
|     }, | ||||
|   ]; | ||||
|   const compoentA = defineComponent( | ||||
|     { | ||||
|   const compoentA = defineComponent({ | ||||
|     directives: { customDirective }, | ||||
|       render: () => (<div {...{ directives }}>123</div>), | ||||
|     }, | ||||
|   ); | ||||
|   const { node } = createMountedApp(compoentA); | ||||
|     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: { abc: true }, dir: customDirective, arg: 'arg', value: 123, | ||||
|     modifiers: { modifier: true }, | ||||
|     dir: customDirective, | ||||
|     arg: 'arg', | ||||
|     value: 123, | ||||
|   }); | ||||
| }); | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| const path = require('path'); | ||||
|  | ||||
| const jsxInjectionPATH = 'PACKAGE/lib/jsxInjection'; | ||||
|  | ||||
| module.exports = { | ||||
|   mode: 'development', | ||||
|   devtool: 'cheap-module-eval-source-map', | ||||
| @@ -17,6 +19,14 @@ module.exports = { | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|   resolve: { | ||||
|     alias: { | ||||
|       [jsxInjectionPATH]: path.resolve( | ||||
|         __dirname, | ||||
|         './lib/jsxInjection', | ||||
|       ), | ||||
|     }, | ||||
|   }, | ||||
|   devServer: { | ||||
|     inline: true, | ||||
|     open: true, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user