Browse Source

feat(compiler-vapor): resolve directive

三咲智子 Kevin Deng 1 year ago
parent
commit
e2b51d6e7a

+ 8 - 4
packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap

@@ -16,30 +16,34 @@ exports[`compile > custom directive > basic 1`] = `
 const t0 = _template("<div></div>")
 
 export function render(_ctx) {
+  const _directive_test = _resolveDirective("test")
+  const _directive_hello = _resolveDirective("hello")
   const n0 = t0()
-  _withDirectives(n0, [[_resolveDirective("vTest")], [_resolveDirective("vHello"), void 0, void 0, { world: true }]])
+  _withDirectives(n0, [[_directive_test], [_directive_hello, void 0, void 0, { world: true }]])
   return n0
 }"
 `;
 
 exports[`compile > custom directive > component 1`] = `
-"import { resolveComponent as _resolveComponent, createComponent as _createComponent, resolveDirective as _resolveDirective, withDirectives as _withDirectives, insert as _insert, createIf as _createIf, template as _template } from 'vue/vapor';
+"import { resolveComponent as _resolveComponent, resolveDirective as _resolveDirective, createComponent as _createComponent, withDirectives as _withDirectives, insert as _insert, createIf as _createIf, template as _template } from 'vue/vapor';
 const t0 = _template("<div></div>")
 
 export function render(_ctx) {
   const _component_Bar = _resolveComponent("Bar")
   const _component_Comp = _resolveComponent("Comp")
+  const _directive_hello = _resolveDirective("hello")
+  const _directive_test = _resolveDirective("test")
   const n4 = _createComponent(_component_Comp, null, { default: () => {
     const n0 = _createIf(() => (true), () => {
       const n3 = t0()
       const n2 = _createComponent(_component_Bar)
-      _withDirectives(n2, [[_resolveDirective("vHello"), void 0, void 0, { world: true }]])
+      _withDirectives(n2, [[_directive_hello, void 0, void 0, { world: true }]])
       _insert(n2, n3)
       return n3
     })
     return n0
   } }, null, true)
-  _withDirectives(n4, [[_resolveDirective("vTest")]])
+  _withDirectives(n4, [[_directive_test]])
   return n4
 }"
 `;

+ 2 - 1
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformSlotOutlet.spec.ts.snap

@@ -71,9 +71,10 @@ export function render(_ctx) {
 `;
 
 exports[`compiler: transform <slot> outlets > error on unexpected custom directive on <slot> 1`] = `
-"import { createSlot as _createSlot } from 'vue/vapor';
+"import { resolveDirective as _resolveDirective, createSlot as _createSlot } from 'vue/vapor';
 
 export function render(_ctx) {
+  const _directive_foo = _resolveDirective("foo")
   const n0 = _createSlot("default", null)
   return n0
 }"

+ 4 - 4
packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts

@@ -36,7 +36,7 @@ describe('compiler: element transform', () => {
           type: IRNodeTypes.CREATE_COMPONENT_NODE,
           id: 0,
           tag: 'Foo',
-          resolve: true,
+          asset: true,
           root: true,
           props: [[]],
         },
@@ -66,7 +66,7 @@ describe('compiler: element transform', () => {
         {
           type: IRNodeTypes.CREATE_COMPONENT_NODE,
           tag: 'Example',
-          resolve: false,
+          asset: false,
         },
       ])
     })
@@ -172,7 +172,7 @@ describe('compiler: element transform', () => {
           type: IRNodeTypes.CREATE_COMPONENT_NODE,
           id: 0,
           tag: 'Example',
-          resolve: true,
+          asset: true,
         },
       ])
     })
