feat(vapor): merge inherited attrs with current attrs

This commit is contained in:
三咲智子 Kevin Deng 2024-11-14 20:01:10 +08:00
parent c91586528a
commit 247617612a
No known key found for this signature in database
33 changed files with 506 additions and 179 deletions

View File

@ -29,7 +29,7 @@
"dev-prepare-cjs": "node scripts/prepare-cjs.js || npm run build-all-cjs",
"dev-compiler": "run-p \"dev template-explorer\" serve open",
"dev-sfc": "run-s dev-prepare-cjs dev-sfc-run",
"dev-sfc-serve": "vite packages-private/sfc-playground --host",
"dev-sfc-serve": "vite packages-private/sfc-playground",
"dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-bundler-runtime\" \"dev vue -ipf esm-browser-runtime\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve",
"dev-vapor": "pnpm -C playground run dev",
"serve": "serve",

View File

@ -140,11 +140,12 @@ export function render(_ctx) {
`;
exports[`compile > directives > v-pre > basic 1`] = `
"import { template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, template as _template } from 'vue/vapor';
const t0 = _template("<div :id=\\"foo\\"><Comp></Comp>{{ bar }}</div>")
export function render(_ctx, $props) {
const n0 = t0()
_setInheritAttrs(false)
return n0
}"
`;
@ -176,15 +177,16 @@ export function render(_ctx) {
`;
exports[`compile > dynamic root nodes and interpolation 1`] = `
"import { delegate as _delegate, renderEffect as _renderEffect, setText as _setText, setDynamicProp as _setDynamicProp, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
"import { delegate as _delegate, setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setText as _setText, setDynamicProp as _setDynamicProp, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<button></button>")
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
_delegate(n0, "click", () => _ctx.handleClick)
_setInheritAttrs(["id"])
_renderEffect(() => _setText(n0, _ctx.count, "foo", _ctx.count, "foo", _ctx.count))
_renderEffect(() => _setDynamicProp(n0, "id", _ctx.count))
_renderEffect(() => _setDynamicProp(n0, "id", _ctx.count, true))
return n0
}"
`;
@ -199,7 +201,8 @@ exports[`compile > expression parsing > interpolation 1`] = `
exports[`compile > expression parsing > v-bind 1`] = `
"((_ctx) => {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, { [key.value+1]: _unref(foo)[key.value+1]() }))
_setInheritAttrs(true)
_renderEffect(() => _setDynamicProps(n0, [{ [key.value+1]: _unref(foo)[key.value+1]() }], true))
return n0
})()"
`;

View File

@ -286,22 +286,24 @@ export function render(_ctx) {
`;
exports[`compiler: element transform > props + children 1`] = `
"import { template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, template as _template } from 'vue/vapor';
const t0 = _template("<div id=\\"foo\\"><span></span></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(false)
return n0
}"
`;
exports[`compiler: element transform > props merging: class 1`] = `
"import { renderEffect as _renderEffect, setClass as _setClass, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setClass as _setClass, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setClass(n0, ["foo", { bar: _ctx.isBar }]))
_setInheritAttrs(["class"])
_renderEffect(() => _setClass(n0, ["foo", { bar: _ctx.isBar }], true))
return n0
}"
`;
@ -324,66 +326,72 @@ export function render(_ctx) {
`;
exports[`compiler: element transform > props merging: style 1`] = `
"import { renderEffect as _renderEffect, setStyle as _setStyle, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setStyle as _setStyle, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setStyle(n0, ["color: green", { color: 'red' }]))
_setInheritAttrs(["style"])
_renderEffect(() => _setStyle(n0, ["color: green", { color: 'red' }], true))
return n0
}"
`;
exports[`compiler: element transform > static props 1`] = `
"import { template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, template as _template } from 'vue/vapor';
const t0 = _template("<div id=\\"foo\\" class=\\"bar\\"></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(false)
return n0
}"
`;
exports[`compiler: element transform > v-bind="obj" 1`] = `
"import { renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, _ctx.obj))
_setInheritAttrs(true)
_renderEffect(() => _setDynamicProps(n0, [_ctx.obj], true))
return n0
}"
`;
exports[`compiler: element transform > v-bind="obj" after static prop 1`] = `
"import { renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, { id: "foo" }, _ctx.obj))
_setInheritAttrs(true)
_renderEffect(() => _setDynamicProps(n0, [{ id: "foo" }, _ctx.obj], true))
return n0
}"
`;
exports[`compiler: element transform > v-bind="obj" before static prop 1`] = `
"import { renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, _ctx.obj, { id: "foo" }))
_setInheritAttrs(true)
_renderEffect(() => _setDynamicProps(n0, [_ctx.obj, { id: "foo" }], true))
return n0
}"
`;
exports[`compiler: element transform > v-bind="obj" between static props 1`] = `
"import { renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, { id: "foo" }, _ctx.obj, { class: "bar" }))
_setInheritAttrs(true)
_renderEffect(() => _setDynamicProps(n0, [{ id: "foo" }, _ctx.obj, { class: "bar" }], true))
return n0
}"
`;

View File

@ -1,177 +1,193 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler v-bind > .attr modifier 1`] = `
"import { renderEffect as _renderEffect, setAttr as _setAttr, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setAttr as _setAttr, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setAttr(n0, "foo-bar", _ctx.id))
_setInheritAttrs(["foo-bar"])
_renderEffect(() => _setAttr(n0, "foo-bar", _ctx.id, true))
return n0
}"
`;
exports[`compiler v-bind > .attr modifier w/ no expression 1`] = `
"import { renderEffect as _renderEffect, setAttr as _setAttr, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setAttr as _setAttr, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setAttr(n0, "foo-bar", _ctx.fooBar))
_setInheritAttrs(["foo-bar"])
_renderEffect(() => _setAttr(n0, "foo-bar", _ctx.fooBar, true))
return n0
}"
`;
exports[`compiler v-bind > .camel modifier 1`] = `
"import { renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProp(n0, "fooBar", _ctx.id))
_setInheritAttrs(["fooBar"])
_renderEffect(() => _setDynamicProp(n0, "fooBar", _ctx.id, true))
return n0
}"
`;
exports[`compiler v-bind > .camel modifier w/ dynamic arg 1`] = `
"import { camelize as _camelize } from 'vue';
import { renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, { [_camelize(_ctx.foo)]: _ctx.id }))
_setInheritAttrs(true)
_renderEffect(() => _setDynamicProps(n0, [{ [_camelize(_ctx.foo)]: _ctx.id }], true))
return n0
}"
`;
exports[`compiler v-bind > .camel modifier w/ no expression 1`] = `
"import { renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProp(n0, "fooBar", _ctx.fooBar))
_setInheritAttrs(["fooBar"])
_renderEffect(() => _setDynamicProp(n0, "fooBar", _ctx.fooBar, true))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier (shortband) w/ no expression 1`] = `
"import { renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.fooBar))
_setInheritAttrs(["fooBar"])
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.fooBar, true))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier (shorthand) 1`] = `
"import { renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.id))
_setInheritAttrs(["fooBar"])
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.id, true))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier 1`] = `
"import { renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.id))
_setInheritAttrs(["fooBar"])
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.id, true))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier w/ dynamic arg 1`] = `
"import { renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, { ["." + _ctx.fooBar]: _ctx.id }))
_setInheritAttrs(true)
_renderEffect(() => _setDynamicProps(n0, [{ ["." + _ctx.fooBar]: _ctx.id }], true))
return n0
}"
`;
exports[`compiler v-bind > .prop modifier w/ no expression 1`] = `
"import { renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDOMProp as _setDOMProp, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.fooBar))
_setInheritAttrs(["fooBar"])
_renderEffect(() => _setDOMProp(n0, "fooBar", _ctx.fooBar, true))
return n0
}"
`;
exports[`compiler v-bind > basic 1`] = `
"import { renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProp(n0, "id", _ctx.id))
_setInheritAttrs(["id"])
_renderEffect(() => _setDynamicProp(n0, "id", _ctx.id, true))
return n0
}"
`;
exports[`compiler v-bind > dynamic arg 1`] = `
"import { renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, { [_ctx.id]: _ctx.id, [_ctx.title]: _ctx.title }))
_setInheritAttrs(true)
_renderEffect(() => _setDynamicProps(n0, [{ [_ctx.id]: _ctx.id, [_ctx.title]: _ctx.title }], true))
return n0
}"
`;
exports[`compiler v-bind > dynamic arg w/ static attribute 1`] = `
"import { renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProps(n0, { [_ctx.id]: _ctx.id, foo: "bar", checked: "" }))
_setInheritAttrs(true)
_renderEffect(() => _setDynamicProps(n0, [{ [_ctx.id]: _ctx.id, foo: "bar", checked: "" }], true))
return n0
}"
`;
exports[`compiler v-bind > no expression (shorthand) 1`] = `
"import { renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProp(n0, "camel-case", _ctx.camelCase))
_setInheritAttrs(["camel-case"])
_renderEffect(() => _setDynamicProp(n0, "camel-case", _ctx.camelCase, true))
return n0
}"
`;
exports[`compiler v-bind > no expression 1`] = `
"import { renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setDynamicProp(n0, "id", _ctx.id))
_setInheritAttrs(["id"])
_renderEffect(() => _setDynamicProp(n0, "id", _ctx.id, true))
return n0
}"
`;
exports[`compiler v-bind > should error if empty expression 1`] = `
"import { template as _template } from 'vue/vapor';
"import { setInheritAttrs as _setInheritAttrs, template as _template } from 'vue/vapor';
const t0 = _template("<div arg></div>")
export function render(_ctx) {
const n0 = t0()
_setInheritAttrs(false)
return n0
}"
`;

