]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler-vapor): support custom directives argument & modifiers (#34)
author白雾三语 <32354856+baiwusanyu-c@users.noreply.github.com>
Wed, 6 Dec 2023 17:41:17 +0000 (01:41 +0800)
committerGitHub <noreply@github.com>
Wed, 6 Dec 2023 17:41:17 +0000 (01:41 +0800)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap
packages/compiler-vapor/__tests__/compile.test.ts
packages/compiler-vapor/src/generate.ts
packages/compiler-vapor/src/ir.ts
packages/compiler-vapor/src/transforms/transformElement.ts
packages/runtime-core/src/index.ts
packages/runtime-vapor/src/directives.ts
playground/src/directive.vue

index 92164b013614b7acc21ff673f360ad8f41ed7c00..11119db643b58ecd645a5b196cb88cb28443c81b 100644 (file)
@@ -16,6 +16,90 @@ export function render(_ctx) {
 }"
 `;
 
+exports[`compile > directives > custom directive > basic 1`] = `
+"import { template as _template, children as _children, withDirectives as _withDirectives } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _withDirectives(n1, [[_ctx.vExample]])
+  return n0
+}"
+`;
+
+exports[`compile > directives > custom directive > binding value 1`] = `
+"import { template as _template, children as _children, withDirectives as _withDirectives } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _withDirectives(n1, [[_ctx.vExample, _ctx.msg]])
+  return n0
+}"
+`;
+
+exports[`compile > directives > custom directive > dynamic parameters 1`] = `
+"import { template as _template, children as _children, withDirectives as _withDirectives } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _withDirectives(n1, [[_ctx.vExample, _ctx.msg, _ctx.foo]])
+  return n0
+}"
+`;
+
+exports[`compile > directives > custom directive > modifiers 1`] = `
+"import { template as _template, children as _children, withDirectives as _withDirectives } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _withDirectives(n1, [[_ctx.vExample, _ctx.msg, void 0, { bar: true }]])
+  return n0
+}"
+`;
+
+exports[`compile > directives > custom directive > modifiers w/o binding 1`] = `
+"import { template as _template, children as _children, withDirectives as _withDirectives } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _withDirectives(n1, [[_ctx.vExample, void 0, void 0, { "foo-bar": true }]])
+  return n0
+}"
+`;
+
+exports[`compile > directives > custom directive > static parameters 1`] = `
+"import { template as _template, children as _children, withDirectives as _withDirectives } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _withDirectives(n1, [[_ctx.vExample, _ctx.msg, "foo"]])
+  return n0
+}"
+`;
+
+exports[`compile > directives > custom directive > static parameters and modifiers 1`] = `
+"import { template as _template, children as _children, withDirectives as _withDirectives } from 'vue/vapor';
+
+export function render(_ctx) {
+  const t0 = _template("<div></div>")
+  const n0 = t0()
+  const { 0: [n1],} = _children(n0)
+  _withDirectives(n1, [[_ctx.vExample, _ctx.msg, "foo", { bar: true }]])
+  return n0
+}"
+`;
+
 exports[`compile > directives > v-bind > .camel modifier 1`] = `
 "import { template as _template, children as _children, effect as _effect, setAttr as _setAttr } from 'vue/vapor';
 
index 9459ac00fdb254127beed2654261625e23388886..95f74715e8dee3b331134ac6d32a28122e464a85 100644 (file)
@@ -332,6 +332,76 @@ describe('compile', () => {
         expect(code).not.contains('v-cloak')
       })
     })
