diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
index 3693cd197..e658e4a2f 100644
--- a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
@@ -1,7 +1,7 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compile > bindings 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, insert as _insert, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, insert as _insert, renderEffect as _renderEffect, setText as _setText } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("
count is .
")
@@ -9,7 +9,7 @@ export function render(_ctx) {
const { 0: [n3, { 1: [n2],}],} = _children(n0)
const n1 = _createTextNode(_ctx.count)
_insert(n1, n3, n2)
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n1, undefined, _ctx.count)
})
return n0
@@ -121,7 +121,7 @@ export function render(_ctx) {
`;
exports[`compile > directives > v-pre > self-closing v-pre 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, watchEffect as _watchEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, renderEffect as _renderEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("
")
@@ -129,10 +129,10 @@ export function render(_ctx) {
const { 1: [n2],} = _children(n0)
const n1 = _createTextNode(_ctx.bar)
_append(n2, n1)
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n1, undefined, _ctx.bar)
})
- _watchEffect(() => {
+ _renderEffect(() => {
_setAttr(n2, "id", undefined, _ctx.foo)
})
return n0
@@ -140,7 +140,7 @@ export function render(_ctx) {
`;
exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, watchEffect as _watchEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, append as _append, renderEffect as _renderEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("
{{ bar }}
")
@@ -148,10 +148,10 @@ export function render(_ctx) {
const { 1: [n2],} = _children(n0)
const n1 = _createTextNode(_ctx.bar)
_append(n2, n1)
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n1, undefined, _ctx.bar)
})
- _watchEffect(() => {
+ _renderEffect(() => {
_setAttr(n2, "id", undefined, _ctx.foo)
})
return n0
@@ -159,7 +159,7 @@ export function render(_ctx) {
`;
exports[`compile > dynamic root 1`] = `
-"import { fragment as _fragment, createTextNode as _createTextNode, append as _append, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
+"import { fragment as _fragment, createTextNode as _createTextNode, append as _append, renderEffect as _renderEffect, setText as _setText } from 'vue/vapor';
export function render(_ctx) {
const t0 = _fragment()
@@ -168,10 +168,10 @@ export function render(_ctx) {
const n1 = _createTextNode(1)
const n2 = _createTextNode(2)
_append(n0, n1, n2)
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n1, undefined, 1)
})
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n2, undefined, 2)
})
return n0
@@ -179,7 +179,7 @@ export function render(_ctx) {
`;
exports[`compile > dynamic root nodes and interpolation 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, prepend as _prepend, insert as _insert, append as _append, on as _on, watchEffect as _watchEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, prepend as _prepend, insert as _insert, append as _append, on as _on, renderEffect as _renderEffect, setText as _setText, setAttr as _setAttr } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("")
@@ -192,7 +192,7 @@ export function render(_ctx) {
_insert(n2, n4, n5)
_append(n4, n3)
_on(n4, "click", (...args) => (_ctx.handleClick && _ctx.handleClick(...args)))
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n1, undefined, _ctx.count)
_setText(n2, undefined, _ctx.count)
_setText(n3, undefined, _ctx.count)
@@ -207,7 +207,7 @@ exports[`compile > expression parsing > interpolation 1`] = `
const t0 = _fragment()
const n0 = t0()
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n0, undefined, a + b.value)
})
return n0
@@ -219,7 +219,7 @@ exports[`compile > expression parsing > v-bind 1`] = `
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_setAttr(n1, key.value+1, undefined, _unref(foo)[key.value+1]())
})
return n0
@@ -237,7 +237,7 @@ export function render(_ctx) {
`;
exports[`compile > static + dynamic root 1`] = `
-"import { template as _template, children as _children, createTextNode as _createTextNode, prepend as _prepend, insert as _insert, append as _append, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
+"import { template as _template, children as _children, createTextNode as _createTextNode, prepend as _prepend, insert as _insert, append as _append, renderEffect as _renderEffect, setText as _setText } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("369")
@@ -255,28 +255,28 @@ export function render(_ctx) {
_insert([n3, n4], n0, n9)
_insert([n5, n6], n0, n10)
_append(n0, n7, n8)
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n1, undefined, 1)
})
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n2, undefined, 2)
})
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n3, undefined, 4)
})
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n4, undefined, 5)
})
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n5, undefined, 7)
})
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n6, undefined, 8)
})
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n7, undefined, 'A')
})
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n8, undefined, 'B')
})
return n0
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
index 63936f19e..fa70b0f37 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vBind.spec.ts.snap
@@ -1,13 +1,13 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler v-bind > .camel modifier 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_setAttr(n1, "fooBar", undefined, _ctx.id)
})
return n0
@@ -21,7 +21,7 @@ export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_setAttr(n1, _camelize(_ctx.foo), undefined, _ctx.id)
})
return n0
@@ -29,13 +29,13 @@ export function render(_ctx) {
`;
exports[`compiler v-bind > .camel modifier w/ no expression 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_setAttr(n1, "fooBar", undefined, _ctx.fooBar)
})
return n0
@@ -43,13 +43,13 @@ export function render(_ctx) {
`;
exports[`compiler v-bind > basic 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_setAttr(n1, "id", undefined, _ctx.id)
})
return n0
@@ -57,13 +57,13 @@ export function render(_ctx) {
`;
exports[`compiler v-bind > dynamic arg 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_setAttr(n1, _ctx.id, undefined, _ctx.id)
})
return n0
@@ -71,13 +71,13 @@ export function render(_ctx) {
`;
exports[`compiler v-bind > no expression (shorthand) 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_setAttr(n1, "camel-case", undefined, _ctx.camelCase)
})
return n0
@@ -85,13 +85,13 @@ export function render(_ctx) {
`;
exports[`compiler v-bind > no expression 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setAttr as _setAttr } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setAttr as _setAttr } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_setAttr(n1, "id", undefined, _ctx.id)
})
return n0
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vHtml.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vHtml.spec.ts.snap
index e1da7d157..865e46319 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vHtml.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vHtml.spec.ts.snap
@@ -1,13 +1,13 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`v-html > should convert v-html to innerHTML 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setHtml as _setHtml } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setHtml as _setHtml } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_setHtml(n1, undefined, _ctx.code)
})
return n0
@@ -15,13 +15,13 @@ export function render(_ctx) {
`;
exports[`v-html > should raise error and ignore children when v-html is present 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setHtml as _setHtml } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setHtml as _setHtml } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_setHtml(n1, undefined, _ctx.test)
})
return n0
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap
index c1f85b96a..efea7f51d 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap
@@ -13,13 +13,13 @@ export function render(_ctx) {
`;
exports[`v-on > dynamic arg 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, on as _on } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, on as _on } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_on(n1, _ctx.event, (...args) => (_ctx.handler && _ctx.handler(...args)))
})
return n0
@@ -109,13 +109,13 @@ export function render(_ctx) {
`;
exports[`v-on > should transform click.middle 2`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_on(n1, (_ctx.event) === "click" ? "mouseup" : (_ctx.event), _withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["middle"]))
})
return n0
@@ -135,31 +135,19 @@ export function render(_ctx) {
`;
exports[`v-on > should transform click.right 2`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_on(n1, (_ctx.event) === "click" ? "contextmenu" : (_ctx.event), _withKeys(_withModifiers((...args) => (_ctx.test && _ctx.test(...args)), ["right"]), ["right"]))
})
return n0
}"
`;
-exports[`v-on > should wrap as function if expression is inline statement 1`] = `
-"import { template as _template, children as _children, on as _on } from 'vue/vapor';
-
-export function render(_ctx) {
- const t0 = _template("")
- const n0 = t0()
- const { 0: [n1],} = _children(n0)
- _on(n1, "click", (...args) => (_ctx.i++ && _ctx.i++(...args)))
- return n0
-}"
-`;
-
exports[`v-on > should wrap keys guard for keyboard events or dynamic events 1`] = `
"import { template as _template, children as _children, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vText.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vText.spec.ts.snap
index 8beed48c7..0ae00ce44 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vText.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vText.spec.ts.snap
@@ -1,13 +1,13 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`v-text > should convert v-text to textContent 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setText as _setText } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n1, undefined, _ctx.str)
})
return n0
@@ -15,13 +15,13 @@ export function render(_ctx) {
`;
exports[`v-text > should raise error and ignore children when v-text is present 1`] = `
-"import { template as _template, children as _children, watchEffect as _watchEffect, setText as _setText } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, setText as _setText } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _watchEffect(() => {
+ _renderEffect(() => {
_setText(n1, undefined, _ctx.test)
})
return n0
diff --git a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
index b6ef80f30..3c7932684 100644
--- a/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/vBind.spec.ts
@@ -210,7 +210,7 @@ describe('compiler v-bind', () => {
})
expect(code).matchSnapshot()
- expect(code).contains('watchEffect')
+ expect(code).contains('renderEffect')
expect(code).contains('_setAttr(n1, "fooBar", undefined, _ctx.fooBar)')
})
@@ -230,7 +230,7 @@ describe('compiler v-bind', () => {
})
expect(code).matchSnapshot()
- expect(code).contains('watchEffect')
+ expect(code).contains('renderEffect')
expect(code).contains(
`_setAttr(n1, _camelize(_ctx.foo), undefined, _ctx.id)`,
)
diff --git a/packages/compiler-vapor/__tests__/transforms/vOn.spec.ts b/packages/compiler-vapor/__tests__/transforms/vOn.spec.ts
index 691c31467..08a73b430 100644
--- a/packages/compiler-vapor/__tests__/transforms/vOn.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/vOn.spec.ts
@@ -102,7 +102,7 @@ describe('v-on', () => {
const { code, ir } = compileWithVOn(``)
expect(ir.vaporHelpers).contains('on')
- expect(ir.vaporHelpers).contains('watchEffect')
+ expect(ir.vaporHelpers).contains('renderEffect')
expect(ir.helpers.size).toBe(0)
expect(ir.operation).toEqual([])
diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts
index 7d0cea4e6..6caf9a0d0 100644
--- a/packages/compiler-vapor/src/generate.ts
+++ b/packages/compiler-vapor/src/generate.ts
@@ -293,7 +293,7 @@ export function generate(
}
for (const { operations } of ir.effect) {
- pushNewline(`${vaporHelper('watchEffect')}(() => {`)
+ pushNewline(`${vaporHelper('renderEffect')}(() => {`)
withIndent(() => {
for (const operation of operations) {
genOperation(operation, ctx)
diff --git a/packages/runtime-vapor/__tests__/apiWatch.spec.ts b/packages/runtime-vapor/__tests__/apiWatch.spec.ts
index 741a816b1..228c59cb2 100644
--- a/packages/runtime-vapor/__tests__/apiWatch.spec.ts
+++ b/packages/runtime-vapor/__tests__/apiWatch.spec.ts
@@ -1,28 +1,12 @@
-import { EffectScope, type Ref, ref } from '@vue/reactivity'
+import type { Ref } from '@vue/reactivity'
import {
+ EffectScope,
+ nextTick,
onEffectCleanup,
+ ref,
watchEffect,
- watchPostEffect,
watchSyncEffect,
-} from '../src/apiWatch'
-import { nextTick } from '../src/scheduler'
-import { defineComponent } from 'vue'
-import { render } from '../src/render'
-import { template } from '../src/template'
-
-let host: HTMLElement
-
-const initHost = () => {
- host = document.createElement('div')
- host.setAttribute('id', 'host')
- document.body.appendChild(host)
-}
-beforeEach(() => {
- initHost()
-})
-afterEach(() => {
- host.remove()
-})
+} from '../src'
describe('watchEffect and onEffectCleanup', () => {
test('basic', async () => {
@@ -93,71 +77,4 @@ describe('watchEffect and onEffectCleanup', () => {
await nextTick()
expect(dummy).toBe(15)
})
-
- test('scheduling order', async () => {
- const calls: string[] = []
-
- const demo = defineComponent({
- setup() {
- const source = ref(0)
- const change = () => source.value++
-
- watchPostEffect(() => {
- const current = source.value
- calls.push(`post ${current}`)
- onEffectCleanup(() => calls.push(`post cleanup ${current}`))
- })
- watchEffect(() => {
- const current = source.value
- calls.push(`pre ${current}`)
- onEffectCleanup(() => calls.push(`pre cleanup ${current}`))
- })
- watchSyncEffect(() => {
- const current = source.value
- calls.push(`sync ${current}`)
- onEffectCleanup(() => calls.push(`sync cleanup ${current}`))
- })
- const __returned__ = { source, change }
- Object.defineProperty(__returned__, '__isScriptSetup', {
- enumerable: false,
- value: true,
- })
- return __returned__
- },
- })
-
- demo.render = (_ctx: any) => {
- const t0 = template('')
- watchEffect(() => {
- const current = _ctx.source
- calls.push(`render ${current}`)
- onEffectCleanup(() => calls.push(`render cleanup ${current}`))
- })
- return t0()
- }
-
- const instance = render(demo as any, {}, '#host')
- const { change } = instance.setupState as any
-
- expect(calls).toEqual(['pre 0', 'sync 0', 'render 0'])
- calls.length = 0
-
- await nextTick()
- expect(calls).toEqual(['post 0'])
- calls.length = 0
-
- change()
- expect(calls).toEqual(['sync cleanup 0', 'sync 1'])
- calls.length = 0
-
- await nextTick()
- expect(calls).toEqual([
- 'pre cleanup 0',
- 'pre 1',
- 'render cleanup 0',
- 'render 1',
- 'post cleanup 0',
- 'post 1',
- ])
- })
})
diff --git a/packages/runtime-vapor/__tests__/renderWatch.spec.ts b/packages/runtime-vapor/__tests__/renderWatch.spec.ts
new file mode 100644
index 000000000..0d43ad90f
--- /dev/null
+++ b/packages/runtime-vapor/__tests__/renderWatch.spec.ts
@@ -0,0 +1,133 @@
+import { defineComponent } from 'vue'
+import {
+ nextTick,
+ onEffectCleanup,
+ ref,
+ render,
+ renderEffect,
+ renderWatch,
+ template,
+ watchEffect,
+ watchPostEffect,
+ watchSyncEffect,
+} from '../src'
+
+let host: HTMLElement
+
+const initHost = () => {
+ host = document.createElement('div')
+ host.setAttribute('id', 'host')
+ document.body.appendChild(host)
+}
+beforeEach(() => {
+ initHost()
+})
+afterEach(() => {
+ host.remove()
+})
+
+describe('renderWatch', () => {
+ test('effect', async () => {
+ let dummy: any
+ const source = ref(0)
+ renderEffect(() => {
+ dummy = source.value
+ })
+ await nextTick()
+ expect(dummy).toBe(0)
+ source.value++
+ await nextTick()
+ expect(dummy).toBe(1)
+ })
+
+ test('watch', async () => {
+ let dummy: any
+ const source = ref(0)
+ renderWatch(source, () => {
+ dummy = source.value
+ })
+ await nextTick()
+ expect(dummy).toBe(undefined)
+ source.value++
+ await nextTick()
+ expect(dummy).toBe(1)
+ })
+
+ test('scheduling order', async () => {
+ const calls: string[] = []
+
+ const demo = defineComponent({
+ setup() {
+ const source = ref(0)
+ const renderSource = ref(0)
+ const change = () => source.value++
+ const changeRender = () => renderSource.value++
+
+ watchPostEffect(() => {
+ const current = source.value
+ calls.push(`post ${current}`)
+ onEffectCleanup(() => calls.push(`post cleanup ${current}`))
+ })
+ watchEffect(() => {
+ const current = source.value
+ calls.push(`pre ${current}`)
+ onEffectCleanup(() => calls.push(`pre cleanup ${current}`))
+ })
+ watchSyncEffect(() => {
+ const current = source.value
+ calls.push(`sync ${current}`)
+ onEffectCleanup(() => calls.push(`sync cleanup ${current}`))
+ })
+ const __returned__ = { source, change, renderSource, changeRender }
+ Object.defineProperty(__returned__, '__isScriptSetup', {
+ enumerable: false,
+ value: true,
+ })
+ return __returned__
+ },
+ })
+
+ demo.render = (_ctx: any) => {
+ const t0 = template('')
+ renderEffect(() => {
+ const current = _ctx.renderSource
+ calls.push(`renderEffect ${current}`)
+ onEffectCleanup(() => calls.push(`renderEffect cleanup ${current}`))
+ })
+ renderWatch(
+ () => _ctx.renderSource,
+ (value) => {
+ calls.push(`renderWatch ${value}`)
+ onEffectCleanup(() => calls.push(`renderWatch cleanup ${value}`))
+ },
+ )
+ return t0()
+ }
+
+ const instance = render(demo as any, {}, '#host')
+ const { change, changeRender } = instance.setupState as any
+
+ expect(calls).toEqual(['pre 0', 'sync 0', 'renderEffect 0'])
+ calls.length = 0
+
+ await nextTick()
+ expect(calls).toEqual(['post 0'])
+ calls.length = 0
+
+ changeRender()
+ change()
+ expect(calls).toEqual(['sync cleanup 0', 'sync 1'])
+ calls.length = 0
+
+ await nextTick()
+ expect(calls).toEqual([
+ 'pre cleanup 0',
+ 'pre 1',
+ 'renderEffect cleanup 0',
+ 'renderEffect 1',
+ 'renderWatch 1',
+ 'post cleanup 0',
+ 'post 1',
+ ])
+ })
+})
diff --git a/packages/runtime-vapor/src/apiWatch.ts b/packages/runtime-vapor/src/apiWatch.ts
index ca03b9f87..2c7cd8f63 100644
--- a/packages/runtime-vapor/src/apiWatch.ts
+++ b/packages/runtime-vapor/src/apiWatch.ts
@@ -1,40 +1,21 @@
import {
+ type BaseWatchErrorCodes,
+ type BaseWatchOptions,
type ComputedRef,
type DebuggerOptions,
- type EffectScheduler,
- ReactiveEffect,
- ReactiveFlags,
type Ref,
+ baseWatch,
getCurrentScope,
- isReactive,
- isRef,
} from '@vue/reactivity'
-import {
- EMPTY_OBJ,
- NOOP,
- extend,
- hasChanged,
- isArray,
- isFunction,
- isMap,
- isObject,
- isPlainObject,
- isSet,
- remove,
-} from '@vue/shared'
+import { EMPTY_OBJ, NOOP, extend, isFunction, remove } from '@vue/shared'
import { currentInstance } from './component'
import {
- type Scheduler,
- type SchedulerJob,
- getVaporSchedulerByFlushMode,
- vaporPostScheduler,
- vaporSyncScheduler,
+ type SchedulerFactory,
+ createVaporPostScheduler,
+ createVaporPreScheduler,
+ createVaporSyncScheduler,
} from './scheduler'
-import {
- VaporErrorCodes,
- callWithAsyncErrorHandling,
- callWithErrorHandling,
-} from './errorHandling'
+import { handleError as handleErrorWithInstance } from './errorHandling'
import { warn } from './warning'
export type WatchEffect = (onCleanup: OnCleanup) => void
@@ -76,10 +57,9 @@ export type WatchStopHandle = () => void
// Simple effect.
export function watchEffect(
effect: WatchEffect,
- options: WatchOptionsBase = EMPTY_OBJ,
+ options?: WatchOptionsBase,
): WatchStopHandle {
- const { flush } = options
- return doWatch(effect, null, getVaporSchedulerByFlushMode(flush), options)
+ return doWatch(effect, null, options)
}
export function watchPostEffect(
@@ -89,7 +69,6 @@ export function watchPostEffect(
return doWatch(
effect,
null,
- vaporPostScheduler,
__DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
)
}
@@ -101,16 +80,19 @@ export function watchSyncEffect(
return doWatch(
effect,
null,
- vaporSyncScheduler,
__DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' },
)
}
-// initial value for watchers to trigger on undefined initial values
-const INITIAL_WATCHER_VALUE = {}
-
type MultiWatchSources = (WatchSource | object)[]
+// overload: single source + cb
+export function watch = false>(
+ source: WatchSource,
+ cb: WatchCallback,
+ options?: WatchOptions,
+): WatchStopHandle
+
// overload: array of multiple sources + cb
export function watch<
T extends MultiWatchSources,
@@ -133,13 +115,6 @@ export function watch<
options?: WatchOptions,
): WatchStopHandle
-// overload: single source + cb
-export function watch = false>(
- source: WatchSource,
- cb: WatchCallback,
- options?: WatchOptions,
-): WatchStopHandle
-
// overload: watching reactive object w/ cb
export function watch<
T extends object,
@@ -154,7 +129,7 @@ export function watch<
export function watch = false>(
source: T | WatchSource,
cb: any,
- options: WatchOptions = EMPTY_OBJ,
+ options?: WatchOptions,
): WatchStopHandle {
if (__DEV__ && !isFunction(cb)) {
warn(
@@ -163,46 +138,33 @@ export function watch = false>(
`supports \`watch(source, cb, options?) signature.`,
)
}
- const { flush } = options
- return doWatch(
- source as any,
- cb,
- getVaporSchedulerByFlushMode(flush),
- options,
- )
+ return doWatch(source as any, cb, options)
}
-const cleanupMap: WeakMap void)[]> = new WeakMap()
-let activeEffect: ReactiveEffect | undefined = undefined
-
-// TODO: extract it to the reactivity package
-export function onEffectCleanup(cleanupFn: () => void) {
- if (activeEffect) {
- const cleanups =
- cleanupMap.get(activeEffect) ||
- cleanupMap.set(activeEffect, []).get(activeEffect)!
- cleanups.push(cleanupFn)
+function getScheduler(flush: WatchOptionsBase['flush']): SchedulerFactory {
+ if (flush === 'post') {
+ return createVaporPostScheduler
}
-}
-
-export interface doWatchOptions extends DebuggerOptions {
- immediate?: Immediate
- deep?: boolean
- once?: boolean
+ if (flush === 'sync') {
+ return createVaporSyncScheduler
+ }
+ // default: 'pre'
+ return createVaporPreScheduler
}
function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
- scheduler: Scheduler,
- { immediate, deep, once, onTrack, onTrigger }: doWatchOptions = EMPTY_OBJ,
+ options: WatchOptions = EMPTY_OBJ,
): WatchStopHandle {
- if (cb && once) {
- const _cb = cb
- cb = (...args) => {
- _cb(...args)
- unwatch()
- }
+ const { immediate, deep, flush, once } = options
+
+ // TODO remove in 3.5
+ if (__DEV__ && deep !== void 0 && typeof deep === 'number') {
+ warn(
+ `watch() "deep" option with number value will be used as watch depth in future versions. ` +
+ `Please use a boolean instead to avoid potential breakage.`,
+ )
}
if (__DEV__ && !cb) {
@@ -226,214 +188,43 @@ function doWatch(
}
}
- const warnInvalidSource = (s: unknown) => {
- warn(
- `Invalid watch source: `,
- s,
- `A watch source can only be a getter/effect function, a ref, ` +
- `a reactive object, or an array of these types.`,
- )
- }
+ const extendOptions: BaseWatchOptions = {}
- const instance =
- getCurrentScope() === currentInstance?.scope ? currentInstance : null
- // const instance = currentInstance
- let getter: () => any
- let forceTrigger = false
- let isMultiSource = false
+ if (__DEV__) extendOptions.onWarn = warn
- if (isRef(source)) {
- getter = () => source.value
- } else if (isReactive(source)) {
- getter = () => source
- deep = true
- } else if (isArray(source)) {
- getter = () =>
- source.map((s) => {
- if (isRef(s)) {
- return s.value
- } else if (isReactive(s)) {
- return traverse(s)
- } else if (isFunction(s)) {
- return callWithErrorHandling(
- s,
- instance,
- VaporErrorCodes.WATCH_GETTER,
- )
- } else {
- __DEV__ && warnInvalidSource(s)
- }
- })
- } else if (isFunction(source)) {
- if (cb) {
- // getter with cb
- getter = () =>
- callWithErrorHandling(source, instance, VaporErrorCodes.WATCH_GETTER)
- } else {
- // no cb -> simple effect
- getter = () => {
- if (instance && instance.isUnmounted) {
- return
- }
- if (cleanup) {
- cleanup()
- }
- const currentEffect = activeEffect
- activeEffect = effect
- try {
- return callWithAsyncErrorHandling(
- source,
- instance,
- VaporErrorCodes.WATCH_CALLBACK,
- [onEffectCleanup],
- )
- } finally {
- activeEffect = currentEffect
- }
- }
- }
- } else {
- getter = NOOP
- __DEV__ && warnInvalidSource(source)
- }
-
- if (cb && deep) {
- const baseGetter = getter
- getter = () => traverse(baseGetter())
- }
-
- // TODO: ssr
+ let ssrCleanup: (() => void)[] | undefined
+ // TODO: SSR
// if (__SSR__ && isInSSRComponentSetup) {
+ // if (flush === 'sync') {
+ // const ctx = useSSRContext()!
+ // ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
+ // } else if (!cb || immediate) {
+ // // immediately watch or watchEffect
+ // extendOptions.once = true
+ // } else {
+ // // watch(source, cb)
+ // return NOOP
+ // }
// }
- let oldValue: any = isMultiSource
- ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
- : INITIAL_WATCHER_VALUE
- const job: SchedulerJob = () => {
- if (!effect.active || !effect.dirty) {
- return
- }
- if (cb) {
- // watch(source, cb)
- const newValue = effect.run()
- if (
- deep ||
- forceTrigger ||
- (isMultiSource
- ? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i]))
- : hasChanged(newValue, oldValue))
- ) {
- // cleanup before running cb again
- if (cleanup) {
- cleanup()
- }
- const currentEffect = activeEffect
- activeEffect = effect
- try {
- callWithAsyncErrorHandling(
- cb,
- instance,
- VaporErrorCodes.WATCH_CALLBACK,
- [
- newValue,
- // pass undefined as the old value when it's changed for the first time
- oldValue === INITIAL_WATCHER_VALUE
- ? undefined
- : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
- ? []
- : oldValue,
- onEffectCleanup,
- ],
- )
- oldValue = newValue
- } finally {
- activeEffect = currentEffect
- }
- }
- } else {
- // watchEffect
- effect.run()
- }
- }
-
- // important: mark the job as a watcher callback so that scheduler knows
- // it is allowed to self-trigger (#1727)
- job.allowRecurse = !!cb
-
- let effectScheduler: EffectScheduler = () =>
- scheduler({
- effect,
- job,
- instance: instance,
- isInit: false,
- })
+ const instance = currentInstance
- const effect = new ReactiveEffect(getter, NOOP, effectScheduler)
+ extendOptions.onError = (err: unknown, type: BaseWatchErrorCodes) =>
+ handleErrorWithInstance(err, instance, type)
+ extendOptions.scheduler = getScheduler(flush)(instance)
- const cleanup = (effect.onStop = () => {
- const cleanups = cleanupMap.get(effect)
- if (cleanups) {
- cleanups.forEach((cleanup) => cleanup())
- cleanupMap.delete(effect)
- }
- })
+ let effect = baseWatch(source, cb, extend({}, options, extendOptions))
- const unwatch = () => {
- effect.stop()
- if (instance && instance.scope) {
- remove(instance.scope.effects!, effect)
- }
- }
-
- if (__DEV__) {
- effect.onTrack = onTrack
- effect.onTrigger = onTrigger
- }
-
- // initial run
- if (cb) {
- if (immediate) {
- job()
- } else {
- oldValue = effect.run()
- }
- } else {
- scheduler({
- effect,
- job,
- instance: instance,
- isInit: true,
- })
- }
+ const scope = getCurrentScope()
+ const unwatch = !effect
+ ? NOOP
+ : () => {
+ effect!.stop()
+ if (scope) {
+ remove(scope.effects, effect)
+ }
+ }
- // TODO: ssr
- // if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
+ if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
return unwatch
}
-
-export function traverse(value: unknown, seen?: Set) {
- if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
- return value
- }
- seen = seen || new Set()
- if (seen.has(value)) {
- return value
- }
- seen.add(value)
- if (isRef(value)) {
- traverse(value.value, seen)
- } else if (isArray(value)) {
- for (let i = 0; i < value.length; i++) {
- traverse(value[i], seen)
- }
- } else if (isSet(value) || isMap(value)) {
- value.forEach((v: any) => {
- traverse(v, seen)
- })
- } else if (isPlainObject(value)) {
- for (const key in value) {
- traverse(value[key], seen)
- }
- }
- return value
-}
diff --git a/packages/runtime-vapor/src/errorHandling.ts b/packages/runtime-vapor/src/errorHandling.ts
index 0867d4bd9..fa3954280 100644
--- a/packages/runtime-vapor/src/errorHandling.ts
+++ b/packages/runtime-vapor/src/errorHandling.ts
@@ -7,16 +7,20 @@ import type { ComponentInternalInstance } from './component'
import { isFunction, isPromise } from '@vue/shared'
import { warn } from './warning'
import { VaporLifecycleHooks } from './enums'
+import { BaseWatchErrorCodes } from '@vue/reactivity'
// contexts where user provided function may be executed, in addition to
// lifecycle hooks.
export enum VaporErrorCodes {
SETUP_FUNCTION,
RENDER_FUNCTION,
- WATCH_GETTER,
- WATCH_CALLBACK,
- WATCH_CLEANUP,
- NATIVE_EVENT_HANDLER,
+ // The error codes for the watch have been transferred to the reactivity
+ // package along with baseWatch to maintain code compatibility. Hence,
+ // it is essential to keep these values unchanged.
+ // WATCH_GETTER,
+ // WATCH_CALLBACK,
+ // WATCH_CLEANUP,
+ NATIVE_EVENT_HANDLER = 5,
COMPONENT_EVENT_HANDLER,
VNODE_HOOK,
DIRECTIVE_HOOK,
@@ -28,10 +32,12 @@ export enum VaporErrorCodes {
SCHEDULER,
}
-export const ErrorTypeStrings: Record<
- VaporLifecycleHooks | VaporErrorCodes,
- string
-> = {
+export type ErrorTypes =
+ | VaporLifecycleHooks
+ | VaporErrorCodes
+ | BaseWatchErrorCodes
+
+export const ErrorTypeStrings: Record = {
// [VaporLifecycleHooks.SERVER_PREFETCH]: 'serverPrefetch hook',
[VaporLifecycleHooks.BEFORE_CREATE]: 'beforeCreate hook',
[VaporLifecycleHooks.CREATED]: 'created hook',
@@ -48,9 +54,9 @@ export const ErrorTypeStrings: Record<
[VaporLifecycleHooks.RENDER_TRIGGERED]: 'renderTriggered hook',
[VaporErrorCodes.SETUP_FUNCTION]: 'setup function',
[VaporErrorCodes.RENDER_FUNCTION]: 'render function',
- [VaporErrorCodes.WATCH_GETTER]: 'watcher getter',
- [VaporErrorCodes.WATCH_CALLBACK]: 'watcher callback',
- [VaporErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function',
+ [BaseWatchErrorCodes.WATCH_GETTER]: 'watcher getter',
+ [BaseWatchErrorCodes.WATCH_CALLBACK]: 'watcher callback',
+ [BaseWatchErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function',
[VaporErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
[VaporErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
[VaporErrorCodes.VNODE_HOOK]: 'vnode hook',
@@ -65,8 +71,6 @@ export const ErrorTypeStrings: Record<
'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/core',
}
-export type ErrorTypes = VaporLifecycleHooks | VaporErrorCodes
-
export function callWithErrorHandling(
fn: Function,
instance: ComponentInternalInstance | null,
diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts
index d34c9f483..c740912ef 100644
--- a/packages/runtime-vapor/src/index.ts
+++ b/packages/runtime-vapor/src/index.ts
@@ -29,6 +29,7 @@ export {
// effect
stop,
ReactiveEffect,
+ onEffectCleanup,
// effect scope
effectScope,
EffectScope,
@@ -39,6 +40,7 @@ export { withModifiers, withKeys } from '@vue/runtime-dom'
export * from './on'
export * from './render'
+export * from './renderWatch'
export * from './template'
export * from './scheduler'
export * from './apiWatch'
diff --git a/packages/runtime-vapor/src/renderWatch.ts b/packages/runtime-vapor/src/renderWatch.ts
new file mode 100644
index 000000000..fd9385fc5
--- /dev/null
+++ b/packages/runtime-vapor/src/renderWatch.ts
@@ -0,0 +1,55 @@
+import {
+ type BaseWatchErrorCodes,
+ type BaseWatchOptions,
+ baseWatch,
+ getCurrentScope,
+} from '@vue/reactivity'
+import { NOOP, remove } from '@vue/shared'
+import { currentInstance } from './component'
+import { createVaporRenderingScheduler } from './scheduler'
+import { handleError as handleErrorWithInstance } from './errorHandling'
+import { warn } from './warning'
+
+type WatchStopHandle = () => void
+
+export function renderEffect(effect: () => void): WatchStopHandle {
+ return doWatch(effect)
+}
+
+export function renderWatch(
+ source: any,
+ cb: (value: any, oldValue: any) => void,
+): WatchStopHandle {
+ return doWatch(source as any, cb)
+}
+
+function doWatch(source: any, cb?: any): WatchStopHandle {
+ const extendOptions: BaseWatchOptions = {}
+
+ if (__DEV__) extendOptions.onWarn = warn
+
+ // TODO: Life Cycle Hooks
+
+ // TODO: SSR
+ // if (__SSR__) {}
+
+ const instance =
+ getCurrentScope() === currentInstance?.scope ? currentInstance : null
+
+ extendOptions.onError = (err: unknown, type: BaseWatchErrorCodes) =>
+ handleErrorWithInstance(err, instance, type)
+ extendOptions.scheduler = createVaporRenderingScheduler(instance)
+
+ let effect = baseWatch(source, cb, extendOptions)
+
+ const unwatch = !effect
+ ? NOOP
+ : () => {
+ effect!.stop()
+ if (instance && instance.scope) {
+ remove(instance.scope.effects!, effect)
+ }
+ }
+
+ return unwatch
+}
diff --git a/packages/runtime-vapor/src/scheduler.ts b/packages/runtime-vapor/src/scheduler.ts
index de511787e..2be470254 100644
--- a/packages/runtime-vapor/src/scheduler.ts
+++ b/packages/runtime-vapor/src/scheduler.ts
@@ -1,6 +1,5 @@
-import type { ReactiveEffect } from '@vue/reactivity'
+import type { Scheduler } from '@vue/reactivity'
import type { ComponentInternalInstance } from './component'
-import { getIsRendering } from '.'
export interface SchedulerJob extends Function {
id?: number
@@ -38,13 +37,6 @@ export type QueueEffect = (
suspense: ComponentInternalInstance | null,
) => void
-export type Scheduler = (context: {
- effect: ReactiveEffect
- job: SchedulerJob
- instance: ComponentInternalInstance | null
- isInit: boolean
-}) => void
-
let isFlushing = false
let isFlushPending = false
@@ -205,64 +197,46 @@ const comparator = (a: SchedulerJob, b: SchedulerJob): number => {
return diff
}
-export function getVaporSchedulerByFlushMode(
- flush?: 'pre' | 'post' | 'sync',
-): Scheduler {
- if (flush === 'post') {
- return vaporPostScheduler
- }
- if (flush === 'sync') {
- return vaporSyncScheduler
- }
- if (getIsRendering()) {
- return vaporRenderingScheduler
- }
- // default: 'pre'
- return vaporPreScheduler
-}
+export type SchedulerFactory = (
+ instance: ComponentInternalInstance | null,
+) => Scheduler
-export const vaporSyncScheduler: Scheduler = ({ isInit, effect, job }) => {
- if (isInit) {
- effect.run()
- } else {
- job()
+export const createVaporSyncScheduler: SchedulerFactory =
+ (instance) => (job, effect, isInit) => {
+ if (isInit) {
+ effect.run()
+ } else {
+ job()
+ }
}
-}
-export const vaporPreScheduler: Scheduler = ({
- isInit,
- effect,
- instance,
- job,
-}) => {
- if (isInit) {
- effect.run()
- } else {
- job.pre = true
- if (instance) job.id = instance.uid
- queueJob(job)
+export const createVaporPreScheduler: SchedulerFactory =
+ (instance) => (job, effect, isInit) => {
+ if (isInit) {
+ effect.run()
+ } else {
+ job.pre = true
+ if (instance) job.id = instance.uid
+ queueJob(job)
+ }
}
-}
-export const vaporRenderingScheduler: Scheduler = ({
- isInit,
- effect,
- instance,
- job,
-}) => {
- if (isInit) {
- effect.run()
- } else {
- job.pre = false
- if (instance) job.id = instance.uid
- queueJob(job)
+export const createVaporRenderingScheduler: SchedulerFactory =
+ (instance) => (job, effect, isInit) => {
+ if (isInit) {
+ effect.run()
+ } else {
+ job.pre = false
+ if (instance) job.id = instance.uid
+ queueJob(job)
+ }
}
-}
-export const vaporPostScheduler: Scheduler = ({ isInit, effect, job }) => {
- if (isInit) {
- queuePostRenderEffect(effect.run.bind(effect))
- } else {
- queuePostRenderEffect(job)
+export const createVaporPostScheduler: SchedulerFactory =
+ (instance) => (job, effect, isInit) => {
+ if (isInit) {
+ queuePostRenderEffect(effect.run.bind(effect))
+ } else {
+ queuePostRenderEffect(job)
+ }
}
-}