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

Commit

Permalink
fix: directive parser (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoxiangmoe authored Nov 28, 2021
1 parent ac7ba52 commit 9dcd34e
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 28 deletions.
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@
"@babel/traverse": "^7.16.3",
"@babel/types": "^7.16.0",
"@rollup/pluginutils": "^4.1.1",
"@vue/ref-transform": "^3.2.21",
"@vue/shared": "^3.2.21",
"@vue/compiler-core": "^3.2.23",
"@vue/ref-transform": "^3.2.23",
"@vue/shared": "^3.2.23",
"defu": "^5.0.0",
"htmlparser2": "5.0.1",
"magic-string": "^0.25.7",
Expand All @@ -63,7 +64,7 @@
"@types/jest": "^27.0.2",
"@types/node": "^16.11.7",
"@vue/composition-api": "^1.4.0",
"@vue/runtime-dom": "^3.2.21",
"@vue/runtime-dom": "^3.2.23",
"bumpp": "^7.1.1",
"eslint": "^8.2.0",
"eslint-plugin-jest": "^25.2.4",
Expand Down
60 changes: 54 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 40 additions & 8 deletions src/core/parseSFC.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Parser as HTMLParser, ParserOptions as HTMLParserOptions } from 'htmlparser2'
import type { ParserOptions } from '@babel/parser'
import { NodeTypes, baseCompile } from '@vue/compiler-core'
import { camelize, isHTMLTag, isSVGTag, isVoidTag } from '@vue/shared'
import { ParsedSFC, ScriptSetupTransformOptions, ScriptTagMeta } from '../types'
import { getIdentifierUsages } from './identifiers'
Expand Down Expand Up @@ -31,6 +32,32 @@ const BUILD_IN_DIRECTIVES = new Set([
// 'ref',
])

const parseDirective = (attr: string) => {
try {
const elementNode = baseCompile(`<a ${attr}></a>`).ast.children[0]
if (elementNode?.type !== NodeTypes.ELEMENT) return undefined

const directiveNode = elementNode.props[0]
if (directiveNode?.type !== NodeTypes.DIRECTIVE) return undefined

const { arg, modifiers, name } = directiveNode
const argExpression
= arg?.type !== NodeTypes.SIMPLE_EXPRESSION
? undefined
: arg.isStatic
? JSON.stringify(arg.content)
: arg.content
return {
argExpression,
modifiers,
name,
}
}
catch (error) {
return undefined
}
}

