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 = { | module.exports = { | ||||||
|   globals: { |   moduleNameMapper:{ | ||||||
|     "_h": h, |     [jsxInjectionPATH]:path.resolve(__dirname,'./lib/jsxInjection') | ||||||
|     "_mergeProps": mergeProps |  | ||||||
|   }, |   }, | ||||||
|   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 svgTags = require('svg-tags'); | ||||||
| const helperModuleImports = require('@babel/helper-module-imports'); | const helperModuleImports = require('@babel/helper-module-imports'); | ||||||
|  |  | ||||||
|  | const jsxInjectionPATH = 'PACKAGE/lib/jsxInjection'; | ||||||
| const xlinkRE = /^xlink([A-Z])/; | const xlinkRE = /^xlink([A-Z])/; | ||||||
| const eventRE = /^on[A-Z][a-z]+$/; | const eventRE = /^on[A-Z][a-z]+$/; | ||||||
| const rootAttributes = ['class', 'style']; | const rootAttributes = ['class', 'style']; | ||||||
|  | const dirRE = /^v-/; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * click --> onClick |  * click --> onClick | ||||||
| @@ -84,7 +86,7 @@ const getJSXAttributeValue = (path, injected) => { | |||||||
|   return null; |   return null; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const transformJSXAttribute = (path, attributesToMerge, injected) => { | const transformJSXAttribute = (path, attributesToMerge, injected, directives) => { | ||||||
|   let name = getJSXAttributeName(path); |   let name = getJSXAttributeName(path); | ||||||
|   if (name === 'on') { |   if (name === 'on') { | ||||||
|     const { properties = [] } = getJSXAttributeValue(path); |     const { properties = [] } = getJSXAttributeValue(path); | ||||||
| @@ -98,6 +100,14 @@ const transformJSXAttribute = (path, attributesToMerge, injected) => { | |||||||
|     }); |     }); | ||||||
|     return null; |     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)) { |   if (rootAttributes.includes(name) || eventRE.test(name)) { | ||||||
|     attributesToMerge.push( |     attributesToMerge.push( | ||||||
|       t.objectExpression([ |       t.objectExpression([ | ||||||
| @@ -147,8 +157,8 @@ const transformJSXSpreadAttribute = (path, attributesToMerge) => { | |||||||
|   }))); |   }))); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const transformAttribute = (path, attributesToMerge, injected) => (path.isJSXAttribute() | const transformAttribute = (path, attributesToMerge, injected, directives) => (path.isJSXAttribute() | ||||||
|   ? transformJSXAttribute(path, attributesToMerge, injected) |   ? transformJSXAttribute(path, attributesToMerge, injected, directives) | ||||||
|   : transformJSXSpreadAttribute(path, attributesToMerge)); |   : transformJSXSpreadAttribute(path, attributesToMerge)); | ||||||
|  |  | ||||||
| const getAttributes = (path, injected) => { | const getAttributes = (path, injected) => { | ||||||
| @@ -159,9 +169,10 @@ const getAttributes = (path, injected) => { | |||||||
|  |  | ||||||
|   const attributesToMerge = []; |   const attributesToMerge = []; | ||||||
|   const attributeArray = []; |   const attributeArray = []; | ||||||
|  |   const directives = []; | ||||||
|   attributes |   attributes | ||||||
|     .forEach((attribute) => { |     .forEach((attribute) => { | ||||||
|       const attr = transformAttribute(attribute, attributesToMerge, injected); |       const attr = transformAttribute(attribute, attributesToMerge, injected, directives); | ||||||
|       if (attr) { |       if (attr) { | ||||||
|         attributeArray.push(attr); |         attributeArray.push(attr); | ||||||
|       } |       } | ||||||
| @@ -171,6 +182,7 @@ const getAttributes = (path, injected) => { | |||||||
|     [ |     [ | ||||||
|       ...attributesToMerge, |       ...attributesToMerge, | ||||||
|       t.objectExpression(attributeArray), |       t.objectExpression(attributeArray), | ||||||
|  |       t.objectExpression([t.objectProperty(t.identifier('directives'), t.arrayExpression(directives))]), | ||||||
|     ], |     ], | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
| @@ -279,10 +291,10 @@ module.exports = () => ({ | |||||||
|     JSXElement: { |     JSXElement: { | ||||||
|       exit(path, state) { |       exit(path, state) { | ||||||
|         if (!state.vueCreateElementInjected) { |         if (!state.vueCreateElementInjected) { | ||||||
|           state.vueCreateElementInjected = helperModuleImports.addNamed(path, 'h', 'vue'); |           state.vueCreateElementInjected = helperModuleImports.addNamed(path, 'jsxRender', jsxInjectionPATH); | ||||||
|         } |         } | ||||||
|         if (!state.vueMergePropsInjected) { |         if (!state.vueMergePropsInjected) { | ||||||
|           state.vueMergePropsInjected = helperModuleImports.addNamed(path, 'mergeProps', 'vue'); |           state.vueMergePropsInjected = helperModuleImports.addNamed(path, 'jsxMergeProps', jsxInjectionPATH); | ||||||
|         } |         } | ||||||
|         path.replaceWith( |         path.replaceWith( | ||||||
|           transformJSXElement(path, { |           transformJSXElement(path, { | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
|  | import { shallowMount } from '@vue/test-utils'; | ||||||
| import { defineComponent } from 'vue'; | import { defineComponent } from 'vue'; | ||||||
| import { createMountedApp } from './util'; |  | ||||||
| test('directive', () => { | test('directive', () => { | ||||||
|   const calls = []; |   const calls = []; | ||||||
|   const customDirective = { |   const customDirective = { | ||||||
| @@ -7,30 +8,30 @@ test('directive', () => { | |||||||
|       calls.push(1); |       calls.push(1); | ||||||
|     }, |     }, | ||||||
|   }; |   }; | ||||||
|   const compoentA = defineComponent( |   const compoentA = defineComponent({ | ||||||
|     { |     directives: { custom: customDirective }, | ||||||
|       directives: { customDirective }, |  | ||||||
|     render: () => ( |     render: () => ( | ||||||
|         <div |       <a | ||||||
|           v-custom-directive={ |         v-custom={{ | ||||||
|             { |  | ||||||
|           value: 123, |           value: 123, | ||||||
|           modifiers: { modifier: true }, |           modifiers: { modifier: true }, | ||||||
|           arg: 'arg', |           arg: 'arg', | ||||||
|             } |         }} | ||||||
|           } /> |       /> | ||||||
|     ), |     ), | ||||||
|     }, |   }); | ||||||
|   ); |   const wrapper = shallowMount(compoentA); | ||||||
|   const { node } = createMountedApp(compoentA); |   const node = wrapper.vm.$.subTree; | ||||||
|   expect(calls).toEqual(expect.arrayContaining([1])); |   expect(calls).toEqual(expect.arrayContaining([1])); | ||||||
|   expect(node.dirs).toHaveLength(1); |   expect(node.dirs).toHaveLength(1); | ||||||
|   expect(node.dirs[0]).toMatchObject({ |   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', () => { | test('directive in spread object property', () => { | ||||||
|   const calls = []; |   const calls = []; | ||||||
|   const customDirective = { |   const customDirective = { | ||||||
| @@ -40,19 +41,24 @@ test('directive in spread object property', () => { | |||||||
|   }; |   }; | ||||||
|   const directives = [ |   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 }, |     directives: { customDirective }, | ||||||
|       render: () => (<div {...{ directives }}>123</div>), |     render: () => <a {...{ directives }}>123</a>, | ||||||
|     }, |   }); | ||||||
|   ); |   const wrapper = shallowMount(compoentA); | ||||||
|   const { node } = createMountedApp(compoentA); |   const node = wrapper.vm.$.subTree; | ||||||
|   expect(calls).toEqual(expect.arrayContaining([1])); |   expect(calls).toEqual(expect.arrayContaining([1])); | ||||||
|   expect(node.dirs).toHaveLength(1); |   expect(node.dirs).toHaveLength(1); | ||||||
|   expect(node.dirs[0]).toMatchObject({ |   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 path = require('path'); | ||||||
|  |  | ||||||
|  | const jsxInjectionPATH = 'PACKAGE/lib/jsxInjection'; | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   mode: 'development', |   mode: 'development', | ||||||
|   devtool: 'cheap-module-eval-source-map', |   devtool: 'cheap-module-eval-source-map', | ||||||
| @@ -17,6 +19,14 @@ module.exports = { | |||||||
|       }, |       }, | ||||||
|     ], |     ], | ||||||
|   }, |   }, | ||||||
|  |   resolve: { | ||||||
|  |     alias: { | ||||||
|  |       [jsxInjectionPATH]: path.resolve( | ||||||
|  |         __dirname, | ||||||
|  |         './lib/jsxInjection', | ||||||
|  |       ), | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|   devServer: { |   devServer: { | ||||||
|     inline: true, |     inline: true, | ||||||
|     open: true, |     open: true, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user