@@ -212,7 +212,7 @@ describe('compiler: element transform', () => {
         {
           type: IRNodeTypes.CREATE_COMPONENT_NODE,
           tag: 'Foo',
-          resolve: true,
+          asset: true,
           root: true,
           props: [
             [

+ 17 - 11
packages/compiler-vapor/src/generators/block.ts

@@ -1,4 +1,4 @@
-import type { BlockIRNode } from '../ir'
+import type { BlockIRNode, VaporHelper } from '../ir'
 import {
   type CodeFragment,
   DELIMITERS_ARRAY,
@@ -12,6 +12,7 @@ import {
 import type { CodegenContext } from '../generate'
 import { genEffects, genOperations } from './operation'
 import { genChildren } from './template'
+import { toValidAssetId } from '@vue/compiler-dom'
 
 export function genBlock(
   oper: BlockIRNode,
@@ -43,16 +44,8 @@ export function genBlockContent(
   const resetBlock = context.enterBlock(block)
 
   if (root) {
-    for (const name of context.ir.component) {
-      push(
-        NEWLINE,
-        `const _component_${name} = `,
-        ...genCall(
-          context.vaporHelper('resolveComponent'),
-          JSON.stringify(name),
-        ),
-      )
-    }
+    genResolveAssets('component', 'resolveComponent')
+    genResolveAssets('directive', 'resolveDirective')
   }
 
   for (const child of dynamic.children) {
@@ -77,4 +70,17 @@ export function genBlockContent(
 
   resetBlock()
   return frag
+
+  function genResolveAssets(
+    kind: 'component' | 'directive',
+    helper: VaporHelper,
+  ) {
+    for (const name of context.ir[kind]) {
+      push(
+        NEWLINE,
+        `const ${toValidAssetId(name, kind)} = `,
+        ...genCall(context.vaporHelper(helper), JSON.stringify(name)),
+      )
+    }
+  }
 }

+ 3 - 3
packages/compiler-vapor/src/generators/component.ts

@@ -21,7 +21,7 @@ import {
 } from './utils'
 import { genExpression } from './expression'
 import { genPropKey } from './prop'
-import { createSimpleExpression } from '@vue/compiler-dom'
+import { createSimpleExpression, toValidAssetId } from '@vue/compiler-dom'
 import { genEventHandler } from './event'
 import { genDirectiveModifiers, genDirectivesForElement } from './directive'
 import { genModelHandler } from './modelValue'
@@ -52,8 +52,8 @@ export function genCreateComponent(
   ]
 
   function genTag() {
-    if (oper.resolve) {
-      return [`_component_${oper.tag}`]
+    if (oper.asset) {
+      return toValidAssetId(oper.tag, 'component')
     } else {
       return genExpression(
         extend(createSimpleExpression(oper.tag, false), { ast: null }),

+ 20 - 20
packages/compiler-vapor/src/generators/directive.ts

@@ -1,5 +1,9 @@
-import { createSimpleExpression, isSimpleIdentifier } from '@vue/compiler-dom'
-import { camelize } from '@vue/shared'
+import {
+  createSimpleExpression,
+  isSimpleIdentifier,
+  toValidAssetId,
+} from '@vue/compiler-dom'
+import { extend } from '@vue/shared'
 import { genExpression } from './expression'
 import type { CodegenContext } from '../generate'
 import {
@@ -36,7 +40,12 @@ export function genWithDirective(
     ...genCall(vaporHelper('withDirectives'), element, directives),
   ]
 
-  function genDirective({ dir, builtin }: WithDirectiveIRNode): CodeFragment[] {
+  function genDirective({
+    dir,
+    name,
+    builtin,
+    asset,
+  }: WithDirectiveIRNode): CodeFragment[] {
     const directive = genDirective()
     const value = dir.exp && ['() => ', ...genExpression(dir.exp, context)]
     const argument = dir.arg && genExpression(dir.arg, context)
@@ -55,24 +64,15 @@ export function genWithDirective(
     )
 
     function genDirective() {
-      const {
-        vaporHelper,
-        options: { bindingMetadata },
-      } = context
-      if (dir.name === 'show') {
-        return [vaporHelper('vShow')]
-      } else if (builtin) {
-        return [vaporHelper(builtin)]
+      if (builtin) {
+        return vaporHelper(name as any)
+      } else if (asset) {
+        return toValidAssetId(name, 'directive')
       } else {
-        const directiveReference = camelize(`v-${dir.name}`)
-        // TODO resolve directive
-        if (bindingMetadata[directiveReference]) {
-          const directiveExpression = createSimpleExpression(directiveReference)
-          directiveExpression.ast = null
-          return genExpression(directiveExpression, context)
-        } else {
-          return `${vaporHelper('resolveDirective')}("${directiveReference}")`
-        }
+        return genExpression(
+          extend(createSimpleExpression(name, false), { ast: null }),
+          context,
+        )
       }
     }
   }

+ 5 - 2
packages/compiler-vapor/src/ir.ts

@@ -60,6 +60,7 @@ export interface RootIRNode {
   source: string
   template: string[]
   component: Set<string>
+  directive: Set<string>
   block: BlockIRNode
 }
 
@@ -197,7 +198,9 @@ export interface WithDirectiveIRNode extends BaseIRNode {
   type: IRNodeTypes.WITH_DIRECTIVE
   element: number
   dir: VaporDirectiveNode
-  builtin?: VaporHelper
+  name: string
+  builtin?: boolean
+  asset?: boolean
 }
 
 export interface ComponentSlotBlockIRNode extends BlockIRNode {
@@ -219,7 +222,7 @@ export interface CreateComponentIRNode extends BaseIRNode {
   slots?: ComponentSlots
   dynamicSlots?: ComponentDynamicSlot[]
 
-  resolve: boolean
+  asset: boolean
   root: boolean
 }
 

+ 3 - 0
packages/compiler-vapor/src/transform.ts

@@ -79,6 +79,8 @@ export class TransformContext<T extends AllNode = AllNode> {
 
   comment: CommentNode[] = []
   component: Set<string> = this.ir.component
+  directive: Set<string> = this.ir.directive
+
   slots?: ComponentSlots
   dynamicSlots?: ComponentDynamicSlot[]
 
@@ -220,6 +222,7 @@ export function transform(
     source: node.source,
     template: [],
     component: new Set(),
+    directive: new Set(),
     block: newBlock(node),
   }
 

+ 16 - 6
packages/compiler-vapor/src/transforms/transformElement.ts

@@ -72,24 +72,24 @@ function transformComponentElement(
   propsResult: PropsResult,
   context: TransformContext,
 ) {
-  let resolve = true
+  let asset = true
 
   if (!__BROWSER__) {
     const fromSetup = resolveSetupReference(tag, context)
     if (fromSetup) {
       tag = fromSetup
-      resolve = false
+      asset = false
     }
     const dotIndex = tag.indexOf('.')
     if (dotIndex > 0) {
       const ns = resolveSetupReference(tag.slice(0, dotIndex), context)
       if (ns) {
         tag = ns + tag.slice(dotIndex)
-        resolve = false
+        asset = false
       }
     }
   }
-  if (resolve) {
+  if (asset) {
     context.component.add(tag)
   }
 
@@ -102,7 +102,7 @@ function transformComponentElement(
     id: context.reference(),
     tag,
     props: propsResult[0] ? propsResult[1] : [propsResult[1]],
-    resolve,
+    asset,
     root,
     slots: context.slots,
     dynamicSlots: context.dynamicSlots,
@@ -287,7 +287,7 @@ function transformProp(
   node: ElementNode,
   context: TransformContext<ElementNode>,
 ): DirectiveTransformResult | void {
-  const { name } = prop
+  let { name } = prop
 
   if (prop.type === NodeTypes.ATTRIBUTE) {
     if (isReservedProp(name)) return
@@ -305,10 +305,20 @@ function transformProp(
   }
 
   if (!isBuiltInDirective(name)) {
+    const fromSetup =
+      !__BROWSER__ && resolveSetupReference(`v-${name}`, context)
+    if (fromSetup) {
+      name = fromSetup
+    } else {
+      context.directive.add(name)
+    }
+
     context.registerOperation({
       type: IRNodeTypes.WITH_DIRECTIVE,
       element: context.reference(),
       dir: prop,
+      name,
+      asset: !fromSetup,
     })
   }
 }

+ 63 - 64
packages/compiler-vapor/src/transforms/vModel.ts

@@ -62,8 +62,6 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => {
   }
 
   const isComponent = node.tagType === ElementTypes.COMPONENT
-  let runtimeDirective: VaporHelper | undefined
-
   if (isComponent) {
     return {
       key: arg ? arg : createSimpleExpression('modelValue', true),
@@ -71,74 +69,74 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => {
       model: true,
       modelModifiers: dir.modifiers,
     }
-  } else {
-    if (dir.arg)
-      context.options.onError(
-        createDOMCompilerError(
-          DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
-          dir.arg.loc,
-        ),
-      )
-    const { tag } = node
-    const isCustomElement = context.options.isCustomElement(tag)
-    runtimeDirective = 'vModelText'
-    if (
-      tag === 'input' ||
-      tag === 'textarea' ||
-      tag === 'select' ||
-      isCustomElement
-    ) {
-      if (tag === 'input' || isCustomElement) {
-        const type = findProp(node, 'type')
-        if (type) {
-          if (type.type === NodeTypes.DIRECTIVE) {
-            // :type="foo"
-            runtimeDirective = 'vModelDynamic'
-          } else if (type.value) {
-            switch (type.value.content) {
-              case 'radio':
-                runtimeDirective = 'vModelRadio'
-                break
-              case 'checkbox':
-                runtimeDirective = 'vModelCheckbox'
-                break
-              case 'file':
-                runtimeDirective = undefined
-                context.options.onError(
-                  createDOMCompilerError(
-                    DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
-                    dir.loc,
-                  ),
-                )
-                break
-              default:
-                // text type
-                __DEV__ && checkDuplicatedValue()
-                break
-            }
-          }
-        } else if (hasDynamicKeyVBind(node)) {
-          // element has bindings with dynamic keys, which can possibly contain
-          // "type".
+  }
+
+  if (dir.arg)
+    context.options.onError(
+      createDOMCompilerError(
+        DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
+        dir.arg.loc,
+      ),
+    )
+  const { tag } = node
+  const isCustomElement = context.options.isCustomElement(tag)
+  let runtimeDirective: VaporHelper | undefined = 'vModelText'
+  if (
+    tag === 'input' ||
+    tag === 'textarea' ||
+    tag === 'select' ||
+    isCustomElement
+  ) {
+    if (tag === 'input' || isCustomElement) {
+      const type = findProp(node, 'type')
+      if (type) {
+        if (type.type === NodeTypes.DIRECTIVE) {
+          // :type="foo"
           runtimeDirective = 'vModelDynamic'
-        } else {
-          // text type
-          __DEV__ && checkDuplicatedValue()
+        } else if (type.value) {
+          switch (type.value.content) {
+            case 'radio':
+              runtimeDirective = 'vModelRadio'
+              break
+            case 'checkbox':
+              runtimeDirective = 'vModelCheckbox'
+              break
+            case 'file':
+              runtimeDirective = undefined
+              context.options.onError(
+                createDOMCompilerError(
+                  DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
+                  dir.loc,
+                ),
+              )
+              break
+            default:
+              // text type
+              __DEV__ && checkDuplicatedValue()
+              break
+          }
         }
-      } else if (tag === 'select') {
-        runtimeDirective = 'vModelSelect'
+      } else if (hasDynamicKeyVBind(node)) {
+        // element has bindings with dynamic keys, which can possibly contain
+        // "type".
+        runtimeDirective = 'vModelDynamic'
       } else {
-        // textarea
+        // text type
         __DEV__ && checkDuplicatedValue()
       }
+    } else if (tag === 'select') {
+      runtimeDirective = 'vModelSelect'
     } else {
-      context.options.onError(
-        createDOMCompilerError(
-          DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
-          dir.loc,
-        ),
-      )
+      // textarea
+      __DEV__ && checkDuplicatedValue()
     }
+  } else {
+    context.options.onError(
+      createDOMCompilerError(
+        DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
+        dir.loc,
+      ),
+    )
   }
 
   context.registerOperation({
@@ -154,7 +152,8 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => {
       type: IRNodeTypes.WITH_DIRECTIVE,
       element: context.reference(),
       dir,
-      builtin: runtimeDirective,
+      name: runtimeDirective,
+      builtin: true,
     })
 
   function checkDuplicatedValue() {

+ 2 - 0
packages/compiler-vapor/src/transforms/vShow.ts

@@ -14,5 +14,7 @@ export const transformVShow: DirectiveTransform = (dir, node, context) => {
     type: IRNodeTypes.WITH_DIRECTIVE,
     element: context.reference(),
     dir,
+    name: 'vShow',
+    builtin: true,
   })
 }