Skip to content
This repository was archived by the owner on Dec 25, 2024. It is now read-only.

Commit 9dcd34e

Browse files
authored
fix: directive parser (#76)
1 parent ac7ba52 commit 9dcd34e

File tree

5 files changed

+157
-28
lines changed

5 files changed

+157
-28
lines changed

package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@
5050
"@babel/traverse": "^7.16.3",
5151
"@babel/types": "^7.16.0",
5252
"@rollup/pluginutils": "^4.1.1",
53-
"@vue/ref-transform": "^3.2.21",
54-
"@vue/shared": "^3.2.21",
53+
"@vue/compiler-core": "^3.2.23",
54+
"@vue/ref-transform": "^3.2.23",
55+
"@vue/shared": "^3.2.23",
5556
"defu": "^5.0.0",
5657
"htmlparser2": "5.0.1",
5758
"magic-string": "^0.25.7",
@@ -63,7 +64,7 @@
6364
"@types/jest": "^27.0.2",
6465
"@types/node": "^16.11.7",
6566
"@vue/composition-api": "^1.4.0",
66-
"@vue/runtime-dom": "^3.2.21",
67+
"@vue/runtime-dom": "^3.2.23",
6768
"bumpp": "^7.1.1",
6869
"eslint": "^8.2.0",
6970
"eslint-plugin-jest": "^25.2.4",

pnpm-lock.yaml

+54-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/parseSFC.ts

+40-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Parser as HTMLParser, ParserOptions as HTMLParserOptions } from 'htmlparser2'
22
import type { ParserOptions } from '@babel/parser'
3+
import { NodeTypes, baseCompile } from '@vue/compiler-core'
34
import { camelize, isHTMLTag, isSVGTag, isVoidTag } from '@vue/shared'
45
import { ParsedSFC, ScriptSetupTransformOptions, ScriptTagMeta } from '../types'
56
import { getIdentifierUsages } from './identifiers'
@@ -31,6 +32,32 @@ const BUILD_IN_DIRECTIVES = new Set([
3132
// 'ref',
3233
])
3334

