Skip to content
This repository was archived by the owner on Dec 25, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>