diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap index 92164b013..11119db64 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.test.ts.snap @@ -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("
") + 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("
") + 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("
") + 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("
") + 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("
") + 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("
") + 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("
") + 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'; diff --git a/packages/compiler-vapor/__tests__/compile.test.ts b/packages/compiler-vapor/__tests__/compile.test.ts index 9459ac00f..95f74715e 100644 --- a/packages/compiler-vapor/__tests__/compile.test.ts +++ b/packages/compiler-vapor/__tests__/compile.test.ts @@ -332,6 +332,76 @@ describe('compile', () => { expect(code).not.contains('v-cloak') }) }) + + describe('custom directive', () => { + test('basic', async () => { + const code = await compile(`
`, { + bindingMetadata: { + vExample: BindingTypes.SETUP_CONST, + }, + }) + expect(code).matchSnapshot() + }) + + test('binding value', async () => { + const code = await compile(`
`, { + bindingMetadata: { + msg: BindingTypes.SETUP_REF, + vExample: BindingTypes.SETUP_CONST, + }, + }) + expect(code).matchSnapshot() + }) + + test('static parameters', async () => { + const code = await compile(`
`, { + bindingMetadata: { + msg: BindingTypes.SETUP_REF, + vExample: BindingTypes.SETUP_CONST, + }, + }) + expect(code).matchSnapshot() + }) + + test('modifiers', async () => { + const code = await compile(`
`, { + bindingMetadata: { + msg: BindingTypes.SETUP_REF, + vExample: BindingTypes.SETUP_CONST, + }, + }) + expect(code).matchSnapshot() + }) + + test('modifiers w/o binding', async () => { + const code = await compile(`
`, { + bindingMetadata: { + vExample: BindingTypes.SETUP_CONST, + }, + }) + expect(code).matchSnapshot() + }) + + test('static parameters and modifiers', async () => { + const code = await compile(`
`, { + bindingMetadata: { + msg: BindingTypes.SETUP_REF, + vExample: BindingTypes.SETUP_CONST, + }, + }) + expect(code).matchSnapshot() + }) + + test('dynamic parameters', async () => { + const code = await compile(`
`, { + bindingMetadata: { + foo: BindingTypes.SETUP_REF, + vExample: BindingTypes.SETUP_CONST, + }, + }) + expect(code).matchSnapshot() + }) + }) }) describe('expression parsing', () => { diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index 8acc18f30..fee38022b 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -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(', ') +} diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index bbfd6e7a4..a8c46ce16 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -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 = diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index b27509a28..ea468d29b 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -59,7 +59,7 @@ function transformProp( node: ElementNode, context: TransformContext, ): 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, }) } } diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index fcc460c43..8c6675ecd 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -279,7 +279,8 @@ export type { DirectiveHook, ObjectDirective, FunctionDirective, - DirectiveArguments + DirectiveArguments, + DirectiveModifiers } from './directives' export type { SuspenseBoundary } from './components/Suspense' export type { diff --git a/packages/runtime-vapor/src/directives.ts b/packages/runtime-vapor/src/directives.ts index 160ec8026..24bc39ae7 100644 --- a/packages/runtime-vapor/src/directives.ts +++ b/packages/runtime-vapor/src/directives.ts @@ -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 { instance: ComponentInternalInstance | null value: V oldValue: V | null arg?: string - // TODO: should we support modifiers for custom directives? - // modifiers: DirectiveModifiers + modifiers?: DirectiveModifiers dir: ObjectDirective } @@ -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( @@ -56,7 +61,7 @@ export function withDirectives( 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( value, oldValue: void 0, arg, + modifiers, } if (dir.created) dir.created(node, binding) bindings.push(binding) diff --git a/playground/src/directive.vue b/playground/src/directive.vue index 56315ecad..1b9202606 100644 --- a/playground/src/directive.vue +++ b/playground/src/directive.vue @@ -21,5 +21,5 @@ const vDirective: ObjectDirective = {