View File

@ -126,13 +126,14 @@ export function render(_ctx) {
`;
exports[`compiler: vModel transform > should support input (checkbox) 1`] = `
"import { vModelCheckbox as _vModelCheckbox, withDirectives as _withDirectives, delegate as _delegate, template as _template } from 'vue/vapor';
"import { vModelCheckbox as _vModelCheckbox, withDirectives as _withDirectives, delegate as _delegate, setInheritAttrs as _setInheritAttrs, template as _template } from 'vue/vapor';
const t0 = _template("<input type=\\"checkbox\\">")
export function render(_ctx) {
const n0 = t0()
_withDirectives(n0, [[_vModelCheckbox, () => _ctx.model]])
_delegate(n0, "update:modelValue", () => $event => (_ctx.model = $event))
_setInheritAttrs(false)
return n0
}"
`;
@ -150,25 +151,27 @@ export function render(_ctx) {
`;
exports[`compiler: vModel transform > should support input (radio) 1`] = `
"import { vModelRadio as _vModelRadio, withDirectives as _withDirectives, delegate as _delegate, template as _template } from 'vue/vapor';
"import { vModelRadio as _vModelRadio, withDirectives as _withDirectives, delegate as _delegate, setInheritAttrs as _setInheritAttrs, template as _template } from 'vue/vapor';
const t0 = _template("<input type=\\"radio\\">")
export function render(_ctx) {
const n0 = t0()
_withDirectives(n0, [[_vModelRadio, () => _ctx.model]])
_delegate(n0, "update:modelValue", () => $event => (_ctx.model = $event))
_setInheritAttrs(false)
return n0
}"
`;
exports[`compiler: vModel transform > should support input (text) 1`] = `
"import { vModelText as _vModelText, withDirectives as _withDirectives, delegate as _delegate, template as _template } from 'vue/vapor';
"import { vModelText as _vModelText, withDirectives as _withDirectives, delegate as _delegate, setInheritAttrs as _setInheritAttrs, template as _template } from 'vue/vapor';
const t0 = _template("<input type=\\"text\\">")
export function render(_ctx) {
const n0 = t0()
_withDirectives(n0, [[_vModelText, () => _ctx.model]])
_delegate(n0, "update:modelValue", () => $event => (_ctx.model = $event))
_setInheritAttrs(false)
return n0
}"
`;
@ -243,14 +246,15 @@ export function render(_ctx) {
`;
exports[`compiler: vModel transform > should support w/ dynamic v-bind 1`] = `
"import { vModelDynamic as _vModelDynamic, withDirectives as _withDirectives, delegate as _delegate, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
"import { vModelDynamic as _vModelDynamic, withDirectives as _withDirectives, delegate as _delegate, setInheritAttrs as _setInheritAttrs, renderEffect as _renderEffect, setDynamicProps as _setDynamicProps, template as _template } from 'vue/vapor';
const t0 = _template("<input>")
export function render(_ctx) {
const n0 = t0()
_withDirectives(n0, [[_vModelDynamic, () => _ctx.model]])
_delegate(n0, "update:modelValue", () => $event => (_ctx.model = $event))
_renderEffect(() => _setDynamicProps(n0, _ctx.obj))
_setInheritAttrs(true)
_renderEffect(() => _setDynamicProps(n0, [_ctx.obj], true))
return n0
}"
`;

View File

@ -1,12 +1,13 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: v-once > as root node 1`] = `
"import { setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor';
"import { setDynamicProp as _setDynamicProp, setInheritAttrs as _setInheritAttrs, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = t0()
_setDynamicProp(n0, "id", _ctx.foo)
_setDynamicProp(n0, "id", _ctx.foo, true)
_setInheritAttrs(["id"])
return n0
}"
`;

View File

@ -588,7 +588,7 @@ describe('compiler: element transform', () => {
],
},
])
expect(code).contains('_setDynamicProps(n0, _ctx.obj)')
expect(code).contains('_setDynamicProps(n0, [_ctx.obj], true)')
})
test('v-bind="obj" after static prop', () => {
@ -624,7 +624,9 @@ describe('compiler: element transform', () => {
],
},
])
expect(code).contains('_setDynamicProps(n0, { id: "foo" }, _ctx.obj)')
expect(code).contains(
'_setDynamicProps(n0, [{ id: "foo" }, _ctx.obj], true)',
)
})
test('v-bind="obj" before static prop', () => {
@ -650,7 +652,9 @@ describe('compiler: element transform', () => {
],
},
])
expect(code).contains('_setDynamicProps(n0, _ctx.obj, { id: "foo" })')
expect(code).contains(
'_setDynamicProps(n0, [_ctx.obj, { id: "foo" }], true)',
)
})
test('v-bind="obj" between static props', () => {
@ -678,7 +682,7 @@ describe('compiler: element transform', () => {
},
])
expect(code).contains(
'_setDynamicProps(n0, { id: "foo" }, _ctx.obj, { class: "bar" })',
'_setDynamicProps(n0, [{ id: "foo" }, _ctx.obj, { class: "bar" }], true)',
)
})
@ -723,6 +727,9 @@ describe('compiler: element transform', () => {
delegate: true,
effect: false,
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
})

