wip: support resolving directives from setup scope variables by naming convention

v-my-dir can be resovled from setup scope variable named "vMyDir".
This commit is contained in:
Evan You 2020-11-23 16:32:24 -05:00
parent 2f32e4a077
commit ae2caad740
4 changed files with 121 additions and 35 deletions

View File

@ -29,8 +29,7 @@ import {
isObject, isObject,
isReservedProp, isReservedProp,
capitalize, capitalize,
camelize, camelize
EMPTY_OBJ
} from '@vue/shared' } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { import {
@ -255,34 +254,16 @@ export function resolveComponentType(
} }
// 3. user component (from setup bindings) // 3. user component (from setup bindings)
const bindings = context.bindingMetadata // this is skipped in browser build since browser builds do not perform
if (bindings !== EMPTY_OBJ) { // binding analysis.
const checkType = (type: BindingTypes) => { if (!__BROWSER__) {
let resolvedTag = tag const fromSetup = resolveSetupReference(
if ( tag,
bindings[resolvedTag] === type || capitalize(camelize(tag)),
bindings[(resolvedTag = camelize(tag))] === type || context
bindings[(resolvedTag = capitalize(camelize(tag)))] === type )
) { if (fromSetup) {
return resolvedTag return fromSetup
}
}
const tagFromConst = checkType(BindingTypes.SETUP_CONST)
if (tagFromConst) {
return context.inline
? // in inline mode, const setup bindings (e.g. imports) can be used as-is
tagFromConst
: `$setup[${JSON.stringify(tagFromConst)}]`
}
const tagFromSetup =
checkType(BindingTypes.SETUP_LET) ||
checkType(BindingTypes.SETUP_REF) ||
checkType(BindingTypes.SETUP_MAYBE_REF)
if (tagFromSetup) {
return context.inline
? // setup scope bindings that may be refs need to be unrefed
`${context.helperString(UNREF)}(${tagFromSetup})`
: `$setup[${JSON.stringify(tagFromSetup)}]`
} }
} }
@ -292,6 +273,45 @@ export function resolveComponentType(
return toValidAssetId(tag, `component`) return toValidAssetId(tag, `component`)
} }
function resolveSetupReference(
name: string,
interopName: string,
context: TransformContext
) {
const bindings = context.bindingMetadata
if (!bindings) {
return
}
const checkType = (type: BindingTypes) => {
if (bindings[name] === type) {
return name
}
if (bindings[interopName] === type) {
return interopName
}
}
const fromConst = checkType(BindingTypes.SETUP_CONST)
if (fromConst) {
return context.inline
? // in inline mode, const setup bindings (e.g. imports) can be used as-is
fromConst
: `$setup[${JSON.stringify(fromConst)}]`
}
const fromMaybeRef =
checkType(BindingTypes.SETUP_LET) ||
checkType(BindingTypes.SETUP_REF) ||
checkType(BindingTypes.SETUP_MAYBE_REF)
if (fromMaybeRef) {
return context.inline
? // setup scope bindings that may be refs need to be unrefed
`${context.helperString(UNREF)}(${fromMaybeRef})`
: `$setup[${JSON.stringify(fromMaybeRef)}]`
}
}
export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
export function buildProps( export function buildProps(
@ -590,13 +610,29 @@ function buildDirectiveArgs(
const dirArgs: ArrayExpression['elements'] = [] const dirArgs: ArrayExpression['elements'] = []
const runtime = directiveImportMap.get(dir) const runtime = directiveImportMap.get(dir)
if (runtime) { if (runtime) {
// built-in directive with runtime
dirArgs.push(context.helperString(runtime)) dirArgs.push(context.helperString(runtime))
} else {
// user directive.
// see if we have directives exposed via <script setup>
const fromSetup =
!__BROWSER__ &&
resolveSetupReference(
dir.name,
// v-my-dir -> vMyDir
'v' + capitalize(camelize(dir.name)),
context
)
if (fromSetup) {
dirArgs.push(fromSetup)
} else { } else {
// inject statement for resolving directive // inject statement for resolving directive
context.helper(RESOLVE_DIRECTIVE) context.helper(RESOLVE_DIRECTIVE)
context.directives.add(dir.name) context.directives.add(dir.name)
dirArgs.push(toValidAssetId(dir.name, `directive`)) dirArgs.push(toValidAssetId(dir.name, `directive`))
} }
}
const { loc } = dir const { loc } = dir
if (dir.exp) dirArgs.push(dir.exp) if (dir.exp) dirArgs.push(dir.exp)
if (dir.arg) { if (dir.arg) {

View File

@ -177,6 +177,32 @@ return (_ctx, _cache) => {
}" }"
`; `;
exports[`SFC compile <script setup> inlineTemplate mode referencing scope components and directives 1`] = `
"import { unref as _unref, createVNode as _createVNode, withDirectives as _withDirectives, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
import ChildComp from './Child.vue'
import SomeOtherComp from './Other.vue'
import vMyDir from './my-dir'
export default {
expose: [],
setup(__props) {
return (_ctx, _cache) => {
return (_openBlock(), _createBlock(_Fragment, null, [
_withDirectives(_createVNode(\\"div\\", null, null, 512 /* NEED_PATCH */), [
[_unref(vMyDir)]
]),
_createVNode(ChildComp),
_createVNode(SomeOtherComp)
], 64 /* STABLE_FRAGMENT */))
}
}
}"
`;
exports[`SFC compile <script setup> inlineTemplate mode should work 1`] = ` exports[`SFC compile <script setup> inlineTemplate mode should work 1`] = `
"import { toDisplayString as _toDisplayString, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\" "import { toDisplayString as _toDisplayString, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"

View File

@ -147,6 +147,29 @@ const bar = 1
assertCode(content) assertCode(content)
}) })
test('referencing scope components and directives', () => {
const { content } = compile(
`
<script setup>
import ChildComp from './Child.vue'
import SomeOtherComp from './Other.vue'
import vMyDir from './my-dir'
</script>
<template>
<div v-my-dir></div>
<ChildComp/>
<some-other-comp/>
</template>
`,
{ inlineTemplate: true }
)
expect(content).toMatch('[_unref(vMyDir)]')
expect(content).toMatch('_createVNode(ChildComp)')
// kebab-case component support
expect(content).toMatch('_createVNode(SomeOtherComp)')
assertCode(content)
})
test('avoid unref() when necessary', () => { test('avoid unref() when necessary', () => {
// function, const, component import // function, const, component import
const { content } = compile( const { content } = compile(

View File

@ -19,7 +19,8 @@ export const compilerOptions: CompilerOptions = reactive({
setupConst: BindingTypes.SETUP_CONST, setupConst: BindingTypes.SETUP_CONST,
setupLet: BindingTypes.SETUP_LET, setupLet: BindingTypes.SETUP_LET,
setupMaybeRef: BindingTypes.SETUP_MAYBE_REF, setupMaybeRef: BindingTypes.SETUP_MAYBE_REF,
setupProp: BindingTypes.PROPS setupProp: BindingTypes.PROPS,
vMySetupDir: BindingTypes.SETUP_CONST
} }
}) })