}"
`;
+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';
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', () => {
createSimpleExpression,
walkIdentifiers,
advancePositionWithClone,
+ isSimpleIdentifier,
} from '@vue/compiler-dom'
import {
type IRDynamicChildren,
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
}
push(id, NewlineType.None, loc, name)
}
+
+function genDirectiveModifiers(modifiers: string[]) {
+ return modifiers
+ .map(
+ (value) =>
+ `${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
+ )
+ .join(', ')
+}
export interface WithDirectiveIRNode extends BaseIRNode {
type: IRNodeTypes.WITH_DIRECTIVE
element: number
- name: string
- binding: IRExpression | undefined
+ dir: VaporDirectiveNode
}
export type IRNode =
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}"`
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,
})
}
}
DirectiveHook,
ObjectDirective,
FunctionDirective,
- DirectiveArguments
+ DirectiveArguments,
+ DirectiveModifiers
} from './directives'
export type { SuspenseBoundary } from './components/Suspense'
export type {
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>
}
| [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>(
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
value,
oldValue: void 0,
arg,
+ modifiers,
}
if (dir.created) dir.created(node, binding)
bindings.push(binding)
</script>
<template>
- <div v-directive v-text="text" />
+ <div v-directive:foo.bar="text" v-text="text" />
</template>