feat: support v-bind="{}" (#106)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
parent
920b36490e
commit
d573a3d2f2
|
@ -232,7 +232,7 @@ exports[`compile > expression parsing > v-bind 1`] = `
|
|||
const n0 = t0()
|
||||
const { 0: [n1],} = _children(n0)
|
||||
_renderEffect(() => {
|
||||
_setDynamicProp(n1, key.value+1, _unref(foo)[key.value+1]())
|
||||
_setDynamicProps(n1, { [key.value+1]: _unref(foo)[key.value+1]() })
|
||||
})
|
||||
return n0
|
||||
})()"
|
||||
|
|
|
@ -44,14 +44,14 @@ export function render(_ctx) {
|
|||
|
||||
exports[`compiler v-bind > .camel modifier w/ dynamic arg 1`] = `
|
||||
"import { camelize as _camelize } from 'vue';
|
||||
import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
|
||||
import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const t0 = _template("<div></div>")
|
||||
const n0 = t0()
|
||||
const { 0: [n1],} = _children(n0)
|
||||
_renderEffect(() => {
|
||||
_setDynamicProp(n1, _camelize(_ctx.foo), _ctx.id)
|
||||
_setDynamicProps(n1, { [_camelize(_ctx.foo)]: _ctx.id })
|
||||
})
|
||||
return n0
|
||||
}"
|
||||
|
@ -114,14 +114,14 @@ export function render(_ctx) {
|
|||
`;
|
||||
|
||||
exports[`compiler v-bind > .prop modifier w/ dynamic arg 1`] = `
|
||||
"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
|
||||
"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const t0 = _template("<div></div>")
|
||||
const n0 = t0()
|
||||
const { 0: [n1],} = _children(n0)
|
||||
_renderEffect(() => {
|
||||
_setDynamicProp(n1, \`.\${_ctx.fooBar}\`, _ctx.id)
|
||||
_setDynamicProps(n1, { [\`.\${_ctx.fooBar}\`]: _ctx.id })
|
||||
})
|
||||
return n0
|
||||
}"
|
||||
|
@ -156,14 +156,14 @@ export function render(_ctx) {
|
|||
`;
|
||||
|
||||
exports[`compiler v-bind > dynamic arg 1`] = `
|
||||
"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp } from 'vue/vapor';
|
||||
"import { template as _template, children as _children, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps } from 'vue/vapor';
|
||||
|
||||
export function render(_ctx) {
|
||||
const t0 = _template("<div></div>")
|
||||
const n0 = t0()
|
||||
const { 0: [n1],} = _children(n0)
|
||||
_renderEffect(() => {
|
||||
_setDynamicProp(n1, _ctx.id, _ctx.id)
|
||||
_setDynamicProps(n1, { [_ctx.id]: _ctx.id, [_ctx.title]: _ctx.title })
|
||||
})
|
||||
return n0
|
||||
}"
|
||||
|
|
|
@ -41,25 +41,33 @@ describe('compiler v-bind', () => {
|
|||
{
|
||||
type: IRNodeTypes.SET_PROP,
|
||||
element: 1,
|
||||
key: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'id',
|
||||
isStatic: true,
|
||||
loc: {
|
||||
start: { line: 1, column: 13, offset: 12 },
|
||||
end: { line: 1, column: 15, offset: 14 },
|
||||
source: 'id',
|
||||
prop: {
|
||||
key: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'id',
|
||||
isStatic: true,
|
||||
loc: {
|
||||
start: { line: 1, column: 13, offset: 12 },
|
||||
end: { line: 1, column: 15, offset: 14 },
|
||||
source: 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'id',
|
||||
isStatic: false,
|
||||
loc: {
|
||||
source: 'id',
|
||||
start: { line: 1, column: 17, offset: 16 },
|
||||
end: { line: 1, column: 19, offset: 18 },
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'id',
|
||||
isStatic: false,
|
||||
loc: {
|
||||
source: 'id',
|
||||
start: { line: 1, column: 17, offset: 16 },
|
||||
end: { line: 1, column: 19, offset: 18 },
|
||||
},
|
||||
},
|
||||
loc: {
|
||||
start: { column: 6, line: 1, offset: 5 },
|
||||
end: { column: 20, line: 1, offset: 19 },
|
||||
source: 'v-bind:id="id"',
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -72,68 +80,91 @@ describe('compiler v-bind', () => {
|
|||
test('no expression', () => {
|
||||
const { ir, code } = compileWithVBind(`<div v-bind:id />`)
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
type: IRNodeTypes.SET_PROP,
|
||||
key: {
|
||||
content: `id`,
|
||||
isStatic: true,
|
||||
loc: {
|
||||
start: { line: 1, column: 13, offset: 12 },
|
||||
end: { line: 1, column: 15, offset: 14 },
|
||||
prop: {
|
||||
key: {
|
||||
content: `id`,
|
||||
isStatic: true,
|
||||
loc: {
|
||||
start: { line: 1, column: 13, offset: 12 },
|
||||
end: { line: 1, column: 15, offset: 14 },
|
||||
},
|
||||
},
|
||||
},
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
loc: {
|
||||
start: { line: 1, column: 13, offset: 12 },
|
||||
end: { line: 1, column: 15, offset: 14 },
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
loc: {
|
||||
start: { line: 1, column: 13, offset: 12 },
|
||||
end: { line: 1, column: 15, offset: 14 },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(code).contains('_setDynamicProp(n1, "id", _ctx.id)')
|
||||
})
|
||||
|
||||
test('no expression (shorthand)', () => {
|
||||
const { ir, code } = compileWithVBind(`<div :camel-case />`)
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
type: IRNodeTypes.SET_PROP,
|
||||
key: {
|
||||
content: `camel-case`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `camelCase`,
|
||||
isStatic: false,
|
||||
prop: {
|
||||
key: {
|
||||
content: `camel-case`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `camelCase`,
|
||||
isStatic: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(code).contains('_setDynamicProp(n1, "camel-case", _ctx.camelCase)')
|
||||
})
|
||||
|
||||
test('dynamic arg', () => {
|
||||
const { ir, code } = compileWithVBind(`<div v-bind:[id]="id"/>`)
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
type: IRNodeTypes.SET_PROP,
|
||||
element: 1,
|
||||
key: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'id',
|
||||
isStatic: false,
|
||||
},
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'id',
|
||||
isStatic: false,
|
||||
},
|
||||
})
|
||||
|
||||
const { ir, code } = compileWithVBind(
|
||||
`<div v-bind:[id]="id" v-bind:[title]="title" />`,
|
||||
)
|
||||
expect(code).matchSnapshot()
|
||||
expect(code).contains('_setDynamicProp(n1, _ctx.id, _ctx.id)')
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
type: IRNodeTypes.SET_DYNAMIC_PROPS,
|
||||
element: 1,
|
||||
props: [
|
||||
[
|
||||
{
|
||||
key: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'id',
|
||||
isStatic: false,
|
||||
},
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'id',
|
||||
isStatic: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'title',
|
||||
isStatic: false,
|
||||
},
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'title',
|
||||
isStatic: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
})
|
||||
expect(code).contains(
|
||||
'_setDynamicProps(n1, { [_ctx.id]: _ctx.id, [_ctx.title]: _ctx.title })',
|
||||
)
|
||||
})
|
||||
|
||||
test('should error if empty expression', () => {
|
||||
|
@ -162,16 +193,18 @@ describe('compiler v-bind', () => {
|
|||
const { ir, code } = compileWithVBind(`<div v-bind:foo-bar.camel="id"/>`)
|
||||
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
key: {
|
||||
content: `fooBar`,
|
||||
isStatic: true,
|
||||
prop: {
|
||||
key: {
|
||||
content: `fooBar`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: undefined,
|
||||
},
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: undefined,
|
||||
})
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
|
@ -181,20 +214,21 @@ describe('compiler v-bind', () => {
|
|||
test('.camel modifier w/ no expression', () => {
|
||||
const { ir, code } = compileWithVBind(`<div v-bind:foo-bar.camel />`)
|
||||
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
key: {
|
||||
content: `fooBar`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `fooBar`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: undefined,
|
||||
})
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
prop: {
|
||||
key: {
|
||||
content: `fooBar`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `fooBar`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: undefined,
|
||||
},
|
||||
})
|
||||
expect(code).contains('renderEffect')
|
||||
expect(code).contains('_setDynamicProp(n1, "fooBar", _ctx.fooBar)')
|
||||
})
|
||||
|
@ -203,21 +237,30 @@ describe('compiler v-bind', () => {
|
|||
const { ir, code } = compileWithVBind(`<div v-bind:[foo].camel="id"/>`)
|
||||
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
key: {
|
||||
content: `foo`,
|
||||
isStatic: false,
|
||||
},
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: true,
|
||||
modifier: undefined,
|
||||
type: IRNodeTypes.SET_DYNAMIC_PROPS,
|
||||
props: [
|
||||
[
|
||||
{
|
||||
key: {
|
||||
content: `foo`,
|
||||
isStatic: false,
|
||||
},
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: true,
|
||||
modifier: undefined,
|
||||
},
|
||||
],
|
||||
],
|
||||
})
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(code).contains('renderEffect')
|
||||
expect(code).contains(`_setDynamicProp(n1, _camelize(_ctx.foo), _ctx.id)`)
|
||||
expect(code).contains(
|
||||
`_setDynamicProps(n1, { [_camelize(_ctx.foo)]: _ctx.id })`,
|
||||
)
|
||||
})
|
||||
|
||||
test.todo('.camel modifier w/ dynamic arg + prefixIdentifiers')
|
||||
|
@ -225,20 +268,21 @@ describe('compiler v-bind', () => {
|
|||
test('.prop modifier', () => {
|
||||
const { ir, code } = compileWithVBind(`<div v-bind:fooBar.prop="id"/>`)
|
||||
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
key: {
|
||||
content: `fooBar`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: '.',
|
||||
})
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
prop: {
|
||||
key: {
|
||||
content: `fooBar`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: '.',
|
||||
},
|
||||
})
|
||||
expect(code).contains('renderEffect')
|
||||
expect(code).contains('_setDOMProp(n1, "fooBar", _ctx.id)')
|
||||
})
|
||||
|
@ -246,20 +290,21 @@ describe('compiler v-bind', () => {
|
|||
test('.prop modifier w/ no expression', () => {
|
||||
const { ir, code } = compileWithVBind(`<div v-bind:fooBar.prop />`)
|
||||
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
key: {
|
||||
content: `fooBar`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `fooBar`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: '.',
|
||||
})
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
prop: {
|
||||
key: {
|
||||
content: `fooBar`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `fooBar`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: '.',
|
||||
},
|
||||
})
|
||||
expect(code).contains('renderEffect')
|
||||
expect(code).contains('_setDOMProp(n1, "fooBar", _ctx.fooBar)')
|
||||
})
|
||||
|
@ -267,22 +312,30 @@ describe('compiler v-bind', () => {
|
|||
test('.prop modifier w/ dynamic arg', () => {
|
||||
const { ir, code } = compileWithVBind(`<div v-bind:[fooBar].prop="id"/>`)
|
||||
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
key: {
|
||||
content: `fooBar`,
|
||||
isStatic: false,
|
||||
},
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: '.',
|
||||
})
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
type: IRNodeTypes.SET_DYNAMIC_PROPS,
|
||||
props: [
|
||||
[
|
||||
{
|
||||
key: {
|
||||
content: `fooBar`,
|
||||
isStatic: false,
|
||||
},
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: '.',
|
||||
},
|
||||
],
|
||||
],
|
||||
})
|
||||
expect(code).contains('renderEffect')
|
||||
expect(code).contains('_setDynamicProp(n1, `.${_ctx.fooBar}`, _ctx.id)')
|
||||
expect(code).contains(
|
||||
'_setDynamicProps(n1, { [`.${_ctx.fooBar}`]: _ctx.id })',
|
||||
)
|
||||
})
|
||||
|
||||
test.todo('.prop modifier w/ dynamic arg + prefixIdentifiers')
|
||||
|
@ -290,20 +343,21 @@ describe('compiler v-bind', () => {
|
|||
test('.prop modifier (shorthand)', () => {
|
||||
const { ir, code } = compileWithVBind(`<div .fooBar="id"/>`)
|
||||
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
key: {
|
||||
content: `fooBar`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: '.',
|
||||
})
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
prop: {
|
||||
key: {
|
||||
content: `fooBar`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: '.',
|
||||
},
|
||||
})
|
||||
expect(code).contains('renderEffect')
|
||||
expect(code).contains('_setDOMProp(n1, "fooBar", _ctx.id)')
|
||||
})
|
||||
|
@ -311,20 +365,21 @@ describe('compiler v-bind', () => {
|
|||
test('.prop modifier (shortband) w/ no expression', () => {
|
||||
const { ir, code } = compileWithVBind(`<div .fooBar />`)
|
||||
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
key: {
|
||||
content: `fooBar`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `fooBar`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: '.',
|
||||
})
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
prop: {
|
||||
key: {
|
||||
content: `fooBar`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `fooBar`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: '.',
|
||||
},
|
||||
})
|
||||
expect(code).contains('renderEffect')
|
||||
expect(code).contains('_setDOMProp(n1, "fooBar", _ctx.fooBar)')
|
||||
})
|
||||
|
@ -332,20 +387,21 @@ describe('compiler v-bind', () => {
|
|||
test('.attr modifier', () => {
|
||||
const { ir, code } = compileWithVBind(`<div v-bind:foo-bar.attr="id"/>`)
|
||||
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
key: {
|
||||
content: `foo-bar`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: '^',
|
||||
})
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
prop: {
|
||||
key: {
|
||||
content: `foo-bar`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: '^',
|
||||
},
|
||||
})
|
||||
expect(code).contains('renderEffect')
|
||||
expect(code).contains('_setAttr(n1, "foo-bar", _ctx.id)')
|
||||
})
|
||||
|
@ -353,20 +409,22 @@ describe('compiler v-bind', () => {
|
|||
test('.attr modifier w/ no expression', () => {
|
||||
const { ir, code } = compileWithVBind(`<div v-bind:foo-bar.attr />`)
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(ir.effect[0].operations[0]).toMatchObject({
|
||||
key: {
|
||||
content: `foo-bar`,
|
||||
isStatic: true,
|
||||
prop: {
|
||||
key: {
|
||||
content: `foo-bar`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `fooBar`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: '^',
|
||||
},
|
||||
value: {
|
||||
content: `fooBar`,
|
||||
isStatic: false,
|
||||
},
|
||||
runtimeCamelize: false,
|
||||
modifier: '^',
|
||||
})
|
||||
|
||||
expect(code).matchSnapshot()
|
||||
expect(code).contains('renderEffect')
|
||||
expect(code).contains('_setAttr(n1, "foo-bar", _ctx.fooBar)')
|
||||
})
|
||||
|
|
|
@ -43,16 +43,18 @@ describe('compiler: v-once', () => {
|
|||
},
|
||||
{
|
||||
element: 2,
|
||||
key: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'class',
|
||||
isStatic: true,
|
||||
},
|
||||
type: IRNodeTypes.SET_PROP,
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'clz',
|
||||
isStatic: false,
|
||||
prop: {
|
||||
key: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'class',
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'clz',
|
||||
isStatic: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -73,15 +75,17 @@ describe('compiler: v-once', () => {
|
|||
{
|
||||
type: IRNodeTypes.SET_PROP,
|
||||
element: 1,
|
||||
key: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'id',
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'foo',
|
||||
isStatic: false,
|
||||
prop: {
|
||||
key: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'id',
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'foo',
|
||||
isStatic: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
@ -100,16 +104,18 @@ describe('compiler: v-once', () => {
|
|||
{
|
||||
type: IRNodeTypes.SET_PROP,
|
||||
element: 1,
|
||||
runtimeCamelize: false,
|
||||
key: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'id',
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'foo',
|
||||
isStatic: false,
|
||||
prop: {
|
||||
runtimeCamelize: false,
|
||||
key: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'id',
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'foo',
|
||||
isStatic: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
|
|
@ -13,7 +13,7 @@ import { genFor } from './for'
|
|||
import { genSetHtml } from './html'
|
||||
import { genIf } from './if'
|
||||
import { genSetModelValue } from './modelValue'
|
||||
import { genSetProp } from './prop'
|
||||
import { genDynamicProps, genSetProp } from './prop'
|
||||
import { genSetRef } from './ref'
|
||||
import { genCreateTextNode, genSetText } from './text'
|
||||
|
||||
|
@ -32,6 +32,8 @@ export function genOperation(
|
|||
switch (oper.type) {
|
||||
case IRNodeTypes.SET_PROP:
|
||||
return genSetProp(oper, context)
|
||||
case IRNodeTypes.SET_DYNAMIC_PROPS:
|
||||
return genDynamicProps(oper, context)
|
||||
case IRNodeTypes.SET_TEXT:
|
||||
return genSetText(oper, context)
|
||||
case IRNodeTypes.SET_EVENT:
|
||||
|
|
|
@ -1,54 +1,107 @@
|
|||
import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate'
|
||||
import type { SetPropIRNode, VaporHelper } from '../ir'
|
||||
import type { SetDynamicPropsIRNode, SetPropIRNode, VaporHelper } from '../ir'
|
||||
import { genExpression } from './expression'
|
||||
import { isString } from '@vue/shared'
|
||||
import type { DirectiveTransformResult } from '../transform'
|
||||
import { isSimpleIdentifier } from '@vue/compiler-core'
|
||||
|
||||
// only the static key prop will reach here
|
||||
export function genSetProp(
|
||||
oper: SetPropIRNode,
|
||||
context: CodegenContext,
|
||||
): CodeFragment[] {
|
||||
const { call, vaporHelper, helper } = context
|
||||
const { call, vaporHelper } = context
|
||||
const {
|
||||
prop: { key, value, modifier },
|
||||
} = oper
|
||||
|
||||
const element = `n${oper.element}`
|
||||
const key = genExpression(oper.key, context)
|
||||
const value = genExpression(oper.value, context)
|
||||
const keyName = key.content
|
||||
|
||||
// fast path for static props
|
||||
if (isString(oper.key) || oper.key.isStatic) {
|
||||
const keyName = isString(oper.key) ? oper.key : oper.key.content
|
||||
|
||||
let helperName: VaporHelper | undefined
|
||||
let omitKey = false
|
||||
if (keyName === 'class') {
|
||||
helperName = 'setClass'
|
||||
omitKey = true
|
||||
} else if (keyName === 'style') {
|
||||
helperName = 'setStyle'
|
||||
omitKey = true
|
||||
} else if (oper.modifier) {
|
||||
helperName = oper.modifier === '.' ? 'setDOMProp' : 'setAttr'
|
||||
}
|
||||
|
||||
if (helperName) {
|
||||
return [
|
||||
NEWLINE,
|
||||
...call(vaporHelper(helperName), element, omitKey ? false : key, value),
|
||||
]
|
||||
}
|
||||
let helperName: VaporHelper
|
||||
let omitKey = false
|
||||
if (keyName === 'class') {
|
||||
helperName = 'setClass'
|
||||
omitKey = true
|
||||
} else if (keyName === 'style') {
|
||||
helperName = 'setStyle'
|
||||
omitKey = true
|
||||
} else if (modifier) {
|
||||
helperName = modifier === '.' ? 'setDOMProp' : 'setAttr'
|
||||
} else {
|
||||
helperName = 'setDynamicProp'
|
||||
}
|
||||
|
||||
return [
|
||||
NEWLINE,
|
||||
...call(vaporHelper('setDynamicProp'), element, genDynamicKey(), value),
|
||||
...call(
|
||||
vaporHelper(helperName),
|
||||
`n${oper.element}`,
|
||||
omitKey ? false : genExpression(key, context),
|
||||
genExpression(value, context),
|
||||
),
|
||||
]
|
||||
|
||||
function genDynamicKey(): CodeFragment[] {
|
||||
if (oper.runtimeCamelize) {
|
||||
return call(helper('camelize'), key)
|
||||
} else if (oper.modifier) {
|
||||
return [`\`${oper.modifier}\${`, ...key, `}\``]
|
||||
} else {
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dynamic key props and v-bind="{}" will reach here
|
||||
export function genDynamicProps(
|
||||
oper: SetDynamicPropsIRNode,
|
||||
context: CodegenContext,
|
||||
): CodeFragment[] {
|
||||
const { call, vaporHelper } = context
|
||||
return [
|
||||
NEWLINE,
|
||||
...call(
|
||||
vaporHelper('setDynamicProps'),
|
||||
`n${oper.element}`,
|
||||
...oper.props.map(
|
||||
props =>
|
||||
Array.isArray(props)
|
||||
? genLiteralObjectProps(props, context) // static and dynamic arg props
|
||||
: genExpression(props, context), // v-bind="{}"
|
||||
),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
function genLiteralObjectProps(
|
||||
props: DirectiveTransformResult[],
|
||||
context: CodegenContext,
|
||||
): CodeFragment[] {
|
||||
const { multi } = context
|
||||
return multi(
|
||||
['{ ', ' }', ', '],
|
||||
...props.map(prop => [
|
||||
...genPropertyKey(prop, context),
|
||||
`: `,
|
||||
...genExpression(prop.value, context),
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
function genPropertyKey(
|
||||
{ key: node, runtimeCamelize, modifier }: DirectiveTransformResult,
|
||||
context: CodegenContext,
|
||||
): CodeFragment[] {
|
||||
const { call, helper } = context
|
||||
|
||||
// static arg was transformed by v-bind transformer
|
||||
if (node.isStatic) {
|
||||
// only quote keys if necessary
|
||||
const keyName = node.content
|
||||
return [isSimpleIdentifier(keyName) ? keyName : JSON.stringify(keyName)]
|
||||
}
|
||||
|
||||
const key = genExpression(node, context)
|
||||
if (runtimeCamelize && modifier) {
|
||||
return [`[\`${modifier}\${`, ...call(helper('camelize'), key), `}\`]`]
|
||||
}
|
||||
|
||||
if (runtimeCamelize) {
|
||||
return [`[`, ...call(helper('camelize'), key), `]`]
|
||||
}
|
||||
|
||||
if (modifier) {
|
||||
return [`[\`${modifier}\${`, ...key, `}\`]`]
|
||||
}
|
||||
|
||||
return [`[`, ...key, `]`]
|
||||
}
|
||||
|
|
|
@ -7,7 +7,11 @@ import type {
|
|||
TemplateChildNode,
|
||||
} from '@vue/compiler-dom'
|
||||
import type { Prettify } from '@vue/shared'
|
||||
import type { DirectiveTransform, NodeTransform } from './transform'
|
||||
import type {
|
||||
DirectiveTransform,
|
||||
DirectiveTransformResult,
|
||||
NodeTransform,
|
||||
} from './transform'
|
||||
|
||||
export enum IRNodeTypes {
|
||||
ROOT,
|
||||
|
@ -17,6 +21,7 @@ export enum IRNodeTypes {
|
|||
FRAGMENT_FACTORY,
|
||||
|
||||
SET_PROP,
|
||||
SET_DYNAMIC_PROPS,
|
||||
SET_TEXT,
|
||||
SET_EVENT,
|
||||
SET_HTML,
|
||||
|
@ -86,10 +91,15 @@ export interface FragmentFactoryIRNode extends BaseIRNode {
|
|||
export interface SetPropIRNode extends BaseIRNode {
|
||||
type: IRNodeTypes.SET_PROP
|
||||
element: number
|
||||
key: IRExpression
|
||||
value: IRExpression
|
||||
modifier?: '.' | '^'
|
||||
runtimeCamelize: boolean
|
||||
prop: DirectiveTransformResult
|
||||
}
|
||||
|
||||
export type PropsExpression = DirectiveTransformResult[] | SimpleExpressionNode
|
||||
|
||||
export interface SetDynamicPropsIRNode extends BaseIRNode {
|
||||
type: IRNodeTypes.SET_DYNAMIC_PROPS
|
||||
element: number
|
||||
props: PropsExpression[]
|
||||
}
|
||||
|
||||
export interface SetTextIRNode extends BaseIRNode {
|
||||
|
@ -174,6 +184,7 @@ export type IRNode =
|
|||
| FragmentFactoryIRNode
|
||||
export type OperationNode =
|
||||
| SetPropIRNode
|
||||
| SetDynamicPropsIRNode
|
||||
| SetTextIRNode
|
||||
| SetEventIRNode
|
||||
| SetHtmlIRNode
|
||||
|
|
|
@ -9,6 +9,8 @@ import {
|
|||
NodeTypes,
|
||||
type ParentNode,
|
||||
type RootNode,
|
||||
type SimpleExpressionNode,
|
||||
type SourceLocation,
|
||||
type TemplateChildNode,
|
||||
type TemplateNode,
|
||||
defaultOnError,
|
||||
|
@ -39,7 +41,15 @@ export type DirectiveTransform = (
|
|||
dir: VaporDirectiveNode,
|
||||
node: ElementNode,
|
||||
context: TransformContext<ElementNode>,
|
||||
) => void
|
||||
) => DirectiveTransformResult | void
|
||||
|
||||
export interface DirectiveTransformResult {
|
||||
key: SimpleExpressionNode
|
||||
value: SimpleExpressionNode
|
||||
loc: SourceLocation
|
||||
modifier?: '.' | '^'
|
||||
runtimeCamelize?: boolean
|
||||
}
|
||||
|
||||
// A structural directive transform is technically also a NodeTransform;
|
||||
// Only v-if and v-for fall into this category.
|
||||
|
|
|
@ -2,11 +2,22 @@ import {
|
|||
type AttributeNode,
|
||||
type ElementNode,
|
||||
ElementTypes,
|
||||
ErrorCodes,
|
||||
NodeTypes,
|
||||
type SimpleExpressionNode,
|
||||
createCompilerError,
|
||||
} from '@vue/compiler-dom'
|
||||
import { isBuiltInDirective, isReservedProp, isVoidTag } from '@vue/shared'
|
||||
import type { NodeTransform, TransformContext } from '../transform'
|
||||
import { IRNodeTypes, type VaporDirectiveNode } from '../ir'
|
||||
import type {
|
||||
DirectiveTransformResult,
|
||||
NodeTransform,
|
||||
TransformContext,
|
||||
} from '../transform'
|
||||
import {
|
||||
IRNodeTypes,
|
||||
type PropsExpression,
|
||||
type VaporDirectiveNode,
|
||||
} from '../ir'
|
||||
|
||||
export const transformElement: NodeTransform = (node, context) => {
|
||||
return function postTransformElement() {
|
||||
|
@ -49,8 +60,75 @@ function buildProps(
|
|||
props: ElementNode['props'] = node.props,
|
||||
isComponent: boolean,
|
||||
) {
|
||||
for (const prop of props) {
|
||||
transformProp(prop as VaporDirectiveNode | AttributeNode, node, context)
|
||||
const dynamicArgs: PropsExpression[] = []
|
||||
const dynamicExpr: SimpleExpressionNode[] = []
|
||||
let results: DirectiveTransformResult[] = []
|
||||
|
||||
function pushExpressions(...exprs: SimpleExpressionNode[]) {
|
||||
for (const expr of exprs) {
|
||||
if (!expr.isStatic) dynamicExpr.push(expr)
|
||||
}
|
||||
}
|
||||
|
||||
function pushMergeArg() {
|
||||
if (results.length) {
|
||||
dynamicArgs.push(results)
|
||||
results = []
|
||||
}
|
||||
}
|
||||
|
||||
for (const prop of props as (VaporDirectiveNode | AttributeNode)[]) {
|
||||
if (
|
||||
prop.type === NodeTypes.DIRECTIVE &&
|
||||
prop.name === 'bind' &&
|
||||
!prop.arg
|
||||
) {
|
||||
if (prop.exp) {
|
||||
pushExpressions(prop.exp)
|
||||
pushMergeArg()
|
||||
dynamicArgs.push(prop.exp)
|
||||
} else {
|
||||
context.options.onError(
|
||||
createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, prop.loc),
|
||||
)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
const result = transformProp(prop, node, context)
|
||||
if (result) {
|
||||
results.push(result)
|
||||
pushExpressions(result.key, result.value)
|
||||
}
|
||||
}
|
||||
|
||||
// take rest of props as dynamic props
|
||||
if (dynamicArgs.length || results.some(({ key }) => !key.isStatic)) {
|
||||
pushMergeArg()
|
||||
}
|
||||
|
||||
// has dynamic key or v-bind="{}"
|
||||
if (dynamicArgs.length) {
|
||||
context.registerEffect(dynamicExpr, [
|
||||
{
|
||||
type: IRNodeTypes.SET_DYNAMIC_PROPS,
|
||||
element: context.reference(),
|
||||
props: dynamicArgs,
|
||||
},
|
||||
])
|
||||
} else {
|
||||
for (const result of results) {
|
||||
context.registerEffect(
|
||||
[result.value],
|
||||
[
|
||||
{
|
||||
type: IRNodeTypes.SET_PROP,
|
||||
element: context.reference(),
|
||||
prop: result,
|
||||
},
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,7 +136,7 @@ function transformProp(
|
|||
prop: VaporDirectiveNode | AttributeNode,
|
||||
node: ElementNode,
|
||||
context: TransformContext<ElementNode>,
|
||||
): void {
|
||||
): DirectiveTransformResult | void {
|
||||
const { name } = prop
|
||||
if (isReservedProp(name)) return
|
||||
|
||||
|
@ -70,7 +148,7 @@ function transformProp(
|
|||
|
||||
const directiveTransform = context.options.directiveTransforms[name]
|
||||
if (directiveTransform) {
|
||||
directiveTransform(prop, node, context)
|
||||
return directiveTransform(prop, node, context)
|
||||
} else if (!isBuiltInDirective(name)) {
|
||||
context.registerOperation({
|
||||
type: IRNodeTypes.WITH_DIRECTIVE,
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
createSimpleExpression,
|
||||
} from '@vue/compiler-dom'
|
||||
import { camelize, isReservedProp } from '@vue/shared'
|
||||
import { IRNodeTypes } from '../ir'
|
||||
import type { DirectiveTransform } from '../transform'
|
||||
|
||||
export function normalizeBindShorthand(
|
||||
|
@ -19,12 +18,9 @@ export function normalizeBindShorthand(
|
|||
}
|
||||
|
||||
export const transformVBind: DirectiveTransform = (dir, node, context) => {
|
||||
let { arg, exp, loc, modifiers } = dir
|
||||
let { exp, loc, modifiers } = dir
|
||||
const arg = dir.arg!
|
||||
|
||||
if (!arg) {
|
||||
// TODO support v-bind="{}"
|
||||
return
|
||||
}
|
||||
if (arg.isStatic && isReservedProp(arg.content)) return
|
||||
|
||||
if (!exp) exp = normalizeBindShorthand(arg)
|
||||
|
@ -46,21 +42,15 @@ export const transformVBind: DirectiveTransform = (dir, node, context) => {
|
|||
return
|
||||
}
|
||||
|
||||
context.registerEffect(
|
||||
[exp],
|
||||
[
|
||||
{
|
||||
type: IRNodeTypes.SET_PROP,
|
||||
element: context.reference(),
|
||||
key: arg,
|
||||
value: exp,
|
||||
runtimeCamelize: camel,
|
||||
modifier: modifiers.includes('prop')
|
||||
? '.'
|
||||
: modifiers.includes('attr')
|
||||
? '^'
|
||||
: undefined,
|
||||
},
|
||||
],
|
||||
)
|
||||
return {
|
||||
key: arg,
|
||||
value: exp,
|
||||
loc,
|
||||
runtimeCamelize: camel,
|
||||
modifier: modifiers.includes('prop')
|
||||
? '.'
|
||||
: modifiers.includes('attr')
|
||||
? '^'
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import {
|
||||
type Data,
|
||||
isArray,
|
||||
isFunction,
|
||||
isOn,
|
||||
isString,
|
||||
normalizeClass,
|
||||
normalizeStyle,
|
||||
|
@ -80,6 +83,47 @@ export function setDynamicProp(el: Element, key: string, value: any) {
|
|||
}
|
||||
}
|
||||
|
||||
export function setDynamicProps(el: Element, ...args: any) {
|
||||
const props = args.length > 1 ? mergeProps(...args) : args[0]
|
||||
|
||||
// TODO remove all of old props before set new props since there is containing dynamic key
|
||||
for (const key in props) {
|
||||
setDynamicProp(el, key, props[key])
|
||||
}
|
||||
}
|
||||
|
||||
// TODO copied from runtime-core
|
||||
function mergeProps(...args: Data[]) {
|
||||
const ret: Data = {}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const toMerge = args[i]
|
||||
for (const key in toMerge) {
|
||||
if (key === 'class') {
|
||||
if (ret.class !== toMerge.class) {
|
||||
ret.class = normalizeClass([ret.class, toMerge.class])
|
||||
}
|
||||
} else if (key === 'style') {
|
||||
ret.style = normalizeStyle([ret.style, toMerge.style])
|
||||
} else if (isOn(key)) {
|
||||
const existing = ret[key]
|
||||
const incoming = toMerge[key]
|
||||
if (
|
||||
incoming &&
|
||||
existing !== incoming &&
|
||||
!(isArray(existing) && existing.includes(incoming))
|
||||
) {
|
||||
ret[key] = existing
|
||||
? [].concat(existing as any, incoming as any)
|
||||
: incoming
|
||||
}
|
||||
} else if (key !== '') {
|
||||
ret[key] = toMerge[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
export function setText(el: Node, value: any) {
|
||||
const oldVal = recordPropMetadata(
|
||||
el,
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref } from '@vue/vapor'
|
||||
|
||||
const count = ref(1)
|
||||
const obj = computed(() => ({ id: String(count.value), subObj: { a: 'xxx' } }))
|
||||
const key = ref('id')
|
||||
|
||||
const handleClick = () => {
|
||||
count.value++
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button @click="handleClick">{{ count }}</button>
|
||||
|
||||
<!-- prop id's value should update reactively -->
|
||||
<button :id="'before'" :[key]="'dynamic key after' + count">
|
||||
{{ count }}
|
||||
</button>
|
||||
<!-- prop id's value should update only once -->
|
||||
<button :[key]="'dynamic key before' + count" :id="'before'">
|
||||
{{ count }}
|
||||
</button>
|
||||
<!-- object props should update reactively -->
|
||||
<button v-bind="obj">{{ count }}</button>
|
||||
<button v-bind="{ id: `${count}`, subObj: { a: 'xxx' } }">
|
||||
{{ count }}
|
||||
</button>
|
||||
<!-- prop id's value should update reactively since it was override by object props -->
|
||||
<button :id="'before'" v-bind="obj">{{ count }}</button>
|
||||
<button :[key]="'dynamic key before'" v-bind="obj">
|
||||
{{ count }}
|
||||
</button>
|
||||
<!-- prop id's value should update only once since the prop id in object props was override -->
|
||||
<button v-bind="obj" :id="'after'">{{ count }}</button>
|
||||
<button v-bind="obj" :[key]="'dynamic key after'">{{ count }}</button>
|
||||
</template>
|
Loading…
Reference in New Issue