View File

@ -30,8 +30,8 @@ describe('compiler: template ref transform', () => {
flags: DynamicFlag.REFERENCED,
})
expect(ir.template).toEqual(['<div></div>'])
expect(ir.block.operation).lengthOf(1)
expect(ir.block.operation[0]).toMatchObject({
expect(ir.block.operation).lengthOf(2)
expect(ir.block.operation[1]).toMatchObject({
type: IRNodeTypes.SET_TEMPLATE_REF,
element: 0,
value: {
@ -56,6 +56,9 @@ describe('compiler: template ref transform', () => {
})
expect(ir.template).toEqual(['<div></div>'])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
{
type: IRNodeTypes.DECLARE_OLD_REF,
id: 0,

View File

@ -74,7 +74,7 @@ describe('compiler v-bind', () => {
})
expect(code).matchSnapshot()
expect(code).contains('_setDynamicProp(n0, "id", _ctx.id)')
expect(code).contains('_setDynamicProp(n0, "id", _ctx.id, true)')
})
test('no expression', () => {
@ -104,7 +104,7 @@ describe('compiler v-bind', () => {
],
},
})
expect(code).contains('_setDynamicProp(n0, "id", _ctx.id)')
expect(code).contains('_setDynamicProp(n0, "id", _ctx.id, true)')
})
test('no expression (shorthand)', () => {
@ -126,7 +126,9 @@ describe('compiler v-bind', () => {
],
},
})
expect(code).contains('_setDynamicProp(n0, "camel-case", _ctx.camelCase)')
expect(code).contains(
'_setDynamicProp(n0, "camel-case", _ctx.camelCase, true)',
)
})
test('dynamic arg', () => {
@ -171,7 +173,7 @@ describe('compiler v-bind', () => {
],
})
expect(code).contains(
'_setDynamicProps(n0, { [_ctx.id]: _ctx.id, [_ctx.title]: _ctx.title })',
'_setDynamicProps(n0, [{ [_ctx.id]: _ctx.id, [_ctx.title]: _ctx.title }], true)',
)
})
@ -224,7 +226,7 @@ describe('compiler v-bind', () => {
],
})
expect(code).contains(
'_setDynamicProps(n0, { [_ctx.id]: _ctx.id, foo: "bar", checked: "" })',
'_setDynamicProps(n0, [{ [_ctx.id]: _ctx.id, foo: "bar", checked: "" }], true)',
)
})
@ -286,7 +288,7 @@ describe('compiler v-bind', () => {
})
expect(code).matchSnapshot()
expect(code).contains('_setDynamicProp(n0, "fooBar", _ctx.id)')
expect(code).contains('_setDynamicProp(n0, "fooBar", _ctx.id, true)')
})
test('.camel modifier w/ no expression', () => {
@ -310,7 +312,7 @@ describe('compiler v-bind', () => {
},
})
expect(code).contains('renderEffect')
expect(code).contains('_setDynamicProp(n0, "fooBar", _ctx.fooBar)')
expect(code).contains('_setDynamicProp(n0, "fooBar", _ctx.fooBar, true)')
})
test('.camel modifier w/ dynamic arg', () => {
@ -341,7 +343,7 @@ describe('compiler v-bind', () => {
expect(code).matchSnapshot()
expect(code).contains('renderEffect')
expect(code).contains(
`_setDynamicProps(n0, { [_camelize(_ctx.foo)]: _ctx.id })`,
`_setDynamicProps(n0, [{ [_camelize(_ctx.foo)]: _ctx.id }], true)`,
)
})
@ -368,7 +370,7 @@ describe('compiler v-bind', () => {
},
})
expect(code).contains('renderEffect')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.id)')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.id, true)')
})
test('.prop modifier w/ no expression', () => {
@ -392,7 +394,7 @@ describe('compiler v-bind', () => {
},
})
expect(code).contains('renderEffect')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.fooBar)')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.fooBar, true)')
})
test('.prop modifier w/ dynamic arg', () => {
@ -422,7 +424,7 @@ describe('compiler v-bind', () => {
})
expect(code).contains('renderEffect')
expect(code).contains(
`_setDynamicProps(n0, { ["." + _ctx.fooBar]: _ctx.id })`,
`_setDynamicProps(n0, [{ ["." + _ctx.fooBar]: _ctx.id }], true)`,
)
})
@ -449,7 +451,7 @@ describe('compiler v-bind', () => {
},
})
expect(code).contains('renderEffect')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.id)')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.id, true)')
})
test('.prop modifier (shortband) w/ no expression', () => {
@ -473,7 +475,7 @@ describe('compiler v-bind', () => {
},
})
expect(code).contains('renderEffect')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.fooBar)')
expect(code).contains('_setDOMProp(n0, "fooBar", _ctx.fooBar, true)')
})
test('.attr modifier', () => {
@ -497,7 +499,7 @@ describe('compiler v-bind', () => {
},
})
expect(code).contains('renderEffect')
expect(code).contains('_setAttr(n0, "foo-bar", _ctx.id)')
expect(code).contains('_setAttr(n0, "foo-bar", _ctx.id, true)')
})
test('.attr modifier w/ no expression', () => {
@ -522,6 +524,6 @@ describe('compiler v-bind', () => {
})
expect(code).contains('renderEffect')
expect(code).contains('_setAttr(n0, "foo-bar", _ctx.fooBar)')
expect(code).contains('_setAttr(n0, "foo-bar", _ctx.fooBar, true)')
})
})