+
+    describe('custom directive', () => {
+      test('basic', async () => {
+        const code = await compile(`<div v-example></div>`, {
+          bindingMetadata: {
+            vExample: BindingTypes.SETUP_CONST,
+          },
+        })
+        expect(code).matchSnapshot()
+      })
+
+      test('binding value', async () => {
+        const code = await compile(`<div v-example="msg"></div>`, {
+          bindingMetadata: {
+            msg: BindingTypes.SETUP_REF,
+            vExample: BindingTypes.SETUP_CONST,
+          },
+        })
+        expect(code).matchSnapshot()
+      })
+
+      test('static parameters', async () => {
+        const code = await compile(`<div v-example:foo="msg"></div>`, {
+          bindingMetadata: {
+            msg: BindingTypes.SETUP_REF,
+            vExample: BindingTypes.SETUP_CONST,
+          },
+        })
+        expect(code).matchSnapshot()
+      })
+
+      test('modifiers', async () => {
+        const code = await compile(`<div v-example.bar="msg"></div>`, {
+          bindingMetadata: {
+            msg: BindingTypes.SETUP_REF,
+            vExample: BindingTypes.SETUP_CONST,
+          },
+        })
+        expect(code).matchSnapshot()
+      })
+
+      test('modifiers w/o binding', async () => {
+        const code = await compile(`<div v-example.foo-bar></div>`, {
+          bindingMetadata: {
+            vExample: BindingTypes.SETUP_CONST,
+          },
+        })
+        expect(code).matchSnapshot()
+      })
+
+      test('static parameters and modifiers', async () => {
+        const code = await compile(`<div v-example:foo.bar="msg"></div>`, {
+          bindingMetadata: {
+            msg: BindingTypes.SETUP_REF,
+            vExample: BindingTypes.SETUP_CONST,
+          },
+        })
+        expect(code).matchSnapshot()
+      })
+
+      test('dynamic parameters', async () => {
+        const code = await compile(`<div v-example:[foo]="msg"></div>`, {
+          bindingMetadata: {
+            foo: BindingTypes.SETUP_REF,
+            vExample: BindingTypes.SETUP_CONST,
+          },
+        })
+        expect(code).matchSnapshot()
+      })
+    })
   })
 
   describe('expression parsing', () => {
index 8acc18f305e350ea6708448a55a759e6a46c66f0..fee38022b12546411080ddbbde0e493579e319f7 100644 (file)
@@ -10,6 +10,7 @@ import {
   createSimpleExpression,
   walkIdentifiers,
   advancePositionWithClone,
+  isSimpleIdentifier,
 } from '@vue/compiler-dom'
 import {
   type IRDynamicChildren,
@@ -469,21 +470,38 @@ function genSetEvent(oper: SetEventIRNode, context: CodegenContext) {
 
 function genWithDirective(oper: WithDirectiveIRNode, context: CodegenContext) {
   const { push, pushWithNewline, vaporHelper, bindingMetadata } = context
+  const { dir } = oper
 
   // TODO merge directive for the same node
   pushWithNewline(`${vaporHelper('withDirectives')}(n${oper.element}, [[`)
 
   // TODO resolve directive
-  const directiveReference = camelize(`v-${oper.name}`)
+  const directiveReference = camelize(`v-${dir.name}`)
   if (bindingMetadata[directiveReference]) {
     const directiveExpression = createSimpleExpression(directiveReference)
     directiveExpression.ast = null
     genExpression(directiveExpression, context)
   }
 
-  if (oper.binding) {
+  if (dir.exp) {
     push(', ')
-    genExpression(oper.binding, context)
+    genExpression(dir.exp, context)
+  } else if (dir.arg || dir.modifiers.length) {
+    push(', void 0')
+  }
+
+  if (dir.arg) {
+    push(', ')
+    genExpression(dir.arg, context)
+  } else if (dir.modifiers.length) {
+    push(', void 0')
+  }
+
+  if (dir.modifiers.length) {
+    push(', ')
+    push('{ ')
+    push(genDirectiveModifiers(dir.modifiers))
+    push(' }')
   }
   push(']])')
   return
@@ -576,3 +594,12 @@ function genIdentifier(
   }
   push(id, NewlineType.None, loc, name)
 }
+
+function genDirectiveModifiers(modifiers: string[]) {
+  return modifiers
+    .map(
+      (value) =>
+        `${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
+    )
+    .join(', ')
+}
index bbfd6e7a446f421d4b4353e3ed7280ae1705ed0d..a8c46ce164cfecf375dcea33874af5d600484d2b 100644 (file)
@@ -117,8 +117,7 @@ export interface AppendNodeIRNode extends BaseIRNode {
 export interface WithDirectiveIRNode extends BaseIRNode {
   type: IRNodeTypes.WITH_DIRECTIVE
   element: number
-  name: string
-  binding: IRExpression | undefined
+  dir: VaporDirectiveNode
 }
 
 export type IRNode =
index b27509a281fb4c127ab3ea046e3af50cd969d706..ea468d29badf8804ee231ef61e5cf023d5b99dba 100644 (file)
@@ -59,7 +59,7 @@ function transformProp(
   node: ElementNode,
   context: TransformContext<ElementNode>,
 ): void {
-  const { name } = prop
+  const { name, loc } = prop
   if (prop.type === NodeTypes.ATTRIBUTE) {
     context.template += ` ${name}`
     if (prop.value) context.template += `="${prop.value.content}"`
@@ -70,13 +70,11 @@ function transformProp(
   if (directiveTransform) {
     directiveTransform(prop, node, context)
   } else if (!isBuiltInDirective(name)) {
-    // custom directive
     context.registerOperation({
       type: IRNodeTypes.WITH_DIRECTIVE,
       element: context.reference(),
-      name,
-      binding: prop.exp,
-      loc: prop.loc,
+      dir: prop,
+      loc: loc,
     })
   }
 }
index fcc460c43d0a46e130f19bd91820d5dcb9f0e02a..8c6675ecd24fcb2400b01557ff73256945292ce0 100644 (file)
@@ -279,7 +279,8 @@ export type {
   DirectiveHook,
   ObjectDirective,
   FunctionDirective,
-  DirectiveArguments
+  DirectiveArguments,
+  DirectiveModifiers
 } from './directives'
 export type { SuspenseBoundary } from './components/Suspense'
 export type {
index 160ec8026651a5443813c95179a71fb2357620e6..24bc39ae7d0c0121b3b316ebec599b26be5586cf 100644 (file)
@@ -1,13 +1,12 @@
 import { isFunction } from '@vue/shared'
 import { currentInstance, type ComponentInternalInstance } from './component'
-
+import type { DirectiveModifiers } from '@vue/runtime-dom'
 export interface DirectiveBinding<V = any> {
   instance: ComponentInternalInstance | null
   value: V
   oldValue: V | null
   arg?: string
-  // TODO: should we support modifiers for custom directives?
-  // modifiers: DirectiveModifiers
+  modifiers?: DirectiveModifiers
   dir: ObjectDirective<any, V>
 }
 
@@ -41,6 +40,12 @@ export type DirectiveArguments = Array<
   | [Directive | undefined]
   | [Directive | undefined, value: any]
   | [Directive | undefined, value: any, argument: string]
+  | [
+      Directive | undefined,
+      value: any,
+      argument: string,
+      modifiers: DirectiveModifiers,
+    ]
 >
 
 export function withDirectives<T extends Node>(
@@ -56,7 +61,7 @@ export function withDirectives<T extends Node>(
   const bindings = currentInstance.dirs.get(node)!
 
   for (const directive of directives) {
-    let [dir, value, arg] = directive
+    let [dir, value, arg, modifiers] = directive
     if (!dir) continue
     if (isFunction(dir)) {
       // TODO function directive
@@ -71,6 +76,7 @@ export function withDirectives<T extends Node>(
       value,
       oldValue: void 0,
       arg,
+      modifiers,
     }
     if (dir.created) dir.created(node, binding)
     bindings.push(binding)
index 56315ecad3f017a682106ac6196b5b759bed5127..1b9202606085cf65548362aa3ead176560a45437 100644 (file)
@@ -21,5 +21,5 @@ const vDirective: ObjectDirective<HTMLDivElement, undefined> = {
 </script>
 
 <template>
-  <div v-directive v-text="text" />
+  <div v-directive:foo.bar="text" v-text="text" />
 </template>