diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap index 0fcf9b1e1..ed6a96272 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap @@ -100,9 +100,9 @@ exports[`compiler: element transform > component > should wrap as function if v- export function render(_ctx) { const _component_Foo = _resolveComponent("Foo") - const n0 = _createComponent(_component_Foo, [{ - onBar: () => $event => (_ctx.handleBar($event)) - }], true) + const n0 = _createComponent(_component_Foo, [ + { onBar: () => $event => (_ctx.handleBar($event)) } + ], true) return n0 }" `; @@ -112,10 +112,12 @@ exports[`compiler: element transform > component > static props 1`] = ` export function render(_ctx) { const _component_Foo = _resolveComponent("Foo") - const n0 = _createComponent(_component_Foo, [{ - id: () => ("foo"), - class: () => ("bar") - }], true) + const n0 = _createComponent(_component_Foo, [ + { + id: () => ("foo"), + class: () => ("bar") + } + ], true) return n0 }" `; @@ -125,7 +127,9 @@ exports[`compiler: element transform > component > v-bind="obj" 1`] = ` export function render(_ctx) { const _component_Foo = _resolveComponent("Foo") - const n0 = _createComponent(_component_Foo, [() => (_ctx.obj)], true) + const n0 = _createComponent(_component_Foo, [ + () => (_ctx.obj) + ], true) return n0 }" `; @@ -135,9 +139,10 @@ exports[`compiler: element transform > component > v-bind="obj" after static pro export function render(_ctx) { const _component_Foo = _resolveComponent("Foo") - const n0 = _createComponent(_component_Foo, [{ - id: () => ("foo") - }, () => (_ctx.obj)], true) + const n0 = _createComponent(_component_Foo, [ + { id: () => ("foo") }, + () => (_ctx.obj) + ], true) return n0 }" `; @@ -147,9 +152,10 @@ exports[`compiler: element transform > component > v-bind="obj" before static pr export function render(_ctx) { const _component_Foo = _resolveComponent("Foo") - const n0 = _createComponent(_component_Foo, [() => (_ctx.obj), { - id: () => ("foo") - }], true) + const n0 = _createComponent(_component_Foo, [ + () => (_ctx.obj), + { id: () => ("foo") } + ], true) return n0 }" `; @@ -159,11 +165,11 @@ exports[`compiler: element transform > component > v-bind="obj" between static p export function render(_ctx) { const _component_Foo = _resolveComponent("Foo") - const n0 = _createComponent(_component_Foo, [{ - id: () => ("foo") - }, () => (_ctx.obj), { - class: () => ("bar") - }], true) + const n0 = _createComponent(_component_Foo, [ + { id: () => ("foo") }, + () => (_ctx.obj), + { class: () => ("bar") } + ], true) return n0 }" `; @@ -174,7 +180,36 @@ import { resolveComponent as _resolveComponent, createComponent as _createCompon export function render(_ctx) { const _component_Foo = _resolveComponent("Foo") - const n0 = _createComponent(_component_Foo, [() => (_toHandlers(_ctx.obj))], true) + const n0 = _createComponent(_component_Foo, [ + () => (_toHandlers(_ctx.obj)) + ], true) + return n0 +}" +`; + +exports[`compiler: element transform > component with dynamic event arguments 1`] = ` +"import { toHandlerKey as _toHandlerKey } from 'vue'; +import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor'; + +export function render(_ctx) { + const _component_Foo = _resolveComponent("Foo") + const n0 = _createComponent(_component_Foo, [ + () => ({ [_toHandlerKey(_ctx.foo-_ctx.bar)]: () => _ctx.bar }), + () => ({ [_toHandlerKey(_ctx.baz)]: () => _ctx.qux }) + ], true) + return n0 +}" +`; + +exports[`compiler: element transform > component with dynamic prop arguments 1`] = ` +"import { resolveComponent as _resolveComponent, createComponent as _createComponent } from 'vue/vapor'; + +export function render(_ctx) { + const _component_Foo = _resolveComponent("Foo") + const n0 = _createComponent(_component_Foo, [ + () => ({ [_ctx.foo-_ctx.bar]: _ctx.bar }), + () => ({ [_ctx.baz]: _ctx.qux }) + ], true) return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap index a8764d6e1..5f44f54cf 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap @@ -5,11 +5,11 @@ exports[`compiler: vModel transform > component > v-model for component should g export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") - const n0 = _createComponent(_component_Comp, [{ - modelValue: () => (_ctx.foo), + const n0 = _createComponent(_component_Comp, [ + { modelValue: () => (_ctx.foo), "onUpdate:modelValue": () => $event => (_ctx.foo = $event), - modelModifiers: () => ({ trim: true, "bar-baz": true }) - }], true) + modelModifiers: () => ({ trim: true, "bar-baz": true }) } + ], true) return n0 }" `; @@ -19,10 +19,10 @@ exports[`compiler: vModel transform > component > v-model for component should w export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") - const n0 = _createComponent(_component_Comp, [{ - modelValue: () => (_ctx.foo), - "onUpdate:modelValue": () => $event => (_ctx.foo = $event) - }], true) + const n0 = _createComponent(_component_Comp, [ + { modelValue: () => (_ctx.foo), + "onUpdate:modelValue": () => $event => (_ctx.foo = $event) } + ], true) return n0 }" `; @@ -32,14 +32,16 @@ exports[`compiler: vModel transform > component > v-model with arguments for com export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") - const n0 = _createComponent(_component_Comp, [{ - foo: () => (_ctx.foo), - "onUpdate:foo": () => $event => (_ctx.foo = $event), - fooModifiers: () => ({ trim: true }), - bar: () => (_ctx.bar), - "onUpdate:bar": () => $event => (_ctx.bar = $event), - barModifiers: () => ({ number: true }) - }], true) + const n0 = _createComponent(_component_Comp, [ + { + foo: () => (_ctx.foo), + "onUpdate:foo": () => $event => (_ctx.foo = $event), + fooModifiers: () => ({ trim: true }), + bar: () => (_ctx.bar), + "onUpdate:bar": () => $event => (_ctx.bar = $event), + barModifiers: () => ({ number: true }) + } + ], true) return n0 }" `; @@ -49,10 +51,10 @@ exports[`compiler: vModel transform > component > v-model with arguments for com export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") - const n0 = _createComponent(_component_Comp, [{ - bar: () => (_ctx.foo), - "onUpdate:bar": () => $event => (_ctx.foo = $event) - }], true) + const n0 = _createComponent(_component_Comp, [ + { bar: () => (_ctx.foo), + "onUpdate:bar": () => $event => (_ctx.foo = $event) } + ], true) return n0 }" `; @@ -62,14 +64,14 @@ exports[`compiler: vModel transform > component > v-model with dynamic arguments export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") - const n0 = _createComponent(_component_Comp, [{ - [_ctx.foo]: () => (_ctx.foo), + const n0 = _createComponent(_component_Comp, [ + () => ({ [_ctx.foo]: _ctx.foo, ["onUpdate:" + _ctx.foo]: () => $event => (_ctx.foo = $event), - [_ctx.foo + "Modifiers"]: () => ({ trim: true }), - [_ctx.bar]: () => (_ctx.bar), + [_ctx.foo + "Modifiers"]: () => ({ trim: true }) }), + () => ({ [_ctx.bar]: _ctx.bar, ["onUpdate:" + _ctx.bar]: () => $event => (_ctx.bar = $event), - [_ctx.bar + "Modifiers"]: () => ({ number: true }) - }], true) + [_ctx.bar + "Modifiers"]: () => ({ number: true }) }) + ], true) return n0 }" `; @@ -79,10 +81,10 @@ exports[`compiler: vModel transform > component > v-model with dynamic arguments export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") - const n0 = _createComponent(_component_Comp, [{ - [_ctx.arg]: () => (_ctx.foo), - ["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event) - }], true) + const n0 = _createComponent(_component_Comp, [ + () => ({ [_ctx.arg]: _ctx.foo, + ["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event) }) + ], true) return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts index 2fa5245a9..4bf38ba3e 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts @@ -1,5 +1,6 @@ import { makeCompile } from './_utils' import { + IRDynamicPropsKind, IRNodeTypes, transformChildren, transformElement, @@ -198,10 +199,12 @@ describe('compiler: element transform', () => { ) expect(code).toMatchSnapshot() - expect(code).contains('_createComponent(_component_Foo, [{') - expect(code).contains(' id: () => ("foo")') - expect(code).contains(' class: () => ("bar")') - expect(code).contains('}], true)') + expect(code).contains(`[ + { + id: () => ("foo"), + class: () => ("bar") + } + ]`) expect(ir.block.operation).toMatchObject([ { @@ -248,12 +251,19 @@ describe('compiler: element transform', () => { test('v-bind="obj"', () => { const { code, ir } = compileWithElementTransform(``) expect(code).toMatchSnapshot() - expect(code).contains('[() => (_ctx.obj)]') + expect(code).contains(`[ + () => (_ctx.obj) + ]`) expect(ir.block.operation).toMatchObject([ { type: IRNodeTypes.CREATE_COMPONENT_NODE, tag: 'Foo', - props: [{ value: { content: 'obj', isStatic: false } }], + props: [ + { + kind: IRDynamicPropsKind.EXPRESSION, + value: { content: 'obj', isStatic: false }, + }, + ], }, ]) }) @@ -263,15 +273,20 @@ describe('compiler: element transform', () => { ``, ) expect(code).toMatchSnapshot() - expect(code).contains('id: () => ("foo")') - expect(code).contains('}, () => (_ctx.obj)]') + expect(code).contains(`[ + { id: () => ("foo") }, + () => (_ctx.obj) + ]`) expect(ir.block.operation).toMatchObject([ { type: IRNodeTypes.CREATE_COMPONENT_NODE, tag: 'Foo', props: [ [{ key: { content: 'id' }, values: [{ content: 'foo' }] }], - { value: { content: 'obj' } }, + { + kind: IRDynamicPropsKind.EXPRESSION, + value: { content: 'obj' }, + }, ], }, ]) @@ -282,14 +297,19 @@ describe('compiler: element transform', () => { ``, ) expect(code).toMatchSnapshot() - expect(code).contains('[() => (_ctx.obj), {') - expect(code).contains('id: () => ("foo")') + expect(code).contains(`[ + () => (_ctx.obj), + { id: () => ("foo") } + ]`) expect(ir.block.operation).toMatchObject([ { type: IRNodeTypes.CREATE_COMPONENT_NODE, tag: 'Foo', props: [ - { value: { content: 'obj' } }, + { + kind: IRDynamicPropsKind.EXPRESSION, + value: { content: 'obj' }, + }, [{ key: { content: 'id' }, values: [{ content: 'foo' }] }], ], }, @@ -301,16 +321,21 @@ describe('compiler: element transform', () => { ``, ) expect(code).toMatchSnapshot() - expect(code).contains('id: () => ("foo")') - expect(code).contains('}, () => (_ctx.obj), {') - expect(code).contains('class: () => ("bar")') + expect(code).contains(`[ + { id: () => ("foo") }, + () => (_ctx.obj), + { class: () => ("bar") } + ]`) expect(ir.block.operation).toMatchObject([ { type: IRNodeTypes.CREATE_COMPONENT_NODE, tag: 'Foo', props: [ [{ key: { content: 'id' }, values: [{ content: 'foo' }] }], - { value: { content: 'obj' } }, + { + kind: IRDynamicPropsKind.EXPRESSION, + value: { content: 'obj' }, + }, [{ key: { content: 'class' }, values: [{ content: 'bar' }] }], ], }, @@ -356,12 +381,20 @@ describe('compiler: element transform', () => { test('v-on="obj"', () => { const { code, ir } = compileWithElementTransform(``) expect(code).toMatchSnapshot() - expect(code).contains('[() => (_toHandlers(_ctx.obj))]') + expect(code).contains(`[ + () => (_toHandlers(_ctx.obj)) + ]`) expect(ir.block.operation).toMatchObject([ { type: IRNodeTypes.CREATE_COMPONENT_NODE, tag: 'Foo', - props: [{ value: { content: 'obj' }, handler: true }], + props: [ + { + kind: IRDynamicPropsKind.EXPRESSION, + value: { content: 'obj' }, + handler: true, + }, + ], }, ]) }) @@ -432,6 +465,7 @@ describe('compiler: element transform', () => { element: 0, props: [ { + kind: IRDynamicPropsKind.EXPRESSION, value: { type: NodeTypes.SIMPLE_EXPRESSION, content: 'obj', @@ -467,6 +501,7 @@ describe('compiler: element transform', () => { props: [ [{ key: { content: 'id' }, values: [{ content: 'foo' }] }], { + kind: IRDynamicPropsKind.EXPRESSION, value: { type: NodeTypes.SIMPLE_EXPRESSION, content: 'obj', @@ -494,7 +529,10 @@ describe('compiler: element transform', () => { type: IRNodeTypes.SET_DYNAMIC_PROPS, element: 0, props: [ - { value: { content: 'obj' } }, + { + kind: IRDynamicPropsKind.EXPRESSION, + value: { content: 'obj' }, + }, [{ key: { content: 'id' }, values: [{ content: 'foo' }] }], ], }, @@ -518,7 +556,10 @@ describe('compiler: element transform', () => { element: 0, props: [ [{ key: { content: 'id' }, values: [{ content: 'foo' }] }], - { value: { content: 'obj' } }, + { + kind: IRDynamicPropsKind.EXPRESSION, + value: { content: 'obj' }, + }, [{ key: { content: 'class' }, values: [{ content: 'bar' }] }], ], }, @@ -691,6 +732,58 @@ describe('compiler: element transform', () => { expect(code).contains('_setDynamicEvents(n0, _ctx.obj)') }) + test('component with dynamic prop arguments', () => { + const { code, ir } = compileWithElementTransform( + ``, + ) + expect(code).toMatchSnapshot() + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.CREATE_COMPONENT_NODE, + tag: 'Foo', + props: [ + { + kind: IRDynamicPropsKind.ATTRIBUTE, + key: { content: 'foo-bar' }, + values: [{ content: 'bar' }], + }, + { + kind: IRDynamicPropsKind.ATTRIBUTE, + key: { content: 'baz' }, + values: [{ content: 'qux' }], + }, + ], + }, + ]) + }) + + test('component with dynamic event arguments', () => { + const { code, ir } = compileWithElementTransform( + ``, + ) + expect(code).toMatchSnapshot() + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.CREATE_COMPONENT_NODE, + tag: 'Foo', + props: [ + { + kind: IRDynamicPropsKind.ATTRIBUTE, + key: { content: 'foo-bar' }, + values: [{ content: 'bar' }], + handler: true, + }, + { + kind: IRDynamicPropsKind.ATTRIBUTE, + key: { content: 'baz' }, + values: [{ content: 'qux' }], + handler: true, + }, + ], + }, + ]) + }) + test('invalid html nesting', () => { const { code, ir } = compileWithElementTransform( `

123

diff --git a/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts b/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts index 80a22c880..921145643 100644 --- a/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts @@ -259,7 +259,7 @@ describe('compiler: vModel transform', () => { const { code, ir } = compileWithVModel('') expect(code).toMatchSnapshot() expect(code).contains( - `[_ctx.arg]: () => (_ctx.foo), + `[_ctx.arg]: _ctx.foo, ["onUpdate:" + _ctx.arg]: () => $event => (_ctx.foo = $event)`, ) expect(ir.block.operation).toMatchObject([ @@ -267,14 +267,12 @@ describe('compiler: vModel transform', () => { type: IRNodeTypes.CREATE_COMPONENT_NODE, tag: 'Comp', props: [ - [ - { - key: { content: 'arg', isStatic: false }, - values: [{ content: 'foo', isStatic: false }], - model: true, - modelModifiers: [], - }, - ], + { + key: { content: 'arg', isStatic: false }, + values: [{ content: 'foo', isStatic: false }], + model: true, + modelModifiers: [], + }, ], }, ]) @@ -349,20 +347,18 @@ describe('compiler: vModel transform', () => { type: IRNodeTypes.CREATE_COMPONENT_NODE, tag: 'Comp', props: [ - [ - { - key: { content: 'foo', isStatic: false }, - values: [{ content: 'foo', isStatic: false }], - model: true, - modelModifiers: ['trim'], - }, - { - key: { content: 'bar', isStatic: false }, - values: [{ content: 'bar', isStatic: false }], - model: true, - modelModifiers: ['number'], - }, - ], + { + key: { content: 'foo', isStatic: false }, + values: [{ content: 'foo', isStatic: false }], + model: true, + modelModifiers: ['trim'], + }, + { + key: { content: 'bar', isStatic: false }, + values: [{ content: 'bar', isStatic: false }], + model: true, + modelModifiers: ['number'], + }, ], }, ]) diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index 5f3d060b8..fba45ab1e 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -1,10 +1,17 @@ import { camelize, extend, isArray } from '@vue/shared' import type { CodegenContext } from '../generate' -import type { CreateComponentIRNode, IRProp } from '../ir' +import { + type CreateComponentIRNode, + IRDynamicPropsKind, + type IRProp, + type IRProps, + type IRPropsStatic, +} from '../ir' import { type CodeFragment, NEWLINE, - SEGMENTS_ARRAY, + SEGMENTS_ARRAY_NEWLINE, + SEGMENTS_OBJECT, SEGMENTS_OBJECT_NEWLINE, genCall, genMulti, @@ -21,11 +28,11 @@ export function genCreateComponent( oper: CreateComponentIRNode, context: CodegenContext, ): CodeFragment[] { - const { helper, vaporHelper } = context + const { vaporHelper } = context const tag = genTag() const isRoot = oper.root - const rawProps = genRawProps() + const rawProps = genRawProps(oper.props, context) return [ NEWLINE, @@ -49,63 +56,80 @@ export function genCreateComponent( ) } } +} - function genRawProps() { - const props = oper.props - .map(props => { - if (isArray(props)) { - if (!props.length) return - return genStaticProps(props) - } else { - let expr = genExpression(props.value, context) - if (props.handler) expr = genCall(helper('toHandlers'), expr) - return ['() => (', ...expr, ')'] +export function genRawProps(props: IRProps[], context: CodegenContext) { + const frag = props + .map(props => { + if (isArray(props)) { + if (!props.length) return + return genStaticProps(props, context) + } else { + let expr: CodeFragment[] + if (props.kind === IRDynamicPropsKind.ATTRIBUTE) + expr = genMulti(SEGMENTS_OBJECT, genProp(props, context)) + else { + expr = genExpression(props.value, context) + if (props.handler) expr = genCall(context.helper('toHandlers'), expr) } - }) - .filter(Boolean) - if (props.length) { - return genMulti(SEGMENTS_ARRAY, ...props) - } + return ['() => (', ...expr, ')'] + } + }) + .filter( + Boolean as any as (v: CodeFragment[] | undefined) => v is CodeFragment[], + ) + if (frag.length) { + return genMulti(SEGMENTS_ARRAY_NEWLINE, ...frag) } +} - function genStaticProps(props: IRProp[]) { - return genMulti( - SEGMENTS_OBJECT_NEWLINE, - ...props.map(prop => { - return [ - ...genPropKey(prop, context), - ': ', - ...(prop.handler - ? genEventHandler(context, prop.values[0]) - : ['() => (', ...genExpression(prop.values[0], context), ')']), - ...(prop.model - ? [...genModelEvent(prop), ...genModelModifiers(prop)] - : []), - ] - }), - ) +function genStaticProps( + props: IRPropsStatic, + context: CodegenContext, +): CodeFragment[] { + return genMulti( + props.length > 1 ? SEGMENTS_OBJECT_NEWLINE : SEGMENTS_OBJECT, + ...props.map(prop => genProp(prop, context, true)), + ) +} - function genModelEvent(prop: IRProp): CodeFragment[] { - const name = prop.key.isStatic - ? [JSON.stringify(`onUpdate:${camelize(prop.key.content)}`)] - : ['["onUpdate:" + ', ...genExpression(prop.key, context), ']'] - const handler = genModelHandler(prop.values[0], context) +function genProp(prop: IRProp, context: CodegenContext, isStatic?: boolean) { + return [ + ...genPropKey(prop, context), + ': ', + ...(prop.handler + ? genEventHandler(context, prop.values[0]) + : isStatic + ? ['() => (', ...genExpression(prop.values[0], context), ')'] + : genExpression(prop.values[0], context)), + ...(prop.model + ? [...genModelEvent(prop, context), ...genModelModifiers(prop, context)] + : []), + ] +} - return [',', NEWLINE, ...name, ': ', ...handler] - } +function genModelEvent(prop: IRProp, context: CodegenContext): CodeFragment[] { + const name = prop.key.isStatic + ? [JSON.stringify(`onUpdate:${camelize(prop.key.content)}`)] + : ['["onUpdate:" + ', ...genExpression(prop.key, context), ']'] + const handler = genModelHandler(prop.values[0], context) - function genModelModifiers(prop: IRProp): CodeFragment[] { - const { key, modelModifiers } = prop - if (!modelModifiers || !modelModifiers.length) return [] + return [',', NEWLINE, ...name, ': ', ...handler] +} - const modifiersKey = key.isStatic - ? key.content === 'modelValue' - ? [`modelModifiers`] - : [`${key.content}Modifiers`] - : ['[', ...genExpression(key, context), ' + "Modifiers"]'] +function genModelModifiers( + prop: IRProp, + context: CodegenContext, +): CodeFragment[] { + const { key, modelModifiers } = prop + if (!modelModifiers || !modelModifiers.length) return [] - const modifiersVal = genDirectiveModifiers(modelModifiers) - return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`] - } - } + const modifiersKey = key.isStatic + ? key.content === 'modelValue' + ? [`modelModifiers`] + : [`${key.content}Modifiers`] + : ['[', ...genExpression(key, context), ' + "Modifiers"]'] + + const modifiersVal = genDirectiveModifiers(modelModifiers) + return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`] } diff --git a/packages/compiler-vapor/src/generators/prop.ts b/packages/compiler-vapor/src/generators/prop.ts index 1f31e4ceb..861397c9a 100644 --- a/packages/compiler-vapor/src/generators/prop.ts +++ b/packages/compiler-vapor/src/generators/prop.ts @@ -4,11 +4,12 @@ import { isSimpleIdentifier, } from '@vue/compiler-core' import type { CodegenContext } from '../generate' -import type { - IRProp, - SetDynamicPropsIRNode, - SetPropIRNode, - VaporHelper, +import { + IRDynamicPropsKind, + type IRProp, + type SetDynamicPropsIRNode, + type SetPropIRNode, + type VaporHelper, } from '../ir' import { genExpression } from './expression' import { @@ -73,7 +74,9 @@ export function genDynamicProps( props => Array.isArray(props) ? genLiteralObjectProps(props, context) // static and dynamic arg props - : genExpression(props.value, context), // v-bind="" + : props.kind === IRDynamicPropsKind.ATTRIBUTE + ? genLiteralObjectProps([props], context) // dynamic arg props + : genExpression(props.value, context), // v-bind="" ), ), ] diff --git a/packages/compiler-vapor/src/generators/utils.ts b/packages/compiler-vapor/src/generators/utils.ts index c2e71630a..9a31bc6cb 100644 --- a/packages/compiler-vapor/src/generators/utils.ts +++ b/packages/compiler-vapor/src/generators/utils.ts @@ -57,6 +57,11 @@ export function genMulti( } } export const SEGMENTS_ARRAY: Segments = ['[', ']', ', '] +export const SEGMENTS_ARRAY_NEWLINE: Segments = [ + ['[', INDENT_START, NEWLINE], + [INDENT_END, NEWLINE, ']'], + [', ', NEWLINE], +] export const SEGMENTS_OBJECT: Segments = ['{ ', ' }', ', '] export const SEGMENTS_OBJECT_NEWLINE: Segments = [ ['{', INDENT_START, NEWLINE], diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index e5ba223d7..408e37557 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -83,12 +83,25 @@ export interface ForIRNode extends BaseIRNode { export interface IRProp extends Omit { values: SimpleExpressionNode[] } + +export enum IRDynamicPropsKind { + EXPRESSION, // v-bind="value" + ATTRIBUTE, // v-bind:[foo]="value" +} + +export type IRPropsStatic = IRProp[] +export interface IRPropsDynamicExpression { + kind: IRDynamicPropsKind.EXPRESSION + value: SimpleExpressionNode + handler?: boolean +} +export interface IRPropsDynamicAttribute extends IRProp { + kind: IRDynamicPropsKind.ATTRIBUTE +} export type IRProps = - | IRProp[] - | { - value: SimpleExpressionNode - handler?: boolean - } + | IRPropsStatic + | IRPropsDynamicAttribute + | IRPropsDynamicExpression export interface SetPropIRNode extends BaseIRNode { type: IRNodeTypes.SET_PROP diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index e0ce97b35..b7de58506 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -24,9 +24,11 @@ import type { } from '../transform' import { DynamicFlag, + IRDynamicPropsKind, IRNodeTypes, type IRProp, type IRProps, + type IRPropsDynamicAttribute, type VaporDirectiveNode, } from '../ir' import { EMPTY_EXPRESSION } from './utils' @@ -205,7 +207,10 @@ function buildProps( if (prop.exp) { dynamicExpr.push(prop.exp) pushMergeArg() - dynamicArgs.push({ value: prop.exp }) + dynamicArgs.push({ + kind: IRDynamicPropsKind.EXPRESSION, + value: prop.exp, + }) } else { context.options.onError( createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, prop.loc), @@ -218,7 +223,11 @@ function buildProps( if (isComponent) { dynamicExpr.push(prop.exp) pushMergeArg() - dynamicArgs.push({ value: prop.exp, handler: true }) + dynamicArgs.push({ + kind: IRDynamicPropsKind.EXPRESSION, + value: prop.exp, + handler: true, + }) } else { context.registerEffect( [prop.exp], @@ -241,8 +250,19 @@ function buildProps( const result = transformProp(prop, node, context) if (result) { - results.push(result) dynamicExpr.push(result.key, result.value) + if (isComponent && !result.key.isStatic) { + // v-bind:[name]="value" or v-on:[name]="value" + pushMergeArg() + dynamicArgs.push( + extend(resolveDirectiveResult(result), { + kind: IRDynamicPropsKind.ATTRIBUTE, + }) as IRPropsDynamicAttribute, + ) + } else { + // other static props + results.push(result) + } } } @@ -297,7 +317,7 @@ function dedupeProperties(results: DirectiveTransformResult[]): IRProp[] { const deduped: IRProp[] = [] for (const result of results) { - const prop = normalizeIRProp(result) + const prop = resolveDirectiveResult(result) // dynamic keys are always allowed if (!prop.key.isStatic) { deduped.push(prop) @@ -307,7 +327,7 @@ function dedupeProperties(results: DirectiveTransformResult[]): IRProp[] { const existing = knownProps.get(name) if (existing) { if (name === 'style' || name === 'class') { - mergeAsArray(existing, prop) + mergePropValues(existing, prop) } // unexpected duplicate, should have emitted error during parse } else { @@ -318,11 +338,14 @@ function dedupeProperties(results: DirectiveTransformResult[]): IRProp[] { return deduped } -function normalizeIRProp(prop: DirectiveTransformResult): IRProp { - return extend({}, prop, { value: undefined, values: [prop.value] }) +function resolveDirectiveResult(prop: DirectiveTransformResult): IRProp { + return extend({}, prop, { + value: undefined, + values: [prop.value], + }) } -function mergeAsArray(existing: IRProp, incoming: IRProp) { +function mergePropValues(existing: IRProp, incoming: IRProp) { const newValues = incoming.values existing.values.push(...newValues) }