From 85c5de7e9eb399849918f9cfc2182d76fc74ae27 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Tue, 2 Jan 2024 20:30:57 +0800 Subject: [PATCH 01/11] refactor: use baseWatch to drive watchAPI --- .../runtime-vapor/__tests__/apiWatch.spec.ts | 14 +- packages/runtime-vapor/src/apiWatch.ts | 337 ++++-------------- packages/runtime-vapor/src/errorHandling.ts | 22 +- packages/runtime-vapor/src/index.ts | 1 + packages/runtime-vapor/src/scheduler.ts | 112 +++--- 5 files changed, 133 insertions(+), 353 deletions(-) diff --git a/packages/runtime-vapor/__tests__/apiWatch.spec.ts b/packages/runtime-vapor/__tests__/apiWatch.spec.ts index 741a816b1..9e8ccc3e6 100644 --- a/packages/runtime-vapor/__tests__/apiWatch.spec.ts +++ b/packages/runtime-vapor/__tests__/apiWatch.spec.ts @@ -1,14 +1,16 @@ -import { EffectScope, type Ref, ref } from '@vue/reactivity' +import type { Ref } from '@vue/reactivity' +import { defineComponent } from 'vue' import { + EffectScope, + nextTick, onEffectCleanup, + ref, + render, + template, 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' +} from '../src/index' let host: HTMLElement diff --git a/packages/runtime-vapor/src/apiWatch.ts b/packages/runtime-vapor/src/apiWatch.ts index ca03b9f87..a81cee6d3 100644 --- a/packages/runtime-vapor/src/apiWatch.ts +++ b/packages/runtime-vapor/src/apiWatch.ts @@ -1,40 +1,22 @@ import { + type BaseWatchErrorCodes, + type BaseWatchOptions, type ComputedRef, type DebuggerOptions, - type EffectScheduler, - ReactiveEffect, - ReactiveFlags, + type ReactiveEffect, 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, + useVaporPostScheduler, + useVaporPreScheduler, + useVaporSyncScheduler, } 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 +58,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 +70,6 @@ export function watchPostEffect( return doWatch( effect, null, - vaporPostScheduler, __DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' }, ) } @@ -101,16 +81,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 +116,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 +130,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,48 +139,28 @@ 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 getSchedulerByFlushMode( + flush: WatchOptionsBase['flush'], +): SchedulerJob { + if (flush === 'post') { + return useVaporPostScheduler } -} - -export interface doWatchOptions extends DebuggerOptions { - immediate?: Immediate - deep?: boolean - once?: boolean + if (flush === 'sync') { + return useVaporSyncScheduler + } + // default: 'pre' + return useVaporPreScheduler } 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 if (__DEV__ && !cb) { if (immediate !== undefined) { warn( @@ -226,214 +182,45 @@ 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 effect = new ReactiveEffect(getter, NOOP, effectScheduler) + const instance = + getCurrentScope() === currentInstance?.scope ? currentInstance : null - const cleanup = (effect.onStop = () => { - const cleanups = cleanupMap.get(effect) - if (cleanups) { - cleanups.forEach((cleanup) => cleanup()) - cleanupMap.delete(effect) - } - }) + extendOptions.onError = (err: unknown, type: BaseWatchErrorCodes) => + handleErrorWithInstance(err, instance, type) - const unwatch = () => { - effect.stop() - if (instance && instance.scope) { - remove(instance.scope.effects!, effect) - } - } + const scheduler = getSchedulerByFlushMode(flush)({ instance }) + extendOptions.scheduler = scheduler - if (__DEV__) { - effect.onTrack = onTrack - effect.onTrigger = onTrigger - } + let effect = baseWatch(source, cb, extend({}, options, extendOptions)) - // initial run - if (cb) { - if (immediate) { - job() - } else { - oldValue = effect.run() - } - } else { - scheduler({ - effect, - job, - instance: instance, - isInit: true, - }) - } + const unwatch = !effect + ? NOOP + : () => { + effect!.stop() + if (instance && instance.scope) { + remove(instance.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..d68667d40 100644 --- a/packages/runtime-vapor/src/errorHandling.ts +++ b/packages/runtime-vapor/src/errorHandling.ts @@ -7,15 +7,13 @@ 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, COMPONENT_EVENT_HANDLER, VNODE_HOOK, @@ -28,10 +26,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 +48,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 +65,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..60d440c1a 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, diff --git a/packages/runtime-vapor/src/scheduler.ts b/packages/runtime-vapor/src/scheduler.ts index de511787e..73c99a2ff 100644 --- a/packages/runtime-vapor/src/scheduler.ts +++ b/packages/runtime-vapor/src/scheduler.ts @@ -38,13 +38,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 +198,63 @@ 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 Scheduler = (options: { + instance: ComponentInternalInstance | null +}) => (context: { + effect: ReactiveEffect + job: SchedulerJob + isInit: boolean +}) => void -export const vaporSyncScheduler: Scheduler = ({ isInit, effect, job }) => { - if (isInit) { - effect.run() - } else { - job() +export const useVaporSyncScheduler: Scheduler = + ({ instance }) => + ({ isInit, effect, job }) => { + if (instance && instance.isUnmounted) { + return + } + 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 useVaporPreScheduler: Scheduler = + ({ instance }) => + ({ isInit, effect, job }) => { + if (instance && instance.isUnmounted) { + return + } + 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 useVaporRenderingScheduler: Scheduler = + ({ instance }) => + ({ isInit, effect, job }) => { + if (instance && instance.isUnmounted) { + return + } + 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 useVaporPostScheduler: Scheduler = + ({ instance }) => + ({ isInit, effect, job }) => { + if (isInit) { + queuePostRenderEffect(effect.run.bind(effect)) + } else { + queuePostRenderEffect(job) + } } -} From 3f8b7271c07d47fe914bab70599819819d9fee3e Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Tue, 2 Jan 2024 22:12:11 +0800 Subject: [PATCH 02/11] feat: basic implementation of renderWatch and effectId --- packages/runtime-vapor/src/apiWatch.ts | 1 - packages/runtime-vapor/src/renderWatch.ts | 91 +++++++++++++++++++++++ packages/runtime-vapor/src/scheduler.ts | 16 +++- 3 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 packages/runtime-vapor/src/renderWatch.ts diff --git a/packages/runtime-vapor/src/apiWatch.ts b/packages/runtime-vapor/src/apiWatch.ts index a81cee6d3..85a46aa6b 100644 --- a/packages/runtime-vapor/src/apiWatch.ts +++ b/packages/runtime-vapor/src/apiWatch.ts @@ -3,7 +3,6 @@ import { type BaseWatchOptions, type ComputedRef, type DebuggerOptions, - type ReactiveEffect, type Ref, baseWatch, getCurrentScope, diff --git a/packages/runtime-vapor/src/renderWatch.ts b/packages/runtime-vapor/src/renderWatch.ts new file mode 100644 index 000000000..b2b1f8f08 --- /dev/null +++ b/packages/runtime-vapor/src/renderWatch.ts @@ -0,0 +1,91 @@ +import { + type BaseWatchErrorCodes, + type BaseWatchOptions, + type ComputedRef, + type DebuggerOptions, + type Ref, + baseWatch, + getCurrentScope, +} from '@vue/reactivity' +import { EMPTY_OBJ, NOOP, extend, remove } from '@vue/shared' +import { currentInstance } from './component' +import { useVaporRenderingScheduler } from './scheduler' +import { handleError as handleErrorWithInstance } from './errorHandling' +import { warn } from './warning' + +type WatchSource = Ref | ComputedRef | (() => T) + +type WatchCallback = ( + value: V, + oldValue: OV, + onCleanup: OnCleanup, +) => any + +type OnCleanup = (cleanupFn: () => void) => void + +export interface renderWatchOptionsBase extends DebuggerOptions {} + +export interface WatchOptions + extends renderWatchOptionsBase { + immediate?: Immediate + deep?: boolean + once?: boolean +} + +export type WatchStopHandle = () => void + +export function renderEffect( + effect: () => void, + options?: renderWatchOptionsBase, +): WatchStopHandle { + return doWatch(effect, null, options) +} + +// implementation +export function renderWatch< + T = any, + Immediate extends Readonly = false, +>( + source: T | WatchSource, + cb: any, + options?: WatchOptions, +): WatchStopHandle { + return doWatch(source as any, cb, options) +} + +function doWatch( + source: WatchSource | WatchSource[] | (() => void) | object, + cb: WatchCallback | null, + options: WatchOptions = EMPTY_OBJ, +): WatchStopHandle { + const extendOptions: BaseWatchOptions = {} + + if (__DEV__) extendOptions.onWarn = warn + + // TODO: SSR + // if (__SSR__) {} + + const instance = + getCurrentScope() === currentInstance?.scope ? currentInstance : null + + extendOptions.onError = (err: unknown, type: BaseWatchErrorCodes) => + handleErrorWithInstance(err, instance, type) + + const effectId = instance?.scope.effects.length + + const scheduler = useVaporRenderingScheduler({ instance, effectId }) + extendOptions.scheduler = scheduler + + let effect = baseWatch(source, cb, extend({}, options, 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 73c99a2ff..ccc5ebca1 100644 --- a/packages/runtime-vapor/src/scheduler.ts +++ b/packages/runtime-vapor/src/scheduler.ts @@ -1,9 +1,9 @@ import type { ReactiveEffect } from '@vue/reactivity' import type { ComponentInternalInstance } from './component' -import { getIsRendering } from '.' export interface SchedulerJob extends Function { id?: number + effectId?: number pre?: boolean active?: boolean computed?: boolean @@ -189,17 +189,22 @@ function findInsertionIndex(id: number) { const getId = (job: SchedulerJob): number => job.id == null ? Infinity : job.id +const getEffectId = (job: SchedulerJob): number => + job.effectId == null ? Infinity : job.effectId + const comparator = (a: SchedulerJob, b: SchedulerJob): number => { const diff = getId(a) - getId(b) if (diff === 0) { if (a.pre && !b.pre) return -1 if (b.pre && !a.pre) return 1 + return getEffectId(a) - getEffectId(b) } return diff } export type Scheduler = (options: { instance: ComponentInternalInstance | null + effectId?: number }) => (context: { effect: ReactiveEffect job: SchedulerJob @@ -235,7 +240,7 @@ export const useVaporPreScheduler: Scheduler = } export const useVaporRenderingScheduler: Scheduler = - ({ instance }) => + ({ instance, effectId }) => ({ isInit, effect, job }) => { if (instance && instance.isUnmounted) { return @@ -244,13 +249,16 @@ export const useVaporRenderingScheduler: Scheduler = effect.run() } else { job.pre = false - if (instance) job.id = instance.uid + if (instance) { + job.id = instance.uid + job.effectId = effectId + } queueJob(job) } } export const useVaporPostScheduler: Scheduler = - ({ instance }) => + () => ({ isInit, effect, job }) => { if (isInit) { queuePostRenderEffect(effect.run.bind(effect)) From 2eccfb9cc85f2507a00baadbad1ee0620e068857 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Wed, 3 Jan 2024 19:48:56 +0800 Subject: [PATCH 03/11] chore: remove effect id --- packages/runtime-vapor/src/renderWatch.ts | 4 +--- packages/runtime-vapor/src/scheduler.ts | 13 ++----------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/packages/runtime-vapor/src/renderWatch.ts b/packages/runtime-vapor/src/renderWatch.ts index b2b1f8f08..77684d7a1 100644 --- a/packages/runtime-vapor/src/renderWatch.ts +++ b/packages/runtime-vapor/src/renderWatch.ts @@ -71,9 +71,7 @@ function doWatch( extendOptions.onError = (err: unknown, type: BaseWatchErrorCodes) => handleErrorWithInstance(err, instance, type) - const effectId = instance?.scope.effects.length - - const scheduler = useVaporRenderingScheduler({ instance, effectId }) + const scheduler = useVaporRenderingScheduler({ instance }) extendOptions.scheduler = scheduler let effect = baseWatch(source, cb, extend({}, options, extendOptions)) diff --git a/packages/runtime-vapor/src/scheduler.ts b/packages/runtime-vapor/src/scheduler.ts index ccc5ebca1..ff15c541e 100644 --- a/packages/runtime-vapor/src/scheduler.ts +++ b/packages/runtime-vapor/src/scheduler.ts @@ -3,7 +3,6 @@ import type { ComponentInternalInstance } from './component' export interface SchedulerJob extends Function { id?: number - effectId?: number pre?: boolean active?: boolean computed?: boolean @@ -189,22 +188,17 @@ function findInsertionIndex(id: number) { const getId = (job: SchedulerJob): number => job.id == null ? Infinity : job.id -const getEffectId = (job: SchedulerJob): number => - job.effectId == null ? Infinity : job.effectId - const comparator = (a: SchedulerJob, b: SchedulerJob): number => { const diff = getId(a) - getId(b) if (diff === 0) { if (a.pre && !b.pre) return -1 if (b.pre && !a.pre) return 1 - return getEffectId(a) - getEffectId(b) } return diff } export type Scheduler = (options: { instance: ComponentInternalInstance | null - effectId?: number }) => (context: { effect: ReactiveEffect job: SchedulerJob @@ -240,7 +234,7 @@ export const useVaporPreScheduler: Scheduler = } export const useVaporRenderingScheduler: Scheduler = - ({ instance, effectId }) => + ({ instance }) => ({ isInit, effect, job }) => { if (instance && instance.isUnmounted) { return @@ -249,10 +243,7 @@ export const useVaporRenderingScheduler: Scheduler = effect.run() } else { job.pre = false - if (instance) { - job.id = instance.uid - job.effectId = effectId - } + if (instance) job.id = instance.uid queueJob(job) } } From 227f8e06dd9f2edbf3ba0cf66b27b2a144adcedc Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Wed, 3 Jan 2024 21:21:41 +0800 Subject: [PATCH 04/11] chore: export and simplify types --- packages/runtime-vapor/src/index.ts | 1 + packages/runtime-vapor/src/renderWatch.ts | 53 +++++------------------ 2 files changed, 11 insertions(+), 43 deletions(-) diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index 60d440c1a..c740912ef 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -40,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 index 77684d7a1..4b1c62ad0 100644 --- a/packages/runtime-vapor/src/renderWatch.ts +++ b/packages/runtime-vapor/src/renderWatch.ts @@ -1,63 +1,30 @@ import { type BaseWatchErrorCodes, type BaseWatchOptions, - type ComputedRef, - type DebuggerOptions, - type Ref, baseWatch, getCurrentScope, } from '@vue/reactivity' -import { EMPTY_OBJ, NOOP, extend, remove } from '@vue/shared' +import { NOOP, remove } from '@vue/shared' import { currentInstance } from './component' import { useVaporRenderingScheduler } from './scheduler' import { handleError as handleErrorWithInstance } from './errorHandling' import { warn } from './warning' -type WatchSource = Ref | ComputedRef | (() => T) +type WatchStopHandle = () => void -type WatchCallback = ( - value: V, - oldValue: OV, - onCleanup: OnCleanup, -) => any - -type OnCleanup = (cleanupFn: () => void) => void - -export interface renderWatchOptionsBase extends DebuggerOptions {} - -export interface WatchOptions - extends renderWatchOptionsBase { - immediate?: Immediate - deep?: boolean - once?: boolean -} - -export type WatchStopHandle = () => void - -export function renderEffect( - effect: () => void, - options?: renderWatchOptionsBase, -): WatchStopHandle { - return doWatch(effect, null, options) +export function renderEffect(effect: () => void): WatchStopHandle { + return doWatch(effect) } // implementation -export function renderWatch< - T = any, - Immediate extends Readonly = false, ->( - source: T | WatchSource, - cb: any, - options?: WatchOptions, +export function renderWatch( + source: any, + cb: (value: any, oldValue: any) => void, ): WatchStopHandle { - return doWatch(source as any, cb, options) + return doWatch(source as any, cb) } -function doWatch( - source: WatchSource | WatchSource[] | (() => void) | object, - cb: WatchCallback | null, - options: WatchOptions = EMPTY_OBJ, -): WatchStopHandle { +function doWatch(source: any, cb?: any): WatchStopHandle { const extendOptions: BaseWatchOptions = {} if (__DEV__) extendOptions.onWarn = warn @@ -74,7 +41,7 @@ function doWatch( const scheduler = useVaporRenderingScheduler({ instance }) extendOptions.scheduler = scheduler - let effect = baseWatch(source, cb, extend({}, options, extendOptions)) + let effect = baseWatch(source, cb, extendOptions) const unwatch = !effect ? NOOP From 326b0e63ea0eb0812ef045b72e389724e679ef69 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Wed, 3 Jan 2024 21:22:20 +0800 Subject: [PATCH 05/11] test: render watch --- .../runtime-vapor/__tests__/apiWatch.spec.ts | 85 ----------- .../__tests__/renderWatch.spec.ts | 133 ++++++++++++++++++ 2 files changed, 133 insertions(+), 85 deletions(-) create mode 100644 packages/runtime-vapor/__tests__/renderWatch.spec.ts diff --git a/packages/runtime-vapor/__tests__/apiWatch.spec.ts b/packages/runtime-vapor/__tests__/apiWatch.spec.ts index 9e8ccc3e6..3973bdbb4 100644 --- a/packages/runtime-vapor/__tests__/apiWatch.spec.ts +++ b/packages/runtime-vapor/__tests__/apiWatch.spec.ts @@ -1,31 +1,13 @@ import type { Ref } from '@vue/reactivity' -import { defineComponent } from 'vue' import { EffectScope, nextTick, onEffectCleanup, ref, - render, - template, watchEffect, - watchPostEffect, watchSyncEffect, } from '../src/index' -let host: HTMLElement - -const initHost = () => { - host = document.createElement('div') - host.setAttribute('id', 'host') - document.body.appendChild(host) -} -beforeEach(() => { - initHost() -}) -afterEach(() => { - host.remove() -}) - describe('watchEffect and onEffectCleanup', () => { test('basic', async () => { let dummy = 0 @@ -95,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..3fb57d56d --- /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/index' + +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', + ]) + }) +}) From c4db74c84eaeb5327089ec973d7560fb1ea4ada6 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Wed, 3 Jan 2024 21:24:27 +0800 Subject: [PATCH 06/11] chore: add todo comment --- packages/runtime-vapor/src/renderWatch.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/runtime-vapor/src/renderWatch.ts b/packages/runtime-vapor/src/renderWatch.ts index 4b1c62ad0..c349c681a 100644 --- a/packages/runtime-vapor/src/renderWatch.ts +++ b/packages/runtime-vapor/src/renderWatch.ts @@ -29,6 +29,8 @@ function doWatch(source: any, cb?: any): WatchStopHandle { if (__DEV__) extendOptions.onWarn = warn + // TODO: Life Cycle Hooks + // TODO: SSR // if (__SSR__) {} From 28d0549883297f79b6a635dbdcb18c77f5127411 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Thu, 4 Jan 2024 23:09:57 +0800 Subject: [PATCH 07/11] fix: sync code changes according to the review in PR #82 --- packages/runtime-vapor/src/apiWatch.ts | 35 +++++++++++------- packages/runtime-vapor/src/renderWatch.ts | 4 +-- packages/runtime-vapor/src/scheduler.ts | 43 +++++++---------------- 3 files changed, 37 insertions(+), 45 deletions(-) diff --git a/packages/runtime-vapor/src/apiWatch.ts b/packages/runtime-vapor/src/apiWatch.ts index 85a46aa6b..4d1163fa3 100644 --- a/packages/runtime-vapor/src/apiWatch.ts +++ b/packages/runtime-vapor/src/apiWatch.ts @@ -10,10 +10,10 @@ import { import { EMPTY_OBJ, NOOP, extend, isFunction, remove } from '@vue/shared' import { currentInstance } from './component' import { - type SchedulerJob, - useVaporPostScheduler, - useVaporPreScheduler, - useVaporSyncScheduler, + type CreateScheduler, + createVaporPostScheduler, + createVaporPreScheduler, + createVaporSyncScheduler, } from './scheduler' import { handleError as handleErrorWithInstance } from './errorHandling' import { warn } from './warning' @@ -143,15 +143,15 @@ export function watch = false>( function getSchedulerByFlushMode( flush: WatchOptionsBase['flush'], -): SchedulerJob { +): CreateScheduler { if (flush === 'post') { - return useVaporPostScheduler + return createVaporPostScheduler } if (flush === 'sync') { - return useVaporSyncScheduler + return createVaporSyncScheduler } // default: 'pre' - return useVaporPreScheduler + return createVaporPreScheduler } function doWatch( @@ -160,6 +160,15 @@ function doWatch( options: WatchOptions = EMPTY_OBJ, ): WatchStopHandle { 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) { if (immediate !== undefined) { warn( @@ -200,23 +209,23 @@ function doWatch( // } // } - const instance = - getCurrentScope() === currentInstance?.scope ? currentInstance : null + const instance = currentInstance extendOptions.onError = (err: unknown, type: BaseWatchErrorCodes) => handleErrorWithInstance(err, instance, type) - const scheduler = getSchedulerByFlushMode(flush)({ instance }) + const scheduler = getSchedulerByFlushMode(flush)(instance) extendOptions.scheduler = scheduler let effect = baseWatch(source, cb, extend({}, options, extendOptions)) + const scope = getCurrentScope() const unwatch = !effect ? NOOP : () => { effect!.stop() - if (instance && instance.scope) { - remove(instance.scope.effects!, effect) + if (scope) { + remove(scope.effects, effect) } } diff --git a/packages/runtime-vapor/src/renderWatch.ts b/packages/runtime-vapor/src/renderWatch.ts index c349c681a..027ee0440 100644 --- a/packages/runtime-vapor/src/renderWatch.ts +++ b/packages/runtime-vapor/src/renderWatch.ts @@ -6,7 +6,7 @@ import { } from '@vue/reactivity' import { NOOP, remove } from '@vue/shared' import { currentInstance } from './component' -import { useVaporRenderingScheduler } from './scheduler' +import { createVaporRenderingScheduler } from './scheduler' import { handleError as handleErrorWithInstance } from './errorHandling' import { warn } from './warning' @@ -40,7 +40,7 @@ function doWatch(source: any, cb?: any): WatchStopHandle { extendOptions.onError = (err: unknown, type: BaseWatchErrorCodes) => handleErrorWithInstance(err, instance, type) - const scheduler = useVaporRenderingScheduler({ instance }) + const scheduler = createVaporRenderingScheduler(instance) extendOptions.scheduler = scheduler let effect = baseWatch(source, cb, extendOptions) diff --git a/packages/runtime-vapor/src/scheduler.ts b/packages/runtime-vapor/src/scheduler.ts index ff15c541e..2d3e58c42 100644 --- a/packages/runtime-vapor/src/scheduler.ts +++ b/packages/runtime-vapor/src/scheduler.ts @@ -1,4 +1,4 @@ -import type { ReactiveEffect } from '@vue/reactivity' +import type { Scheduler } from '@vue/reactivity' import type { ComponentInternalInstance } from './component' export interface SchedulerJob extends Function { @@ -197,20 +197,12 @@ const comparator = (a: SchedulerJob, b: SchedulerJob): number => { return diff } -export type Scheduler = (options: { - instance: ComponentInternalInstance | null -}) => (context: { - effect: ReactiveEffect - job: SchedulerJob - isInit: boolean -}) => void - -export const useVaporSyncScheduler: Scheduler = - ({ instance }) => - ({ isInit, effect, job }) => { - if (instance && instance.isUnmounted) { - return - } +export type CreateScheduler = ( + instance: ComponentInternalInstance | null, +) => Scheduler + +export const createVaporSyncScheduler: CreateScheduler = + (instance) => (job, effect, isInit) => { if (isInit) { effect.run() } else { @@ -218,12 +210,8 @@ export const useVaporSyncScheduler: Scheduler = } } -export const useVaporPreScheduler: Scheduler = - ({ instance }) => - ({ isInit, effect, job }) => { - if (instance && instance.isUnmounted) { - return - } +export const createVaporPreScheduler: CreateScheduler = + (instance) => (job, effect, isInit) => { if (isInit) { effect.run() } else { @@ -233,12 +221,8 @@ export const useVaporPreScheduler: Scheduler = } } -export const useVaporRenderingScheduler: Scheduler = - ({ instance }) => - ({ isInit, effect, job }) => { - if (instance && instance.isUnmounted) { - return - } +export const createVaporRenderingScheduler: CreateScheduler = + (instance) => (job, effect, isInit) => { if (isInit) { effect.run() } else { @@ -248,9 +232,8 @@ export const useVaporRenderingScheduler: Scheduler = } } -export const useVaporPostScheduler: Scheduler = - () => - ({ isInit, effect, job }) => { +export const createVaporPostScheduler: CreateScheduler = + (instance) => (job, effect, isInit) => { if (isInit) { queuePostRenderEffect(effect.run.bind(effect)) } else { From 0617e9c820010cced7807149da720b83489a27fa Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Thu, 4 Jan 2024 23:14:10 +0800 Subject: [PATCH 08/11] fix: enum values conflict --- packages/runtime-vapor/src/errorHandling.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/runtime-vapor/src/errorHandling.ts b/packages/runtime-vapor/src/errorHandling.ts index d68667d40..fa3954280 100644 --- a/packages/runtime-vapor/src/errorHandling.ts +++ b/packages/runtime-vapor/src/errorHandling.ts @@ -14,7 +14,13 @@ import { BaseWatchErrorCodes } from '@vue/reactivity' export enum VaporErrorCodes { SETUP_FUNCTION, RENDER_FUNCTION, - 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, From 24cc80374b872d7e6ae9f0d9fb3bd6b8803e807f Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Thu, 4 Jan 2024 23:40:26 +0800 Subject: [PATCH 09/11] chore: rename --- packages/runtime-vapor/__tests__/apiWatch.spec.ts | 2 +- packages/runtime-vapor/__tests__/renderWatch.spec.ts | 2 +- packages/runtime-vapor/src/apiWatch.ts | 8 +++----- packages/runtime-vapor/src/scheduler.ts | 10 +++++----- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/runtime-vapor/__tests__/apiWatch.spec.ts b/packages/runtime-vapor/__tests__/apiWatch.spec.ts index 3973bdbb4..228c59cb2 100644 --- a/packages/runtime-vapor/__tests__/apiWatch.spec.ts +++ b/packages/runtime-vapor/__tests__/apiWatch.spec.ts @@ -6,7 +6,7 @@ import { ref, watchEffect, watchSyncEffect, -} from '../src/index' +} from '../src' describe('watchEffect and onEffectCleanup', () => { test('basic', async () => { diff --git a/packages/runtime-vapor/__tests__/renderWatch.spec.ts b/packages/runtime-vapor/__tests__/renderWatch.spec.ts index 3fb57d56d..0d43ad90f 100644 --- a/packages/runtime-vapor/__tests__/renderWatch.spec.ts +++ b/packages/runtime-vapor/__tests__/renderWatch.spec.ts @@ -10,7 +10,7 @@ import { watchEffect, watchPostEffect, watchSyncEffect, -} from '../src/index' +} from '../src' let host: HTMLElement diff --git a/packages/runtime-vapor/src/apiWatch.ts b/packages/runtime-vapor/src/apiWatch.ts index 4d1163fa3..c694f71ba 100644 --- a/packages/runtime-vapor/src/apiWatch.ts +++ b/packages/runtime-vapor/src/apiWatch.ts @@ -10,7 +10,7 @@ import { import { EMPTY_OBJ, NOOP, extend, isFunction, remove } from '@vue/shared' import { currentInstance } from './component' import { - type CreateScheduler, + type SchedulerFactory, createVaporPostScheduler, createVaporPreScheduler, createVaporSyncScheduler, @@ -141,9 +141,7 @@ export function watch = false>( return doWatch(source as any, cb, options) } -function getSchedulerByFlushMode( - flush: WatchOptionsBase['flush'], -): CreateScheduler { +function getScheduler(flush: WatchOptionsBase['flush']): SchedulerFactory { if (flush === 'post') { return createVaporPostScheduler } @@ -214,7 +212,7 @@ function doWatch( extendOptions.onError = (err: unknown, type: BaseWatchErrorCodes) => handleErrorWithInstance(err, instance, type) - const scheduler = getSchedulerByFlushMode(flush)(instance) + const scheduler = getScheduler(flush)(instance) extendOptions.scheduler = scheduler let effect = baseWatch(source, cb, extend({}, options, extendOptions)) diff --git a/packages/runtime-vapor/src/scheduler.ts b/packages/runtime-vapor/src/scheduler.ts index 2d3e58c42..2be470254 100644 --- a/packages/runtime-vapor/src/scheduler.ts +++ b/packages/runtime-vapor/src/scheduler.ts @@ -197,11 +197,11 @@ const comparator = (a: SchedulerJob, b: SchedulerJob): number => { return diff } -export type CreateScheduler = ( +export type SchedulerFactory = ( instance: ComponentInternalInstance | null, ) => Scheduler -export const createVaporSyncScheduler: CreateScheduler = +export const createVaporSyncScheduler: SchedulerFactory = (instance) => (job, effect, isInit) => { if (isInit) { effect.run() @@ -210,7 +210,7 @@ export const createVaporSyncScheduler: CreateScheduler = } } -export const createVaporPreScheduler: CreateScheduler = +export const createVaporPreScheduler: SchedulerFactory = (instance) => (job, effect, isInit) => { if (isInit) { effect.run() @@ -221,7 +221,7 @@ export const createVaporPreScheduler: CreateScheduler = } } -export const createVaporRenderingScheduler: CreateScheduler = +export const createVaporRenderingScheduler: SchedulerFactory = (instance) => (job, effect, isInit) => { if (isInit) { effect.run() @@ -232,7 +232,7 @@ export const createVaporRenderingScheduler: CreateScheduler = } } -export const createVaporPostScheduler: CreateScheduler = +export const createVaporPostScheduler: SchedulerFactory = (instance) => (job, effect, isInit) => { if (isInit) { queuePostRenderEffect(effect.run.bind(effect)) From 41ddba492cb9304785b2aeda5f426a03ba133460 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Fri, 5 Jan 2024 18:38:36 +0800 Subject: [PATCH 10/11] feat: change watchEffect to renderEffect in compiler-vapor --- .../__snapshots__/compile.spec.ts.snap | 48 +++++++++---------- .../__snapshots__/vBind.spec.ts.snap | 26 +++++----- .../__snapshots__/vHtml.spec.ts.snap | 8 ++-- .../transforms/__snapshots__/vOn.spec.ts.snap | 24 +++------- .../__snapshots__/vText.spec.ts.snap | 8 ++-- .../__tests__/transforms/vBind.spec.ts | 4 +- .../__tests__/transforms/vOn.spec.ts | 2 +- packages/compiler-vapor/src/generate.ts | 2 +- 8 files changed, 55 insertions(+), 67 deletions(-) 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) From cca46633b1ee7da6390064063c00cf96397477d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Sun, 7 Jan 2024 01:29:28 +0800 Subject: [PATCH 11/11] chore: update --- packages/runtime-vapor/src/apiWatch.ts | 4 +--- packages/runtime-vapor/src/renderWatch.ts | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/runtime-vapor/src/apiWatch.ts b/packages/runtime-vapor/src/apiWatch.ts index c694f71ba..2c7cd8f63 100644 --- a/packages/runtime-vapor/src/apiWatch.ts +++ b/packages/runtime-vapor/src/apiWatch.ts @@ -211,9 +211,7 @@ function doWatch( extendOptions.onError = (err: unknown, type: BaseWatchErrorCodes) => handleErrorWithInstance(err, instance, type) - - const scheduler = getScheduler(flush)(instance) - extendOptions.scheduler = scheduler + extendOptions.scheduler = getScheduler(flush)(instance) let effect = baseWatch(source, cb, extend({}, options, extendOptions)) diff --git a/packages/runtime-vapor/src/renderWatch.ts b/packages/runtime-vapor/src/renderWatch.ts index 027ee0440..fd9385fc5 100644 --- a/packages/runtime-vapor/src/renderWatch.ts +++ b/packages/runtime-vapor/src/renderWatch.ts @@ -16,7 +16,6 @@ export function renderEffect(effect: () => void): WatchStopHandle { return doWatch(effect) } -// implementation export function renderWatch( source: any, cb: (value: any, oldValue: any) => void, @@ -39,9 +38,7 @@ function doWatch(source: any, cb?: any): WatchStopHandle { extendOptions.onError = (err: unknown, type: BaseWatchErrorCodes) => handleErrorWithInstance(err, instance, type) - - const scheduler = createVaporRenderingScheduler(instance) - extendOptions.scheduler = scheduler + extendOptions.scheduler = createVaporRenderingScheduler(instance) let effect = baseWatch(source, cb, extendOptions)