35+
const parseDirective = (attr: string) => {
36+
try {
37+
const elementNode = baseCompile(`<a ${attr}></a>`).ast.children[0]
38+
if (elementNode?.type !== NodeTypes.ELEMENT) return undefined
39+
40+
const directiveNode = elementNode.props[0]
41+
if (directiveNode?.type !== NodeTypes.DIRECTIVE) return undefined
42+
43+
const { arg, modifiers, name } = directiveNode
44+
const argExpression
45+
= arg?.type !== NodeTypes.SIMPLE_EXPRESSION
46+
? undefined
47+
: arg.isStatic
48+
? JSON.stringify(arg.content)
49+
: arg.content
50+
return {
51+
argExpression,
52+
modifiers,
53+
name,
54+
}
55+
}
56+
catch (error) {
57+
return undefined
58+
}
59+
}
60+
3461
export function parseSFC(code: string, id?: string, options?: ScriptSetupTransformOptions): ParsedSFC {
3562
/** foo-bar -> FooBar */
3663
const components = new Set<string>()
@@ -78,25 +105,30 @@ export function parseSFC(code: string, id?: string, options?: ScriptSetupTransfo
78105

79106
function handleTemplateContent(name: string, attributes: Record<string, string>) {
80107
if (!isHTMLTag(name) && !isSVGTag(name) && !isVoidTag(name))
81-
components.add(pascalize((name)))
108+
components.add(pascalize(name))
82109

83110
Object.entries(attributes).forEach(([key, value]) => {
84-
if (!value)
111+
// ref
112+
if (key === 'ref') {
113+
identifiers.add(value)
85114
return
86-
if (key.startsWith('v-') || key.startsWith('@') || key.startsWith(':')) {
115+
}
116+
117+
if (value !== '' && (key.startsWith('v-') || key.startsWith('@') || key.startsWith(':'))) {
87118
if (key === 'v-for')
88119
// we strip out delectations for v-for before `in` or `of`
89120
expressions.add(`(${value.replace(/^.*\s(?:in|of)\s/, '')})`)
90121
else
91122
expressions.add(`(${value})`)
92123
}
124+
93125
if (key.startsWith('v-')) {
94-
const directiveName = key.slice('v-'.length).split(':')[0].split('.')[0]
95-
if (!BUILD_IN_DIRECTIVES.has(directiveName))
96-
directives.add(camelize(directiveName))
126+
const parsedDirective = parseDirective(key)
127+
if (parsedDirective && !BUILD_IN_DIRECTIVES.has(parsedDirective.name))
128+
directives.add(camelize(parsedDirective.name))
129+
if (parsedDirective?.argExpression)
130+
expressions.add(parsedDirective.argExpression)
97131
}
98-
if (key === 'ref')
99-
identifiers.add(value)
100132
})
101133
}
102134

test/__snapshots__/transform.test.ts.snap

+37-7
Original file line numberDiff line numberDiff line change
@@ -183,25 +183,49 @@ export default __sfc_main;
183183
exports[`transform fixtures test/fixtures/ComponentsDirectives.vue 1`] = `
184184
"<template>
185185
<div>
186-
<FooView ref=\\"fooView\\" v-foo-bar=\\"a\\" v-demo:foo.a.b=\\"message\\"></FooView>
186+
<FooView
187+
ref=\\"fooView\\"
188+
v-foo-bar=\\"message0\\"
189+
v-d0-demo:foo.a.b=\\"message1\\"
190+
v-d1-modifiers.a=\\"message2\\"
191+
v-d2-modifiers-no-value.b.c
192+
v-d3-arg:click=\\"message3\\"
193+
v-d4-arg-no-value:click
194+
v-d5-arg-dynamic:[direction1+direction2.length].c=\\"message4\\"
195+
v-d6-arg-dynamic-no-value:[direction3]
196+
v-d6-arg-dynamic-no-value:shouldNotUsed
197+
></FooView>
187198
<router-view></router-view>
188199
</div>
189200
</template>
190201
191202
<script lang=\\"ts\\">
192203
import { ref } from '@vue/runtime-dom';
193204
import FooView from './FooView.vue';
194-
import { vFooBar, vDemo } from './directive';
205+
import { vFooBar, vDemo as vD0Demo, vD1Modifiers, vD2ModifiersNoValue, vD3Arg, vD4ArgNoValue, vD5ArgDynamic, vD6ArgDynamicNoValue } from './directive';
195206
const __sfc_main = {};
196207
197208
__sfc_main.setup = (__props, __ctx) => {
198209
const fooView = ref<null | InstanceType<typeof FooView>>(null);
199-
const a = ref(1);
200-
const message = ref('hello');
210+
const message0 = ref('hello');
211+
const message1 = ref('hello');
212+
const message2 = ref('hello');
213+
const message3 = ref('hello');
214+
const message4 = ref('hello');
215+
const direction1 = ref('top');
216+
const direction2 = ref('top');
217+
const direction3 = ref('top');
218+
const shouldNotUsed = ref('');
201219
return {
202220
fooView,
203-
a,
204-
message
221+
message0,
222+
message1,
223+
message2,
224+
message3,
225+
message4,
226+
direction1,
227+
direction2,
228+
direction3
205229
};
206230
};
207231
@@ -210,7 +234,13 @@ __sfc_main.components = Object.assign({
210234
}, __sfc_main.components);
211235
__sfc_main.directives = Object.assign({
212236
fooBar: vFooBar,
213-
demo: vDemo
237+
d0Demo: vD0Demo,
238+
d1Modifiers: vD1Modifiers,
239+
d2ModifiersNoValue: vD2ModifiersNoValue,
240+
d3Arg: vD3Arg,
241+
d4ArgNoValue: vD4ArgNoValue,
242+
d5ArgDynamic: vD5ArgDynamic,
243+
d6ArgDynamicNoValue: vD6ArgDynamicNoValue
214244
}, __sfc_main.directives);
215245
export default __sfc_main;
216246
</script>
+22-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,34 @@
11
<template>
22
<div>
3-
<FooView ref="fooView" v-foo-bar="a" v-demo:foo.a.b="message"></FooView>
3+
<FooView
4+
ref="fooView"
5+
v-foo-bar="message0"
6+
v-d0-demo:foo.a.b="message1"
7+
v-d1-modifiers.a="message2"
8+
v-d2-modifiers-no-value.b.c
9+
v-d3-arg:click="message3"
10+
v-d4-arg-no-value:click
11+
v-d5-arg-dynamic:[direction1+direction2.length].c="message4"
12+
v-d6-arg-dynamic-no-value:[direction3]
13+
v-d6-arg-dynamic-no-value:shouldNotUsed
14+
></FooView>
415
<router-view></router-view>
516
</div>
617
</template>
718

819
<script setup lang="ts">
920
import { ref } from '@vue/runtime-dom'
1021
import FooView from './FooView.vue'
11-
import { vFooBar, vDemo } from './directive'
22+
import { vFooBar, vDemo as vD0Demo, vD1Modifiers, vD2ModifiersNoValue, vD3Arg, vD4ArgNoValue, vD5ArgDynamic, vD6ArgDynamicNoValue } from './directive'
1223
1324
const fooView = ref<null | InstanceType<typeof FooView>>(null)
14-
const a = ref(1)
15-
const message = ref('hello')
25+
const message0 = ref('hello')
26+
const message1 = ref('hello')
27+
const message2 = ref('hello')
28+
const message3 = ref('hello')
29+
const message4 = ref('hello')
30+
const direction1 = ref('top')
31+
const direction2 = ref('top')
32+
const direction3 = ref('top')
33+
const shouldNotUsed = ref('')
1634
</script>

0 commit comments

Comments
 (0)