View File

@ -28,7 +28,11 @@ describe('v-html', () => {
expect(vaporHelpers).contains('setHtml')
expect(helpers.size).toBe(0)
expect(ir.block.operation).toEqual([])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(ir.block.effect).toMatchObject([
{
expressions: [
@ -70,7 +74,11 @@ describe('v-html', () => {
// children should have been removed
expect(ir.template).toEqual(['<div></div>'])
expect(ir.block.operation).toEqual([])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(ir.block.effect).toMatchObject([
{
expressions: [

View File

@ -47,6 +47,9 @@ describe('v-on', () => {
keyOverride: undefined,
delegate: true,
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
})
@ -91,7 +94,11 @@ describe('v-on', () => {
expect(vaporHelpers).contains('on')
expect(vaporHelpers).contains('renderEffect')
expect(helpers.size).toBe(0)
expect(ir.block.operation).toEqual([])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(ir.block.effect[0].operations[0]).toMatchObject({
type: IRNodeTypes.SET_EVENT,
@ -130,7 +137,11 @@ describe('v-on', () => {
expect(vaporHelpers).contains('on')
expect(vaporHelpers).contains('renderEffect')
expect(helpers.size).toBe(0)
expect(ir.block.operation).toEqual([])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(ir.block.effect[0].operations[0]).toMatchObject({
type: IRNodeTypes.SET_EVENT,
@ -169,6 +180,9 @@ describe('v-on', () => {
},
delegate: true,
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(code).contains(`_delegate(n0, "click", () => $event => (_ctx.i++))`)
})
@ -206,6 +220,9 @@ describe('v-on', () => {
type: IRNodeTypes.SET_EVENT,
value: { content: 'foo();bar()' },
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
// should wrap with `{` for multiple statements
// in this case the return value is discarded and the behavior is
@ -224,6 +241,9 @@ describe('v-on', () => {
type: IRNodeTypes.SET_EVENT,
value: { content: '\nfoo();\nbar()\n' },
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
// should wrap with `{` for multiple statements
// in this case the return value is discarded and the behavior is
@ -244,6 +264,9 @@ describe('v-on', () => {
type: IRNodeTypes.SET_EVENT,
value: { content: 'foo($event)' },
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
// should NOT prefix $event
expect(code).contains(
@ -262,6 +285,9 @@ describe('v-on', () => {
type: IRNodeTypes.SET_EVENT,
value: { content: 'foo($event);bar()' },
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
// should NOT prefix $event
expect(code).contains(
@ -278,6 +304,9 @@ describe('v-on', () => {
type: IRNodeTypes.SET_EVENT,
value: { content: '$event => foo($event)' },
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(code).contains(
`_delegate(n0, "click", () => $event => _ctx.foo($event))`,
@ -296,6 +325,9 @@ describe('v-on', () => {
type: IRNodeTypes.SET_EVENT,
value: { content: '(e: any): any => foo(e)' },
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(code).contains(
`_delegate(n0, "click", () => (e: any): any => _ctx.foo(e))`,
@ -323,6 +355,9 @@ describe('v-on', () => {
`,
},
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
})
@ -349,6 +384,9 @@ describe('v-on', () => {
type: IRNodeTypes.SET_EVENT,
value: { content: `a['b' + c]` },
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(code).matchSnapshot()
@ -361,6 +399,9 @@ describe('v-on', () => {
type: IRNodeTypes.SET_EVENT,
value: { content: `a['b' + c]` },
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(code).matchSnapshot()
@ -378,6 +419,9 @@ describe('v-on', () => {
type: IRNodeTypes.SET_EVENT,
value: { content: `e => foo(e)` },
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(code).contains(`_delegate(n0, "click", () => e => _ctx.foo(e))`)
})
@ -432,6 +476,9 @@ describe('v-on', () => {
keyOverride: undefined,
delegate: false,
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(code).contains(
`_on(n0, "click", () => _ctx.test, {
@ -487,6 +534,9 @@ describe('v-on', () => {
options: [],
},
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(code).matchSnapshot()
@ -529,6 +579,9 @@ describe('v-on', () => {
options: ['capture'],
},
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(code).matchSnapshot()
@ -543,6 +596,9 @@ describe('v-on', () => {
type: IRNodeTypes.SET_EVENT,
modifiers: { nonKeys: ['exact'] },
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(code).matchSnapshot()
@ -562,6 +618,9 @@ describe('v-on', () => {
options: [],
},
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(code).matchSnapshot()
@ -604,6 +663,9 @@ describe('v-on', () => {
modifiers: { nonKeys: ['right'] },
keyOverride: undefined,
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(code).matchSnapshot()
@ -645,6 +707,9 @@ describe('v-on', () => {
modifiers: { nonKeys: ['middle'] },
keyOverride: undefined,
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(code).matchSnapshot()
@ -694,6 +759,9 @@ describe('v-on', () => {
type: IRNodeTypes.SET_EVENT,
delegate: true,
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
})
})

View File

@ -68,6 +68,7 @@ describe('compiler: v-once', () => {
elements: [0],
parent: 2,
},
{ type: IRNodeTypes.SET_INHERIT_ATTRS },
])
})
@ -96,6 +97,9 @@ describe('compiler: v-once', () => {
],
},
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(code).not.contains('effect')
})
@ -128,6 +132,9 @@ describe('compiler: v-once', () => {
],
},
},
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
})
@ -147,6 +154,7 @@ describe('compiler: v-once', () => {
elements: [0],
parent: 1,
},
{ type: IRNodeTypes.SET_INHERIT_ATTRS },
])
})
@ -160,7 +168,7 @@ describe('compiler: v-once', () => {
expect(code).toMatchSnapshot()
expect(helpers).lengthOf(0)
expect(ir.block.effect).lengthOf(0)
expect(ir.block.operation).lengthOf(0)
expect(ir.block.operation).lengthOf(1)
})
test.todo('with hoistStatic: true')

View File

@ -28,7 +28,11 @@ describe('v-text', () => {
expect(vaporHelpers).contains('setText')
expect(helpers.size).toBe(0)
expect(ir.block.operation).toEqual([])
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SET_INHERIT_ATTRS,
},
])
expect(ir.block.effect).toMatchObject([
{

View File

@ -28,7 +28,7 @@ import {
genMulti,
} from './utils'
import { genExpression } from './expression'
import { genPropKey } from './prop'
import { genPropKey, genPropValue } from './prop'
import {
createSimpleExpression,
toValidAssetId,
@ -121,14 +121,15 @@ function genStaticProps(
}
function genProp(prop: IRProp, context: CodegenContext, isStatic?: boolean) {
const values = genPropValue(prop.values, context)
return [
...genPropKey(prop, context),
': ',
...(prop.handler
? genEventHandler(context, prop.values[0])
: isStatic
? ['() => (', ...genExpression(prop.values[0], context), ')']
: genExpression(prop.values[0], context)),
? ['() => (', ...values, ')']
: values),
...(prop.model
? [...genModelEvent(prop, context), ...genModelModifiers(prop, context)]
: []),

View File

@ -132,7 +132,7 @@ function genIdentifier(
prefix = `${raw}: `
}
const type = bindingMetadata[raw]
const type = bindingMetadata && bindingMetadata[raw]
if (inline) {
switch (type) {
case BindingTypes.SETUP_LET:

View File

@ -6,7 +6,7 @@ import { genFor } from './for'
import { genSetHtml } from './html'
import { genIf } from './if'
import { genSetModelValue } from './modelValue'
import { genDynamicProps, genSetProp } from './prop'
import { genDynamicProps, genSetInheritAttrs, genSetProp } from './prop'
import { genDeclareOldRef, genSetTemplateRef } from './templateRef'
import { genCreateTextNode, genSetText } from './text'
import {
@ -67,6 +67,8 @@ export function genOperation(
return genDeclareOldRef(oper)
case IRNodeTypes.SLOT_OUTLET_NODE:
return genSlotOutlet(oper, context)
case IRNodeTypes.SET_INHERIT_ATTRS:
return genSetInheritAttrs(oper, context)
}
return []

View File

@ -8,6 +8,7 @@ import {
IRDynamicPropsKind,
type IRProp,
type SetDynamicPropsIRNode,
type SetInheritAttrsIRNode,
type SetPropIRNode,
type VaporHelper,
} from '../ir'
@ -55,6 +56,7 @@ export function genSetProp(
`n${oper.element}`,
omitKey ? false : genExpression(key, context),
genPropValue(values, context),
oper.root && 'true',
),
]
}
@ -70,14 +72,18 @@ export function genDynamicProps(
...genCall(
vaporHelper('setDynamicProps'),
`n${oper.element}`,
...oper.props.map(
props =>
Array.isArray(props)
? genLiteralObjectProps(props, context) // static and dynamic arg props
: props.kind === IRDynamicPropsKind.ATTRIBUTE
? genLiteralObjectProps([props], context) // dynamic arg props
: genExpression(props.value, context), // v-bind=""
genMulti(
DELIMITERS_ARRAY,
...oper.props.map(
props =>
Array.isArray(props)
? genLiteralObjectProps(props, context) // static and dynamic arg props
: props.kind === IRDynamicPropsKind.ATTRIBUTE
? genLiteralObjectProps([props], context) // dynamic arg props
: genExpression(props.value, context), // v-bind=""
),
),
oper.root && 'true',
),
]
}
@ -125,7 +131,10 @@ export function genPropKey(
return ['[', modifier && `${JSON.stringify(modifier)} + `, ...key, ']']
}
function genPropValue(values: SimpleExpressionNode[], context: CodegenContext) {
export function genPropValue(
values: SimpleExpressionNode[],
context: CodegenContext,
): CodeFragment[] {
if (values.length === 1) {
return genExpression(values[0], context)
}
@ -134,3 +143,28 @@ function genPropValue(values: SimpleExpressionNode[], context: CodegenContext) {
...values.map(expr => genExpression(expr, context)),
)
}
export function genSetInheritAttrs(
{ staticProps, dynamicProps }: SetInheritAttrsIRNode,
context: CodegenContext,
): CodeFragment[] {
const { vaporHelper } = context
// - `undefined` : no props
// - `false` : all props are static
// - `string[]` : list of props are dynamic
// - `true` : all props as dynamic
const value =
dynamicProps === true
? 'true'
: dynamicProps.length
? genMulti(
DELIMITERS_ARRAY,
...dynamicProps.map(p => JSON.stringify(p)),
)
: staticProps
? 'false'
: null
if (value == null) return []
return [NEWLINE, ...genCall(vaporHelper('setInheritAttrs'), value)]
}

View File

@ -46,7 +46,7 @@ export function genMulti(
...frags: CodeFragments[]
): CodeFragment[] {
if (placeholder) {
while (!frags[frags.length - 1]) {
while (frags.length > 0 && !frags[frags.length - 1]) {
frags.pop()
}
frags = frags.map(frag => frag || placeholder)

View File

@ -24,6 +24,7 @@ export enum IRNodeTypes {
SET_HTML,
SET_TEMPLATE_REF,
SET_MODEL_VALUE,
SET_INHERIT_ATTRS,
INSERT_NODE,
PREPEND_NODE,
@ -93,12 +94,14 @@ export interface SetPropIRNode extends BaseIRNode {
type: IRNodeTypes.SET_PROP
element: number
prop: IRProp
root: boolean
}
export interface SetDynamicPropsIRNode extends BaseIRNode {
type: IRNodeTypes.SET_DYNAMIC_PROPS
element: number
props: IRProps[]
root: boolean
}
export interface SetDynamicEventsIRNode extends BaseIRNode {
@ -156,6 +159,12 @@ export interface SetModelValueIRNode extends BaseIRNode {
isComponent: boolean
}
export interface SetInheritAttrsIRNode extends BaseIRNode {
type: IRNodeTypes.SET_INHERIT_ATTRS
staticProps: boolean
dynamicProps: true | string[]
}
export interface CreateTextNodeIRNode extends BaseIRNode {
type: IRNodeTypes.CREATE_TEXT_NODE
id: number
@ -220,6 +229,7 @@ export type OperationNode =
| SetHtmlIRNode
| SetTemplateRefIRNode
| SetModelValueIRNode
| SetInheritAttrsIRNode
| CreateTextNodeIRNode
| InsertNodeIRNode
| PrependNodeIRNode

View File

@ -64,9 +64,15 @@ export const transformElement: NodeTransform = (node, context) => {
isDynamicComponent,
)
const singleRoot =
context.root === context.parent &&
context.parent.node.children.filter(
child => child.type !== NodeTypes.COMMENT,
).length === 1
;(isComponent ? transformComponentElement : transformNativeElement)(
node as any,
propsResult,
singleRoot,
context as TransformContext<ElementNode>,
isDynamicComponent,
)
@ -76,6 +82,7 @@ export const transformElement: NodeTransform = (node, context) => {
function transformComponentElement(
node: ComponentNode,
propsResult: PropsResult,
singleRoot: boolean,
context: TransformContext,
isDynamicComponent: boolean,
) {
@ -108,16 +115,13 @@ function transformComponentElement(
}
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT
const root =
context.root === context.parent && context.parent.node.children.length === 1
context.registerOperation({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: context.reference(),
tag,
props: propsResult[0] ? propsResult[1] : [propsResult[1]],
asset,
root,
root: singleRoot,
slots: [...context.slots],
once: context.inVOnce,
dynamic: dynamicComponent,
@ -162,6 +166,7 @@ function resolveSetupReference(name: string, context: TransformContext) {
function transformNativeElement(
node: PlainElementNode,
propsResult: PropsResult,
singleRoot: boolean,
context: TransformContext<ElementNode>,
) {
const { tag } = node
@ -172,29 +177,43 @@ function transformNativeElement(
template += `<${tag}`
if (scopeId) template += ` ${scopeId}`
let staticProps = false
const dynamicProps: string[] = []
if (propsResult[0] /* dynamic props */) {
const [, dynamicArgs, expressions] = propsResult
context.registerEffect(expressions, {
type: IRNodeTypes.SET_DYNAMIC_PROPS,
element: context.reference(),
props: dynamicArgs,
root: singleRoot,
})
} else {
for (const prop of propsResult[1]) {
const { key, values } = prop
if (key.isStatic && values.length === 1 && values[0].isStatic) {
staticProps = true
template += ` ${key.content}`
if (values[0].content) template += `="${values[0].content}"`
} else {
dynamicProps.push(key.content)
context.registerEffect(values, {
type: IRNodeTypes.SET_PROP,
element: context.reference(),
prop,
root: singleRoot,
})
}
}
}
if (singleRoot) {
context.registerOperation({
type: IRNodeTypes.SET_INHERIT_ATTRS,
staticProps: staticProps,
dynamicProps: propsResult[0] ? true : dynamicProps,
})
}
template += `>` + context.childrenTemplate.join('')
// TODO remove unnecessary close tag, e.g. if it's the last element of the template
if (!isVoidTag(tag)) {

View File

@ -11,6 +11,7 @@ import {
ref,
renderEffect,
setDynamicProps,
setInheritAttrs,
template,
watchEffect,
} from '../src'
@ -77,9 +78,7 @@ describe('api: setup context', () => {
inheritAttrs: false,
setup(props, { attrs }) {
const el = document.createElement('div')
renderEffect(() => {
setDynamicProps(el, attrs)
})
renderEffect(() => setDynamicProps(el, [attrs]))
return el
},
})
@ -103,23 +102,24 @@ describe('api: setup context', () => {
const toggle = ref(true)
const Wrapper = defineComponent({
setup(_, { slots }) {
return slots.default!()
setup(_) {
const n0 = createSlot('default')
setInheritAttrs(false, true)
return n0
},
})
const Child = defineComponent({
inheritAttrs: false,
setup(_: any, { attrs }: any) {
return createComponent(Wrapper, null, {
const n0 = createComponent(Wrapper, null, {
default: () => {
const n0 = template('<div>')() as HTMLDivElement
renderEffect(() => {
setDynamicProps(n0, attrs)
})
renderEffect(() => setDynamicProps(n0, [attrs], true))
return n0
},
})
return n0
},
})

View File

@ -3,6 +3,7 @@ import {
getCurrentInstance,
nextTick,
ref,
setInheritAttrs,
setText,
template,
watchEffect,
@ -18,7 +19,8 @@ describe('attribute fallthrough', () => {
props: ['foo'],
render() {
const instance = getCurrentInstance()!
const n0 = t0()
const n0 = t0() as Element
setInheritAttrs()
watchEffect(() => setText(n0, instance.props.foo))
return n0
},
@ -62,7 +64,8 @@ describe('attribute fallthrough', () => {
inheritAttrs: false,
render() {
const instance = getCurrentInstance()!
const n0 = t0()
const n0 = t0() as Element
setInheritAttrs()
watchEffect(() => setText(n0, instance.props.foo))
return n0
},
@ -105,7 +108,8 @@ describe('attribute fallthrough', () => {
props: ['custom-attr'],
render() {
const instance = getCurrentInstance()!
const n0 = t0()
const n0 = t0() as Element
setInheritAttrs()
watchEffect(() => setText(n0, instance.attrs.foo))
return n0
},

View File

@ -407,25 +407,25 @@ describe('patchProp', () => {
describe('setDynamicProps', () => {
test('basic set dynamic props', () => {
const el = document.createElement('div')
setDynamicProps(el, { foo: 'val' }, { bar: 'val' })
setDynamicProps(el, [{ foo: 'val' }, { bar: 'val' }])
expect(el.getAttribute('foo')).toBe('val')
expect(el.getAttribute('bar')).toBe('val')
})
test('should merge props', () => {
const el = document.createElement('div')
setDynamicProps(el, { foo: 'val' }, { foo: 'newVal' })
setDynamicProps(el, [{ foo: 'val' }, { foo: 'newVal' }])
expect(el.getAttribute('foo')).toBe('newVal')
})
test('should reset old props', () => {
const el = document.createElement('div')
setDynamicProps(el, { foo: 'val' })
setDynamicProps(el, [{ foo: 'val' }])
expect(el.attributes.length).toBe(1)
expect(el.getAttribute('foo')).toBe('val')
setDynamicProps(el, { bar: 'val' })
setDynamicProps(el, [{ bar: 'val' }])
expect(el.attributes.length).toBe(1)
expect(el.getAttribute('bar')).toBe('val')
expect(el.getAttribute('foo')).toBeNull()
@ -434,18 +434,18 @@ describe('patchProp', () => {
test('should reset old modifier props', () => {
const el = document.createElement('div')
setDynamicProps(el, { ['.foo']: 'val' })
setDynamicProps(el, [{ ['.foo']: 'val' }])
expect((el as any).foo).toBe('val')
setDynamicProps(el, { ['.bar']: 'val' })
setDynamicProps(el, [{ ['.bar']: 'val' }])
expect((el as any).bar).toBe('val')
expect((el as any).foo).toBe('')
setDynamicProps(el, { ['^foo']: 'val' })
setDynamicProps(el, [{ ['^foo']: 'val' }])
expect(el.attributes.length).toBe(1)
expect(el.getAttribute('foo')).toBe('val')
setDynamicProps(el, { ['^bar']: 'val' })
setDynamicProps(el, [{ ['^bar']: 'val' }])
expect(el.attributes.length).toBe(1)
expect(el.getAttribute('bar')).toBe('val')
expect(el.getAttribute('foo')).toBeNull()

View File

@ -12,11 +12,12 @@ import {
walkRawProps,
} from './componentProps'
import { type RawSlots, isDynamicSlotFn } from './componentSlots'
import { withAttrs } from './componentAttrs'
import { setInheritAttrs, withAttrs } from './componentAttrs'
import { isString } from '@vue/shared'
import { renderEffect } from './renderEffect'
import { normalizeBlock } from './dom/element'
import { setDynamicProp } from './dom/prop'
import { setClass, setDynamicProp } from './dom/prop'
import { setStyle } from './dom/style'
export function createComponent(
comp: Component | string,
@ -25,11 +26,12 @@ export function createComponent(
singleRoot: boolean = false,
once: boolean = false,
): ComponentInternalInstance | HTMLElement {
const current = currentInstance!
if (isString(comp)) {
return fallbackComponent(comp, rawProps, slots)
return fallbackComponent(comp, rawProps, slots, current, singleRoot)
}
const current = currentInstance!
const instance = createComponentInstance(
comp,
singleRoot ? withAttrs(rawProps) : rawProps,
@ -48,16 +50,31 @@ function fallbackComponent(
comp: string,
rawProps: RawProps | null,
slots: RawSlots | null,
instance: ComponentInternalInstance,
singleRoot: boolean = false,
): HTMLElement {
// eslint-disable-next-line no-restricted-globals
const el = document.createElement(comp)
if (rawProps) {
rawProps = normalizeRawProps(rawProps)
if (rawProps || Object.keys(instance.attrs).length) {
rawProps = [() => instance.attrs, ...normalizeRawProps(rawProps)]
renderEffect(() => {
walkRawProps(rawProps as NormalizedRawProps, (key, value, getter) => {
setDynamicProp(el, key, getter ? value() : value)
})
let classes: unknown[] | undefined
let styles: unknown[] | undefined
walkRawProps(
rawProps as NormalizedRawProps,
(key, valueOrGetter, getter) => {
const value = getter ? valueOrGetter() : valueOrGetter
if (key === 'class') (classes ||= []).push(value)
else if (key === 'style') (styles ||= []).push(value)
else setDynamicProp(el, key, value)
},
)
if (classes) setClass(el, classes)
if (styles) setStyle(el, styles)
})
}
@ -72,5 +89,9 @@ function fallbackComponent(
}
}
if (singleRoot) {
setInheritAttrs(true)
}
return el
}

View File

@ -16,10 +16,10 @@ import {
shallowReadonly,
} from '@vue/reactivity'
import { isArray, isFunction, isObject } from '@vue/shared'
import { fallThroughAttrs } from './componentAttrs'
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
import { endMeasure, startMeasure } from './profiling'
import { devtoolsComponentAdded } from './devtools'
import { fallThroughAttrs } from './componentAttrs'
export const fragmentKey: unique symbol = Symbol(__DEV__ ? `fragmentKey` : ``)
@ -86,9 +86,6 @@ export function setupComponent(instance: ComponentInternalInstance): void {
resetTracking()
}
if (block instanceof DocumentFragment) {
block = Array.from(block.childNodes)
}
if (!block) {
// TODO: warn no template
block = []

View File

@ -174,6 +174,13 @@ export interface ComponentInternalInstance {
emit: EmitFn
emitted: Record<string, boolean> | null
attrs: Data
/**
* - `undefined` : no props
* - `false` : all props are static
* - `string[]` : list of props are dynamic
* - `true` : all props as dynamic
*/
dynamicAttrs?: string[] | boolean
slots: StaticSlots
refs: Data
// exposed properties via expose()

View File

@ -1,11 +1,14 @@
import { camelize, isArray } from '@vue/shared'
import { camelize, isArray, normalizeClass, normalizeStyle } from '@vue/shared'
import { type ComponentInternalInstance, currentInstance } from './component'
import { isEmitListener } from './componentEmits'
import { setDynamicProps } from './dom/prop'
import { type RawProps, walkRawProps } from './componentProps'
import { renderEffect } from './renderEffect'
import { mergeProp, setDynamicProp } from './dom/prop'
export function patchAttrs(instance: ComponentInternalInstance): void {
export function patchAttrs(
instance: ComponentInternalInstance,
hasDynamicProps?: boolean,
): void {
const {
attrs,
rawProps,
@ -14,6 +17,8 @@ export function patchAttrs(instance: ComponentInternalInstance): void {
if (!rawProps.length) return
const keys = new Set<string>()
const classes: any[] = []
const styles: any[] = []
walkRawProps(rawProps, registerAttr)
for (const key in attrs) {
@ -22,14 +27,42 @@ export function patchAttrs(instance: ComponentInternalInstance): void {
}
}
setClassOrStyle(classes, 'class', normalizeClass)
setClassOrStyle(styles, 'style', normalizeStyle)
function setClassOrStyle(
values: any[],
field: 'class' | 'style',
normalize: (value: any) => any,
) {
if (values.length) {
if (hasDynamicProps) {
Object.defineProperty(attrs, field, {
get() {
return normalize(values.map(value => value()))
},
enumerable: true,
configurable: true,
})
} else {
attrs[field] = normalizeClass(values)
}
}
}
function registerAttr(key: string, value: any, getter?: boolean) {
if (
(!options || !(camelize(key) in options)) &&
!isEmitListener(instance.emitsOptions, key) &&
!keys.has(key)
(key === 'class' || key === 'style' || !keys.has(key))
) {
keys.add(key)
if (getter) {
if (key === 'class' || key === 'style') {
;(key === 'class' ? classes : styles).push(
hasDynamicProps ? (getter ? value : () => value) : value,
)
} else if (getter) {
Object.defineProperty(attrs, key, {
get: value,
enumerable: true,
@ -57,16 +90,47 @@ export function fallThroughAttrs(instance: ComponentInternalInstance): void {
const {
block,
type: { inheritAttrs },
dynamicAttrs,
} = instance
if (inheritAttrs === false) return
if (
inheritAttrs === false ||
!(block instanceof Element) ||
// all props as dynamic
dynamicAttrs === true
)
return
if (block instanceof Element) {
const hasStaticAttrs = dynamicAttrs || dynamicAttrs === false
let initial: Record<string, string> | undefined
if (hasStaticAttrs) {
// attrs in static template
const initial: Record<string, string> = {}
initial = {}
for (let i = 0; i < block.attributes.length; i++) {
const attr = block.attributes[i]
if (dynamicAttrs && dynamicAttrs.includes(attr.name)) continue
initial[attr.name] = attr.value
}
renderEffect(() => setDynamicProps(block, instance.attrs, initial))
}
renderEffect(() => {
for (const key in instance.attrs) {
if (dynamicAttrs && dynamicAttrs.includes(key)) continue
let value: unknown
if (hasStaticAttrs) {
value = mergeProp(key, instance.attrs[key], initial![key])
} else {
value = instance.attrs[key]
}
setDynamicProp(block, key, value)
}
})
}
export function setInheritAttrs(dynamicAttrs?: string[] | boolean): void {
const instance = currentInstance!
if (instance.type.inheritAttrs === false) return
instance.dynamicAttrs = dynamicAttrs
}

View File

@ -116,7 +116,7 @@ export function initProps(
}
if (hasDynamicProps) {
firstEffect(instance, () => patchAttrs(instance))
firstEffect(instance, () => patchAttrs(instance, true))
} else {
patchAttrs(instance)
}

View File

@ -18,9 +18,20 @@ import {
} from '../componentMetadata'
import { on } from './event'
import type { Data } from '@vue/runtime-shared'
import { currentInstance } from '../component'
export function mergeInheritAttr(key: string, value: any): unknown {
const instance = currentInstance!
return mergeProp(key, instance.attrs[key], value)
}
export function setClass(el: Element, value: any, root?: boolean): void {
const prev = recordPropMetadata(
el,
'class',
(value = normalizeClass(root ? mergeInheritAttr('class', value) : value)),
)
export function setClass(el: Element, value: any): void {
const prev = recordPropMetadata(el, 'class', (value = normalizeClass(value)))
if (value !== prev && (value || prev)) {
el.className = value
}
@ -132,8 +143,15 @@ export function setDynamicProp(el: Element, key: string, value: any): void {
}
}
export function setDynamicProps(el: Element, ...args: any): void {
export function setDynamicProps(
el: Element,
args: any[],
root?: boolean,
): void {
const oldProps = getMetadata(el)[MetadataKind.prop]
if (root) {
args.unshift(currentInstance!.attrs)
}
const props = args.length > 1 ? mergeProps(...args) : args[0]
for (const key in oldProps) {
@ -153,32 +171,36 @@ export function setDynamicProps(el: Element, ...args: any): void {
}
}
// TODO copied from runtime-core
export function mergeProp(
key: string,
existing: unknown,
incoming: unknown,
): unknown {
if (key === 'class') {
if (existing !== incoming) {
return normalizeClass([existing, incoming])
}
} else if (key === 'style') {
return normalizeStyle([existing, incoming])
} else if (isOn(key)) {
if (
incoming &&
existing !== incoming &&
!(isArray(existing) && existing.includes(incoming))
) {
return existing ? [].concat(existing as any, incoming as any) : incoming
}
}
return incoming
}
export function mergeProps(...args: Data[]): 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]
if (key !== '') {
ret[key] = mergeProp(key, ret[key], toMerge[key])
}
}
}

View File

@ -8,9 +8,14 @@ import {
} from '@vue/shared'
import { warn } from '../warning'
import { recordPropMetadata } from '../componentMetadata'
import { mergeInheritAttr } from './prop'
export function setStyle(el: HTMLElement, value: any): void {
const prev = recordPropMetadata(el, 'style', (value = normalizeStyle(value)))
export function setStyle(el: HTMLElement, value: any, root?: boolean): void {
const prev = recordPropMetadata(
el,
'style',
(value = normalizeStyle(root ? mergeInheritAttr('style', value) : value)),
)
patchStyle(el, prev, value)
}

View File

@ -130,6 +130,7 @@ export { createIf } from './apiCreateIf'
export { createFor, createForSlots } from './apiCreateFor'
export { createComponent } from './apiCreateComponent'
export { createSelector } from './apiCreateSelector'
export { setInheritAttrs } from './componentAttrs'
export {
resolveComponent,

View File

@ -12,7 +12,7 @@
"vue": "workspace:*"
},
"devDependencies": {
"@vitejs/plugin-vue": "https://pkg.pr.new/@vitejs/plugin-vue@e3c5ce5",
"@vitejs/plugin-vue": "https://pkg.pr.new/@vitejs/plugin-vue@481bcd4",
"vite": "catalog:",
"vite-hyper-config": "^0.4.0",
"vite-plugin-inspect": "^0.8.7"

View File

@ -521,8 +521,8 @@ importers:
version: link:../packages/vue
devDependencies:
'@vitejs/plugin-vue':
specifier: https://pkg.pr.new/@vitejs/plugin-vue@e3c5ce5
version: https://pkg.pr.new/@vitejs/plugin-vue@e3c5ce5(vite@5.4.8(@types/node@22.8.7)(sass@1.80.6)(terser@5.33.0))(vue@packages+vue)
specifier: https://pkg.pr.new/@vitejs/plugin-vue@481bcd4
version: https://pkg.pr.new/@vitejs/plugin-vue@481bcd4(vite@5.4.8(@types/node@22.8.7)(sass@1.80.6)(terser@5.33.0))(vue@packages+vue)
vite:
specifier: 'catalog:'
version: 5.4.8(@types/node@22.8.7)(sass@1.80.6)(terser@5.33.0)
@ -1416,6 +1416,14 @@ packages:
vite: ^5.0.0
vue: ^3.2.25
'@vitejs/plugin-vue@https://pkg.pr.new/@vitejs/plugin-vue@481bcd4':
resolution: {tarball: https://pkg.pr.new/@vitejs/plugin-vue@481bcd4}
version: 5.1.5
engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies:
vite: ^5.0.0
vue: ^3.2.25
'@vitejs/plugin-vue@https://pkg.pr.new/@vitejs/plugin-vue@e3c5ce5':
resolution: {tarball: https://pkg.pr.new/@vitejs/plugin-vue@e3c5ce5}
version: 5.1.4
@ -4495,16 +4503,16 @@ snapshots:
vite: 5.4.8(@types/node@22.8.7)(sass@1.80.6)(terser@5.33.0)
vue: link:packages/vue
'@vitejs/plugin-vue@https://pkg.pr.new/@vitejs/plugin-vue@481bcd4(vite@5.4.8(@types/node@22.8.7)(sass@1.80.6)(terser@5.33.0))(vue@packages+vue)':
dependencies:
vite: 5.4.8(@types/node@22.8.7)(sass@1.80.6)(terser@5.33.0)
vue: link:packages/vue
'@vitejs/plugin-vue@https://pkg.pr.new/@vitejs/plugin-vue@e3c5ce5(vite@5.4.8(@types/node@22.8.7)(sass@1.80.6)(terser@5.33.0))(vue@3.5.12(typescript@5.6.2))':
dependencies:
vite: 5.4.8(@types/node@22.8.7)(sass@1.80.6)(terser@5.33.0)
vue: 3.5.12(typescript@5.6.2)
'@vitejs/plugin-vue@https://pkg.pr.new/@vitejs/plugin-vue@e3c5ce5(vite@5.4.8(@types/node@22.8.7)(sass@1.80.6)(terser@5.33.0))(vue@packages+vue)':
dependencies:
vite: 5.4.8(@types/node@22.8.7)(sass@1.80.6)(terser@5.33.0)
vue: link:packages/vue
'@vitest/coverage-v8@2.1.1(vitest@2.1.1)':
dependencies:
'@ampproject/remapping': 2.3.0