Skip to content

Commit 672f27d

Browse files
committed
refactor: new directive API
1 parent d495546 commit 672f27d

File tree

5 files changed

+197
-128
lines changed

5 files changed

+197
-128
lines changed

README-zh_CN.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,28 @@ export default {
137137
}
138138
```
139139
140+
* 或者使用这种实现
141+
142+
```jsx
143+
<input vModel={[val, ['trim']]} />
144+
```
145+
146+
```jsx
147+
<A vModel={[val, 'foo', ['bar']]} />
148+
```
149+
150+
会变编译成:
151+
152+
```js
153+
h(A, {
154+
'foo': val,
155+
"fooModifiers": {
156+
"bar": true
157+
},
158+
"onUpdate:foo": $event => val = $event
159+
})
160+
```
161+
140162
自定义指令
141163
142164
```jsx
@@ -145,17 +167,15 @@ const App = {
145167
setup() {
146168
return () => (
147169
<a
148-
vCustom={{
149-
value: 123,
150-
modifiers: { modifier: true },
151-
arg: 'arg',
152-
}}
170+
vCustom={[val, 'arg', ['a', 'b']]}
153171
/>
154172
);
155173
},
156174
}
157175
```
158176
177+
> 注意:如果想要使用 `arg`, 第二个参数需要为字符串
178+
159179
### 插槽
160180
161181
```jsx

