From 80b9cb15707baa57124ff48947c4d5b6a99ae75a Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Mon, 18 May 2020 00:09:50 +0800 Subject: [PATCH] refactor: directives --- .jest.js | 18 ++----- lib/jsxInjection.js | 69 --------------------------- src/babel-plugin-transform-vue-jsx.js | 69 ++++++++++++++++++--------- test/directive.test.js | 64 ------------------------- test/index.test.js | 42 +++++++++++++++- 5 files changed, 92 insertions(+), 170 deletions(-) delete mode 100644 lib/jsxInjection.js delete mode 100644 test/directive.test.js diff --git a/.jest.js b/.jest.js index bdb0189..d8b28a5 100644 --- a/.jest.js +++ b/.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 :["/lib/"], globals: { - _jsxRender:jsxRender, - _jsxMergeProps:jsxMergeProps - } + '_h': h, + '_mergeProps': mergeProps + }, + setupFiles: ['./test/setup.js'], } diff --git a/lib/jsxInjection.js b/lib/jsxInjection.js deleted file mode 100644 index 50ee07d..0000000 --- a/lib/jsxInjection.js +++ /dev/null @@ -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: - // 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, -}; diff --git a/src/babel-plugin-transform-vue-jsx.js b/src/babel-plugin-transform-vue-jsx.js index 3728725..6cc3ea7 100644 --- a/src/babel-plugin-transform-vue-jsx.js +++ b/src/babel-plugin-transform-vue-jsx.js @@ -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, }), ); }, diff --git a/test/directive.test.js b/test/directive.test.js deleted file mode 100644 index 85a2422..0000000 --- a/test/directive.test.js +++ /dev/null @@ -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: () => ( - - ), - }); - 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: () => 123, - }); - 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, - }); -}); diff --git a/test/index.test.js b/test/index.test.js index a2bcb46..c7475b0 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -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