export function parseSFC(code: string, id?: string, options?: ScriptSetupTransformOptions): ParsedSFC {
/** foo-bar -> FooBar */
const components = new Set<string>()
Expand Down Expand Up @@ -78,25 +105,30 @@ export function parseSFC(code: string, id?: string, options?: ScriptSetupTransfo

function handleTemplateContent(name: string, attributes: Record<string, string>) {
if (!isHTMLTag(name) && !isSVGTag(name) && !isVoidTag(name))
components.add(pascalize((name)))
components.add(pascalize(name))

Object.entries(attributes).forEach(([key, value]) => {
if (!value)
// ref
if (key === 'ref') {
identifiers.add(value)
return
if (key.startsWith('v-') || key.startsWith('@') || key.startsWith(':')) {
}

if (value !== '' && (key.startsWith('v-') || key.startsWith('@') || key.startsWith(':'))) {
if (key === 'v-for')
// we strip out delectations for v-for before `in` or `of`
expressions.add(`(${value.replace(/^.*\s(?:in|of)\s/, '')})`)
else
expressions.add(`(${value})`)
}

if (key.startsWith('v-')) {
const directiveName = key.slice('v-'.length).split(':')[0].split('.')[0]
if (!BUILD_IN_DIRECTIVES.has(directiveName))
directives.add(camelize(directiveName))
const parsedDirective = parseDirective(key)
if (parsedDirective && !BUILD_IN_DIRECTIVES.has(parsedDirective.name))
directives.add(camelize(parsedDirective.name))
if (parsedDirective?.argExpression)
expressions.add(parsedDirective.argExpression)
}
if (key === 'ref')
identifiers.add(value)
})
}

Expand Down
44 changes: 37 additions & 7 deletions test/__snapshots__/transform.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -183,25 +183,49 @@ export default __sfc_main;
exports[`transform fixtures test/fixtures/ComponentsDirectives.vue 1`] = `
"<template>
<div>
<FooView ref=\\"fooView\\" v-foo-bar=\\"a\\" v-demo:foo.a.b=\\"message\\"></FooView>
<FooView
ref=\\"fooView\\"
v-foo-bar=\\"message0\\"
v-d0-demo:foo.a.b=\\"message1\\"
v-d1-modifiers.a=\\"message2\\"
v-d2-modifiers-no-value.b.c
v-d3-arg:click=\\"message3\\"
v-d4-arg-no-value:click
v-d5-arg-dynamic:[direction1+direction2.length].c=\\"message4\\"
v-d6-arg-dynamic-no-value:[direction3]
v-d6-arg-dynamic-no-value:shouldNotUsed
></FooView>
<router-view></router-view>
</div>
</template>
<script lang=\\"ts\\">
import { ref } from '@vue/runtime-dom';
import FooView from './FooView.vue';
import { vFooBar, vDemo } from './directive';
import { vFooBar, vDemo as vD0Demo, vD1Modifiers, vD2ModifiersNoValue, vD3Arg, vD4ArgNoValue, vD5ArgDynamic, vD6ArgDynamicNoValue } from './directive';
const __sfc_main = {};
__sfc_main.setup = (__props, __ctx) => {
const fooView = ref<null | InstanceType<typeof FooView>>(null);
const a = ref(1);
const message = ref('hello');
const message0 = ref('hello');
const message1 = ref('hello');
const message2 = ref('hello');
const message3 = ref('hello');
const message4 = ref('hello');
const direction1 = ref('top');
const direction2 = ref('top');
const direction3 = ref('top');
const shouldNotUsed = ref('');
return {
fooView,
a,
message
message0,
message1,
message2,
message3,
message4,
direction1,
direction2,
direction3
};
};
Expand All @@ -210,7 +234,13 @@ __sfc_main.components = Object.assign({
}, __sfc_main.components);
__sfc_main.directives = Object.assign({
fooBar: vFooBar,
demo: vDemo
d0Demo: vD0Demo,
d1Modifiers: vD1Modifiers,
d2ModifiersNoValue: vD2ModifiersNoValue,
d3Arg: vD3Arg,
d4ArgNoValue: vD4ArgNoValue,
d5ArgDynamic: vD5ArgDynamic,
d6ArgDynamicNoValue: vD6ArgDynamicNoValue
}, __sfc_main.directives);
export default __sfc_main;
</script>
Expand Down
26 changes: 22 additions & 4 deletions test/fixtures/ComponentsDirectives.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
<template>
<div>
<FooView ref="fooView" v-foo-bar="a" v-demo:foo.a.b="message"></FooView>
<FooView
ref="fooView"
v-foo-bar="message0"
v-d0-demo:foo.a.b="message1"
v-d1-modifiers.a="message2"
v-d2-modifiers-no-value.b.c
v-d3-arg:click="message3"
v-d4-arg-no-value:click
v-d5-arg-dynamic:[direction1+direction2.length].c="message4"
v-d6-arg-dynamic-no-value:[direction3]
v-d6-arg-dynamic-no-value:shouldNotUsed
></FooView>
<router-view></router-view>
</div>
</template>

<script setup lang="ts">
import { ref } from '@vue/runtime-dom'
import FooView from './FooView.vue'
import { vFooBar, vDemo } from './directive'
import { vFooBar, vDemo as vD0Demo, vD1Modifiers, vD2ModifiersNoValue, vD3Arg, vD4ArgNoValue, vD5ArgDynamic, vD6ArgDynamicNoValue } from './directive'
const fooView = ref<null | InstanceType<typeof FooView>>(null)
const a = ref(1)
const message = ref('hello')
const message0 = ref('hello')
const message1 = ref('hello')
const message2 = ref('hello')
const message3 = ref('hello')
const message4 = ref('hello')
const direction1 = ref('top')
const direction2 = ref('top')
const direction3 = ref('top')
const shouldNotUsed = ref('')
</script>

0 comments on commit 9dcd34e

Please sign in to comment.