README.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ const App = {
120120
121121
v-model
122122
123-
* You should use underscore (`_`) instead of dot (`.`) for modifiers (`vModel_trim={this.test}`)
123+
* You can use underscore (`_`) instead of dot (`.`) for modifiers (`vModel_trim={this.test}`)
124124
125125
```jsx
126126
export default {
@@ -138,6 +138,28 @@ export default {
138138
}
139139
```
140140
141+
* Or you can use this proposal.
142+
143+
```jsx
144+
<input vModel={[val, ['trim']]} />
145+
```
146+
147+
```jsx
148+
<A vModel={[val, 'foo', ['bar']]} />
149+
```
150+
151+
Will compile to:
152+
153+
```js
154+
h(A, {
155+
'foo': val,
156+
"fooModifiers": {
157+
"bar": true
158+
},
159+
"onUpdate:foo": $event => val = $event
160+
})
161+
```
162+
141163
custom directive
142164
143165
```jsx
@@ -146,17 +168,15 @@ const App = {
146168
setup() {
147169
return () => (
148170
<a
149-
vCustom={{
150-
value: 123,
151-
modifiers: { modifier: true },
152-
arg: 'arg',
153-
}}
171+
vCustom={[val, 'arg', ['a', 'b']]}
154172
/>
155173
);
156174
},
157175
}
158176
```
159177
178+
> Note: You should pass the second param as string for using `arg`.
179+
160180
### Slot
161181
162182
```jsx
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import * as t from '@babel/types';
2+
import { NodePath } from '@babel/traverse';
3+
import { createIdentifier } from './utils';
4+
import { State, ExcludesBoolean } from './';
5+
6+
/**
7+
* Get JSX element type
8+
*
9+
* @param path Path<JSXOpeningElement>
10+
*/
11+
const getType = (path: NodePath<t.JSXOpeningElement>) => {
12+
const typePath = path
13+
.get('attributes')
14+
.find((attribute) => {
15+
if (!t.isJSXAttribute(attribute)) {
16+
return false;
17+
}
18+
return t.isJSXIdentifier(attribute.get('name'))
19+
&& (attribute.get('name') as NodePath<t.JSXIdentifier>).get('name') === 'type'
20+
&& t.isStringLiteral(attribute.get('value'))
21+
},
22+
);
23+
24+
return typePath ? typePath.get('value.value') : '';
25+
};
26+
27+
const parseModifiers = (value: t.Expression) => {
28+
let modifiers: string[] = [];
29+
if (t.isArrayExpression(value)) {
30+
modifiers = (value as t.ArrayExpression).elements.map(el => t.isStringLiteral(el) ? el.value : '').filter(Boolean)
31+
}
32+
return modifiers;
33+
}
34+
35+
const parseDirectives = (args: {
36+
name: string,
37+
path: NodePath<t.JSXAttribute>,
38+
value: t.StringLiteral | t.Expression | null,
39+
state: State,
40+
tag: t.Identifier | t.MemberExpression | t.StringLiteral | t.CallExpression,
41+
isComponent: boolean
42+
}) => {
43+
const {
44+
name, path, value, state, tag, isComponent,
45+
} = args
46+
let modifiers: string[] = name.split('_');
47+
let arg;
48+
let val;
49+
50+
const directiveName: string = modifiers.shift()
51+
?.replace(/^v/, '')
52+
.replace(/^-/, '')
53+
.replace(/^\S/, (s: string) => s.toLowerCase()) || '';
54+
55+
if (directiveName === 'model' && !t.isJSXExpressionContainer(path.get('value'))) {
56+
throw new Error('You have to use JSX Expression inside your v-model');
57+
}
58+
59+
const hasDirective = directiveName !== 'model' || (directiveName === 'model' && !isComponent);
60+
61+
if (t.isArrayExpression(value)) {
62+
const { elements } = value as t.ArrayExpression;
63+
const [first, second, third] = elements;
64+
if (t.isStringLiteral(second)) {
65+
arg = second;
66+
modifiers = parseModifiers(third as t.Expression);
67+
} else if (second) {
68+
modifiers = parseModifiers(second as t.Expression);
69+
}
70+
val = first;
71+
}
72+
73+
const modifiersSet = new Set(modifiers);
74+
75+
return {
76+
directiveName,
77+
modifiers: modifiersSet,
78+
value: val || value,
79+
arg,
80+
directive: hasDirective ? [
81+
resolveDirective(path, state, tag, directiveName),
82+
val as t.Identifier,
83+
!!modifiersSet.size && t.unaryExpression('void', t.numericLiteral(0), true),
84+
!!modifiersSet.size && t.objectExpression(
85+
[...modifiersSet].map(
86+
(modifier) => t.objectProperty(
87+
t.identifier(modifier as string),
88+
t.booleanLiteral(true),
89+
),
90+
),
91+
),
92+
].filter(Boolean as any as ExcludesBoolean) : undefined,
93+
};
94+
};
95+
96+
const resolveDirective = (path: NodePath<t.JSXAttribute>, state: State, tag: any, directiveName: string) => {
97+
if (directiveName === 'show') {
98+
return createIdentifier(state, 'vShow');
99+
}
100+
if (directiveName === 'model') {
101+
let modelToUse;
102+
const type = getType(path.parentPath as NodePath<t.JSXOpeningElement>);
103+
switch (tag.value) {
104+
case 'select':
105+
modelToUse = createIdentifier(state, 'vModelSelect');
106+
break;
107+
case 'textarea':
108+
modelToUse = createIdentifier(state, 'vModelText');
109+
break;
110+
default:
111+
switch (type) {
112+
case 'checkbox':
113+
modelToUse = createIdentifier(state, 'vModelCheckbox');
114+
break;
115+
case 'radio':
116+
modelToUse = createIdentifier(state, 'vModelRadio');
117+
break;
118+
default:
119+
modelToUse = createIdentifier(state, 'vModelText');
120+
}
121+
}
122+
return modelToUse;
123+
}
124+
return t.callExpression(
125+
createIdentifier(state, 'resolveDirective'), [
126+
t.stringLiteral(directiveName),
127+
],
128+
);
129+
};
130+
131+
export default parseDirectives;

packages/babel-plugin-jsx/src/transform-vue-jsx.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import {
1010
getJSXAttributeName,
1111
transformJSXText,
1212
transformJSXExpressionContainer,
13-
parseDirectives,
1413
isFragment,
1514
walksScope,
1615
} from './utils';
16+
import parseDirectives from './parseDirectives';
1717
import { PatchFlags, PatchFlagNames } from './patchFlags';
1818
import { State, ExcludesBoolean } from './';
1919

@@ -198,14 +198,16 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
198198
return;
199199
}
200200
if (isDirective(name)) {
201-
const { directive, modifiers, directiveName } = parseDirectives({
201+
const { directive, modifiers, value, arg, directiveName } = parseDirectives({
202202
tag,
203203
isComponent,
204204
name,
205205
path: prop,
206206
state,
207207
value: attributeValue,
208208
});
209+
const argVal = (arg as t.StringLiteral)?.value;
210+
const propName = argVal || 'modelValue';
209211

210212
if (directiveName === 'slots') {
211213
slots = attributeValue;
@@ -215,16 +217,16 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
215217
} else {
216218
// must be v-model and is a component
217219
properties.push(t.objectProperty(
218-
t.stringLiteral('modelValue'),
220+
arg || t.stringLiteral('modelValue'),
219221
// @ts-ignore
220-
attributeValue,
222+
value,
221223
));
222224

223-
dynamicPropNames.add('modelValue');
225+
dynamicPropNames.add(propName);
224226

225227
if (modifiers.size) {
226228
properties.push(t.objectProperty(
227-
t.stringLiteral('modelModifiers'),
229+
t.stringLiteral(`${argVal || 'model'}Modifiers`),
228230
t.objectExpression(
229231
[...modifiers].map((modifier) => (
230232
t.objectProperty(
@@ -237,17 +239,19 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
237239
}
238240
}
239241

240-
if (directiveName === 'model' && attributeValue) {
242+
console.log(value)
243+
244+
if (directiveName === 'model' && value) {
241245
properties.push(t.objectProperty(
242-
t.stringLiteral('onUpdate:modelValue'),
246+
t.stringLiteral(`onUpdate:${propName}`),
243247
t.arrowFunctionExpression(
244248
[t.identifier('$event')],
245249
// @ts-ignore
246-
t.assignmentExpression('=', attributeValue, t.identifier('$event')),
250+
t.assignmentExpression('=', value, t.identifier('$event')),
247251
),
248252
));
249253

250-
dynamicPropNames.add('onUpdate:modelValue');
254+
dynamicPropNames.add(`onUpdate:${propName}`);
251255
}
252256
return;
253257
}

0 commit comments

Comments
 (0)