mirror of
https://github.com/vuejs/babel-plugin-jsx.git
synced 2024-11-10 09:39:14 +08:00
refactor: new directive API
This commit is contained in:
parent
d495546883
commit
672f27d258
@ -137,6 +137,28 @@ export default {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* 或者使用这种实现
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<input vModel={[val, ['trim']]} />
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<A vModel={[val, 'foo', ['bar']]} />
|
||||||
|
```
|
||||||
|
|
||||||
|
会变编译成:
|
||||||
|
|
||||||
|
```js
|
||||||
|
h(A, {
|
||||||
|
'foo': val,
|
||||||
|
"fooModifiers": {
|
||||||
|
"bar": true
|
||||||
|
},
|
||||||
|
"onUpdate:foo": $event => val = $event
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
自定义指令
|
自定义指令
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
@ -145,17 +167,15 @@ const App = {
|
|||||||
setup() {
|
setup() {
|
||||||
return () => (
|
return () => (
|
||||||
<a
|
<a
|
||||||
vCustom={{
|
vCustom={[val, 'arg', ['a', 'b']]}
|
||||||
value: 123,
|
|
||||||
modifiers: { modifier: true },
|
|
||||||
arg: 'arg',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> 注意:如果想要使用 `arg`, 第二个参数需要为字符串
|
||||||
|
|
||||||
### 插槽
|
### 插槽
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
|
32
README.md
32
README.md
@ -120,7 +120,7 @@ const App = {
|
|||||||
|
|
||||||
v-model
|
v-model
|
||||||
|
|
||||||
* You should use underscore (`_`) instead of dot (`.`) for modifiers (`vModel_trim={this.test}`)
|
* You can use underscore (`_`) instead of dot (`.`) for modifiers (`vModel_trim={this.test}`)
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
export default {
|
export default {
|
||||||
@ -138,6 +138,28 @@ export default {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* Or you can use this proposal.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<input vModel={[val, ['trim']]} />
|
||||||
|
```
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<A vModel={[val, 'foo', ['bar']]} />
|
||||||
|
```
|
||||||
|
|
||||||
|
Will compile to:
|
||||||
|
|
||||||
|
```js
|
||||||
|
h(A, {
|
||||||
|
'foo': val,
|
||||||
|
"fooModifiers": {
|
||||||
|
"bar": true
|
||||||
|
},
|
||||||
|
"onUpdate:foo": $event => val = $event
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
custom directive
|
custom directive
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
@ -146,17 +168,15 @@ const App = {
|
|||||||
setup() {
|
setup() {
|
||||||
return () => (
|
return () => (
|
||||||
<a
|
<a
|
||||||
vCustom={{
|
vCustom={[val, 'arg', ['a', 'b']]}
|
||||||
value: 123,
|
|
||||||
modifiers: { modifier: true },
|
|
||||||
arg: 'arg',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> Note: You should pass the second param as string for using `arg`.
|
||||||
|
|
||||||
### Slot
|
### Slot
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
|
131
packages/babel-plugin-jsx/src/parseDirectives.ts
Normal file
131
packages/babel-plugin-jsx/src/parseDirectives.ts
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import * as t from '@babel/types';
|
||||||
|
import { NodePath } from '@babel/traverse';
|
||||||
|
import { createIdentifier } from './utils';
|
||||||
|
import { State, ExcludesBoolean } from './';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get JSX element type
|
||||||
|
*
|
||||||
|
* @param path Path<JSXOpeningElement>
|
||||||
|
*/
|
||||||
|
const getType = (path: NodePath<t.JSXOpeningElement>) => {
|
||||||
|
const typePath = path
|
||||||
|
.get('attributes')
|
||||||
|
.find((attribute) => {
|
||||||
|
if (!t.isJSXAttribute(attribute)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return t.isJSXIdentifier(attribute.get('name'))
|
||||||
|
&& (attribute.get('name') as NodePath<t.JSXIdentifier>).get('name') === 'type'
|
||||||
|
&& t.isStringLiteral(attribute.get('value'))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return typePath ? typePath.get('value.value') : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseModifiers = (value: t.Expression) => {
|
||||||
|
let modifiers: string[] = [];
|
||||||
|
if (t.isArrayExpression(value)) {
|
||||||
|
modifiers = (value as t.ArrayExpression).elements.map(el => t.isStringLiteral(el) ? el.value : '').filter(Boolean)
|
||||||
|
}
|
||||||
|
return modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseDirectives = (args: {
|
||||||
|
name: string,
|
||||||
|
path: NodePath<t.JSXAttribute>,
|
||||||
|
value: t.StringLiteral | t.Expression | null,
|
||||||
|
state: State,
|
||||||
|
tag: t.Identifier | t.MemberExpression | t.StringLiteral | t.CallExpression,
|
||||||
|
isComponent: boolean
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
name, path, value, state, tag, isComponent,
|
||||||
|
} = args
|
||||||
|
let modifiers: string[] = name.split('_');
|
||||||
|
let arg;
|
||||||
|
let val;
|
||||||
|
|
||||||
|
const directiveName: string = modifiers.shift()
|
||||||
|
?.replace(/^v/, '')
|
||||||
|
.replace(/^-/, '')
|
||||||
|
.replace(/^\S/, (s: string) => s.toLowerCase()) || '';
|
||||||
|
|
||||||
|
if (directiveName === 'model' && !t.isJSXExpressionContainer(path.get('value'))) {
|
||||||
|
throw new Error('You have to use JSX Expression inside your v-model');
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasDirective = directiveName !== 'model' || (directiveName === 'model' && !isComponent);
|
||||||
|
|
||||||
|
if (t.isArrayExpression(value)) {
|
||||||
|
const { elements } = value as t.ArrayExpression;
|
||||||
|
const [first, second, third] = elements;
|
||||||
|
if (t.isStringLiteral(second)) {
|
||||||
|
arg = second;
|
||||||
|
modifiers = parseModifiers(third as t.Expression);
|
||||||
|
} else if (second) {
|
||||||
|
modifiers = parseModifiers(second as t.Expression);
|
||||||
|
}
|
||||||
|
val = first;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifiersSet = new Set(modifiers);
|
||||||
|
|
||||||
|
return {
|
||||||
|
directiveName,
|
||||||
|
modifiers: modifiersSet,
|
||||||
|
value: val || value,
|
||||||
|
arg,
|
||||||
|
directive: hasDirective ? [
|
||||||
|
resolveDirective(path, state, tag, directiveName),
|
||||||
|
val as t.Identifier,
|
||||||
|
!!modifiersSet.size && t.unaryExpression('void', t.numericLiteral(0), true),
|
||||||
|
!!modifiersSet.size && t.objectExpression(
|
||||||
|
[...modifiersSet].map(
|
||||||
|
(modifier) => t.objectProperty(
|
||||||
|
t.identifier(modifier as string),
|
||||||
|
t.booleanLiteral(true),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
].filter(Boolean as any as ExcludesBoolean) : undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveDirective = (path: NodePath<t.JSXAttribute>, state: State, tag: any, directiveName: string) => {
|
||||||
|
if (directiveName === 'show') {
|
||||||
|
return createIdentifier(state, 'vShow');
|
||||||
|
}
|
||||||
|
if (directiveName === 'model') {
|
||||||
|
let modelToUse;
|
||||||
|
const type = getType(path.parentPath as NodePath<t.JSXOpeningElement>);
|
||||||
|
switch (tag.value) {
|
||||||
|
case 'select':
|
||||||
|
modelToUse = createIdentifier(state, 'vModelSelect');
|
||||||
|
break;
|
||||||
|
case 'textarea':
|
||||||
|
modelToUse = createIdentifier(state, 'vModelText');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
switch (type) {
|
||||||
|
case 'checkbox':
|
||||||
|
modelToUse = createIdentifier(state, 'vModelCheckbox');
|
||||||
|
break;
|
||||||
|
case 'radio':
|
||||||
|
modelToUse = createIdentifier(state, 'vModelRadio');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
modelToUse = createIdentifier(state, 'vModelText');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modelToUse;
|
||||||
|
}
|
||||||
|
return t.callExpression(
|
||||||
|
createIdentifier(state, 'resolveDirective'), [
|
||||||
|
t.stringLiteral(directiveName),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default parseDirectives;
|
@ -10,10 +10,10 @@ import {
|
|||||||
getJSXAttributeName,
|
getJSXAttributeName,
|
||||||
transformJSXText,
|
transformJSXText,
|
||||||
transformJSXExpressionContainer,
|
transformJSXExpressionContainer,
|
||||||
parseDirectives,
|
|
||||||
isFragment,
|
isFragment,
|
||||||
walksScope,
|
walksScope,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
import parseDirectives from './parseDirectives';
|
||||||
import { PatchFlags, PatchFlagNames } from './patchFlags';
|
import { PatchFlags, PatchFlagNames } from './patchFlags';
|
||||||
import { State, ExcludesBoolean } from './';
|
import { State, ExcludesBoolean } from './';
|
||||||
|
|
||||||
@ -198,7 +198,7 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isDirective(name)) {
|
if (isDirective(name)) {
|
||||||
const { directive, modifiers, directiveName } = parseDirectives({
|
const { directive, modifiers, value, arg, directiveName } = parseDirectives({
|
||||||
tag,
|
tag,
|
||||||
isComponent,
|
isComponent,
|
||||||
name,
|
name,
|
||||||
@ -206,6 +206,8 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
|
|||||||
state,
|
state,
|
||||||
value: attributeValue,
|
value: attributeValue,
|
||||||
});
|
});
|
||||||
|
const argVal = (arg as t.StringLiteral)?.value;
|
||||||
|
const propName = argVal || 'modelValue';
|
||||||
|
|
||||||
if (directiveName === 'slots') {
|
if (directiveName === 'slots') {
|
||||||
slots = attributeValue;
|
slots = attributeValue;
|
||||||
@ -215,16 +217,16 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
|
|||||||
} else {
|
} else {
|
||||||
// must be v-model and is a component
|
// must be v-model and is a component
|
||||||
properties.push(t.objectProperty(
|
properties.push(t.objectProperty(
|
||||||
t.stringLiteral('modelValue'),
|
arg || t.stringLiteral('modelValue'),
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
attributeValue,
|
value,
|
||||||
));
|
));
|
||||||
|
|
||||||
dynamicPropNames.add('modelValue');
|
dynamicPropNames.add(propName);
|
||||||
|
|
||||||
if (modifiers.size) {
|
if (modifiers.size) {
|
||||||
properties.push(t.objectProperty(
|
properties.push(t.objectProperty(
|
||||||
t.stringLiteral('modelModifiers'),
|
t.stringLiteral(`${argVal || 'model'}Modifiers`),
|
||||||
t.objectExpression(
|
t.objectExpression(
|
||||||
[...modifiers].map((modifier) => (
|
[...modifiers].map((modifier) => (
|
||||||
t.objectProperty(
|
t.objectProperty(
|
||||||
@ -237,17 +239,19 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (directiveName === 'model' && attributeValue) {
|
console.log(value)
|
||||||
|
|
||||||
|
if (directiveName === 'model' && value) {
|
||||||
properties.push(t.objectProperty(
|
properties.push(t.objectProperty(
|
||||||
t.stringLiteral('onUpdate:modelValue'),
|
t.stringLiteral(`onUpdate:${propName}`),
|
||||||
t.arrowFunctionExpression(
|
t.arrowFunctionExpression(
|
||||||
[t.identifier('$event')],
|
[t.identifier('$event')],
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
t.assignmentExpression('=', attributeValue, t.identifier('$event')),
|
t.assignmentExpression('=', value, t.identifier('$event')),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
dynamicPropNames.add('onUpdate:modelValue');
|
dynamicPropNames.add(`onUpdate:${propName}`);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import * as t from '@babel/types';
|
|||||||
import htmlTags from 'html-tags';
|
import htmlTags from 'html-tags';
|
||||||
import svgTags from 'svg-tags';
|
import svgTags from 'svg-tags';
|
||||||
import { NodePath } from '@babel/traverse';
|
import { NodePath } from '@babel/traverse';
|
||||||
import { State, ExcludesBoolean } from './';
|
import { State } from './';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create Identifier
|
* create Identifier
|
||||||
@ -169,111 +169,6 @@ const transformJSXSpreadChild = (
|
|||||||
path: NodePath<t.JSXSpreadChild>
|
path: NodePath<t.JSXSpreadChild>
|
||||||
): t.SpreadElement => t.spreadElement(path.get('expression').node);
|
): t.SpreadElement => t.spreadElement(path.get('expression').node);
|
||||||
|
|
||||||
/**
|
|
||||||
* Get JSX element type
|
|
||||||
*
|
|
||||||
* @param path Path<JSXOpeningElement>
|
|
||||||
*/
|
|
||||||
const getType = (path: NodePath<t.JSXOpeningElement>) => {
|
|
||||||
const typePath = path
|
|
||||||
.get('attributes')
|
|
||||||
.find((attribute) => {
|
|
||||||
if (!t.isJSXAttribute(attribute)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return t.isJSXIdentifier(attribute.get('name'))
|
|
||||||
&& (attribute.get('name') as NodePath<t.JSXIdentifier>).get('name') === 'type'
|
|
||||||
&& t.isStringLiteral(attribute.get('value'))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return typePath ? typePath.get('value.value') : '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolveDirective = (path: NodePath<t.JSXAttribute>, state: State, tag: any, directiveName: string) => {
|
|
||||||
if (directiveName === 'show') {
|
|
||||||
return createIdentifier(state, 'vShow');
|
|
||||||
} if (directiveName === 'model') {
|
|
||||||
let modelToUse;
|
|
||||||
const type = getType(path.parentPath as NodePath<t.JSXOpeningElement>);
|
|
||||||
switch (tag.value) {
|
|
||||||
case 'select':
|
|
||||||
modelToUse = createIdentifier(state, 'vModelSelect');
|
|
||||||
break;
|
|
||||||
case 'textarea':
|
|
||||||
modelToUse = createIdentifier(state, 'vModelText');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
switch (type) {
|
|
||||||
case 'checkbox':
|
|
||||||
modelToUse = createIdentifier(state, 'vModelCheckbox');
|
|
||||||
break;
|
|
||||||
case 'radio':
|
|
||||||
modelToUse = createIdentifier(state, 'vModelRadio');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
modelToUse = createIdentifier(state, 'vModelText');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return modelToUse;
|
|
||||||
}
|
|
||||||
return t.callExpression(
|
|
||||||
createIdentifier(state, 'resolveDirective'), [
|
|
||||||
t.stringLiteral(directiveName),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse directives metadata
|
|
||||||
*
|
|
||||||
* @param path JSXAttribute
|
|
||||||
* @returns null | Object<{ modifiers: Set<string>, valuePath: Path<Expression>}>
|
|
||||||
*/
|
|
||||||
const parseDirectives = (args: {
|
|
||||||
name: string,
|
|
||||||
path: NodePath<t.JSXAttribute>,
|
|
||||||
value: t.StringLiteral | t.Expression | null,
|
|
||||||
state: State,
|
|
||||||
tag: t.Identifier | t.MemberExpression | t.StringLiteral | t.CallExpression,
|
|
||||||
isComponent: boolean
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
name, path, value, state, tag, isComponent,
|
|
||||||
} = args
|
|
||||||
const modifiers: string[] = name.split('_');
|
|
||||||
const directiveName: string = modifiers.shift()
|
|
||||||
?.replace(/^v/, '')
|
|
||||||
.replace(/^-/, '')
|
|
||||||
.replace(/^\S/, (s: string) => s.toLowerCase()) || '';
|
|
||||||
|
|
||||||
if (directiveName === 'model' && !t.isJSXExpressionContainer(path.get('value'))) {
|
|
||||||
throw new Error('You have to use JSX Expression inside your v-model');
|
|
||||||
}
|
|
||||||
|
|
||||||
const modifiersSet = new Set(modifiers);
|
|
||||||
|
|
||||||
const hasDirective = directiveName !== 'model' || (directiveName === 'model' && !isComponent);
|
|
||||||
|
|
||||||
return {
|
|
||||||
directiveName,
|
|
||||||
modifiers: modifiersSet,
|
|
||||||
directive: hasDirective ? [
|
|
||||||
resolveDirective(path, state, tag, directiveName),
|
|
||||||
value,
|
|
||||||
!!modifiersSet.size && t.unaryExpression('void', t.numericLiteral(0), true),
|
|
||||||
!!modifiersSet.size && t.objectExpression(
|
|
||||||
[...modifiersSet].map(
|
|
||||||
(modifier) => t.objectProperty(
|
|
||||||
t.identifier(modifier),
|
|
||||||
t.booleanLiteral(true),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
].filter(Boolean as any as ExcludesBoolean) : undefined,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const walksScope = (path: NodePath, name: string) => {
|
const walksScope = (path: NodePath, name: string) => {
|
||||||
if (path.scope.hasBinding(name) && path.parentPath) {
|
if (path.scope.hasBinding(name) && path.parentPath) {
|
||||||
path.parentPath.setData('optimize', false);
|
path.parentPath.setData('optimize', false);
|
||||||
@ -291,7 +186,6 @@ export {
|
|||||||
transformJSXText,
|
transformJSXText,
|
||||||
transformJSXSpreadChild,
|
transformJSXSpreadChild,
|
||||||
transformJSXExpressionContainer,
|
transformJSXExpressionContainer,
|
||||||
parseDirectives,
|
|
||||||
isFragment,
|
isFragment,
|
||||||
walksScope,
|
walksScope,
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user