diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts index 7d1ddac89..6df35bcb0 100644 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@ -43,7 +43,7 @@ export interface BaseIRNode { type: IRNodeTypes } -export type VaporHelper = keyof typeof import('@vue/runtime-vapor') +export type VaporHelper = keyof typeof import('packages/runtime-vapor/src') export interface BlockIRNode extends BaseIRNode { type: IRNodeTypes.BLOCK diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json index 067810022..947d129a6 100644 --- a/packages/runtime-core/package.json +++ b/packages/runtime-core/package.json @@ -47,7 +47,6 @@ "homepage": "https://github.com/vuejs/vue-vapor/tree/main/packages/runtime-core#readme", "dependencies": { "@vue/shared": "workspace:*", - "@vue/reactivity": "workspace:*", - "@vue/runtime-shared": "workspace:*" + "@vue/reactivity": "workspace:*" } } diff --git a/packages/runtime-core/src/apiCreateApp.ts b/packages/runtime-core/src/apiCreateApp.ts index ada767b1a..511344ebe 100644 --- a/packages/runtime-core/src/apiCreateApp.ts +++ b/packages/runtime-core/src/apiCreateApp.ts @@ -1,6 +1,7 @@ import { type Component, type ConcreteComponent, + type Data, type GenericComponent, type GenericComponentInstance, getComponentPublicInstance, @@ -22,7 +23,6 @@ import { warn } from './warning' import type { VNode } from './vnode' import { devtoolsInitApp, devtoolsUnmountApp } from './devtools' import { NO, extend, isFunction, isObject } from '@vue/shared' -import type { Data } from '@vue/runtime-shared' import { version } from '.' import { installAppCompatProperties } from './compat/global' import type { NormalizedPropsOptions } from './componentProps' @@ -256,8 +256,8 @@ export function createAppContext(): AppContext { } } -export type CreateAppFunction = ( - rootComponent: GenericComponent, +export type CreateAppFunction = ( + rootComponent: Comp, rootProps?: Data | null, ) => App @@ -275,13 +275,13 @@ export type AppUnmountFn = (app: App) => void /** * @internal */ -export function createAppAPI( +export function createAppAPI( // render: RootRenderFunction, // hydrate?: RootHydrateFunction, mount: AppMountFn, unmount: AppUnmountFn, render?: RootRenderFunction, -): CreateAppFunction { +): CreateAppFunction { return function createApp(rootComponent, rootProps = null) { if (!isFunction(rootComponent)) { rootComponent = extend({}, rootComponent) diff --git a/packages/runtime-core/src/compat/global.ts b/packages/runtime-core/src/compat/global.ts index edc57436a..f998260c7 100644 --- a/packages/runtime-core/src/compat/global.ts +++ b/packages/runtime-core/src/compat/global.ts @@ -462,7 +462,7 @@ function installCompatMount( * function simulates that behavior. */ app._createRoot = options => { - const component = app._component + const component = app._component as Component const vnode = createVNode(component, options.propsData || null) vnode.appContext = context diff --git a/packages/runtime-core/src/compat/props.ts b/packages/runtime-core/src/compat/props.ts index 77accd74e..3d79be697 100644 --- a/packages/runtime-core/src/compat/props.ts +++ b/packages/runtime-core/src/compat/props.ts @@ -1,7 +1,6 @@ import { isArray } from '@vue/shared' -import type { Data } from '@vue/runtime-shared' import { inject } from '../apiInject' -import type { ComponentInternalInstance } from '../component' +import type { ComponentInternalInstance, Data } from '../component' import { type ComponentOptions, resolveMergedOptions, diff --git a/packages/runtime-core/src/compat/renderFn.ts b/packages/runtime-core/src/compat/renderFn.ts index 73f45513c..49646eda4 100644 --- a/packages/runtime-core/src/compat/renderFn.ts +++ b/packages/runtime-core/src/compat/renderFn.ts @@ -10,11 +10,11 @@ import { normalizeStyle, toHandlerKey, } from '@vue/shared' -import type { Data } from '@vue/runtime-shared' import type { Component, ComponentInternalInstance, ComponentOptions, + Data, InternalRenderFunction, } from '../component' import { currentRenderingInstance } from '../componentRenderContext' diff --git a/packages/runtime-core/src/compat/renderHelpers.ts b/packages/runtime-core/src/compat/renderHelpers.ts index ecbf85f8d..ac8100877 100644 --- a/packages/runtime-core/src/compat/renderHelpers.ts +++ b/packages/runtime-core/src/compat/renderHelpers.ts @@ -7,13 +7,12 @@ import { isReservedProp, normalizeClass, } from '@vue/shared' -import type { ComponentInternalInstance } from '../component' +import type { ComponentInternalInstance, Data } from '../component' import type { Slot } from '../componentSlots' import { createSlots } from '../helpers/createSlots' import { renderSlot } from '../helpers/renderSlot' import { toHandlers } from '../helpers/toHandlers' import { type VNode, mergeProps } from '../vnode' -import type { Data } from '@vue/runtime-shared' function toObject(arr: Array): Object { const res = {} diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 2a1598d80..84af95837 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -73,7 +73,6 @@ import { isObject, isPromise, } from '@vue/shared' -import type { Data } from '@vue/runtime-shared' import type { SuspenseBoundary } from './components/Suspense' import type { CompilerOptions } from '@vue/compiler-core' import { markAttrsAccessed } from './componentRenderUtils' @@ -98,6 +97,8 @@ import { markAsyncBoundary } from './helpers/useId' import { isAsyncWrapper } from './apiAsyncComponent' import type { RendererElement } from './renderer' +export type Data = Record + /** * Public utility type for extracting the instance type of a component. * Works with all valid component definition types. This is intended to replace @@ -509,7 +510,7 @@ export interface ComponentInternalInstance extends GenericComponentInstance { * setup related * @internal */ - setupState: Data | null + setupState: Data /** * devtools access to additional info * @internal diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index 6d99b2bcd..f864f39e4 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -3,6 +3,7 @@ import { type ComponentInternalInstance, type ComponentInternalOptions, type ConcreteComponent, + type Data, type InternalRenderFunction, type SetupContext, currentInstance, @@ -18,7 +19,6 @@ import { isPromise, isString, } from '@vue/shared' -import type { Data } from '@vue/runtime-shared' import { type Ref, getCurrentScope, isRef, traverse } from '@vue/reactivity' import { computed } from './apiComputed' import { diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index fde2a57c4..d4069d352 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -24,12 +24,12 @@ import { makeMap, toRawType, } from '@vue/shared' -import type { Data } from '@vue/runtime-shared' import { warn } from './warning' import { type ComponentInternalInstance, type ComponentOptions, type ConcreteComponent, + type Data, type GenericComponentInstance, setCurrentInstance, } from './component' diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts index e4a8061cf..e9e7770eb 100644 --- a/packages/runtime-core/src/componentPublicInstance.ts +++ b/packages/runtime-core/src/componentPublicInstance.ts @@ -1,6 +1,7 @@ import { type Component, type ComponentInternalInstance, + type Data, getComponentPublicInstance, isStatefulComponent, } from './component' @@ -23,7 +24,6 @@ import { isGloballyAllowed, isString, } from '@vue/shared' -import type { Data } from '@vue/runtime-shared' import { ReactiveFlags, type ShallowUnwrapRef, diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts index 0d3615841..a1afae620 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -1,5 +1,6 @@ import { type ComponentInternalInstance, + type Data, type FunctionalComponent, getComponentName, } from './component' @@ -15,7 +16,6 @@ import { } from './vnode' import { ErrorCodes, handleError } from './errorHandling' import { PatchFlags, ShapeFlags, isModelListener, isOn } from '@vue/shared' -import type { Data } from '@vue/runtime-shared' import { warn } from './warning' import { isHmrUpdating } from './hmr' import type { NormalizedProps } from './componentProps' diff --git a/packages/runtime-core/src/directives.ts b/packages/runtime-core/src/directives.ts index 11032561c..f6a33f5a2 100644 --- a/packages/runtime-core/src/directives.ts +++ b/packages/runtime-core/src/directives.ts @@ -13,10 +13,10 @@ return withDirectives(h(comp), [ import type { VNode } from './vnode' import { EMPTY_OBJ, isBuiltInDirective, isFunction } from '@vue/shared' -import type { Data } from '@vue/runtime-shared' import { warn } from './warning' import { type ComponentInternalInstance, + type Data, getComponentPublicInstance, } from './component' import { currentRenderingInstance } from './componentRenderContext' diff --git a/packages/runtime-core/src/helpers/renderSlot.ts b/packages/runtime-core/src/helpers/renderSlot.ts index a99b6c5ee..738c51aa6 100644 --- a/packages/runtime-core/src/helpers/renderSlot.ts +++ b/packages/runtime-core/src/helpers/renderSlot.ts @@ -14,9 +14,9 @@ import { openBlock, } from '../vnode' import { PatchFlags, SlotFlags, isSymbol } from '@vue/shared' -import type { Data } from '@vue/runtime-shared' import { warn } from '../warning' import { isAsyncWrapper } from '../apiAsyncComponent' +import type { Data } from '../component' /** * Compiler runtime helper for rendering `` diff --git a/packages/runtime-core/src/helpers/toHandlers.ts b/packages/runtime-core/src/helpers/toHandlers.ts index 5072af893..3e7dcb327 100644 --- a/packages/runtime-core/src/helpers/toHandlers.ts +++ b/packages/runtime-core/src/helpers/toHandlers.ts @@ -1,8 +1,25 @@ -import { toHandlers as _toHandlers } from '@vue/runtime-shared' +import { isObject, toHandlerKey } from '@vue/shared' import { warn } from '../warning' -import { NOOP } from '@vue/shared' -export const toHandlers: ( +/** + * For prefixing keys in v-on="obj" with "on" + * @private + */ +export function toHandlers( obj: Record, - preserveCaseIfNecessary?: boolean | undefined, -) => Record = _toHandlers.bind(undefined, __DEV__ ? warn : NOOP) + preserveCaseIfNecessary?: boolean, +): Record { + const ret: Record = {} + if (__DEV__ && !isObject(obj)) { + warn(`v-on with no argument expects an object value.`) + return ret + } + for (const key in obj) { + ret[ + preserveCaseIfNecessary && /[A-Z]/.test(key) + ? `on:${key}` + : toHandlerKey(key) + ] = obj[key] + } + return ret +} diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 3aa7bad22..e869a4586 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -17,6 +17,7 @@ import { import { type ComponentInternalInstance, type ComponentOptions, + type Data, type LifecycleHook, createComponentInstance, setupComponent, @@ -39,7 +40,6 @@ import { isArray, isReservedProp, } from '@vue/shared' -import type { Data } from '@vue/runtime-shared' import { type SchedulerJob, SchedulerJobFlags, diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index d63f960f1..a8c5340cd 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -12,12 +12,12 @@ import { normalizeClass, normalizeStyle, } from '@vue/shared' -import type { Data } from '@vue/runtime-shared' import { type ClassComponent, type Component, type ComponentInternalInstance, type ConcreteComponent, + type Data, isClassComponent, } from './component' import type { RawSlots } from './componentSlots' diff --git a/packages/runtime-core/src/warning.ts b/packages/runtime-core/src/warning.ts index cf6003e57..222a576c2 100644 --- a/packages/runtime-core/src/warning.ts +++ b/packages/runtime-core/src/warning.ts @@ -1,10 +1,10 @@ import { type ComponentInternalInstance, + type Data, type GenericComponentInstance, formatComponentName, } from './component' import { isFunction, isString } from '@vue/shared' -import type { Data } from '@vue/runtime-shared' import { isRef, pauseTracking, resetTracking, toRaw } from '@vue/reactivity' import { ErrorCodes, callWithErrorHandling } from './errorHandling' import { type VNode, isVNode } from './vnode' diff --git a/packages/runtime-dom/src/index.ts b/packages/runtime-dom/src/index.ts index 3b65be697..4246b3ed0 100644 --- a/packages/runtime-dom/src/index.ts +++ b/packages/runtime-dom/src/index.ts @@ -1,5 +1,6 @@ import { type App, + type Component, type ConcreteComponent, type CreateAppFunction, type DefineComponent, @@ -144,7 +145,7 @@ export const createApp = ((...args) => { } return app -}) as CreateAppFunction +}) as CreateAppFunction export const createSSRApp = ((...args) => { const app = ensureHydrationRenderer().createApp(...args) diff --git a/packages/runtime-shared/LICENSE b/packages/runtime-shared/LICENSE deleted file mode 100644 index 15f1f7e7a..000000000 --- a/packages/runtime-shared/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018-present, Yuxi (Evan) You - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/packages/runtime-shared/README.md b/packages/runtime-shared/README.md deleted file mode 100644 index 82da84bc1..000000000 --- a/packages/runtime-shared/README.md +++ /dev/null @@ -1 +0,0 @@ -# @vue/runtime-shared diff --git a/packages/runtime-shared/index.js b/packages/runtime-shared/index.js deleted file mode 100644 index 2cd4f6df8..000000000 --- a/packages/runtime-shared/index.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict' - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./dist/runtime-shared.cjs.prod.js') -} else { - module.exports = require('./dist/runtime-shared.cjs.js') -} diff --git a/packages/runtime-shared/package.json b/packages/runtime-shared/package.json deleted file mode 100644 index af152cf3b..000000000 --- a/packages/runtime-shared/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@vue/runtime-shared", - "version": "3.0.0-vapor", - "description": "@vue/runtime-shared", - "main": "index.js", - "module": "dist/runtime-shared.esm-bundler.js", - "types": "dist/runtime-shared.d.ts", - "files": [ - "index.js", - "dist" - ], - "exports": { - ".": { - "types": "./dist/runtime-shared.d.ts", - "node": { - "production": "./dist/runtime-shared.cjs.prod.js", - "development": "./dist/runtime-shared.cjs.js", - "default": "./index.js" - }, - "module": "./dist/runtime-shared.esm-bundler.js", - "import": "./dist/runtime-shared.esm-bundler.js", - "require": "./index.js" - }, - "./*": "./*" - }, - "sideEffects": false, - "buildOptions": { - "formats": [ - "esm-bundler", - "cjs" - ] - }, - "repository": { - "type": "git", - "url": "git+https://github.com/vuejs/vue-vapor.git", - "directory": "packages/runtime-shared" - }, - "keywords": [ - "vue" - ], - "author": "Evan You", - "license": "MIT", - "bugs": { - "url": "https://github.com/vuejs/vue-vapor/issues" - }, - "homepage": "https://github.com/vuejs/vue-vapor/tree/main/packages/runtime-shared#readme", - "dependencies": { - "@vue/shared": "workspace:*" - } -} diff --git a/packages/runtime-shared/src/index.ts b/packages/runtime-shared/src/index.ts deleted file mode 100644 index af482926e..000000000 --- a/packages/runtime-shared/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { toHandlers } from './toHandlers' -export { type Data } from './typeUtils' diff --git a/packages/runtime-shared/src/toHandlers.ts b/packages/runtime-shared/src/toHandlers.ts deleted file mode 100644 index bad1d665e..000000000 --- a/packages/runtime-shared/src/toHandlers.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { isObject, toHandlerKey } from '@vue/shared' - -/** - * For prefixing keys in v-on="obj" with "on" - * @private - */ -export function toHandlers( - warn: (msg: string) => void, - obj: Record, - preserveCaseIfNecessary?: boolean, -): Record { - const ret: Record = {} - if (__DEV__ && !isObject(obj)) { - warn(`v-on with no argument expects an object value.`) - return ret - } - for (const key in obj) { - ret[ - preserveCaseIfNecessary && /[A-Z]/.test(key) - ? `on:${key}` - : toHandlerKey(key) - ] = obj[key] - } - return ret -} diff --git a/packages/runtime-shared/src/typeUtils.ts b/packages/runtime-shared/src/typeUtils.ts deleted file mode 100644 index 94c95311a..000000000 --- a/packages/runtime-shared/src/typeUtils.ts +++ /dev/null @@ -1 +0,0 @@ -export type Data = Record diff --git a/packages/runtime-vapor/__tests__/_utils.ts b/packages/runtime-vapor/__tests__/_utils.ts index 7748405aa..ce88d9a1a 100644 --- a/packages/runtime-vapor/__tests__/_utils.ts +++ b/packages/runtime-vapor/__tests__/_utils.ts @@ -6,8 +6,8 @@ import { type SetupFn, createVaporApp, defineComponent, -} from '../src' -import type { RawProps } from '../src/componentProps' +} from '../src/_old' +import type { RawProps } from '../src/_old/componentProps' export interface RenderContext { component: Component diff --git a/packages/runtime-vapor/__tests__/apiCreateSelector.spec.ts b/packages/runtime-vapor/__tests__/apiCreateSelector.spec.ts index fc1979ec5..033751f22 100644 --- a/packages/runtime-vapor/__tests__/apiCreateSelector.spec.ts +++ b/packages/runtime-vapor/__tests__/apiCreateSelector.spec.ts @@ -1,6 +1,6 @@ import { ref } from '@vue/reactivity' import { makeRender } from './_utils' -import { createFor, createSelector, nextTick, renderEffect } from '../src' +import { createFor, createSelector, nextTick, renderEffect } from '../src/_old' const define = makeRender() diff --git a/packages/runtime-vapor/__tests__/apiCreateVaporApp.spec.ts b/packages/runtime-vapor/__tests__/apiCreateVaporApp.spec.ts index 8f6e2d50e..4f8c47901 100644 --- a/packages/runtime-vapor/__tests__/apiCreateVaporApp.spec.ts +++ b/packages/runtime-vapor/__tests__/apiCreateVaporApp.spec.ts @@ -11,8 +11,8 @@ import { resolveComponent, resolveDirective, withDirectives, -} from '../src' -import { warn } from '../src/warning' +} from '../src/_old' +import { warn } from '../src/_old/warning' import { makeRender } from './_utils' const define = makeRender() diff --git a/packages/runtime-vapor/__tests__/apiExpose.spec.ts b/packages/runtime-vapor/__tests__/apiExpose.spec.ts index 895d20383..1b27c4861 100644 --- a/packages/runtime-vapor/__tests__/apiExpose.spec.ts +++ b/packages/runtime-vapor/__tests__/apiExpose.spec.ts @@ -1,12 +1,12 @@ import { ref, shallowRef } from '@vue/reactivity' -import { createComponent } from '../src/apiCreateComponent' +import { createComponent } from '../src/_old/apiCreateComponent' import { setRef } from '../src/dom/templateRef' import { makeRender } from './_utils' import { type ComponentInternalInstance, getCurrentInstance, -} from '../src/component' -import { defineComponent } from '../src/apiDefineComponent' +} from '../src/_old/component' +import { defineComponent } from '../src/_old/apiDefineComponent' const define = makeRender() describe('api: expose', () => { diff --git a/packages/runtime-vapor/__tests__/apiInject.spec.ts b/packages/runtime-vapor/__tests__/apiInject.spec.ts index a469ab146..1bde97b6d 100644 --- a/packages/runtime-vapor/__tests__/apiInject.spec.ts +++ b/packages/runtime-vapor/__tests__/apiInject.spec.ts @@ -13,7 +13,7 @@ import { ref, renderEffect, setText, -} from '../src' +} from '../src/_old' import { makeRender } from './_utils' const define = makeRender() diff --git a/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts b/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts index dc31059f2..f2f3a6b28 100644 --- a/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts +++ b/packages/runtime-vapor/__tests__/apiLifecycle.spec.ts @@ -24,7 +24,7 @@ import { renderEffect, setText, template, -} from '../src' +} from '../src/_old' import { makeRender } from './_utils' import { ITERATE_KEY } from '@vue/reactivity' diff --git a/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts b/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts index 3444c3407..91da73e2a 100644 --- a/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts +++ b/packages/runtime-vapor/__tests__/apiSetupContext.spec.ts @@ -14,7 +14,7 @@ import { setInheritAttrs, template, watchEffect, -} from '../src' +} from '../src/_old' import { makeRender } from './_utils' const define = makeRender() diff --git a/packages/runtime-vapor/__tests__/apiSetupHelpers.spec.ts b/packages/runtime-vapor/__tests__/apiSetupHelpers.spec.ts index 31b9cb391..6c56af6e0 100644 --- a/packages/runtime-vapor/__tests__/apiSetupHelpers.spec.ts +++ b/packages/runtime-vapor/__tests__/apiSetupHelpers.spec.ts @@ -1,4 +1,4 @@ -import type { SetupContext } from '../src/component' +import type { SetupContext } from '../src/_old/component' import { createComponent, defineComponent, @@ -6,7 +6,7 @@ import { template, useAttrs, useSlots, -} from '../src' +} from '../src/_old' import { makeRender } from './_utils' const define = makeRender() diff --git a/packages/runtime-vapor/__tests__/apiWatch.spec.ts b/packages/runtime-vapor/__tests__/apiWatch.spec.ts index 5cdd032f5..821e703aa 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' +} from '../src/_old' describe('watchEffect and onWatcherCleanup', () => { test('basic', async () => { diff --git a/packages/runtime-vapor/__tests__/component.spec.ts b/packages/runtime-vapor/__tests__/component.spec.ts index c166a8c73..d468af667 100644 --- a/packages/runtime-vapor/__tests__/component.spec.ts +++ b/packages/runtime-vapor/__tests__/component.spec.ts @@ -1,4 +1,4 @@ -import { ref, setText, template, watchEffect } from '../src' +import { ref, setText, template, watchEffect } from '../src/_old' import { describe, expect } from 'vitest' import { makeRender } from './_utils' diff --git a/packages/runtime-vapor/__tests__/componentAttrs.spec.ts b/packages/runtime-vapor/__tests__/componentAttrs.spec.ts index d66474559..1efef9cd5 100644 --- a/packages/runtime-vapor/__tests__/componentAttrs.spec.ts +++ b/packages/runtime-vapor/__tests__/componentAttrs.spec.ts @@ -7,7 +7,7 @@ import { setText, template, watchEffect, -} from '../src' +} from '../src/_old' import { makeRender } from './_utils' const define = makeRender() diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index ab7fb04fb..6c9a22f71 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -9,8 +9,8 @@ import { defineComponent, nextTick, onBeforeUnmount, -} from '../src' -import { isEmitListener } from '../src/componentEmits' +} from '../src/_old' +import { isEmitListener } from '../src/_old/componentEmits' import { makeRender } from './_utils' const define = makeRender() diff --git a/packages/runtime-vapor/__tests__/componentProps.spec.ts b/packages/runtime-vapor/__tests__/componentProps.spec.ts index f5d4114e1..dcc67b90e 100644 --- a/packages/runtime-vapor/__tests__/componentProps.spec.ts +++ b/packages/runtime-vapor/__tests__/componentProps.spec.ts @@ -11,7 +11,7 @@ import { toRefs, watch, watchEffect, -} from '../src' +} from '../src/_old' import { makeRender } from './_utils' const define = makeRender() diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts index 224e160e1..3be95c833 100644 --- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts +++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts @@ -15,7 +15,7 @@ import { setText, template, withDestructure, -} from '../src' +} from '../src/_old' import { makeRender } from './_utils' const define = makeRender() diff --git a/packages/runtime-vapor/__tests__/directives/vModel.spec.ts b/packages/runtime-vapor/__tests__/directives/vModel.spec.ts index 8ee22607f..a592784c9 100644 --- a/packages/runtime-vapor/__tests__/directives/vModel.spec.ts +++ b/packages/runtime-vapor/__tests__/directives/vModel.spec.ts @@ -9,7 +9,7 @@ import { vModelDynamic, vModelSelect, withDirectives, -} from '../../src' +} from '../../src/_old' import { makeRender } from '../_utils' import { nextTick } from '@vue/runtime-dom' diff --git a/packages/runtime-vapor/__tests__/directives/vShow.spec.ts b/packages/runtime-vapor/__tests__/directives/vShow.spec.ts index e8795f515..59cac9b8d 100644 --- a/packages/runtime-vapor/__tests__/directives/vShow.spec.ts +++ b/packages/runtime-vapor/__tests__/directives/vShow.spec.ts @@ -5,7 +5,7 @@ import { template, vShow, withDirectives, -} from '../../src' +} from '../../src/_old' import { nextTick, ref } from 'vue' import { describe, expect, test } from 'vitest' import { makeRender } from '../_utils' diff --git a/packages/runtime-vapor/__tests__/dom/prop.spec.ts b/packages/runtime-vapor/__tests__/dom/prop.spec.ts index 50a39cea9..6b2f03558 100644 --- a/packages/runtime-vapor/__tests__/dom/prop.spec.ts +++ b/packages/runtime-vapor/__tests__/dom/prop.spec.ts @@ -13,7 +13,7 @@ import { setStyle } from '../../src/dom/style' import { ComponentInternalInstance, setCurrentInstance, -} from '../../src/component' +} from '../../src/_old/component' import { getMetadata, recordPropMetadata } from '../../src/componentMetadata' import { getCurrentScope } from '@vue/reactivity' diff --git a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts index b8d349c8f..a6f237558 100644 --- a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts +++ b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts @@ -12,7 +12,7 @@ import { setText, template, watchEffect, -} from '../../src' +} from '../../src/_old' import { makeRender } from '../_utils' const define = makeRender() diff --git a/packages/runtime-vapor/__tests__/errorHandling.spec.ts b/packages/runtime-vapor/__tests__/errorHandling.spec.ts index e58348fa7..0cc3de878 100644 --- a/packages/runtime-vapor/__tests__/errorHandling.spec.ts +++ b/packages/runtime-vapor/__tests__/errorHandling.spec.ts @@ -1,11 +1,11 @@ -import type { Component } from '../src/component' +import type { Component } from '../src/_old/component' import { type RefEl, setRef } from '../src/dom/templateRef' -import { onErrorCaptured, onMounted } from '../src/apiLifecycle' -import { createComponent } from '../src/apiCreateComponent' +import { onErrorCaptured, onMounted } from '../src/_old/apiLifecycle' +import { createComponent } from '../src/_old/apiCreateComponent' import { makeRender } from './_utils' import { template } from '../src/dom/template' -import { watch, watchEffect } from '../src/apiWatch' -import { nextTick } from '../src/scheduler' +import { watch, watchEffect } from '../src/_old/apiWatch' +import { nextTick } from '../src/_old/scheduler' import { ref } from '@vue/reactivity' const define = makeRender() diff --git a/packages/runtime-vapor/__tests__/for.spec.ts b/packages/runtime-vapor/__tests__/for.spec.ts index 0ee09a9ad..283184dba 100644 --- a/packages/runtime-vapor/__tests__/for.spec.ts +++ b/packages/runtime-vapor/__tests__/for.spec.ts @@ -7,7 +7,7 @@ import { template, triggerRef, withDestructure, -} from '../src' +} from '../src/_old' import { makeRender } from './_utils' const define = makeRender() diff --git a/packages/runtime-vapor/__tests__/helpers/resolveAssets.spec.ts b/packages/runtime-vapor/__tests__/helpers/resolveAssets.spec.ts index b7a294af4..10b8c6718 100644 --- a/packages/runtime-vapor/__tests__/helpers/resolveAssets.spec.ts +++ b/packages/runtime-vapor/__tests__/helpers/resolveAssets.spec.ts @@ -4,7 +4,7 @@ import { createVaporApp, resolveComponent, resolveDirective, -} from '@vue/runtime-vapor' +} from 'packages/runtime-vapor/src/_old' import { makeRender } from '../_utils' const define = makeRender() diff --git a/packages/runtime-vapor/__tests__/if.spec.ts b/packages/runtime-vapor/__tests__/if.spec.ts index 492d19d61..955659f34 100644 --- a/packages/runtime-vapor/__tests__/if.spec.ts +++ b/packages/runtime-vapor/__tests__/if.spec.ts @@ -8,10 +8,10 @@ import { setText, template, withDirectives, -} from '../src' +} from '../src/_old' import type { Mock } from 'vitest' import { makeRender } from './_utils' -import { unmountComponent } from '../src/apiRender' +import { unmountComponent } from '../src/_old/apiRender' const define = makeRender() diff --git a/packages/runtime-vapor/__tests__/renderEffect.spec.ts b/packages/runtime-vapor/__tests__/renderEffect.spec.ts index 5ebb514bb..fb7645dc9 100644 --- a/packages/runtime-vapor/__tests__/renderEffect.spec.ts +++ b/packages/runtime-vapor/__tests__/renderEffect.spec.ts @@ -11,11 +11,11 @@ import { watchEffect, watchPostEffect, watchSyncEffect, -} from '../src' +} from '../src/_old' import { type ComponentInternalInstance, currentInstance, -} from '../src/component' +} from '../src/_old/component' import { makeRender } from './_utils' const define = makeRender() diff --git a/packages/runtime-vapor/package.json b/packages/runtime-vapor/package.json index 54e4a8231..91703f079 100644 --- a/packages/runtime-vapor/package.json +++ b/packages/runtime-vapor/package.json @@ -36,7 +36,9 @@ "homepage": "https://github.com/vuejs/vue-vapor/tree/dev/packages/runtime-vapor#readme", "dependencies": { "@vue/shared": "workspace:*", - "@vue/reactivity": "workspace:*", - "@vue/runtime-shared": "workspace:*" + "@vue/reactivity": "workspace:*" + }, + "peerDependencies": { + "@vue/runtime-dom": "workspace:*" } } diff --git a/packages/runtime-vapor/src/.gitignore b/packages/runtime-vapor/src/.gitignore new file mode 100644 index 000000000..a1f960b5e --- /dev/null +++ b/packages/runtime-vapor/src/.gitignore @@ -0,0 +1 @@ +_old diff --git a/packages/runtime-vapor/src/_new/component.ts b/packages/runtime-vapor/src/_new/component.ts deleted file mode 100644 index 9bfe4c3f4..000000000 --- a/packages/runtime-vapor/src/_new/component.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { - type ComponentInternalOptions, - type ComponentPropsOptions, - EffectScope, - type EmitsOptions, - type GenericAppContext, - type GenericComponentInstance, - type LifecycleHook, - type NormalizedPropsOptions, - type ObjectEmitsOptions, - nextUid, - popWarningContext, - pushWarningContext, -} from '@vue/runtime-core' -import type { Block } from '../block' -import type { Data } from '@vue/runtime-shared' -import { pauseTracking, resetTracking } from '@vue/reactivity' -import { EMPTY_OBJ, isFunction } from '@vue/shared' -import { - type RawProps, - getDynamicPropsHandlers, - initStaticProps, -} from './componentProps' -import { setDynamicProp } from '../dom/prop' -import { renderEffect } from './renderEffect' - -export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent - -export type VaporSetupFn = ( - props: any, - ctx: SetupContext, -) => Block | Data | undefined - -export type FunctionalVaporComponent = VaporSetupFn & - Omit & { - displayName?: string - } & SharedInternalOptions - -export interface ObjectVaporComponent - extends ComponentInternalOptions, - SharedInternalOptions { - setup?: VaporSetupFn - inheritAttrs?: boolean - props?: ComponentPropsOptions - emits?: EmitsOptions - render?(ctx: any): Block - - name?: string - vapor?: boolean -} - -interface SharedInternalOptions { - /** - * Cached normalized props options. - * In vapor mode there are no mixins so normalized options can be cached - * directly on the component - */ - __propsOptions?: NormalizedPropsOptions - /** - * Cached normalized props proxy handlers. - */ - __propsHandlers?: [ProxyHandler | null, ProxyHandler] - /** - * Cached normalized emits options. - */ - __emitsOptions?: ObjectEmitsOptions -} - -export function createComponent( - component: VaporComponent, - rawProps?: RawProps, - isSingleRoot?: boolean, -): VaporComponentInstance { - // check if we are the single root of the parent - // if yes, inject parent attrs as dynamic props source - if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) { - if (rawProps) { - ;(rawProps.$ || (rawProps.$ = [])).push(currentInstance.attrs) - } else { - rawProps = { $: [currentInstance.attrs] } - } - } - - const instance = new VaporComponentInstance(component, rawProps) - - pauseTracking() - let prevInstance = currentInstance - currentInstance = instance - instance.scope.on() - - if (__DEV__) { - pushWarningContext(instance) - } - - const setupFn = isFunction(component) ? component : component.setup - const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null - instance.block = setupFn!( - instance.props, - // @ts-expect-error - setupContext, - ) as Block // TODO handle return object - - // single root, inherit attrs - if ( - instance.hasFallthrough && - component.inheritAttrs !== false && - instance.block instanceof Element && - Object.keys(instance.attrs).length - ) { - renderEffect(() => { - for (const key in instance.attrs) { - setDynamicProp(instance.block as Element, key, instance.attrs[key]) - } - }) - } - - if (__DEV__) { - popWarningContext() - } - - instance.scope.off() - currentInstance = prevInstance - resetTracking() - return instance -} - -export let currentInstance: VaporComponentInstance | null = null - -const emptyContext: GenericAppContext = { - app: null as any, - config: {}, - provides: /*@__PURE__*/ Object.create(null), -} - -export class VaporComponentInstance implements GenericComponentInstance { - uid: number - type: VaporComponent - parent: GenericComponentInstance | null - appContext: GenericAppContext - - block: Block - scope: EffectScope - rawProps: RawProps | undefined - props: Record - attrs: Record - exposed: Record | null - - emitted: Record | null - propsDefaults: Record | null - - // for useTemplateRef() - refs: Data - // for provide / inject - provides: Data - - hasFallthrough: boolean - - isMounted: boolean - isUnmounted: boolean - isDeactivated: boolean - // LifecycleHooks.ERROR_CAPTURED - ec: LifecycleHook - - // dev only - propsOptions?: NormalizedPropsOptions - emitsOptions?: ObjectEmitsOptions | null - - constructor(comp: VaporComponent, rawProps?: RawProps) { - this.uid = nextUid() - this.type = comp - this.parent = currentInstance - this.appContext = currentInstance - ? currentInstance.appContext - : emptyContext - - this.block = null! // to be set - this.scope = new EffectScope(true) - - this.rawProps = rawProps - this.provides = this.refs = EMPTY_OBJ - this.emitted = this.ec = this.exposed = null - this.isMounted = this.isUnmounted = this.isDeactivated = false - - // init props - this.propsDefaults = null - this.hasFallthrough = false - if (rawProps && rawProps.$) { - // has dynamic props, use proxy - const handlers = getDynamicPropsHandlers(comp, this) - this.props = comp.props ? new Proxy(rawProps, handlers[0]!) : EMPTY_OBJ - this.attrs = new Proxy(rawProps, handlers[1]) - this.hasFallthrough = true - } else { - this.props = {} - this.attrs = {} - this.hasFallthrough = initStaticProps(comp, rawProps, this) - } - - // TODO validate props - // TODO init slots - } -} - -export function isVaporComponent( - value: unknown, -): value is VaporComponentInstance { - return value instanceof VaporComponentInstance -} - -export class SetupContext { - attrs: Record - // emit: EmitFn - // slots: Readonly - expose: (exposed?: Record) => void - - constructor(instance: VaporComponentInstance) { - this.attrs = instance.attrs - // this.emit = instance.emit as EmitFn - // this.slots = instance.slots - this.expose = (exposed = {}) => { - instance.exposed = exposed - } - } -} diff --git a/packages/runtime-vapor/src/_new/componentEmits.ts b/packages/runtime-vapor/src/_new/componentEmits.ts deleted file mode 100644 index dfce33099..000000000 --- a/packages/runtime-vapor/src/_new/componentEmits.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - type EmitFn, - type ObjectEmitsOptions, - baseEmit, -} from '@vue/runtime-core' -import { - type VaporComponent, - type VaporComponentInstance, - currentInstance, -} from './component' -import { EMPTY_OBJ, NOOP, hasOwn, isArray } from '@vue/shared' -import { resolveSource } from './componentProps' - -/** - * The logic from core isn't too reusable so it's better to duplicate here - */ -export function normalizeEmitsOptions( - comp: VaporComponent, -): ObjectEmitsOptions | null { - const cached = comp.__emitsOptions - if (cached) return cached - - const raw = comp.emits - if (!raw) return null - - let normalized: ObjectEmitsOptions - if (isArray(raw)) { - normalized = {} - for (const key in raw) normalized[key] = null - } else { - normalized = raw - } - - return (comp.__emitsOptions = normalized) -} - -export function useEmit(): EmitFn { - if (!currentInstance) { - // TODO warn - return NOOP - } else { - return emit.bind(null, currentInstance) - } -} - -export function emit( - instance: VaporComponentInstance, - event: string, - ...rawArgs: any[] -): void { - baseEmit( - instance, - instance.rawProps || EMPTY_OBJ, - propGetter, - event, - ...rawArgs, - ) -} - -function propGetter(rawProps: Record, key: string) { - const dynamicSources = rawProps.$ - if (dynamicSources) { - let i = dynamicSources.length - while (i--) { - const source = resolveSource(dynamicSources[i]) - if (hasOwn(source, key)) return source[key] - } - } - return rawProps[key] && rawProps[key]() -} diff --git a/packages/runtime-vapor/src/_new/componentProps.ts b/packages/runtime-vapor/src/_new/componentProps.ts deleted file mode 100644 index 560f89240..000000000 --- a/packages/runtime-vapor/src/_new/componentProps.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { EMPTY_ARR, NO, camelize, hasOwn, isFunction } from '@vue/shared' -import type { VaporComponent, VaporComponentInstance } from './component' -import { - type NormalizedPropsOptions, - baseNormalizePropsOptions, - isEmitListener, - resolvePropValue, -} from '@vue/runtime-core' -import { normalizeEmitsOptions } from './componentEmits' - -export interface RawProps { - [key: string]: PropSource - $?: DynamicPropsSource[] -} - -type PropSource = T | (() => T) - -type DynamicPropsSource = PropSource> - -export function initStaticProps( - comp: VaporComponent, - rawProps: RawProps | undefined, - instance: VaporComponentInstance, -): boolean { - let hasAttrs = false - const { props, attrs } = instance - const [propsOptions, needCastKeys] = normalizePropsOptions(comp) - const emitsOptions = normalizeEmitsOptions(comp) - - // for dev emit check - if (__DEV__) { - instance.propsOptions = normalizePropsOptions(comp) - instance.emitsOptions = emitsOptions - } - - for (const key in rawProps) { - const normalizedKey = camelize(key) - const needCast = needCastKeys && needCastKeys.includes(normalizedKey) - const source = rawProps[key] - if (propsOptions && normalizedKey in propsOptions) { - if (isFunction(source)) { - Object.defineProperty(props, normalizedKey, { - enumerable: true, - get: needCast - ? () => - resolvePropValue( - propsOptions, - normalizedKey, - source(), - instance, - resolveDefault, - ) - : source, - }) - } else { - props[normalizedKey] = needCast - ? resolvePropValue( - propsOptions, - normalizedKey, - source, - instance, - resolveDefault, - ) - : source - } - } else if (!isEmitListener(emitsOptions, key)) { - if (isFunction(source)) { - Object.defineProperty(attrs, key, { - enumerable: true, - get: source, - }) - } else { - attrs[normalizedKey] = source - } - hasAttrs = true - } - } - for (const key in propsOptions) { - if (!(key in props)) { - props[key] = resolvePropValue( - propsOptions, - key, - undefined, - instance, - resolveDefault, - true, - ) - } - } - return hasAttrs -} - -function resolveDefault( - factory: (props: Record) => unknown, - instance: VaporComponentInstance, -) { - return factory.call(null, instance.props) -} - -// TODO optimization: maybe convert functions into computeds -export function resolveSource(source: PropSource): Record { - return isFunction(source) ? source() : source -} - -const passThrough = (val: any) => val - -export function getDynamicPropsHandlers( - comp: VaporComponent, - instance: VaporComponentInstance, -): [ProxyHandler | null, ProxyHandler] { - if (comp.__propsHandlers) { - return comp.__propsHandlers - } - let normalizedKeys: string[] | undefined - const propsOptions = normalizePropsOptions(comp)[0] - const emitsOptions = normalizeEmitsOptions(comp) - const isProp = propsOptions ? (key: string) => hasOwn(propsOptions, key) : NO - - const getProp = (target: RawProps, key: string, asProp: boolean) => { - if (key === '$') return - if (asProp) { - if (!isProp(key)) return - } else if (isProp(key) || isEmitListener(emitsOptions, key)) { - return - } - const castProp = propsOptions - ? (value: any, isAbsent = false) => - asProp - ? resolvePropValue( - propsOptions, - key as string, - value, - instance, - resolveDefault, - isAbsent, - ) - : value - : passThrough - - if (key in target) { - return castProp(resolveSource(target[key as string])) - } - if (target.$) { - let i = target.$.length - let source - while (i--) { - source = resolveSource(target.$[i]) - if (hasOwn(source, key)) { - return castProp(source[key]) - } - } - } - return castProp(undefined, true) - } - - const propsHandlers = propsOptions - ? ({ - get: (target, key: string) => getProp(target, key, true), - has: (_, key: string) => isProp(key), - getOwnPropertyDescriptor(target, key: string) { - if (isProp(key)) { - return { - configurable: true, - enumerable: true, - get: () => getProp(target, key, true), - } - } - }, - ownKeys: () => - normalizedKeys || (normalizedKeys = Object.keys(propsOptions)), - set: NO, - deleteProperty: NO, - } satisfies ProxyHandler) - : null - - const hasAttr = (target: RawProps, key: string) => { - if (key === '$' || isProp(key) || isEmitListener(emitsOptions, key)) - return false - if (target.$) { - let i = target.$.length - while (i--) { - if (hasOwn(resolveSource(target.$[i]), key)) { - return true - } - } - } - return hasOwn(target, key) - } - - const attrsHandlers = { - get: (target, key: string) => getProp(target, key, false), - has: hasAttr, - getOwnPropertyDescriptor(target, key: string) { - if (hasAttr(target, key)) { - return { - configurable: true, - enumerable: true, - get: () => getProp(target, key, false), - } - } - }, - ownKeys(target) { - const keys = Object.keys(target) - if (target.$) { - let i = target.$.length - while (i--) { - keys.push(...Object.keys(resolveSource(target.$[i]))) - } - } - return keys.filter(key => hasAttr(target, key)) - }, - set: NO, - deleteProperty: NO, - } satisfies ProxyHandler - - return (comp.__propsHandlers = [propsHandlers, attrsHandlers]) -} - -function normalizePropsOptions(comp: VaporComponent): NormalizedPropsOptions { - const cached = comp.__propsOptions - if (cached) return cached - - const raw = comp.props - if (!raw) return EMPTY_ARR as [] - - const normalized: NormalizedPropsOptions[0] = {} - const needCastKeys: NormalizedPropsOptions[1] = [] - baseNormalizePropsOptions(raw, normalized, needCastKeys) - - return (comp.__propsOptions = [normalized, needCastKeys]) -} diff --git a/packages/runtime-vapor/src/_new/index.ts b/packages/runtime-vapor/src/_new/index.ts deleted file mode 100644 index 886d1e2c5..000000000 --- a/packages/runtime-vapor/src/_new/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { createComponent as createComponentSimple } from './component' -export { renderEffect as renderEffectSimple } from './renderEffect' -export { createVaporApp as createVaporAppSimple } from './apiCreateApp' -export { useEmit } from './componentEmits' diff --git a/packages/runtime-vapor/src/_new/renderEffect.ts b/packages/runtime-vapor/src/_new/renderEffect.ts deleted file mode 100644 index 77220ec5b..000000000 --- a/packages/runtime-vapor/src/_new/renderEffect.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ReactiveEffect } from '@vue/reactivity' -import { type SchedulerJob, queueJob } from '@vue/runtime-core' -import { currentInstance } from './component' - -export function renderEffect(fn: () => void): void { - const updateFn = () => { - fn() - } - const effect = new ReactiveEffect(updateFn) - const job: SchedulerJob = effect.runIfDirty.bind(effect) - job.i = currentInstance as any - job.id = currentInstance!.uid - effect.scheduler = () => queueJob(job) - effect.run() - - // TODO lifecycle - // TODO recurse handling - // TODO measure -} diff --git a/packages/runtime-vapor/src/_new/apiCreateApp.ts b/packages/runtime-vapor/src/apiCreateApp.ts similarity index 77% rename from packages/runtime-vapor/src/_new/apiCreateApp.ts rename to packages/runtime-vapor/src/apiCreateApp.ts index becfc1b86..6ccd70b31 100644 --- a/packages/runtime-vapor/src/_new/apiCreateApp.ts +++ b/packages/runtime-vapor/src/apiCreateApp.ts @@ -1,5 +1,5 @@ -import { normalizeContainer } from '../apiRender' -import { insert } from '../dom/element' +import { normalizeContainer } from './_old/apiRender' +import { insert } from './dom/element' import { type VaporComponent, createComponent } from './component' import { type AppMountFn, @@ -8,7 +8,7 @@ import { createAppAPI, } from '@vue/runtime-core' -let _createApp: CreateAppFunction +let _createApp: CreateAppFunction const mountApp: AppMountFn = (app, container) => { // clear content before mounting @@ -24,7 +24,10 @@ const unmountApp: AppUnmountFn = app => { // TODO } -export function createVaporApp(comp: VaporComponent): any { +export const createVaporApp: CreateAppFunction< + ParentNode, + VaporComponent +> = comp => { if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp) const app = _createApp(comp) const mount = app.mount diff --git a/packages/runtime-vapor/src/apiCreateComponent.ts b/packages/runtime-vapor/src/apiCreateComponent.ts deleted file mode 100644 index 029a4d451..000000000 --- a/packages/runtime-vapor/src/apiCreateComponent.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - type Component, - ComponentInternalInstance, - currentInstance, -} from './component' -import { setupComponent } from './apiRender' -import type { RawProps } from './componentProps' -import type { RawSlots } from './componentSlots' -import { withAttrs } from './componentAttrs' -import { isString } from '@vue/shared' -import { fallbackComponent } from './componentFallback' - -export function createComponent( - comp: Component | string, - rawProps: RawProps | null = null, - slots: RawSlots | null = null, - singleRoot: boolean = false, - once: boolean = false, -): ComponentInternalInstance | HTMLElement { - const current = currentInstance! - - if (isString(comp)) { - return fallbackComponent(comp, rawProps, slots, current, singleRoot) - } - - const instance = new ComponentInternalInstance( - comp, - singleRoot ? withAttrs(rawProps) : rawProps, - slots, - once, - ) - - if (singleRoot) { - instance.scopeIds.push(...current.scopeIds) - } - const scopeId = current.type.__scopeId - if (scopeId) { - instance.scopeIds.push(scopeId) - } - - setupComponent(instance) - - // register sub-component with current component for lifecycle management - current.comps.add(instance) - - return instance -} diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts deleted file mode 100644 index eb307ea30..000000000 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ /dev/null @@ -1,431 +0,0 @@ -import { - type EffectScope, - type ShallowRef, - effectScope, - shallowRef, -} from '@vue/reactivity' -import { isArray, isObject, isString } from '@vue/shared' -import { - createComment, - createTextNode, - insert, - remove as removeBlock, -} from './dom/element' -import { type Block, type Fragment, fragmentKey } from './block' -import { warn } from './warning' -import { currentInstance, isVaporComponent } from './component' -import type { DynamicSlot } from './componentSlots' -import { renderEffect } from './renderEffect' - -interface ForBlock extends Fragment { - scope: EffectScope - state: [ - item: ShallowRef, - key: ShallowRef, - index: ShallowRef, - ] - key: any -} - -type Source = any[] | Record | number | Set | Map - -/*! #__NO_SIDE_EFFECTS__ */ -export const createFor = ( - src: () => Source, - renderItem: (block: ForBlock['state']) => Block, - getKey?: (item: any, key: any, index?: number) => any, - container?: ParentNode, - hydrationNode?: Node, - once?: boolean, -): Fragment => { - let isMounted = false - let oldBlocks: ForBlock[] = [] - let newBlocks: ForBlock[] - let parent: ParentNode | undefined | null - const parentAnchor = container - ? undefined - : __DEV__ - ? createComment('for') - : createTextNode() - const ref: Fragment = { - nodes: oldBlocks, - [fragmentKey]: true, - } - - const instance = currentInstance! - if (__DEV__ && !instance) { - warn('createFor() can only be used inside setup()') - } - - once ? renderList() : renderEffect(renderList) - - return ref - - function renderList() { - const source = src() - const newLength = getLength(source) - const oldLength = oldBlocks.length - newBlocks = new Array(newLength) - - if (!isMounted) { - isMounted = true - mountList(source) - } else { - parent = parent || container || parentAnchor!.parentNode - if (!oldLength) { - // fast path for all new - mountList(source) - } else if (!newLength) { - // fast path for all removed - if (container) { - container.textContent = '' - for (let i = 0; i < oldLength; i++) { - oldBlocks[i].scope.stop() - } - } else { - // fast path for clearing - for (let i = 0; i < oldLength; i++) { - unmount(oldBlocks[i]) - } - } - } else if (!getKey) { - // unkeyed fast path - const commonLength = Math.min(newLength, oldLength) - for (let i = 0; i < commonLength; i++) { - const [item] = getItem(source, i) - update((newBlocks[i] = oldBlocks[i]), item) - } - mountList(source, oldLength) - for (let i = newLength; i < oldLength; i++) { - unmount(oldBlocks[i]) - } - } else { - let i = 0 - let e1 = oldLength - 1 // prev ending index - let e2 = newLength - 1 // next ending index - - // 1. sync from start - // (a b) c - // (a b) d e - while (i <= e1 && i <= e2) { - if (tryPatchIndex(source, i)) { - i++ - } else { - break - } - } - - // 2. sync from end - // a (b c) - // d e (b c) - while (i <= e1 && i <= e2) { - if (tryPatchIndex(source, i)) { - e1-- - e2-- - } else { - break - } - } - - // 3. common sequence + mount - // (a b) - // (a b) c - // i = 2, e1 = 1, e2 = 2 - // (a b) - // c (a b) - // i = 0, e1 = -1, e2 = 0 - if (i > e1) { - if (i <= e2) { - const nextPos = e2 + 1 - const anchor = - nextPos < newLength - ? normalizeAnchor(newBlocks[nextPos].nodes) - : parentAnchor - while (i <= e2) { - mount(source, i, anchor) - i++ - } - } - } - - // 4. common sequence + unmount - // (a b) c - // (a b) - // i = 2, e1 = 2, e2 = 1 - // a (b c) - // (b c) - // i = 0, e1 = 0, e2 = -1 - else if (i > e2) { - while (i <= e1) { - unmount(oldBlocks[i]) - i++ - } - } - - // 5. unknown sequence - // [i ... e1 + 1]: a b [c d e] f g - // [i ... e2 + 1]: a b [e d c h] f g - // i = 2, e1 = 4, e2 = 5 - else { - const s1 = i // prev starting index - const s2 = i // next starting index - - // 5.1 build key:index map for newChildren - const keyToNewIndexMap = new Map() - for (i = s2; i <= e2; i++) { - keyToNewIndexMap.set(getKey(...getItem(source, i)), i) - } - - // 5.2 loop through old children left to be patched and try to patch - // matching nodes & remove nodes that are no longer present - let j - let patched = 0 - const toBePatched = e2 - s2 + 1 - let moved = false - // used to track whether any node has moved - let maxNewIndexSoFar = 0 - // works as Map - // Note that oldIndex is offset by +1 - // and oldIndex = 0 is a special value indicating the new node has - // no corresponding old node. - // used for determining longest stable subsequence - const newIndexToOldIndexMap = new Array(toBePatched).fill(0) - - for (i = s1; i <= e1; i++) { - const prevBlock = oldBlocks[i] - if (patched >= toBePatched) { - // all new children have been patched so this can only be a removal - unmount(prevBlock) - } else { - const newIndex = keyToNewIndexMap.get(prevBlock.key) - if (newIndex == null) { - unmount(prevBlock) - } else { - newIndexToOldIndexMap[newIndex - s2] = i + 1 - if (newIndex >= maxNewIndexSoFar) { - maxNewIndexSoFar = newIndex - } else { - moved = true - } - update( - (newBlocks[newIndex] = prevBlock), - ...getItem(source, newIndex), - ) - patched++ - } - } - } - - // 5.3 move and mount - // generate longest stable subsequence only when nodes have moved - const increasingNewIndexSequence = moved - ? getSequence(newIndexToOldIndexMap) - : [] - j = increasingNewIndexSequence.length - 1 - // looping backwards so that we can use last patched node as anchor - for (i = toBePatched - 1; i >= 0; i--) { - const nextIndex = s2 + i - const anchor = - nextIndex + 1 < newLength - ? normalizeAnchor(newBlocks[nextIndex + 1].nodes) - : parentAnchor - if (newIndexToOldIndexMap[i] === 0) { - // mount new - mount(source, nextIndex, anchor) - } else if (moved) { - // move if: - // There is no stable subsequence (e.g. a reverse) - // OR current node is not among the stable sequence - if (j < 0 || i !== increasingNewIndexSequence[j]) { - insert(newBlocks[nextIndex].nodes, parent!, anchor) - } else { - j-- - } - } - } - } - } - } - - ref.nodes = [(oldBlocks = newBlocks)] - if (parentAnchor) { - ref.nodes.push(parentAnchor) - } - } - - function mount( - source: any, - idx: number, - anchor: Node | undefined = parentAnchor, - ): ForBlock { - const scope = effectScope() - - const [item, key, index] = getItem(source, idx) - const state = [ - shallowRef(item), - shallowRef(key), - shallowRef(index), - ] as ForBlock['state'] - const block: ForBlock = (newBlocks[idx] = { - nodes: null!, // set later - scope, - state, - key: getKey && getKey(item, key, index), - [fragmentKey]: true, - }) - block.nodes = scope.run(() => renderItem(state))! - - if (parent) insert(block.nodes, parent, anchor) - - return block - } - - function mountList(source: any, offset = 0) { - for (let i = offset; i < getLength(source); i++) { - mount(source, i) - } - } - - function tryPatchIndex(source: any, idx: number) { - const block = oldBlocks[idx] - const [item, key, index] = getItem(source, idx) - if (block.key === getKey!(item, key, index)) { - update((newBlocks[idx] = block), item) - return true - } - } - - function update( - block: ForBlock, - newItem: any, - newKey = block.state[1].value, - newIndex = block.state[2].value, - ) { - const [item, key, index] = block.state - let needsUpdate = - newItem !== item.value || newKey !== key.value || newIndex !== index.value - if (needsUpdate) updateState(block, newItem, newKey, newIndex) - } - - function unmount({ nodes, scope }: ForBlock) { - removeBlock(nodes, parent!) - scope.stop() - } -} - -function updateState( - block: ForBlock, - newItem: any, - newKey: any, - newIndex: number | undefined, -) { - const [item, key, index] = block.state - item.value = newItem - key.value = newKey - index.value = newIndex -} - -export function createForSlots( - source: any[] | Record | number | Set | Map, - getSlot: (item: any, key: any, index?: number) => DynamicSlot, -): DynamicSlot[] { - const sourceLength = getLength(source) - const slots = new Array(sourceLength) - for (let i = 0; i < sourceLength; i++) { - const [item, key, index] = getItem(source, i) - slots[i] = getSlot(item, key, index) - } - return slots -} - -function getLength(source: any): number { - if (isArray(source) || isString(source)) { - return source.length - } else if (typeof source === 'number') { - if (__DEV__ && !Number.isInteger(source)) { - warn(`The v-for range expect an integer value but got ${source}.`) - } - return source - } else if (isObject(source)) { - if (source[Symbol.iterator as any]) { - return Array.from(source as Iterable).length - } else { - return Object.keys(source).length - } - } - return 0 -} - -function getItem( - source: any, - idx: number, -): [item: any, key: any, index?: number] { - if (isArray(source) || isString(source)) { - return [source[idx], idx, undefined] - } else if (typeof source === 'number') { - return [idx + 1, idx, undefined] - } else if (isObject(source)) { - if (source[Symbol.iterator as any]) { - source = Array.from(source as Iterable) - return [source[idx], idx, undefined] - } else { - const key = Object.keys(source)[idx] - return [source[key], key, idx] - } - } - return null! -} - -function normalizeAnchor(node: Block): Node { - if (node instanceof Node) { - return node - } else if (isArray(node)) { - return normalizeAnchor(node[0]) - } else if (isVaporComponent(node)) { - return normalizeAnchor(node.block!) - } else { - return normalizeAnchor(node.nodes!) - } -} - -// https://en.wikipedia.org/wiki/Longest_increasing_subsequence -function getSequence(arr: number[]): number[] { - const p = arr.slice() - const result = [0] - let i, j, u, v, c - const len = arr.length - for (i = 0; i < len; i++) { - const arrI = arr[i] - if (arrI !== 0) { - j = result[result.length - 1] - if (arr[j] < arrI) { - p[i] = j - result.push(i) - continue - } - u = 0 - v = result.length - 1 - while (u < v) { - c = (u + v) >> 1 - if (arr[result[c]] < arrI) { - u = c + 1 - } else { - v = c - } - } - if (arrI < arr[result[u]]) { - if (u > 0) { - p[i] = result[u - 1] - } - result[u] = i - } - } - } - u = result.length - v = result[u - 1] - while (u-- > 0) { - result[u] = v - v = p[v] - } - return result -} diff --git a/packages/runtime-vapor/src/apiCreateIf.ts b/packages/runtime-vapor/src/apiCreateIf.ts deleted file mode 100644 index 7f9de1abf..000000000 --- a/packages/runtime-vapor/src/apiCreateIf.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { renderEffect } from './renderEffect' -import { type Block, type Fragment, fragmentKey } from './block' -import { type EffectScope, effectScope, shallowReactive } from '@vue/reactivity' -import { createComment, createTextNode, insert, remove } from './dom/element' - -type BlockFn = () => Block - -/*! #__NO_SIDE_EFFECTS__ */ -export function createBranch( - expression: () => any, - render: (value: any) => BlockFn | undefined, - once?: boolean, - commentLabel?: string, - // hydrationNode?: Node, -): Fragment { - let newValue: any - let oldValue: any - let branch: BlockFn | undefined - let block: Block | undefined - let scope: EffectScope | undefined - const anchor = __DEV__ - ? createComment(commentLabel || 'dynamic') - : createTextNode() - const fragment: Fragment = shallowReactive({ - nodes: [], - anchor, - [fragmentKey]: true, - }) - - // TODO: SSR - // if (isHydrating) { - // parent = hydrationNode!.parentNode - // setCurrentHydrationNode(hydrationNode!) - // } - - if (once) { - doChange() - } else { - renderEffect(() => doChange()) - } - - // TODO: SSR - // if (isHydrating) { - // parent!.insertBefore(anchor, currentHydrationNode) - // } - - return fragment - - function doChange() { - if ((newValue = expression()) !== oldValue) { - const parent = anchor.parentNode - if (block) { - scope!.stop() - remove(block, parent!) - } - oldValue = newValue - if ((branch = render(newValue))) { - scope = effectScope() - fragment.nodes = block = scope.run(branch)! - parent && insert(block, parent, anchor) - } else { - scope = block = undefined - fragment.nodes = [] - } - } - } -} - -/*! #__NO_SIDE_EFFECTS__ */ -export function createIf( - condition: () => any, - b1: BlockFn, - b2?: BlockFn, - once?: boolean, - // hydrationNode?: Node, -): Fragment { - return createBranch( - () => !!condition(), - value => (value ? b1 : b2), - once, - __DEV__ ? 'if' : undefined, - ) -} diff --git a/packages/runtime-vapor/src/apiCreateSelector.ts b/packages/runtime-vapor/src/apiCreateSelector.ts deleted file mode 100644 index 121c36bf6..000000000 --- a/packages/runtime-vapor/src/apiCreateSelector.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { - type MaybeRefOrGetter, - type ShallowRef, - onScopeDispose, - shallowRef, - toValue, -} from '@vue/reactivity' -import { watchEffect } from './apiWatch' - -export function createSelector( - source: MaybeRefOrGetter, - fn: (key: U, value: T) => boolean = (key, value) => key === value, -): (key: U) => boolean { - let subs = new Map() - let val: T - let oldVal: U - - watchEffect(() => { - val = toValue(source) - const keys = [...subs.keys()] - for (let i = 0, len = keys.length; i < len; i++) { - const key = keys[i] - if (fn(key, val)) { - const o = subs.get(key) - o.value = true - } else if (oldVal !== undefined && fn(key, oldVal)) { - const o = subs.get(key) - o.value = false - } - } - oldVal = val as U - }) - - return key => { - let l: ShallowRef & { _count?: number } - if (!(l = subs.get(key))) subs.set(key, (l = shallowRef())) - l.value - l._count ? l._count++ : (l._count = 1) - onScopeDispose(() => (l._count! > 1 ? l._count!-- : subs.delete(key))) - return l.value !== undefined ? l.value : fn(key, val) - } -} diff --git a/packages/runtime-vapor/src/apiCreateVaporApp.ts b/packages/runtime-vapor/src/apiCreateVaporApp.ts deleted file mode 100644 index c52fd0c00..000000000 --- a/packages/runtime-vapor/src/apiCreateVaporApp.ts +++ /dev/null @@ -1,326 +0,0 @@ -import { NO, getGlobalThis, isFunction, isObject } from '@vue/shared' -import { - type Component, - ComponentInternalInstance, - validateComponentName, -} from './component' -import { warn } from './warning' -import { version } from '.' -import { - normalizeContainer, - render, - setupComponent, - unmountComponent, -} from './apiRender' -import type { InjectionKey } from './apiInject' -import type { RawProps } from './componentProps' -import { type Directive, validateDirectiveName } from './directives' -import { devtoolsInitApp, setDevtoolsHook } from './devtools' - -let uid = 0 -export function createVaporApp( - rootComponent: Component, - rootProps: RawProps | null = null, -): App { - if (rootProps != null && !isObject(rootProps) && !isFunction(rootProps)) { - __DEV__ && - warn(`root props passed to app.mount() must be an object or function.`) - rootProps = null - } - - const target = getGlobalThis() - target.__VUE__ = true - if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { - setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__, target) - } - - const context = createAppContext() - const installedPlugins = new WeakSet() - - let instance: ComponentInternalInstance - - const app: App = (context.app = { - _uid: uid++, - _component: rootComponent, - _props: rootProps, - _container: null, - _context: context, - _instance: null, - - version, - - get config() { - return context.config - }, - - set config(v) { - if (__DEV__) { - warn( - `app.config cannot be replaced. Modify individual options instead.`, - ) - } - }, - - use(plugin: Plugin, ...options: any[]) { - if (installedPlugins.has(plugin)) { - __DEV__ && warn(`Plugin has already been applied to target app.`) - } else if (plugin && isFunction(plugin.install)) { - installedPlugins.add(plugin) - plugin.install(app, ...options) - } else if (isFunction(plugin)) { - installedPlugins.add(plugin) - plugin(app, ...options) - } else if (__DEV__) { - warn( - `A plugin must either be a function or an object with an "install" ` + - `function.`, - ) - } - return app - }, - - component(name: string, component?: Component): any { - if (__DEV__) { - validateComponentName(name, context.config) - } - if (!component) { - return context.components[name] - } - if (__DEV__ && context.components[name]) { - warn(`Component "${name}" has already been registered in target app.`) - } - context.components[name] = component - return app - }, - - directive(name: string, directive?: Directive) { - if (__DEV__) { - validateDirectiveName(name) - } - - if (!directive) { - return context.directives[name] as any - } - if (__DEV__ && context.directives[name]) { - warn(`Directive "${name}" has already been registered in target app.`) - } - context.directives[name] = directive - return app - }, - - mount(container): any { - if (!instance) { - container = normalizeContainer(container) - // #5571 - if (__DEV__ && (container as any).__vue_app__) { - warn( - `There is already an app instance mounted on the host container.\n` + - ` If you want to mount another app on the same host container,` + - ` you need to unmount the previous app by calling \`app.unmount()\` first.`, - ) - } - - // clear content before mounting - if (container.nodeType === 1 /* Node.ELEMENT_NODE */) { - container.textContent = '' - } - - instance = new ComponentInternalInstance( - rootComponent, - rootProps, - null, - false, - context, - ) - setupComponent(instance) - render(instance, container) - - app._container = container - // for devtools and telemetry - ;(container as any).__vue_app__ = app - - if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { - app._instance = instance - devtoolsInitApp(app, version) - } - - if (container instanceof Element) { - container.removeAttribute('v-cloak') - container.setAttribute('data-v-app', '') - } - - return instance - } else if (__DEV__) { - warn( - `App has already been mounted.\n` + - `If you want to remount the same app, move your app creation logic ` + - `into a factory function and create fresh app instances for each ` + - `mount - e.g. \`const createMyApp = () => createApp(App)\``, - ) - } - }, - unmount() { - if (instance) { - unmountComponent(instance) - delete (app._container as any).__vue_app__ - } else if (__DEV__) { - warn(`Cannot unmount an app that is not mounted.`) - } - }, - provide(key, value) { - if (__DEV__ && (key as string | symbol) in context.provides) { - warn( - `App already provides property with key "${String(key)}". ` + - `It will be overwritten with the new value.`, - ) - } - - context.provides[key as string | symbol] = value - - return app - }, - runWithContext(fn) { - const lastApp = currentApp - currentApp = app - try { - return fn() - } finally { - currentApp = lastApp - } - }, - }) - - return app -} - -export function createAppContext(): AppContext { - return { - app: null as any, - mixins: [], - config: { - isNativeTag: NO, - performance: false, - errorHandler: undefined, - warnHandler: undefined, - globalProperties: {}, - }, - provides: Object.create(null), - components: {}, - directives: {}, - } -} - -type PluginInstallFunction = Options extends unknown[] - ? (app: App, ...options: Options) => any - : (app: App, options: Options) => any - -export type ObjectPlugin = { - install: PluginInstallFunction -} -export type FunctionPlugin = PluginInstallFunction & - Partial> - -export type Plugin = - | FunctionPlugin - | ObjectPlugin - -export interface App { - version: string - config: AppConfig - - use( - plugin: Plugin, - ...options: Options - ): this - use(plugin: Plugin, options: Options): this - - component(name: string): Component | undefined - component(name: string, component: T): this - directive(name: string): Directive | undefined - directive(name: string, directive: Directive): this - - mount( - rootContainer: ParentNode | string, - isHydrate?: boolean, - ): ComponentInternalInstance - unmount(): void - provide(key: string | InjectionKey, value: T): App - runWithContext(fn: () => T): T - - // internal, but we need to expose these for the server-renderer and devtools - _uid: number - _component: Component - _props: RawProps | null - _container: ParentNode | null - _context: AppContext - _instance: ComponentInternalInstance | null -} - -export interface AppConfig { - // @private - readonly isNativeTag: (tag: string) => boolean - - performance: boolean - errorHandler?: ( - err: unknown, - instance: ComponentInternalInstance | null, - info: string, - ) => void - warnHandler?: ( - msg: string, - instance: ComponentInternalInstance | null, - trace: string, - ) => void - globalProperties: ComponentCustomProperties & Record -} - -export interface AppContext { - app: App // for devtools - config: AppConfig - mixins: never[] // for devtools, but no longer supported - provides: Record - - /** - * Resolved component registry, only for components with mixins or extends - * @internal - */ - components: Record - /** - * Resolved directive registry, only for components with mixins or extends - * @internal - */ - directives: Record -} - -/** - * @internal Used to identify the current app when using `inject()` within - * `app.runWithContext()`. - */ -export let currentApp: App | null = null - -/** - * Custom properties added to component instances in any way and can be accessed through `this` - * - * @example - * Here is an example of adding a property `$router` to every component instance: - * ```ts - * import { createApp } from 'vue' - * import { Router, createRouter } from 'vue-router' - * - * declare module '@vue/runtime-core' { - * interface ComponentCustomProperties { - * $router: Router - * } - * } - * - * // effectively adding the router to every component instance - * const app = createApp({}) - * const router = createRouter() - * app.config.globalProperties.$router = router - * - * const vm = app.mount('#app') - * // we can access the router from the instance - * vm.$router.push('/') - * ``` - */ -export interface ComponentCustomProperties {} diff --git a/packages/runtime-vapor/src/apiDefineComponent.ts b/packages/runtime-vapor/src/apiDefineComponent.ts deleted file mode 100644 index f726e2e7b..000000000 --- a/packages/runtime-vapor/src/apiDefineComponent.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Component } from './component' - -/*! #__NO_SIDE_EFFECTS__ */ -export function defineComponent(comp: Component): Component { - return comp -} diff --git a/packages/runtime-vapor/src/apiInject.ts b/packages/runtime-vapor/src/apiInject.ts deleted file mode 100644 index 109755347..000000000 --- a/packages/runtime-vapor/src/apiInject.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { isFunction } from '@vue/shared' -import { currentInstance } from './component' -import { currentApp } from './apiCreateVaporApp' -import { warn } from './warning' - -export interface InjectionKey extends Symbol {} - -export function provide | string | number>( - key: K, - value: K extends InjectionKey ? V : T, -): void { - if (!currentInstance) { - if (__DEV__) { - warn(`provide() can only be used inside setup().`) - } - } else { - let provides = currentInstance.provides - // by default an instance inherits its parent's provides object - // but when it needs to provide values of its own, it creates its - // own provides object using parent provides object as prototype. - // this way in `inject` we can simply look up injections from direct - // parent and let the prototype chain do the work. - const parentProvides = - currentInstance.parent && currentInstance.parent.provides - if (parentProvides === provides) { - provides = currentInstance.provides = Object.create(parentProvides) - } - // TS doesn't allow symbol as index type - provides[key as string] = value - } -} - -export function inject(key: InjectionKey | string): T | undefined -export function inject( - key: InjectionKey | string, - defaultValue: T, - treatDefaultAsFactory?: false, -): T -export function inject( - key: InjectionKey | string, - defaultValue: T | (() => T), - treatDefaultAsFactory: true, -): T -export function inject( - key: InjectionKey | string, - defaultValue?: unknown, - treatDefaultAsFactory = false, -) { - const instance = currentInstance - - // also support looking up from app-level provides w/ `app.runWithContext()` - if (instance || currentApp) { - // #2400 - // to support `app.use` plugins, - // fallback to appContext's `provides` if the instance is at root - const provides = instance - ? instance.parent == null - ? instance.appContext && instance.appContext.provides - : instance.parent.provides - : currentApp!._context.provides - - if (provides && (key as string | symbol) in provides) { - // TS doesn't allow symbol as index type - return provides[key as string] - } else if (arguments.length > 1) { - return treatDefaultAsFactory && isFunction(defaultValue) - ? defaultValue.call(instance && instance) - : defaultValue - } else if (__DEV__) { - warn(`injection "${String(key)}" not found.`) - } - } else if (__DEV__) { - warn(`inject() can only be used inside setup() or functional components.`) - } -} - -/** - * Returns true if `inject()` can be used without warning about being called in the wrong place (e.g. outside of - * setup()). This is used by libraries that want to use `inject()` internally without triggering a warning to the end - * user. One example is `useRoute()` in `vue-router`. - */ -export function hasInjectionContext(): boolean { - return !!(currentInstance || currentApp) -} diff --git a/packages/runtime-vapor/src/apiLifecycle.ts b/packages/runtime-vapor/src/apiLifecycle.ts deleted file mode 100644 index 5dc64460e..000000000 --- a/packages/runtime-vapor/src/apiLifecycle.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { - type ComponentInternalInstance, - currentInstance, - setCurrentInstance, -} from './component' -import { warn } from './warning' -import { - type DebuggerEvent, - pauseTracking, - resetTracking, -} from '@vue/reactivity' -import { ErrorTypeStrings, callWithAsyncErrorHandling } from './errorHandling' -import { toHandlerKey } from '@vue/shared' -import { VaporLifecycleHooks } from './enums' - -const injectHook = ( - type: VaporLifecycleHooks, - hook: Function & { __weh?: Function }, - target: ComponentInternalInstance | null = currentInstance, - prepend: boolean = false, -) => { - if (target) { - const hooks = target[type] || (target[type] = []) - const wrappedHook = - hook.__weh || - (hook.__weh = (...args: unknown[]) => { - if (target.isUnmounted) { - return - } - pauseTracking() - const reset = setCurrentInstance(target) - const res = target.scope.run(() => - callWithAsyncErrorHandling(hook, target, type, args), - ) - reset() - resetTracking() - return res - }) - if (prepend) { - hooks.unshift(wrappedHook) - } else { - hooks.push(wrappedHook) - } - return wrappedHook - } else if (__DEV__) { - const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, '')) - warn( - `${apiName} is called when there is no active component instance to be ` + - `associated with. ` + - `Lifecycle injection APIs can only be used during execution of setup().` + - (__FEATURE_SUSPENSE__ - ? ` If you are using async setup(), make sure to register lifecycle ` + - `hooks before the first await statement.` - : ``), - ) - } -} -const createHook = - any>(lifecycle: VaporLifecycleHooks) => - (hook: T, target: ComponentInternalInstance | null = currentInstance) => - injectHook(lifecycle, (...args: unknown[]) => hook(...args), target) -type CreateHook = ( - hook: T, - target?: ComponentInternalInstance | null, -) => void - -export const onBeforeMount: CreateHook = createHook( - VaporLifecycleHooks.BEFORE_MOUNT, -) -export const onMounted: CreateHook = createHook(VaporLifecycleHooks.MOUNTED) -export const onBeforeUpdate: CreateHook = createHook( - VaporLifecycleHooks.BEFORE_UPDATE, -) -export const onUpdated: CreateHook = createHook(VaporLifecycleHooks.UPDATED) -export const onBeforeUnmount: CreateHook = createHook( - VaporLifecycleHooks.BEFORE_UNMOUNT, -) -export const onUnmounted: CreateHook = createHook(VaporLifecycleHooks.UNMOUNTED) - -export type DebuggerHook = (e: DebuggerEvent) => void -export const onRenderTriggered: CreateHook = createHook( - VaporLifecycleHooks.RENDER_TRIGGERED, -) -export const onRenderTracked: CreateHook = createHook( - VaporLifecycleHooks.RENDER_TRACKED, -) - -export type ErrorCapturedHook = ( - err: TError, - instance: ComponentInternalInstance | null, - info: string, -) => boolean | void - -export function onErrorCaptured( - hook: ErrorCapturedHook, - target: ComponentInternalInstance | null = currentInstance, -): void { - injectHook(VaporLifecycleHooks.ERROR_CAPTURED, hook, target) -} diff --git a/packages/runtime-vapor/src/apiRender.ts b/packages/runtime-vapor/src/apiRender.ts deleted file mode 100644 index 9cf898e51..000000000 --- a/packages/runtime-vapor/src/apiRender.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { - type ComponentInternalInstance, - createSetupContext, - getAttrsProxy, - getSlotsProxy, - isVaporComponent, - setCurrentInstance, - validateComponentName, -} from './component' -import { insert, querySelector } from './dom/element' -import { flushPostFlushCbs, queuePostFlushCb } from './scheduler' -import { invokeLifecycle } from './componentLifecycle' -import { VaporLifecycleHooks } from './enums' -import { - pauseTracking, - proxyRefs, - resetTracking, - shallowReadonly, -} from '@vue/reactivity' -import { isArray, isFunction, isObject } from '@vue/shared' -import { VaporErrorCodes, callWithErrorHandling } from './errorHandling' -import { endMeasure, startMeasure } from './profiling' -import { devtoolsComponentAdded } from './devtools' -import { fallThroughAttrs } from './componentAttrs' -import { type Block, findFirstRootElement, fragmentKey } from './block' - -export function setupComponent(instance: ComponentInternalInstance): void { - if (__DEV__) { - startMeasure(instance, `init`) - } - const reset = setCurrentInstance(instance) - instance.scope.run(function componentSetupFn() { - const { type: component, props } = instance - - if (__DEV__) { - if (component.name) { - validateComponentName(component.name, instance.appContext.config) - } - } - - const setupFn = isFunction(component) ? component : component.setup - let stateOrNode: Block | undefined - if (setupFn) { - const setupContext = (instance.setupContext = - setupFn && setupFn.length > 1 ? createSetupContext(instance) : null) - pauseTracking() - stateOrNode = callWithErrorHandling( - setupFn, - instance, - VaporErrorCodes.SETUP_FUNCTION, - [__DEV__ ? shallowReadonly(props) : props, setupContext], - ) - resetTracking() - } - - let block: Block | undefined - - // Skip the type check for production since this is only for Dev HMR - if (__DEV__) { - if ( - stateOrNode && - (stateOrNode instanceof Node || - isVaporComponent(stateOrNode) || - isArray(stateOrNode) || - fragmentKey in stateOrNode) - ) { - block = stateOrNode - } else if (isObject(stateOrNode)) { - instance.setupState = proxyRefs(stateOrNode) - } - - if (!block && component.render) { - pauseTracking() - block = callWithErrorHandling( - component.render, - instance, - VaporErrorCodes.RENDER_FUNCTION, - [ - instance.setupState, // _ctx - shallowReadonly(props), // $props - instance.emit, // $emit - getAttrsProxy(instance), // $attrs - getSlotsProxy(instance), // $slots - ], - ) - resetTracking() - } - } else { - block = stateOrNode - } - - if (!block) { - // TODO: warn no template - block = [] - } - instance.block = block - - const rootElement = findFirstRootElement(instance) - if (rootElement) { - fallThroughAttrs(instance, rootElement) - - // attach scopeId - for (const id of instance.scopeIds) { - rootElement.setAttribute(id, '') - } - } - - return block - }) - reset() - if (__DEV__) { - endMeasure(instance, `init`) - } -} - -export function render( - instance: ComponentInternalInstance, - container: string | ParentNode, -): void { - mountComponent(instance, (container = normalizeContainer(container))) - flushPostFlushCbs() -} - -export function normalizeContainer(container: string | ParentNode): ParentNode { - return typeof container === 'string' - ? (querySelector(container) as ParentNode) - : container -} - -function mountComponent( - instance: ComponentInternalInstance, - container: ParentNode, -) { - instance.container = container - - if (__DEV__) { - startMeasure(instance, 'mount') - } - - // hook: beforeMount - invokeLifecycle(instance, VaporLifecycleHooks.BEFORE_MOUNT) - - insert(instance.block!, instance.container) - - // hook: mounted - invokeLifecycle( - instance, - VaporLifecycleHooks.MOUNTED, - instance => (instance.isMounted = true), - true, - ) - - if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { - devtoolsComponentAdded(instance) - } - - if (__DEV__) { - endMeasure(instance, 'mount') - } - - return instance -} - -export function unmountComponent(instance: ComponentInternalInstance): void { - const { container, scope } = instance - - // hook: beforeUnmount - invokeLifecycle(instance, VaporLifecycleHooks.BEFORE_UNMOUNT) - - scope.stop() - container.textContent = '' - - // hook: unmounted - invokeLifecycle( - instance, - VaporLifecycleHooks.UNMOUNTED, - instance => queuePostFlushCb(() => (instance.isUnmounted = true)), - true, - ) - flushPostFlushCbs() -} diff --git a/packages/runtime-vapor/src/apiSetupHelpers.ts b/packages/runtime-vapor/src/apiSetupHelpers.ts deleted file mode 100644 index 1737f6ec4..000000000 --- a/packages/runtime-vapor/src/apiSetupHelpers.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - type SetupContext, - createSetupContext, - getCurrentInstance, -} from './component' -import { warn } from './warning' - -export function useSlots(): SetupContext['slots'] { - return getContext().slots -} - -export function useAttrs(): SetupContext['attrs'] { - return getContext().attrs -} - -function getContext(): SetupContext { - const i = getCurrentInstance()! - if (__DEV__ && !i) { - warn(`useContext() called without active instance.`) - } - return i.setupContext || (i.setupContext = createSetupContext(i)) -} diff --git a/packages/runtime-vapor/src/apiWatch.ts b/packages/runtime-vapor/src/apiWatch.ts deleted file mode 100644 index 758ae9129..000000000 --- a/packages/runtime-vapor/src/apiWatch.ts +++ /dev/null @@ -1,230 +0,0 @@ -import { - type WatchOptions as BaseWatchOptions, - type ComputedRef, - type DebuggerOptions, - type Ref, - watch as baseWatch, -} from '@vue/reactivity' -import { EMPTY_OBJ, extend, isFunction } from '@vue/shared' -import { currentInstance } from './component' -import { - type SchedulerJob, - VaporSchedulerJobFlags, - queueJob, - queuePostFlushCb, -} from './scheduler' -import { callWithAsyncErrorHandling } from './errorHandling' -import { warn } from './warning' - -export type WatchEffect = (onCleanup: OnCleanup) => void - -export type WatchSource = Ref | ComputedRef | (() => T) - -export type WatchCallback = ( - value: V, - oldValue: OV, - onCleanup: OnCleanup, -) => any - -type MapSources = { - [K in keyof T]: T[K] extends WatchSource - ? Immediate extends true - ? V | undefined - : V - : T[K] extends object - ? Immediate extends true - ? T[K] | undefined - : T[K] - : never -} - -type OnCleanup = (cleanupFn: () => void) => void - -export interface WatchOptionsBase extends DebuggerOptions { - flush?: 'pre' | 'post' | 'sync' -} - -export interface WatchOptions extends WatchOptionsBase { - immediate?: Immediate - deep?: boolean - once?: boolean -} - -export type WatchStopHandle = () => void - -// Simple effect. -export function watchEffect( - effect: WatchEffect, - options?: WatchOptionsBase, -): WatchStopHandle { - return doWatch(effect, null, options) -} - -export function watchPostEffect( - effect: WatchEffect, - options?: DebuggerOptions, -): WatchStopHandle { - return doWatch( - effect, - null, - __DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' }, - ) -} - -export function watchSyncEffect( - effect: WatchEffect, - options?: DebuggerOptions, -): WatchStopHandle { - return doWatch( - effect, - null, - __DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' }, - ) -} - -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, - Immediate extends Readonly = false, ->( - sources: [...T], - cb: WatchCallback, MapSources>, - options?: WatchOptions, -): WatchStopHandle - -// overload: multiple sources w/ `as const` -// watch([foo, bar] as const, () => {}) -// somehow [...T] breaks when the type is readonly -export function watch< - T extends Readonly, - Immediate extends Readonly = false, ->( - source: T, - cb: WatchCallback, MapSources>, - options?: WatchOptions, -): WatchStopHandle - -// overload: watching reactive object w/ cb -export function watch< - T extends object, - Immediate extends Readonly = false, ->( - source: T, - cb: WatchCallback, - options?: WatchOptions, -): WatchStopHandle - -// implementation -export function watch = false>( - source: T | WatchSource, - cb: any, - options?: WatchOptions, -): WatchStopHandle { - if (__DEV__ && !isFunction(cb)) { - warn( - `\`watch(fn, options?)\` signature has been moved to a separate API. ` + - `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` + - `supports \`watch(source, cb, options?) signature.`, - ) - } - return doWatch(source as any, cb, options) -} - -function doWatch( - source: WatchSource | WatchSource[] | WatchEffect | object, - cb: WatchCallback | null, - options: WatchOptions = EMPTY_OBJ, -): WatchStopHandle { - const { immediate, deep, flush, once } = options - - if (__DEV__ && !cb) { - if (immediate !== undefined) { - warn( - `watch() "immediate" option is only respected when using the ` + - `watch(source, callback, options?) signature.`, - ) - } - if (deep !== undefined) { - warn( - `watch() "deep" option is only respected when using the ` + - `watch(source, callback, options?) signature.`, - ) - } - if (once !== undefined) { - warn( - `watch() "once" option is only respected when using the ` + - `watch(source, callback, options?) signature.`, - ) - } - } - - const baseWatchOptions: BaseWatchOptions = extend({}, options) - - if (__DEV__) baseWatchOptions.onWarn = warn - - 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 - // } - // } - - const instance = currentInstance - baseWatchOptions.call = (fn, type, args) => - callWithAsyncErrorHandling(fn, instance, type, args) - - // scheduler - let isPre = false - if (flush === 'post') { - baseWatchOptions.scheduler = job => { - queuePostFlushCb(job) - } - } else if (flush !== 'sync') { - // default: 'pre' - isPre = true - baseWatchOptions.scheduler = (job, isFirstRun) => { - if (isFirstRun) { - job() - } else { - queueJob(job) - } - } - } - - baseWatchOptions.augmentJob = (job: SchedulerJob) => { - // important: mark the job as a watcher callback so that scheduler knows - // it is allowed to self-trigger (#1727) - if (cb) { - job.flags! |= VaporSchedulerJobFlags.ALLOW_RECURSE - } - if (isPre) { - job.flags! |= VaporSchedulerJobFlags.PRE - if (instance) { - job.id = instance.uid - ;(job as SchedulerJob).i = instance - } - } - } - - const watchHandle = baseWatch(source, cb, baseWatchOptions) - - if (__SSR__ && ssrCleanup) ssrCleanup.push(watchHandle) - return watchHandle -} diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index 1d3deb466..2650fcfd7 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -1,5 +1,5 @@ import { isArray } from '@vue/shared' -import { type VaporComponentInstance, isVaporComponent } from './_new/component' +import { type VaporComponentInstance, isVaporComponent } from './component' export const fragmentKey: unique symbol = Symbol(__DEV__ ? `fragmentKey` : ``) diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 859b5ed24..2ef4730b6 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -1,112 +1,44 @@ -import { EffectScope, isRef } from '@vue/reactivity' -import { EMPTY_OBJ, isArray, isBuiltInTag, isFunction } from '@vue/shared' -import type { Block } from './block' import { + type ComponentInternalOptions, type ComponentPropsOptions, - type NormalizedPropsOptions, - type NormalizedRawProps, - type RawProps, - initProps, - normalizePropsOptions, -} from './componentProps' -import { - type EmitFn, + EffectScope, type EmitsOptions, + type GenericAppContext, + type GenericComponentInstance, + type LifecycleHook, + type NormalizedPropsOptions, type ObjectEmitsOptions, - emit, - normalizeEmitsOptions, -} from './componentEmits' -import { type RawSlots, type StaticSlots, initSlots } from './componentSlots' -import { VaporLifecycleHooks } from './enums' -import { warn } from './warning' + nextUid, + popWarningContext, + pushWarningContext, +} from '@vue/runtime-core' +import type { Block } from './block' +import { pauseTracking, resetTracking } from '@vue/reactivity' +import { EMPTY_OBJ, isFunction } from '@vue/shared' import { - type AppConfig, - type AppContext, - createAppContext, -} from './apiCreateVaporApp' -import type { Data } from '@vue/runtime-shared' -import type { ComponentInstance } from './apiCreateComponentSimple' + type RawProps, + getDynamicPropsHandlers, + initStaticProps, +} from './componentProps' +import { setDynamicProp } from './dom/prop' +import { renderEffect } from './renderEffect' -export type Component = FunctionalComponent | ObjectComponent +export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent -type SharedInternalOptions = { - __propsOptions?: NormalizedPropsOptions - __propsHandlers?: [ProxyHandler, ProxyHandler] -} - -export type SetupFn = ( +export type VaporSetupFn = ( props: any, ctx: SetupContext, -) => Block | Data | undefined +) => Block | Record | undefined -export type FunctionalComponent = SetupFn & - Omit & { +export type FunctionalVaporComponent = VaporSetupFn & + Omit & { displayName?: string } & SharedInternalOptions -export class SetupContext { - attrs: Data - emit: EmitFn - slots: Readonly - expose: (exposed?: Record) => void - - constructor(instance: ComponentInstance) { - this.attrs = instance.attrs - this.emit = instance.emit as EmitFn - this.slots = instance.slots - this.expose = (exposed = {}) => { - instance.exposed = exposed - } - } -} - -export function createSetupContext( - instance: ComponentInternalInstance, -): SetupContext { - if (__DEV__) { - // We use getters in dev in case libs like test-utils overwrite instance - // properties (overwrites should not be done in prod) - return Object.freeze({ - get attrs() { - return getAttrsProxy(instance) - }, - get slots() { - return getSlotsProxy(instance) - }, - get emit() { - return (event: string, ...args: any[]) => instance.emit(event, ...args) - }, - expose: (exposed?: Record) => { - if (instance.exposed) { - warn(`expose() should be called only once per setup().`) - } - if (exposed != null) { - let exposedType: string = typeof exposed - if (exposedType === 'object') { - if (isArray(exposed)) { - exposedType = 'array' - } else if (isRef(exposed)) { - exposedType = 'ref' - } - } - if (exposedType !== 'object') { - warn( - `expose() should be passed a plain object, received ${exposedType}.`, - ) - } - } - instance.exposed = exposed || {} - }, - }) as SetupContext - } else { - return new SetupContext(instance) - } -} - -export interface ObjectComponent +export interface ObjectVaporComponent extends ComponentInternalOptions, SharedInternalOptions { - setup?: SetupFn + setup?: VaporSetupFn inheritAttrs?: boolean props?: ComponentPropsOptions emits?: EmitsOptions @@ -116,311 +48,176 @@ export interface ObjectComponent vapor?: boolean } -// Note: can't mark this whole interface internal because some public interfaces -// extend it. -export interface ComponentInternalOptions { +interface SharedInternalOptions { /** - * @internal + * Cached normalized props options. + * In vapor mode there are no mixins so normalized options can be cached + * directly on the component */ - __scopeId?: string + __propsOptions?: NormalizedPropsOptions /** - * @internal + * Cached normalized props proxy handlers. */ - __cssModules?: Data + __propsHandlers?: [ProxyHandler | null, ProxyHandler] /** - * @internal + * Cached normalized emits options. */ - __hmrId?: string - /** - * Compat build only, for bailing out of certain compatibility behavior - */ - __isBuiltIn?: boolean - /** - * This one should be exposed so that devtools can make use of it - */ - __file?: string - /** - * name inferred from filename - */ - __name?: string + __emitsOptions?: ObjectEmitsOptions } -type LifecycleHook = TFn[] | null - -export let currentInstance: ComponentInternalInstance | null = null - -export const getCurrentInstance: () => ComponentInternalInstance | null = () => - currentInstance - -export const setCurrentInstance = (instance: ComponentInternalInstance) => { - const prev = currentInstance - currentInstance = instance - return (): void => { - currentInstance = prev +export function createComponent( + component: VaporComponent, + rawProps?: RawProps, + isSingleRoot?: boolean, +): VaporComponentInstance { + // check if we are the single root of the parent + // if yes, inject parent attrs as dynamic props source + if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) { + if (rawProps) { + ;(rawProps.$ || (rawProps.$ = [])).push(currentInstance.attrs) + } else { + rawProps = { $: [currentInstance.attrs] } + } } + + const instance = new VaporComponentInstance(component, rawProps) + + pauseTracking() + let prevInstance = currentInstance + currentInstance = instance + instance.scope.on() + + if (__DEV__) { + pushWarningContext(instance) + } + + const setupFn = isFunction(component) ? component : component.setup + const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null + instance.block = setupFn!( + instance.props, + // @ts-expect-error + setupContext, + ) as Block // TODO handle return object + + // single root, inherit attrs + if ( + instance.hasFallthrough && + component.inheritAttrs !== false && + instance.block instanceof Element && + Object.keys(instance.attrs).length + ) { + renderEffect(() => { + for (const key in instance.attrs) { + setDynamicProp(instance.block as Element, key, instance.attrs[key]) + } + }) + } + + if (__DEV__) { + popWarningContext() + } + + instance.scope.off() + currentInstance = prevInstance + resetTracking() + return instance } -export const unsetCurrentInstance = (): void => { - currentInstance && currentInstance.scope.off() - currentInstance = null +export let currentInstance: VaporComponentInstance | null = null + +const emptyContext: GenericAppContext = { + app: null as any, + config: {}, + provides: /*@__PURE__*/ Object.create(null), } -const emptyAppContext = createAppContext() - -let uid = 0 -export class ComponentInternalInstance { - vapor = true - +export class VaporComponentInstance implements GenericComponentInstance { uid: number - appContext: AppContext + type: VaporComponent + parent: GenericComponentInstance | null + appContext: GenericAppContext - type: Component - block: Block | null - container: ParentNode - parent: ComponentInternalInstance | null - root: ComponentInternalInstance - - provides: Data + block: Block scope: EffectScope - comps: Set - scopeIds: string[] + rawProps: RawProps | undefined + props: Record + attrs: Record + exposed: Record | null - rawProps: NormalizedRawProps - propsOptions: NormalizedPropsOptions - emitsOptions: ObjectEmitsOptions | null - - // state - setupState: Data - setupContext: SetupContext | null - props: Data - emit: EmitFn emitted: Record | null - attrs: Data - /** - * - `undefined` : no props - * - `false` : all props are static - * - `string[]` : list of props are dynamic - * - `true` : all props as dynamic - */ - dynamicAttrs?: string[] | boolean - slots: StaticSlots - refs: Data - // exposed properties via expose() - exposed?: Record + propsDefaults: Record | null - attrsProxy?: Data - slotsProxy?: StaticSlots + // for useTemplateRef() + refs: Record + // for provide / inject + provides: Record + + hasFallthrough: boolean - // lifecycle isMounted: boolean isUnmounted: boolean - isUpdating: boolean - // TODO: registory of provides, lifecycles, ... - /** - * @internal - */ - // [VaporLifecycleHooks.BEFORE_MOUNT]: LifecycleHook; - bm: LifecycleHook - /** - * @internal - */ - // [VaporLifecycleHooks.MOUNTED]: LifecycleHook; - m: LifecycleHook - /** - * @internal - */ - // [VaporLifecycleHooks.BEFORE_UPDATE]: LifecycleHook; - bu: LifecycleHook - /** - * @internal - */ - // [VaporLifecycleHooks.UPDATED]: LifecycleHook; - u: LifecycleHook - /** - * @internal - */ - // [VaporLifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook; - bum: LifecycleHook - /** - * @internal - */ - // [VaporLifecycleHooks.UNMOUNTED]: LifecycleHook; - um: LifecycleHook - /** - * @internal - */ - // [VaporLifecycleHooks.RENDER_TRACKED]: LifecycleHook; - rtc: LifecycleHook - /** - * @internal - */ - // [VaporLifecycleHooks.RENDER_TRIGGERED]: LifecycleHook; - rtg: LifecycleHook - /** - * @internal - */ - // [VaporLifecycleHooks.ACTIVATED]: LifecycleHook; - a: LifecycleHook - /** - * @internal - */ - // [VaporLifecycleHooks.DEACTIVATED]: LifecycleHook; - da: LifecycleHook - /** - * @internal - */ - // [VaporLifecycleHooks.ERROR_CAPTURED]: LifecycleHook + isDeactivated: boolean + // LifecycleHooks.ERROR_CAPTURED ec: LifecycleHook - constructor( - component: Component, - rawProps: RawProps | null, - slots: RawSlots | null, - once: boolean = false, - // application root node only - appContext?: AppContext, - ) { - this.uid = uid++ - const parent = (this.parent = currentInstance) - this.root = parent ? parent.root : this - const _appContext = (this.appContext = - (parent ? parent.appContext : appContext) || emptyAppContext) - this.block = null - this.container = null! - this.root = null! + // dev only + propsOptions?: NormalizedPropsOptions + emitsOptions?: ObjectEmitsOptions | null + + constructor(comp: VaporComponent, rawProps?: RawProps) { + this.uid = nextUid() + this.type = comp + this.parent = currentInstance + this.appContext = currentInstance + ? currentInstance.appContext + : emptyContext + + this.block = null! // to be set this.scope = new EffectScope(true) - this.provides = parent - ? parent.provides - : Object.create(_appContext.provides) - this.type = component - this.comps = new Set() - this.scopeIds = [] - this.rawProps = null! - this.propsOptions = normalizePropsOptions(component) - this.emitsOptions = normalizeEmitsOptions(component) - // state - this.setupState = EMPTY_OBJ - this.setupContext = null - this.props = EMPTY_OBJ - this.emit = emit.bind(null, this) - this.emitted = null - this.attrs = EMPTY_OBJ - this.slots = EMPTY_OBJ - this.refs = EMPTY_OBJ + this.rawProps = rawProps + this.provides = this.refs = EMPTY_OBJ + this.emitted = this.ec = this.exposed = null + this.isMounted = this.isUnmounted = this.isDeactivated = false - // lifecycle - this.isMounted = false - this.isUnmounted = false - this.isUpdating = false - this[VaporLifecycleHooks.BEFORE_MOUNT] = null - this[VaporLifecycleHooks.MOUNTED] = null - this[VaporLifecycleHooks.BEFORE_UPDATE] = null - this[VaporLifecycleHooks.UPDATED] = null - this[VaporLifecycleHooks.BEFORE_UNMOUNT] = null - this[VaporLifecycleHooks.UNMOUNTED] = null - this[VaporLifecycleHooks.RENDER_TRACKED] = null - this[VaporLifecycleHooks.RENDER_TRIGGERED] = null - this[VaporLifecycleHooks.ACTIVATED] = null - this[VaporLifecycleHooks.DEACTIVATED] = null - this[VaporLifecycleHooks.ERROR_CAPTURED] = null + // init props + this.propsDefaults = null + this.hasFallthrough = false + if (rawProps && rawProps.$) { + // has dynamic props, use proxy + const handlers = getDynamicPropsHandlers(comp, this) + this.props = comp.props ? new Proxy(rawProps, handlers[0]!) : EMPTY_OBJ + this.attrs = new Proxy(rawProps, handlers[1]) + this.hasFallthrough = true + } else { + this.props = {} + this.attrs = {} + this.hasFallthrough = initStaticProps(comp, rawProps, this) + } - initProps(this, rawProps, !isFunction(component), once) - initSlots(this, slots) + // TODO validate props + // TODO init slots } } export function isVaporComponent( - val: unknown, -): val is ComponentInternalInstance { - return val instanceof ComponentInternalInstance + value: unknown, +): value is VaporComponentInstance { + return value instanceof VaporComponentInstance } -export function validateComponentName( - name: string, - { isNativeTag }: AppConfig, -): void { - if (isBuiltInTag(name) || isNativeTag(name)) { - warn( - 'Do not use built-in or reserved HTML elements as component id: ' + name, - ) - } -} +export class SetupContext { + attrs: Record + // emit: EmitFn + // slots: Readonly + expose: (exposed?: Record) => void -/** - * Dev-only - */ -export function getAttrsProxy(instance: ComponentInternalInstance): Data { - return ( - instance.attrsProxy || - (instance.attrsProxy = new Proxy(instance.attrs, { - get(target, key: string) { - return target[key] - }, - set() { - warn(`setupContext.attrs is readonly.`) - return false - }, - deleteProperty() { - warn(`setupContext.attrs is readonly.`) - return false - }, - })) - ) -} - -/** - * Dev-only - */ -export function getSlotsProxy( - instance: ComponentInternalInstance, -): StaticSlots { - return ( - instance.slotsProxy || - (instance.slotsProxy = new Proxy(instance.slots, { - get(target, key: string) { - return target[key] - }, - })) - ) -} - -export function getComponentName( - Component: Component, -): string | false | undefined { - return isFunction(Component) - ? Component.displayName || Component.name - : Component.name || Component.__name -} - -export function formatComponentName( - instance: ComponentInternalInstance | null, - Component: Component, - isRoot = false, -): string { - let name = getComponentName(Component) - if (!name && Component.__file) { - const match = Component.__file.match(/([^/\\]+)\.\w+$/) - if (match) { - name = match[1] + constructor(instance: VaporComponentInstance) { + this.attrs = instance.attrs + // this.emit = instance.emit as EmitFn + // this.slots = instance.slots + this.expose = (exposed = {}) => { + instance.exposed = exposed } } - - if (!name && instance && instance.parent) { - // try to infer the name based on reverse resolution - const inferFromRegistry = (registry: Record | undefined) => { - for (const key in registry) { - if (registry[key] === Component) { - return key - } - } - } - name = inferFromRegistry(instance.appContext.components) - } - - return name ? classify(name) : isRoot ? `App` : `Anonymous` } - -const classifyRE = /(?:^|[-_])(\w)/g -const classify = (str: string): string => - str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '') diff --git a/packages/runtime-vapor/src/componentAttrs.ts b/packages/runtime-vapor/src/componentAttrs.ts deleted file mode 100644 index 635b45a7a..000000000 --- a/packages/runtime-vapor/src/componentAttrs.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { camelize, isArray, normalizeClass, normalizeStyle } from '@vue/shared' -import { type ComponentInternalInstance, currentInstance } from './component' -import { isEmitListener } from './componentEmits' -import { type RawProps, walkRawProps } from './componentProps' -import { renderEffect } from './renderEffect' -import { mergeProp, setDynamicProp } from './dom/prop' - -export function patchAttrs( - instance: ComponentInternalInstance, - hasDynamicProps?: boolean, -): void { - const { - attrs, - rawProps, - propsOptions: [options], - } = instance - - if (!rawProps.length) return - const keys = new Set() - const classes: any[] = [] - const styles: any[] = [] - - walkRawProps(rawProps, registerAttr) - for (const key in attrs) { - if (!keys.has(key)) { - delete attrs[key] - } - } - - setClassOrStyle(classes, 'class', normalizeClass) - setClassOrStyle(styles, 'style', normalizeStyle) - - function setClassOrStyle( - values: any[], - field: 'class' | 'style', - normalize: (value: any) => any, - ) { - if (values.length) { - if (hasDynamicProps) { - Object.defineProperty(attrs, field, { - get() { - return normalize(values.map(value => value())) - }, - enumerable: true, - configurable: true, - }) - } else { - attrs[field] = normalizeClass(values) - } - } - } - - function registerAttr(key: string, value: any, getter?: boolean) { - if ( - (!options || !(camelize(key) in options)) && - !isEmitListener(instance.emitsOptions, key) && - (key === 'class' || key === 'style' || !keys.has(key)) - ) { - keys.add(key) - - if (key === 'class' || key === 'style') { - ;(key === 'class' ? classes : styles).push( - hasDynamicProps - ? getter - ? value - : () => value - : getter - ? value() - : value, - ) - } else if (getter) { - Object.defineProperty(attrs, key, { - get: value, - enumerable: true, - configurable: true, - }) - } else { - attrs[key] = value - } - } - } -} - -export function withAttrs(props: RawProps): RawProps { - const instance = currentInstance! - if (instance.type.inheritAttrs === false) return props - const attrsGetter = () => instance.attrs - if (!props) return [attrsGetter] - if (isArray(props)) { - return [attrsGetter, ...props] - } - return [attrsGetter, props] -} - -export function fallThroughAttrs( - instance: ComponentInternalInstance, - element: Element, -): void { - const { - type: { inheritAttrs }, - dynamicAttrs, - } = instance - if ( - inheritAttrs === false || - dynamicAttrs === true // all props as dynamic - ) - return - - const hasStaticAttrs = dynamicAttrs || dynamicAttrs === false - - let initial: Record | undefined - if (hasStaticAttrs) { - // attrs in static template - initial = {} - for (let i = 0; i < element.attributes.length; i++) { - const attr = element.attributes[i] - if (dynamicAttrs && dynamicAttrs.includes(attr.name)) continue - initial[attr.name] = attr.value - } - } - - renderEffect(() => { - for (const key in instance.attrs) { - if (dynamicAttrs && dynamicAttrs.includes(key)) continue - - let value: unknown - if (hasStaticAttrs && key in initial!) { - value = mergeProp(key, instance.attrs[key], initial![key]) - } else { - value = instance.attrs[key] - } - - setDynamicProp(element, key, value) - } - }) -} - -export function setInheritAttrs(dynamicAttrs?: string[] | boolean): void { - const instance = currentInstance! - if (instance.type.inheritAttrs === false) return - instance.dynamicAttrs = dynamicAttrs -} diff --git a/packages/runtime-vapor/src/componentEmits.ts b/packages/runtime-vapor/src/componentEmits.ts index 74ad89163..f792766a8 100644 --- a/packages/runtime-vapor/src/componentEmits.ts +++ b/packages/runtime-vapor/src/componentEmits.ts @@ -1,185 +1,70 @@ import { - type UnionToIntersection, - camelize, - extend, - hasOwn, - hyphenate, - isArray, - isFunction, - isOn, - isString, - looseToNumber, - toHandlerKey, -} from '@vue/shared' -import type { Component, ComponentInternalInstance } from './component' -import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling' -import { type StaticProps, getDynamicPropValue } from './componentProps' -import { warn } from './warning' - -export type ObjectEmitsOptions = Record< - string, - ((...args: any[]) => any) | null // TODO: call validation? -> - -export type EmitsOptions = ObjectEmitsOptions | string[] - -export type EmitFn< - Options = ObjectEmitsOptions, - Event extends keyof Options = keyof Options, -> = - Options extends Array - ? (event: V, ...args: any[]) => void - : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function - ? (event: string, ...args: any[]) => void - : UnionToIntersection< - { - [key in Event]: Options[key] extends (...args: infer Args) => any - ? (event: key, ...args: Args) => void - : (event: key, ...args: any[]) => void - }[Event] - > - -export function emit( - instance: ComponentInternalInstance, - event: string, - ...rawArgs: any[] -): void { - if (instance.isUnmounted) return - - if (__DEV__) { - const { - emitsOptions, - propsOptions: [propsOptions], - } = instance - if (emitsOptions) { - if (!(event in emitsOptions)) { - if (!propsOptions || !(toHandlerKey(event) in propsOptions)) { - warn( - `Component emitted event "${event}" but it is neither declared in ` + - `the emits option nor as an "${toHandlerKey(event)}" prop.`, - ) - } - } else { - const validator = emitsOptions[event] - if (isFunction(validator)) { - const isValid = validator(...rawArgs) - if (!isValid) { - warn( - `Invalid event arguments: event validation failed for event "${event}".`, - ) - } - } - } - } - } - - const { rawProps } = instance - const hasDynamicProps = rawProps.some(isFunction) - - let handlerName: string - let handler: any - let onceHandler: any - - const isModelListener = event.startsWith('update:') - const modelArg = isModelListener && event.slice(7) - let modifiers: any - - // has v-bind or :[eventName] - if (hasDynamicProps) { - tryGet(key => getDynamicPropValue(rawProps, key)[0]) - } else { - const staticProps = rawProps[0] as StaticProps - tryGet(key => staticProps[key] && staticProps[key]()) - } - - function tryGet(getter: (key: string) => any) { - handler = - getter((handlerName = toHandlerKey(event))) || - // also try camelCase event handler (#2249) - getter((handlerName = toHandlerKey(camelize(event)))) - // for v-model update:xxx events, also trigger kebab-case equivalent - // for props passed via kebab-case - if (!handler && isModelListener) { - handler = getter((handlerName = toHandlerKey(hyphenate(event)))) - } - onceHandler = getter(`${handlerName}Once`) - modifiers = - modelArg && - getter(`${modelArg === 'modelValue' ? 'model' : modelArg}Modifiers`) - } - - // for v-model update:xxx events, apply modifiers on args - let args = rawArgs - if (modifiers) { - const { number, trim } = modifiers - if (trim) { - args = rawArgs.map(a => (isString(a) ? a.trim() : a)) - } - if (number) { - args = rawArgs.map(looseToNumber) - } - } - - // TODO: warn - - if (handler) { - callWithAsyncErrorHandling( - handler, - instance, - VaporErrorCodes.COMPONENT_EVENT_HANDLER, - args, - ) - } - - if (onceHandler) { - if (!instance.emitted) { - instance.emitted = {} - } else if (instance.emitted[handlerName!]) { - return - } - instance.emitted[handlerName!] = true - callWithAsyncErrorHandling( - onceHandler, - instance, - VaporErrorCodes.COMPONENT_EVENT_HANDLER, - args, - ) - } -} + type EmitFn, + type ObjectEmitsOptions, + baseEmit, +} from '@vue/runtime-core' +import { + type VaporComponent, + type VaporComponentInstance, + currentInstance, +} from './component' +import { EMPTY_OBJ, NOOP, hasOwn, isArray } from '@vue/shared' +import { resolveSource } from './componentProps' +/** + * The logic from core isn't too reusable so it's better to duplicate here + */ export function normalizeEmitsOptions( - comp: Component, + comp: VaporComponent, ): ObjectEmitsOptions | null { - // TODO: caching? + const cached = comp.__emitsOptions + if (cached) return cached const raw = comp.emits if (!raw) return null - let normalized: ObjectEmitsOptions = {} + let normalized: ObjectEmitsOptions if (isArray(raw)) { - raw.forEach(key => (normalized[key] = null)) + normalized = {} + for (const key in raw) normalized[key] = null } else { - extend(normalized, raw) + normalized = raw } - return normalized + return (comp.__emitsOptions = normalized) } -// Check if an incoming prop key is a declared emit event listener. -// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are -// both considered matched listeners. -export function isEmitListener( - options: ObjectEmitsOptions | null, - key: string, -): boolean { - if (!options || !isOn(key)) { - return false +export function useEmit(): EmitFn { + if (!currentInstance) { + // TODO warn + return NOOP + } else { + return emit.bind(null, currentInstance) } +} - key = key.slice(2).replace(/Once$/, '') - return ( - hasOwn(options, key[0].toLowerCase() + key.slice(1)) || - hasOwn(options, hyphenate(key)) || - hasOwn(options, key) +export function emit( + instance: VaporComponentInstance, + event: string, + ...rawArgs: any[] +): void { + baseEmit( + instance, + instance.rawProps || EMPTY_OBJ, + propGetter, + event, + ...rawArgs, ) } + +function propGetter(rawProps: Record, key: string) { + const dynamicSources = rawProps.$ + if (dynamicSources) { + let i = dynamicSources.length + while (i--) { + const source = resolveSource(dynamicSources[i]) + if (hasOwn(source, key)) return source[key] + } + } + return rawProps[key] && resolveSource(rawProps[key]) +} diff --git a/packages/runtime-vapor/src/componentFallback.ts b/packages/runtime-vapor/src/componentFallback.ts deleted file mode 100644 index adceb5446..000000000 --- a/packages/runtime-vapor/src/componentFallback.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { ComponentInternalInstance } from './component' -import { - type NormalizedRawProps, - type RawProps, - normalizeRawProps, - walkRawProps, -} from './componentProps' -import { type RawSlots, isDynamicSlotFn } from './componentSlots' -import { renderEffect } from './renderEffect' -import { setClass, setDynamicProp } from './dom/prop' -import { setStyle } from './dom/style' -import { normalizeBlock } from './block' - -export function fallbackComponent( - comp: string, - rawProps: RawProps | null, - slots: RawSlots | null, - instance: ComponentInternalInstance, - singleRoot: boolean = false, -): HTMLElement { - // eslint-disable-next-line no-restricted-globals - const el = document.createElement(comp) - - if (rawProps || Object.keys(instance.attrs).length) { - rawProps = [() => instance.attrs, ...normalizeRawProps(rawProps)] - - renderEffect(() => { - let classes: unknown[] | undefined - let styles: unknown[] | undefined - - walkRawProps( - rawProps as NormalizedRawProps, - (key, valueOrGetter, getter) => { - const value = getter ? valueOrGetter() : valueOrGetter - if (key === 'class') (classes ||= []).push(value) - else if (key === 'style') (styles ||= []).push(value) - else setDynamicProp(el, key, value) - }, - ) - - if (classes) setClass(el, classes) - if (styles) setStyle(el, styles) - }) - } - - if (slots) { - if (!Array.isArray(slots)) slots = [slots] - for (let i = 0; i < slots.length; i++) { - const slot = slots[i] - if (!isDynamicSlotFn(slot) && slot.default) { - const block = slot.default && slot.default() - if (block) el.append(...normalizeBlock(block)) - } - } - } - - if (singleRoot) { - instance.dynamicAttrs = true - for (let i = 0; i < instance.scopeIds.length; i++) { - const id = instance.scopeIds[i] - el.setAttribute(id, '') - } - } - - const scopeId = instance.type.__scopeId - if (scopeId) el.setAttribute(scopeId, '') - - return el -} diff --git a/packages/runtime-vapor/src/componentLifecycle.ts b/packages/runtime-vapor/src/componentLifecycle.ts deleted file mode 100644 index 4c2918c3e..000000000 --- a/packages/runtime-vapor/src/componentLifecycle.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { invokeArrayFns } from '@vue/shared' -import type { VaporLifecycleHooks } from './enums' -import { type ComponentInternalInstance, setCurrentInstance } from './component' -import { queuePostFlushCb } from './scheduler' - -export function invokeLifecycle( - instance: ComponentInternalInstance, - lifecycle: VaporLifecycleHooks, - cb?: (instance: ComponentInternalInstance) => void, - post?: boolean, -): void { - invokeArrayFns(post ? [invokeSub, invokeCurrent] : [invokeCurrent, invokeSub]) - - function invokeCurrent() { - cb && cb(instance) - const hooks = instance[lifecycle] - if (hooks) { - const fn = () => { - const reset = setCurrentInstance(instance) - instance.scope.run(() => invokeArrayFns(hooks)) - reset() - } - post ? queuePostFlushCb(fn) : fn() - } - } - - function invokeSub() { - instance.comps.forEach(comp => invokeLifecycle(comp, lifecycle, cb, post)) - } -} diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts index d04e3d576..560f89240 100644 --- a/packages/runtime-vapor/src/componentProps.ts +++ b/packages/runtime-vapor/src/componentProps.ts @@ -1,392 +1,231 @@ +import { EMPTY_ARR, NO, camelize, hasOwn, isFunction } from '@vue/shared' +import type { VaporComponent, VaporComponentInstance } from './component' import { - EMPTY_ARR, - EMPTY_OBJ, - camelize, - extend, - hasOwn, - hyphenate, - isArray, - isFunction, -} from '@vue/shared' -import type { Data } from '@vue/runtime-shared' -import { shallowReactive } from '@vue/reactivity' -import { warn } from './warning' -import type { Component, ComponentInternalInstance } from './component' -import { patchAttrs } from './componentAttrs' -import { firstEffect } from './renderEffect' + type NormalizedPropsOptions, + baseNormalizePropsOptions, + isEmitListener, + resolvePropValue, +} from '@vue/runtime-core' +import { normalizeEmitsOptions } from './componentEmits' -export type ComponentPropsOptions

= - | ComponentObjectPropsOptions

- | string[] - -export type ComponentObjectPropsOptions

= { - [K in keyof P]: Prop | null +export interface RawProps { + [key: string]: PropSource + $?: DynamicPropsSource[] } -export type Prop = PropOptions | PropType +type PropSource = T | (() => T) -type DefaultFactory = (props: Data) => T | null | undefined +type DynamicPropsSource = PropSource> -export interface PropOptions { - type?: PropType | true | null - required?: boolean - default?: D | DefaultFactory | null | undefined | object - validator?(value: unknown, props: Data): boolean - /** - * @internal - */ - skipFactory?: boolean -} +export function initStaticProps( + comp: VaporComponent, + rawProps: RawProps | undefined, + instance: VaporComponentInstance, +): boolean { + let hasAttrs = false + const { props, attrs } = instance + const [propsOptions, needCastKeys] = normalizePropsOptions(comp) + const emitsOptions = normalizeEmitsOptions(comp) -export type PropType = PropConstructor | PropConstructor[] + // for dev emit check + if (__DEV__) { + instance.propsOptions = normalizePropsOptions(comp) + instance.emitsOptions = emitsOptions + } -type PropConstructor = - | { new (...args: any[]): T & {} } - | { (): T } - | PropMethod - -type PropMethod = [T] extends [ - ((...args: any) => any) | undefined, -] // if is function with args, allowing non-required functions - ? { new (): TConstructor; (): T; readonly prototype: TConstructor } // Create Function like constructor - : never - -enum BooleanFlags { - shouldCast, - shouldCastTrue, -} - -type NormalizedProp = - | null - | (PropOptions & { - [BooleanFlags.shouldCast]?: boolean - [BooleanFlags.shouldCastTrue]?: boolean - }) - -export type NormalizedProps = Record -export type NormalizedPropsOptions = - | [props: NormalizedProps, needCastKeys: string[]] - | [] - -export type StaticProps = Record unknown> -type DynamicProps = () => Data -export type NormalizedRawProps = Array -export type RawProps = NormalizedRawProps | StaticProps | DynamicProps | null - -export function initProps( - instance: ComponentInternalInstance, - rawProps: RawProps, - isStateful: boolean, - once: boolean, -): void { - instance.rawProps = rawProps = normalizeRawProps(rawProps) - const props: Data = {} - const attrs = (instance.attrs = shallowReactive({})) - const [options] = instance.propsOptions - // has v-bind or :[eventName] - const hasDynamicProps = rawProps.some(isFunction) - - if (options) { - if (hasDynamicProps) { - for (const key in options) { - const getter = () => - getDynamicPropValue(rawProps as NormalizedRawProps, key) - registerProp(instance, once, props, key, getter, true) + for (const key in rawProps) { + const normalizedKey = camelize(key) + const needCast = needCastKeys && needCastKeys.includes(normalizedKey) + const source = rawProps[key] + if (propsOptions && normalizedKey in propsOptions) { + if (isFunction(source)) { + Object.defineProperty(props, normalizedKey, { + enumerable: true, + get: needCast + ? () => + resolvePropValue( + propsOptions, + normalizedKey, + source(), + instance, + resolveDefault, + ) + : source, + }) + } else { + props[normalizedKey] = needCast + ? resolvePropValue( + propsOptions, + normalizedKey, + source, + instance, + resolveDefault, + ) + : source } - } else { - const staticProps = rawProps[0] as StaticProps - for (const key in options) { - const rawKey = staticProps && getRawKey(staticProps, key) - if (rawKey) { - registerProp(instance, once, props, key, staticProps[rawKey]) - } else { - registerProp(instance, once, props, key, undefined, false, true) + } else if (!isEmitListener(emitsOptions, key)) { + if (isFunction(source)) { + Object.defineProperty(attrs, key, { + enumerable: true, + get: source, + }) + } else { + attrs[normalizedKey] = source + } + hasAttrs = true + } + } + for (const key in propsOptions) { + if (!(key in props)) { + props[key] = resolvePropValue( + propsOptions, + key, + undefined, + instance, + resolveDefault, + true, + ) + } + } + return hasAttrs +} + +function resolveDefault( + factory: (props: Record) => unknown, + instance: VaporComponentInstance, +) { + return factory.call(null, instance.props) +} + +// TODO optimization: maybe convert functions into computeds +export function resolveSource(source: PropSource): Record { + return isFunction(source) ? source() : source +} + +const passThrough = (val: any) => val + +export function getDynamicPropsHandlers( + comp: VaporComponent, + instance: VaporComponentInstance, +): [ProxyHandler | null, ProxyHandler] { + if (comp.__propsHandlers) { + return comp.__propsHandlers + } + let normalizedKeys: string[] | undefined + const propsOptions = normalizePropsOptions(comp)[0] + const emitsOptions = normalizeEmitsOptions(comp) + const isProp = propsOptions ? (key: string) => hasOwn(propsOptions, key) : NO + + const getProp = (target: RawProps, key: string, asProp: boolean) => { + if (key === '$') return + if (asProp) { + if (!isProp(key)) return + } else if (isProp(key) || isEmitListener(emitsOptions, key)) { + return + } + const castProp = propsOptions + ? (value: any, isAbsent = false) => + asProp + ? resolvePropValue( + propsOptions, + key as string, + value, + instance, + resolveDefault, + isAbsent, + ) + : value + : passThrough + + if (key in target) { + return castProp(resolveSource(target[key as string])) + } + if (target.$) { + let i = target.$.length + let source + while (i--) { + source = resolveSource(target.$[i]) + if (hasOwn(source, key)) { + return castProp(source[key]) } } } + return castProp(undefined, true) } - // validation - if (__DEV__) { - validateProps(rawProps, props, options || {}) - } + const propsHandlers = propsOptions + ? ({ + get: (target, key: string) => getProp(target, key, true), + has: (_, key: string) => isProp(key), + getOwnPropertyDescriptor(target, key: string) { + if (isProp(key)) { + return { + configurable: true, + enumerable: true, + get: () => getProp(target, key, true), + } + } + }, + ownKeys: () => + normalizedKeys || (normalizedKeys = Object.keys(propsOptions)), + set: NO, + deleteProperty: NO, + } satisfies ProxyHandler) + : null - if (hasDynamicProps) { - firstEffect(instance, () => patchAttrs(instance, true)) - } else { - patchAttrs(instance) - } - - if (isStateful) { - instance.props = /* TODO isSSR ? props : */ shallowReactive(props) - } else { - // functional w/ optional props, props === attrs - instance.props = instance.propsOptions === EMPTY_ARR ? attrs : props - } -} - -function registerProp( - instance: ComponentInternalInstance, - once: boolean, - props: Data, - rawKey: string, - getter?: (() => unknown) | (() => DynamicPropResult), - isDynamic?: boolean, - isAbsent?: boolean, -) { - const key = camelize(rawKey) - if (key in props) return - - const [options, needCastKeys] = instance.propsOptions - const needCast = needCastKeys && needCastKeys.includes(key) - const withCast = (value: unknown, absent?: boolean) => - resolvePropValue(options!, props, key, value, absent) - - if (isAbsent) { - props[key] = needCast ? withCast(undefined, true) : undefined - } else { - const get: () => unknown = isDynamic - ? needCast - ? () => withCast(...(getter!() as DynamicPropResult)) - : () => (getter!() as DynamicPropResult)[0] - : needCast - ? () => withCast(getter!()) - : getter! - - const descriptor: PropertyDescriptor = once ? { value: get() } : { get } - descriptor.enumerable = true - Object.defineProperty(props, key, descriptor) - } -} - -export function normalizeRawProps(rawProps: RawProps): NormalizedRawProps { - if (!rawProps) return [] - if (!isArray(rawProps)) return [rawProps] - return rawProps -} - -export function walkRawProps( - rawProps: NormalizedRawProps, - cb: (key: string, value: any, getter?: boolean) => void, -): void { - for (const props of Array.from(rawProps).reverse()) { - if (isFunction(props)) { - const resolved = props() - for (const rawKey in resolved) { - cb(rawKey, resolved[rawKey]) - } - } else { - for (const rawKey in props) { - cb(rawKey, props[rawKey], true) + const hasAttr = (target: RawProps, key: string) => { + if (key === '$' || isProp(key) || isEmitListener(emitsOptions, key)) + return false + if (target.$) { + let i = target.$.length + while (i--) { + if (hasOwn(resolveSource(target.$[i]), key)) { + return true + } } } + return hasOwn(target, key) } -} -function getRawKey(obj: Data, key: string) { - return Object.keys(obj).find(k => camelize(k) === key) -} - -type DynamicPropResult = [value: unknown, absent: boolean] -export function getDynamicPropValue( - rawProps: NormalizedRawProps, - key: string, -): DynamicPropResult { - for (const props of Array.from(rawProps).reverse()) { - if (isFunction(props)) { - const resolved = props() - const rawKey = getRawKey(resolved, key) - if (rawKey) return [resolved[rawKey], false] - } else { - const rawKey = getRawKey(props, key) - if (rawKey) return [props[rawKey](), false] - } - } - return [undefined, true] -} - -export function resolvePropValue( - options: NormalizedProps, - props: Data, - key: string, - value: unknown, - isAbsent?: boolean, -): unknown { - const opt = options[key] - if (opt != null) { - const hasDefault = hasOwn(opt, 'default') - // default values - if (hasDefault && value === undefined) { - const defaultValue = opt.default - if ( - opt.type !== Function && - !opt.skipFactory && - isFunction(defaultValue) - ) { - value = defaultValue.call(null, props) - } else { - value = defaultValue + const attrsHandlers = { + get: (target, key: string) => getProp(target, key, false), + has: hasAttr, + getOwnPropertyDescriptor(target, key: string) { + if (hasAttr(target, key)) { + return { + configurable: true, + enumerable: true, + get: () => getProp(target, key, false), + } } - } - // boolean casting - if (opt[BooleanFlags.shouldCast]) { - if (isAbsent && !hasDefault) { - value = false - } else if ( - opt[BooleanFlags.shouldCastTrue] && - (value === '' || value === hyphenate(key)) - ) { - value = true + }, + ownKeys(target) { + const keys = Object.keys(target) + if (target.$) { + let i = target.$.length + while (i--) { + keys.push(...Object.keys(resolveSource(target.$[i]))) + } } - } - } - return value + return keys.filter(key => hasAttr(target, key)) + }, + set: NO, + deleteProperty: NO, + } satisfies ProxyHandler + + return (comp.__propsHandlers = [propsHandlers, attrsHandlers]) } -export function normalizePropsOptions(comp: Component): NormalizedPropsOptions { +function normalizePropsOptions(comp: VaporComponent): NormalizedPropsOptions { const cached = comp.__propsOptions if (cached) return cached const raw = comp.props - const normalized: NormalizedProps | undefined = {} + if (!raw) return EMPTY_ARR as [] + + const normalized: NormalizedPropsOptions[0] = {} const needCastKeys: NormalizedPropsOptions[1] = [] + baseNormalizePropsOptions(raw, normalized, needCastKeys) - if (!raw) { - return EMPTY_ARR as [] - } - - if (isArray(raw)) { - for (let i = 0; i < raw.length; i++) { - const normalizedKey = camelize(raw[i]) - if (validatePropName(normalizedKey)) { - normalized[normalizedKey] = EMPTY_OBJ - } - } - } else { - for (const key in raw) { - const normalizedKey = camelize(key) - if (validatePropName(normalizedKey)) { - const opt = raw[key] - const prop: NormalizedProp = (normalized[normalizedKey] = - isArray(opt) || isFunction(opt) ? { type: opt } : extend({}, opt)) - if (prop) { - const booleanIndex = getTypeIndex(Boolean, prop.type) - const stringIndex = getTypeIndex(String, prop.type) - prop[BooleanFlags.shouldCast] = booleanIndex > -1 - prop[BooleanFlags.shouldCastTrue] = - stringIndex < 0 || booleanIndex < stringIndex - // if the prop needs boolean casting or default value - if (booleanIndex > -1 || hasOwn(prop, 'default')) { - needCastKeys.push(normalizedKey) - } - } - } - } - } - - const res: NormalizedPropsOptions = (comp.__propsOptions = [ - normalized, - needCastKeys, - ]) - return res -} - -function validatePropName(key: string) { - if (key[0] !== '$') { - return true - } else if (__DEV__) { - warn(`Invalid prop name: "${key}" is a reserved property.`) - } - return false -} - -function getType(ctor: Prop): string { - const match = ctor && ctor.toString().match(/^\s*(function|class) (\w+)/) - return match ? match[2] : ctor === null ? 'null' : '' -} - -function isSameType(a: Prop, b: Prop): boolean { - return getType(a) === getType(b) -} - -function getTypeIndex( - type: Prop, - expectedTypes: PropType | void | null | true, -): number { - if (isArray(expectedTypes)) { - return expectedTypes.findIndex(t => isSameType(t, type)) - } else if (isFunction(expectedTypes)) { - return isSameType(expectedTypes, type) ? 0 : -1 - } - return -1 -} - -/** - * dev only - */ -function validateProps( - rawProps: NormalizedRawProps, - props: Data, - options: NormalizedProps, -) { - const presentKeys: string[] = [] - for (const props of rawProps) { - presentKeys.push(...Object.keys(isFunction(props) ? props() : props)) - } - - for (const key in options) { - const opt = options[key] - if (opt != null) - validateProp( - key, - props[key], - opt, - props, - !presentKeys.some(k => camelize(k) === key), - ) - } -} - -/** - * dev only - */ -function validateProp( - name: string, - value: unknown, - option: PropOptions, - props: Data, - isAbsent: boolean, -) { - const { required, validator } = option - // required! - if (required && isAbsent) { - warn('Missing required prop: "' + name + '"') - return - } - // missing but optional - if (value == null && !required) { - return - } - // NOTE: type check is not supported in vapor - // // type check - // if (type != null && type !== true) { - // let isValid = false - // const types = isArray(type) ? type : [type] - // const expectedTypes = [] - // // value is valid as long as one of the specified types match - // for (let i = 0; i < types.length && !isValid; i++) { - // const { valid, expectedType } = assertType(value, types[i]) - // expectedTypes.push(expectedType || '') - // isValid = valid - // } - // if (!isValid) { - // warn(getInvalidTypeMessage(name, value, expectedTypes)) - // return - // } - // } - - // custom validator - if (validator && !validator(value, props)) { - warn('Invalid prop: custom validator check failed for prop "' + name + '".') - } + return (comp.__propsOptions = [normalized, needCastKeys]) } diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts deleted file mode 100644 index b18201e57..000000000 --- a/packages/runtime-vapor/src/componentSlots.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { type IfAny, isArray, isFunction } from '@vue/shared' -import { - type EffectScope, - effectScope, - isReactive, - shallowReactive, - shallowRef, -} from '@vue/reactivity' -import { - type ComponentInternalInstance, - currentInstance, - setCurrentInstance, -} from './component' -import { type Block, type Fragment, fragmentKey, isValidBlock } from './block' -import { firstEffect, renderEffect } from './renderEffect' -import { createComment, createTextNode, insert, remove } from './dom/element' -import type { NormalizedRawProps } from './componentProps' -import type { Data } from '@vue/runtime-shared' -import { mergeProps } from './dom/prop' - -// TODO: SSR - -export type Slot = ( - ...args: IfAny -) => Block - -export type StaticSlots = Record -export type DynamicSlot = { name: string; fn: Slot } -export type DynamicSlotFn = () => DynamicSlot | DynamicSlot[] | undefined -export type NormalizedRawSlots = Array -export type RawSlots = NormalizedRawSlots | StaticSlots | null - -export const isDynamicSlotFn = isFunction as ( - val: StaticSlots | DynamicSlotFn, -) => val is DynamicSlotFn - -export function initSlots( - instance: ComponentInternalInstance, - rawSlots: RawSlots | null = null, -): void { - if (!rawSlots) return - if (!isArray(rawSlots)) rawSlots = [rawSlots] - - if (!rawSlots.some(slot => isDynamicSlotFn(slot))) { - instance.slots = {} - // with ctx - const slots = rawSlots[0] as StaticSlots - for (const name in slots) { - registerSlot(name, slots[name]) - } - return - } - - instance.slots = shallowReactive({}) - const keys: Set[] = [] - rawSlots.forEach((slots, index) => { - const isDynamicSlot = isDynamicSlotFn(slots) - if (isDynamicSlot) { - firstEffect(instance, () => { - const recordNames = keys[index] || (keys[index] = new Set()) - let dynamicSlot: ReturnType - if (isDynamicSlotFn(slots)) { - dynamicSlot = slots() - if (isArray(dynamicSlot)) { - for (const slot of dynamicSlot) { - registerSlot(slot.name, slot.fn, recordNames) - } - } else if (dynamicSlot) { - registerSlot(dynamicSlot.name, dynamicSlot.fn, recordNames) - } - } else { - } - for (const name of recordNames) { - if ( - !(isArray(dynamicSlot) - ? dynamicSlot.some(s => s.name === name) - : dynamicSlot && dynamicSlot.name === name) - ) { - recordNames.delete(name) - delete instance.slots[name] - } - } - }) - } else { - for (const name in slots) { - registerSlot(name, slots[name]) - } - } - }) - - function registerSlot(name: string, fn: Slot, recordNames?: Set) { - instance.slots[name] = withCtx(fn) - recordNames && recordNames.add(name) - } - - function withCtx(fn: Slot): Slot { - return (...args: any[]) => { - const reset = setCurrentInstance(instance.parent!) - try { - return fn(...(args as any)) - } finally { - reset() - } - } - } -} - -export function createSlot( - name: string | (() => string), - binds?: NormalizedRawProps, - fallback?: Slot, -): Block { - const { slots } = currentInstance! - - const slotBlock = shallowRef() - let slotBranch: Slot | undefined - let slotScope: EffectScope | undefined - - let fallbackBlock: Block | undefined - let fallbackBranch: Slot | undefined - let fallbackScope: EffectScope | undefined - - const normalizeBinds = binds && normalizeSlotProps(binds) - - const isDynamicName = isFunction(name) - // fast path for static slots & without fallback - if (!isDynamicName && !isReactive(slots) && !fallback) { - if ((slotBranch = slots[name])) { - return slotBranch(normalizeBinds) - } else { - return [] - } - } - - const anchor = __DEV__ ? createComment('slot') : createTextNode() - const fragment: Fragment = { - nodes: [], - anchor, - [fragmentKey]: true, - } - - // TODO lifecycle hooks - renderEffect(() => { - const parent = anchor.parentNode - - if ( - !slotBlock.value || // not initied - fallbackScope || // in fallback slot - isValidBlock(slotBlock.value) // slot block is valid - ) { - renderSlot(parent) - } else { - renderFallback(parent) - } - }) - - return fragment - - function renderSlot(parent: ParentNode | null) { - // from fallback to slot - const fromFallback = fallbackScope - if (fromFallback) { - // clean fallback slot - fallbackScope!.stop() - remove(fallbackBlock!, parent!) - fallbackScope = fallbackBlock = undefined - } - - const slotName = isFunction(name) ? name() : name - const branch = slots[slotName]! - - if (branch) { - // init slot scope and block or switch branch - if (!slotScope || slotBranch !== branch) { - // clean previous slot - if (slotScope && !fromFallback) { - slotScope.stop() - remove(slotBlock.value!, parent!) - } - - slotBranch = branch - slotScope = effectScope() - slotBlock.value = slotScope.run(() => slotBranch!(normalizeBinds)) - } - - // if slot block is valid, render it - if (slotBlock.value && isValidBlock(slotBlock.value)) { - fragment.nodes = slotBlock.value - parent && insert(slotBlock.value, parent, anchor) - } else { - renderFallback(parent) - } - } else { - renderFallback(parent) - } - } - - function renderFallback(parent: ParentNode | null) { - // if slot branch is initied, remove it from DOM, but keep the scope - if (slotBranch) { - remove(slotBlock.value!, parent!) - } - - fallbackBranch ||= fallback - if (fallbackBranch) { - fallbackScope = effectScope() - fragment.nodes = fallbackBlock = fallbackScope.run(() => - fallbackBranch!(normalizeBinds), - )! - parent && insert(fallbackBlock, parent, anchor) - } else { - fragment.nodes = [] - } - } -} - -function normalizeSlotProps(rawPropsList: NormalizedRawProps) { - const { length } = rawPropsList - const mergings = length > 1 ? shallowReactive([]) : undefined - const result = shallowReactive({}) - - for (let i = 0; i < length; i++) { - const rawProps = rawPropsList[i] - if (isFunction(rawProps)) { - // dynamic props - renderEffect(() => { - const props = rawProps() - if (mergings) { - mergings[i] = props - } else { - setDynamicProps(props) - } - }) - } else { - // static props - const props = mergings - ? (mergings[i] = shallowReactive({})) - : result - for (const key in rawProps) { - const valueSource = rawProps[key] - renderEffect(() => { - props[key] = valueSource() - }) - } - } - } - - if (mergings) { - renderEffect(() => { - setDynamicProps(mergeProps(...mergings)) - }) - } - - return result - - function setDynamicProps(props: Data) { - const otherExistingKeys = new Set(Object.keys(result)) - for (const key in props) { - result[key] = props[key] - otherExistingKeys.delete(key) - } - // delete other stale props - for (const key of otherExistingKeys) { - delete result[key] - } - } -} diff --git a/packages/runtime-vapor/src/destructure.ts b/packages/runtime-vapor/src/destructure.ts deleted file mode 100644 index 8e68f453f..000000000 --- a/packages/runtime-vapor/src/destructure.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - type ShallowUnwrapRef, - proxyRefs, - shallowReactive, -} from '@vue/reactivity' -import { renderEffect } from './renderEffect' - -export function withDestructure( - assign: (data: ShallowUnwrapRef) => any[], - block: (ctx: any[]) => R, -): (data: T) => R { - return (data: T) => { - const ctx = shallowReactive([]) - renderEffect(() => { - const res = assign(proxyRefs(data)) - const len = res.length - for (let i = 0; i < len; i++) { - ctx[i] = res[i] - } - }) - return block(ctx) - } -} diff --git a/packages/runtime-vapor/src/devtools.ts b/packages/runtime-vapor/src/devtools.ts deleted file mode 100644 index 3e358bfc8..000000000 --- a/packages/runtime-vapor/src/devtools.ts +++ /dev/null @@ -1,168 +0,0 @@ -/* eslint-disable no-restricted-globals */ -import type { App } from './apiCreateVaporApp' -import type { ComponentInternalInstance } from './component' - -interface AppRecord { - id: number - app: App - version: string - types: Record -} - -enum DevtoolsHooks { - APP_INIT = 'app:init', - APP_UNMOUNT = 'app:unmount', - COMPONENT_UPDATED = 'component:updated', - COMPONENT_ADDED = 'component:added', - COMPONENT_REMOVED = 'component:removed', - COMPONENT_EMIT = 'component:emit', - PERFORMANCE_START = 'perf:start', - PERFORMANCE_END = 'perf:end', -} - -export interface DevtoolsHook { - enabled?: boolean - emit: (event: string, ...payload: any[]) => void - on: (event: string, handler: Function) => void - once: (event: string, handler: Function) => void - off: (event: string, handler: Function) => void - appRecords: AppRecord[] - /** - * Added at https://github.com/vuejs/devtools/commit/f2ad51eea789006ab66942e5a27c0f0986a257f9 - * Returns whether the arg was buffered or not - */ - cleanupBuffer?: (matchArg: unknown) => boolean -} - -export let devtools: DevtoolsHook - -let buffer: { event: string; args: any[] }[] = [] - -let devtoolsNotInstalled = false - -function emit(event: string, ...args: any[]) { - if (devtools) { - devtools.emit(event, ...args) - } else if (!devtoolsNotInstalled) { - buffer.push({ event, args }) - } -} - -export function setDevtoolsHook(hook: DevtoolsHook, target: any): void { - devtools = hook - if (devtools) { - devtools.enabled = true - buffer.forEach(({ event, args }) => devtools.emit(event, ...args)) - buffer = [] - } else if ( - // handle late devtools injection - only do this if we are in an actual - // browser environment to avoid the timer handle stalling test runner exit - // (#4815) - typeof window !== 'undefined' && - // some envs mock window but not fully - window.HTMLElement && - // also exclude jsdom - // eslint-disable-next-line no-restricted-syntax - !window.navigator?.userAgent?.includes('jsdom') - ) { - const replay = (target.__VUE_DEVTOOLS_HOOK_REPLAY__ = - target.__VUE_DEVTOOLS_HOOK_REPLAY__ || []) - replay.push((newHook: DevtoolsHook) => { - setDevtoolsHook(newHook, target) - }) - // clear buffer after 3s - the user probably doesn't have devtools installed - // at all, and keeping the buffer will cause memory leaks (#4738) - setTimeout(() => { - if (!devtools) { - target.__VUE_DEVTOOLS_HOOK_REPLAY__ = null - devtoolsNotInstalled = true - buffer = [] - } - }, 3000) - } else { - // non-browser env, assume not installed - devtoolsNotInstalled = true - buffer = [] - } -} - -export function devtoolsInitApp(app: App, version: string): void { - emit(DevtoolsHooks.APP_INIT, app, version, {}) -} - -export function devtoolsUnmountApp(app: App): void { - emit(DevtoolsHooks.APP_UNMOUNT, app) -} - -export const devtoolsComponentAdded: DevtoolsComponentHook = - /*#__PURE__*/ createDevtoolsComponentHook(DevtoolsHooks.COMPONENT_ADDED) - -export const devtoolsComponentUpdated: DevtoolsComponentHook = - /*#__PURE__*/ createDevtoolsComponentHook(DevtoolsHooks.COMPONENT_UPDATED) - -const _devtoolsComponentRemoved = /*#__PURE__*/ createDevtoolsComponentHook( - DevtoolsHooks.COMPONENT_REMOVED, -) - -export const devtoolsComponentRemoved = ( - component: ComponentInternalInstance, -): void => { - if ( - devtools && - typeof devtools.cleanupBuffer === 'function' && - // remove the component if it wasn't buffered - !devtools.cleanupBuffer(component) - ) { - _devtoolsComponentRemoved(component) - } -} - -type DevtoolsComponentHook = (component: ComponentInternalInstance) => void - -/*! #__NO_SIDE_EFFECTS__ */ -function createDevtoolsComponentHook( - hook: DevtoolsHooks, -): DevtoolsComponentHook { - return (component: ComponentInternalInstance) => { - emit( - hook, - component.appContext.app, - component.uid, - component.parent ? component.parent.uid : undefined, - component, - ) - } -} - -export const devtoolsPerfStart: DevtoolsPerformanceHook = - /*#__PURE__*/ createDevtoolsPerformanceHook(DevtoolsHooks.PERFORMANCE_START) - -export const devtoolsPerfEnd: DevtoolsPerformanceHook = - /*#__PURE__*/ createDevtoolsPerformanceHook(DevtoolsHooks.PERFORMANCE_END) - -type DevtoolsPerformanceHook = ( - component: ComponentInternalInstance, - type: string, - time: number, -) => void -function createDevtoolsPerformanceHook( - hook: DevtoolsHooks, -): DevtoolsPerformanceHook { - return (component: ComponentInternalInstance, type: string, time: number) => { - emit(hook, component.appContext.app, component.uid, component, type, time) - } -} - -export function devtoolsComponentEmit( - component: ComponentInternalInstance, - event: string, - params: any[], -): void { - emit( - DevtoolsHooks.COMPONENT_EMIT, - component.appContext.app, - component, - event, - params, - ) -} diff --git a/packages/runtime-vapor/src/directives.ts b/packages/runtime-vapor/src/directives.ts deleted file mode 100644 index 9534d0448..000000000 --- a/packages/runtime-vapor/src/directives.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { isBuiltInDirective } from '@vue/shared' -import { - type ComponentInternalInstance, - currentInstance, - isVaporComponent, -} from './component' -import { warn } from './warning' -import { normalizeBlock } from './block' -import { getCurrentScope } from '@vue/reactivity' -import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling' - -export type DirectiveModifiers = Record - -export interface DirectiveBinding { - instance: ComponentInternalInstance - source: () => V - arg?: string - modifiers?: DirectiveModifiers - dir: Directive -} - -export type DirectiveBindingsMap = Map - -export type Directive = ( - node: T, - binding: DirectiveBinding, -) => void - -export function validateDirectiveName(name: string): void { - if (isBuiltInDirective(name)) { - warn('Do not use built-in directive ids as custom directive id: ' + name) - } -} - -export type DirectiveArguments = Array< - | [Directive | undefined] - | [Directive | undefined, () => any] - | [Directive | undefined, () => any, argument: string] - | [ - Directive | undefined, - value: () => any, - argument: string, - modifiers: DirectiveModifiers, - ] -> - -export function withDirectives( - nodeOrComponent: T, - directives: DirectiveArguments, -): T { - if (!currentInstance) { - __DEV__ && warn(`withDirectives can only be used inside render functions.`) - return nodeOrComponent - } - - let node: Node - if (isVaporComponent(nodeOrComponent)) { - const root = getComponentNode(nodeOrComponent) - if (!root) return nodeOrComponent - node = root - } else { - node = nodeOrComponent - } - - const instance = currentInstance! - const parentScope = getCurrentScope() - - if (__DEV__ && !parentScope) { - warn(`Directives should be used inside of RenderEffectScope.`) - } - - for (const directive of directives) { - let [dir, source = () => undefined, arg, modifiers] = directive - if (!dir) continue - - const binding: DirectiveBinding = { - dir, - source, - instance, - arg, - modifiers, - } - - callWithAsyncErrorHandling(dir, instance, VaporErrorCodes.DIRECTIVE_HOOK, [ - node, - binding, - ]) - } - - return nodeOrComponent -} - -function getComponentNode(component: ComponentInternalInstance) { - if (!component.block) return - - const nodes = normalizeBlock(component.block) - if (nodes.length !== 1) { - warn( - `Runtime directive used on component with non-element root node. ` + - `The directives will not function as intended.`, - ) - return - } - - return nodes[0] -} diff --git a/packages/runtime-vapor/src/directives/vModel.ts b/packages/runtime-vapor/src/directives/vModel.ts deleted file mode 100644 index 1c8ad5adf..000000000 --- a/packages/runtime-vapor/src/directives/vModel.ts +++ /dev/null @@ -1,313 +0,0 @@ -import { - invokeArrayFns, - isArray, - isSet, - looseEqual, - looseIndexOf, - looseToNumber, -} from '@vue/shared' -import type { Directive } from '../directives' -import { addEventListener } from '../dom/event' -import { nextTick } from '../scheduler' -import { warn } from '../warning' -import { MetadataKind, getMetadata } from '../componentMetadata' -import { - onBeforeMount, - onBeforeUnmount, - onBeforeUpdate, - onMounted, -} from '../apiLifecycle' -import { renderEffect } from '../renderEffect' - -type AssignerFn = (value: any) => void -function getModelAssigner(el: Element): AssignerFn { - const metadata = getMetadata(el) - const fn = metadata[MetadataKind.event]['update:modelValue'] || [] - return value => invokeArrayFns(fn, value) -} - -function onCompositionStart(e: Event) { - ;(e.target as any).composing = true -} - -function onCompositionEnd(e: Event) { - const target = e.target as any - if (target.composing) { - target.composing = false - target.dispatchEvent(new Event('input')) - } -} - -const assignFnMap = new WeakMap() -const assigningMap = new WeakMap() - -// We are exporting the v-model runtime directly as vnode hooks so that it can -// be tree-shaken in case v-model is never used. -export const vModelText: Directive< - HTMLInputElement | HTMLTextAreaElement, - any, - 'lazy' | 'trim' | 'number' -> = (el, { source, modifiers: { lazy, trim, number } = {} }) => { - onBeforeMount(() => { - const assigner = getModelAssigner(el) - assignFnMap.set(el, assigner) - - const metadata = getMetadata(el) - const castToNumber = number || metadata[MetadataKind.prop].type === 'number' - - addEventListener(el, lazy ? 'change' : 'input', e => { - if ((e.target as any).composing) return - let domValue: string | number = el.value - if (trim) { - domValue = domValue.trim() - } - if (castToNumber) { - domValue = looseToNumber(domValue) - } - assigner(domValue) - }) - if (trim) { - addEventListener(el, 'change', () => { - el.value = el.value.trim() - }) - } - if (!lazy) { - addEventListener(el, 'compositionstart', onCompositionStart) - addEventListener(el, 'compositionend', onCompositionEnd) - // Safari < 10.2 & UIWebView doesn't fire compositionend when - // switching focus before confirming composition choice - // this also fixes the issue where some browsers e.g. iOS Chrome - // fires "change" instead of "input" on autocomplete. - addEventListener(el, 'change', onCompositionEnd) - } - }) - - onMounted(() => { - const value = source() - el.value = value == null ? '' : value - }) - - renderEffect(() => { - const value = source() - assignFnMap.set(el, getModelAssigner(el)) - - // avoid clearing unresolved text. #2302 - if ((el as any).composing) return - - const elValue = - number || el.type === 'number' ? looseToNumber(el.value) : el.value - const newValue = value == null ? '' : value - - if (elValue === newValue) { - return - } - - // eslint-disable-next-line no-restricted-globals - if (document.activeElement === el && el.type !== 'range') { - if (lazy) { - return - } - if (trim && el.value.trim() === newValue) { - return - } - } - - el.value = newValue - }) -} - -export const vModelRadio: Directive = (el, { source }) => { - onBeforeMount(() => { - el.checked = looseEqual(source(), getValue(el)) - assignFnMap.set(el, getModelAssigner(el)) - addEventListener(el, 'change', () => { - assignFnMap.get(el)!(getValue(el)) - }) - }) - - renderEffect(() => { - const value = source() - assignFnMap.set(el, getModelAssigner(el)) - el.checked = looseEqual(value, getValue(el)) - }) -} - -export const vModelSelect: Directive = ( - el, - { source, modifiers: { number = false } = {} }, -) => { - onBeforeMount(() => { - const value = source() - const isSetModel = isSet(value) - addEventListener(el, 'change', () => { - const selectedVal = Array.prototype.filter - .call(el.options, (o: HTMLOptionElement) => o.selected) - .map((o: HTMLOptionElement) => - number ? looseToNumber(getValue(o)) : getValue(o), - ) - assignFnMap.get(el)!( - el.multiple - ? isSetModel - ? new Set(selectedVal) - : selectedVal - : selectedVal[0], - ) - assigningMap.set(el, true) - - nextTick(() => { - assigningMap.set(el, false) - }) - }) - assignFnMap.set(el, getModelAssigner(el)) - setSelected(el, value, number) - }) - - onBeforeUnmount(() => { - assignFnMap.set(el, getModelAssigner(el)) - }) - - renderEffect(() => { - if (!assigningMap.get(el)) { - setSelected(el, source(), number) - } - }) -} - -function setSelected(el: HTMLSelectElement, value: any, number: boolean) { - const isMultiple = el.multiple - const isArrayValue = isArray(value) - if (isMultiple && !isArrayValue && !isSet(value)) { - __DEV__ && - warn( - `