]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: support resolving directives from setup scope variables by naming convention
authorEvan You <yyx990803@gmail.com>
Mon, 23 Nov 2020 21:32:24 +0000 (16:32 -0500)
committerEvan You <yyx990803@gmail.com>
Wed, 25 Nov 2020 00:04:21 +0000 (19:04 -0500)
v-my-dir can be resovled from setup scope variable named "vMyDir".

packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
packages/compiler-sfc/__tests__/compileScript.spec.ts
packages/template-explorer/src/options.ts

index f2f1459e05cd9f83269a4a017f9698e50a1a89c6..c9d1a3ee40502843453d5185eec46fa6fb1210b3 100644 (file)
@@ -29,8 +29,7 @@ import {
   isObject,
   isReservedProp,
   capitalize,
-  camelize,
-  EMPTY_OBJ
+  camelize
 } from '@vue/shared'
 import { createCompilerError, ErrorCodes } from '../errors'
 import {
@@ -255,34 +254,16 @@ export function resolveComponentType(
   }
 
   // 3. user component (from setup bindings)
-  const bindings = context.bindingMetadata
-  if (bindings !== EMPTY_OBJ) {
-    const checkType = (type: BindingTypes) => {
-      let resolvedTag = tag
-      if (
-        bindings[resolvedTag] === type ||
-        bindings[(resolvedTag = camelize(tag))] === type ||
-        bindings[(resolvedTag = capitalize(camelize(tag)))] === type
-      ) {
-        return resolvedTag
-      }
-    }
-    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)}]`
+  // this is skipped in browser build since browser builds do not perform
+  // binding analysis.
+  if (!__BROWSER__) {
+    const fromSetup = resolveSetupReference(
+      tag,
+      capitalize(camelize(tag)),
+      context
+    )
+    if (fromSetup) {
+      return fromSetup
     }
   }
 
@@ -292,6 +273,45 @@ export function resolveComponentType(
   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 function buildProps(
@@ -590,12 +610,28 @@ function buildDirectiveArgs(
   const dirArgs: ArrayExpression['elements'] = []
   const runtime = directiveImportMap.get(dir)
   if (runtime) {
+    // built-in directive with runtime
     dirArgs.push(context.helperString(runtime))
   } else {
-    // inject statement for resolving directive
-    context.helper(RESOLVE_DIRECTIVE)
-    context.directives.add(dir.name)
-    dirArgs.push(toValidAssetId(dir.name, `directive`))
+    // 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 {
+      // inject statement for resolving directive
+      context.helper(RESOLVE_DIRECTIVE)
+      context.directives.add(dir.name)
+      dirArgs.push(toValidAssetId(dir.name, `directive`))
+    }
   }
   const { loc } = dir
   if (dir.exp) dirArgs.push(dir.exp)
index 80a3bfa6c9e99b0bdf81f4a61cf403ba078b37d5..9d05d643a838a8cad125134a84f94068f2951a2a 100644 (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`] = `
 "import { toDisplayString as _toDisplayString, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
 
index 2e4f621b4e1396dab6eeddbf1b19043ef0206e88..9b4035185a7411f3e61a5abbc80735382261a5ac 100644 (file)
@@ -147,6 +147,29 @@ const bar = 1
       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', () => {
       // function, const, component import
       const { content } = compile(
index cdf00ec874a18fd64011025a8d82bad24ca89a05..dab7f8adf8a1605aa402db97cca63994e2db03d5 100644 (file)
@@ -19,7 +19,8 @@ export const compilerOptions: CompilerOptions = reactive({
     setupConst: BindingTypes.SETUP_CONST,
     setupLet: BindingTypes.SETUP_LET,
     setupMaybeRef: BindingTypes.SETUP_MAYBE_REF,
-    setupProp: BindingTypes.PROPS
+    setupProp: BindingTypes.PROPS,
+    vMySetupDir: BindingTypes.SETUP_CONST
   }
 })