feat(compiler-vapor): support custom directives argument & modifiers (#34)

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
白雾三语 2023-12-07 01:41:17 +08:00 committed by GitHub
parent 924b9ad732
commit 0cca23f574
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 201 additions and 16 deletions

View 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';

View 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', () => {

View 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(', ')
}

View 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 =

View 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,
})
}
}

View File

@ -279,7 +279,8 @@ export type {
DirectiveHook,
ObjectDirective,
FunctionDirective,
DirectiveArguments
DirectiveArguments,
DirectiveModifiers
} from './directives'
export type { SuspenseBoundary } from './components/Suspense'
export type {

View 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)

View 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>