mirror of https://github.com/vuejs/core.git
refactor: remove runtime-shared
This commit is contained in:
parent
4fe05bdd74
commit
c73ee16345
|
@ -43,7 +43,7 @@ export interface BaseIRNode {
|
||||||
type: IRNodeTypes
|
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 {
|
export interface BlockIRNode extends BaseIRNode {
|
||||||
type: IRNodeTypes.BLOCK
|
type: IRNodeTypes.BLOCK
|
||||||
|
|
|
@ -47,7 +47,6 @@
|
||||||
"homepage": "https://github.com/vuejs/vue-vapor/tree/main/packages/runtime-core#readme",
|
"homepage": "https://github.com/vuejs/vue-vapor/tree/main/packages/runtime-core#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "workspace:*",
|
"@vue/shared": "workspace:*",
|
||||||
"@vue/reactivity": "workspace:*",
|
"@vue/reactivity": "workspace:*"
|
||||||
"@vue/runtime-shared": "workspace:*"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
type Component,
|
type Component,
|
||||||
type ConcreteComponent,
|
type ConcreteComponent,
|
||||||
|
type Data,
|
||||||
type GenericComponent,
|
type GenericComponent,
|
||||||
type GenericComponentInstance,
|
type GenericComponentInstance,
|
||||||
getComponentPublicInstance,
|
getComponentPublicInstance,
|
||||||
|
@ -22,7 +23,6 @@ import { warn } from './warning'
|
||||||
import type { VNode } from './vnode'
|
import type { VNode } from './vnode'
|
||||||
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
|
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
|
||||||
import { NO, extend, isFunction, isObject } from '@vue/shared'
|
import { NO, extend, isFunction, isObject } from '@vue/shared'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
|
||||||
import { version } from '.'
|
import { version } from '.'
|
||||||
import { installAppCompatProperties } from './compat/global'
|
import { installAppCompatProperties } from './compat/global'
|
||||||
import type { NormalizedPropsOptions } from './componentProps'
|
import type { NormalizedPropsOptions } from './componentProps'
|
||||||
|
@ -256,8 +256,8 @@ export function createAppContext(): AppContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CreateAppFunction<HostElement> = (
|
export type CreateAppFunction<HostElement, Comp = Component> = (
|
||||||
rootComponent: GenericComponent,
|
rootComponent: Comp,
|
||||||
rootProps?: Data | null,
|
rootProps?: Data | null,
|
||||||
) => App<HostElement>
|
) => App<HostElement>
|
||||||
|
|
||||||
|
@ -275,13 +275,13 @@ export type AppUnmountFn = (app: App) => void
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export function createAppAPI<HostElement>(
|
export function createAppAPI<HostElement, Comp = Component>(
|
||||||
// render: RootRenderFunction<HostElement>,
|
// render: RootRenderFunction<HostElement>,
|
||||||
// hydrate?: RootHydrateFunction,
|
// hydrate?: RootHydrateFunction,
|
||||||
mount: AppMountFn<HostElement>,
|
mount: AppMountFn<HostElement>,
|
||||||
unmount: AppUnmountFn,
|
unmount: AppUnmountFn,
|
||||||
render?: RootRenderFunction,
|
render?: RootRenderFunction,
|
||||||
): CreateAppFunction<HostElement> {
|
): CreateAppFunction<HostElement, Comp> {
|
||||||
return function createApp(rootComponent, rootProps = null) {
|
return function createApp(rootComponent, rootProps = null) {
|
||||||
if (!isFunction(rootComponent)) {
|
if (!isFunction(rootComponent)) {
|
||||||
rootComponent = extend({}, rootComponent)
|
rootComponent = extend({}, rootComponent)
|
||||||
|
|
|
@ -462,7 +462,7 @@ function installCompatMount(
|
||||||
* function simulates that behavior.
|
* function simulates that behavior.
|
||||||
*/
|
*/
|
||||||
app._createRoot = options => {
|
app._createRoot = options => {
|
||||||
const component = app._component
|
const component = app._component as Component
|
||||||
const vnode = createVNode(component, options.propsData || null)
|
const vnode = createVNode(component, options.propsData || null)
|
||||||
vnode.appContext = context
|
vnode.appContext = context
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { isArray } from '@vue/shared'
|
import { isArray } from '@vue/shared'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
|
||||||
import { inject } from '../apiInject'
|
import { inject } from '../apiInject'
|
||||||
import type { ComponentInternalInstance } from '../component'
|
import type { ComponentInternalInstance, Data } from '../component'
|
||||||
import {
|
import {
|
||||||
type ComponentOptions,
|
type ComponentOptions,
|
||||||
resolveMergedOptions,
|
resolveMergedOptions,
|
||||||
|
|
|
@ -10,11 +10,11 @@ import {
|
||||||
normalizeStyle,
|
normalizeStyle,
|
||||||
toHandlerKey,
|
toHandlerKey,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
|
||||||
import type {
|
import type {
|
||||||
Component,
|
Component,
|
||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
ComponentOptions,
|
ComponentOptions,
|
||||||
|
Data,
|
||||||
InternalRenderFunction,
|
InternalRenderFunction,
|
||||||
} from '../component'
|
} from '../component'
|
||||||
import { currentRenderingInstance } from '../componentRenderContext'
|
import { currentRenderingInstance } from '../componentRenderContext'
|
||||||
|
|
|
@ -7,13 +7,12 @@ import {
|
||||||
isReservedProp,
|
isReservedProp,
|
||||||
normalizeClass,
|
normalizeClass,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import type { ComponentInternalInstance } from '../component'
|
import type { ComponentInternalInstance, Data } from '../component'
|
||||||
import type { Slot } from '../componentSlots'
|
import type { Slot } from '../componentSlots'
|
||||||
import { createSlots } from '../helpers/createSlots'
|
import { createSlots } from '../helpers/createSlots'
|
||||||
import { renderSlot } from '../helpers/renderSlot'
|
import { renderSlot } from '../helpers/renderSlot'
|
||||||
import { toHandlers } from '../helpers/toHandlers'
|
import { toHandlers } from '../helpers/toHandlers'
|
||||||
import { type VNode, mergeProps } from '../vnode'
|
import { type VNode, mergeProps } from '../vnode'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
|
||||||
|
|
||||||
function toObject(arr: Array<any>): Object {
|
function toObject(arr: Array<any>): Object {
|
||||||
const res = {}
|
const res = {}
|
||||||
|
|
|
@ -73,7 +73,6 @@ import {
|
||||||
isObject,
|
isObject,
|
||||||
isPromise,
|
isPromise,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
|
||||||
import type { SuspenseBoundary } from './components/Suspense'
|
import type { SuspenseBoundary } from './components/Suspense'
|
||||||
import type { CompilerOptions } from '@vue/compiler-core'
|
import type { CompilerOptions } from '@vue/compiler-core'
|
||||||
import { markAttrsAccessed } from './componentRenderUtils'
|
import { markAttrsAccessed } from './componentRenderUtils'
|
||||||
|
@ -98,6 +97,8 @@ import { markAsyncBoundary } from './helpers/useId'
|
||||||
import { isAsyncWrapper } from './apiAsyncComponent'
|
import { isAsyncWrapper } from './apiAsyncComponent'
|
||||||
import type { RendererElement } from './renderer'
|
import type { RendererElement } from './renderer'
|
||||||
|
|
||||||
|
export type Data = Record<string, unknown>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public utility type for extracting the instance type of a component.
|
* Public utility type for extracting the instance type of a component.
|
||||||
* Works with all valid component definition types. This is intended to replace
|
* Works with all valid component definition types. This is intended to replace
|
||||||
|
@ -509,7 +510,7 @@ export interface ComponentInternalInstance extends GenericComponentInstance {
|
||||||
* setup related
|
* setup related
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
setupState: Data | null
|
setupState: Data
|
||||||
/**
|
/**
|
||||||
* devtools access to additional info
|
* devtools access to additional info
|
||||||
* @internal
|
* @internal
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
type ComponentInternalOptions,
|
type ComponentInternalOptions,
|
||||||
type ConcreteComponent,
|
type ConcreteComponent,
|
||||||
|
type Data,
|
||||||
type InternalRenderFunction,
|
type InternalRenderFunction,
|
||||||
type SetupContext,
|
type SetupContext,
|
||||||
currentInstance,
|
currentInstance,
|
||||||
|
@ -18,7 +19,6 @@ import {
|
||||||
isPromise,
|
isPromise,
|
||||||
isString,
|
isString,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
|
||||||
import { type Ref, getCurrentScope, isRef, traverse } from '@vue/reactivity'
|
import { type Ref, getCurrentScope, isRef, traverse } from '@vue/reactivity'
|
||||||
import { computed } from './apiComputed'
|
import { computed } from './apiComputed'
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -24,12 +24,12 @@ import {
|
||||||
makeMap,
|
makeMap,
|
||||||
toRawType,
|
toRawType,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import {
|
import {
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
type ComponentOptions,
|
type ComponentOptions,
|
||||||
type ConcreteComponent,
|
type ConcreteComponent,
|
||||||
|
type Data,
|
||||||
type GenericComponentInstance,
|
type GenericComponentInstance,
|
||||||
setCurrentInstance,
|
setCurrentInstance,
|
||||||
} from './component'
|
} from './component'
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
type Component,
|
type Component,
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
|
type Data,
|
||||||
getComponentPublicInstance,
|
getComponentPublicInstance,
|
||||||
isStatefulComponent,
|
isStatefulComponent,
|
||||||
} from './component'
|
} from './component'
|
||||||
|
@ -23,7 +24,6 @@ import {
|
||||||
isGloballyAllowed,
|
isGloballyAllowed,
|
||||||
isString,
|
isString,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
|
||||||
import {
|
import {
|
||||||
ReactiveFlags,
|
ReactiveFlags,
|
||||||
type ShallowUnwrapRef,
|
type ShallowUnwrapRef,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
|
type Data,
|
||||||
type FunctionalComponent,
|
type FunctionalComponent,
|
||||||
getComponentName,
|
getComponentName,
|
||||||
} from './component'
|
} from './component'
|
||||||
|
@ -15,7 +16,6 @@ import {
|
||||||
} from './vnode'
|
} from './vnode'
|
||||||
import { ErrorCodes, handleError } from './errorHandling'
|
import { ErrorCodes, handleError } from './errorHandling'
|
||||||
import { PatchFlags, ShapeFlags, isModelListener, isOn } from '@vue/shared'
|
import { PatchFlags, ShapeFlags, isModelListener, isOn } from '@vue/shared'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { isHmrUpdating } from './hmr'
|
import { isHmrUpdating } from './hmr'
|
||||||
import type { NormalizedProps } from './componentProps'
|
import type { NormalizedProps } from './componentProps'
|
||||||
|
|
|
@ -13,10 +13,10 @@ return withDirectives(h(comp), [
|
||||||
|
|
||||||
import type { VNode } from './vnode'
|
import type { VNode } from './vnode'
|
||||||
import { EMPTY_OBJ, isBuiltInDirective, isFunction } from '@vue/shared'
|
import { EMPTY_OBJ, isBuiltInDirective, isFunction } from '@vue/shared'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import {
|
import {
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
|
type Data,
|
||||||
getComponentPublicInstance,
|
getComponentPublicInstance,
|
||||||
} from './component'
|
} from './component'
|
||||||
import { currentRenderingInstance } from './componentRenderContext'
|
import { currentRenderingInstance } from './componentRenderContext'
|
||||||
|
|
|
@ -14,9 +14,9 @@ import {
|
||||||
openBlock,
|
openBlock,
|
||||||
} from '../vnode'
|
} from '../vnode'
|
||||||
import { PatchFlags, SlotFlags, isSymbol } from '@vue/shared'
|
import { PatchFlags, SlotFlags, isSymbol } from '@vue/shared'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
|
||||||
import { warn } from '../warning'
|
import { warn } from '../warning'
|
||||||
import { isAsyncWrapper } from '../apiAsyncComponent'
|
import { isAsyncWrapper } from '../apiAsyncComponent'
|
||||||
|
import type { Data } from '../component'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiler runtime helper for rendering `<slot/>`
|
* Compiler runtime helper for rendering `<slot/>`
|
||||||
|
|
|
@ -1,8 +1,25 @@
|
||||||
import { toHandlers as _toHandlers } from '@vue/runtime-shared'
|
import { isObject, toHandlerKey } from '@vue/shared'
|
||||||
import { warn } from '../warning'
|
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<string, any>,
|
obj: Record<string, any>,
|
||||||
preserveCaseIfNecessary?: boolean | undefined,
|
preserveCaseIfNecessary?: boolean,
|
||||||
) => Record<string, any> = _toHandlers.bind(undefined, __DEV__ ? warn : NOOP)
|
): Record<string, any> {
|
||||||
|
const ret: Record<string, any> = {}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
import {
|
import {
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
type ComponentOptions,
|
type ComponentOptions,
|
||||||
|
type Data,
|
||||||
type LifecycleHook,
|
type LifecycleHook,
|
||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
setupComponent,
|
setupComponent,
|
||||||
|
@ -39,7 +40,6 @@ import {
|
||||||
isArray,
|
isArray,
|
||||||
isReservedProp,
|
isReservedProp,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
|
||||||
import {
|
import {
|
||||||
type SchedulerJob,
|
type SchedulerJob,
|
||||||
SchedulerJobFlags,
|
SchedulerJobFlags,
|
||||||
|
|
|
@ -12,12 +12,12 @@ import {
|
||||||
normalizeClass,
|
normalizeClass,
|
||||||
normalizeStyle,
|
normalizeStyle,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
|
||||||
import {
|
import {
|
||||||
type ClassComponent,
|
type ClassComponent,
|
||||||
type Component,
|
type Component,
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
type ConcreteComponent,
|
type ConcreteComponent,
|
||||||
|
type Data,
|
||||||
isClassComponent,
|
isClassComponent,
|
||||||
} from './component'
|
} from './component'
|
||||||
import type { RawSlots } from './componentSlots'
|
import type { RawSlots } from './componentSlots'
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import {
|
import {
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
|
type Data,
|
||||||
type GenericComponentInstance,
|
type GenericComponentInstance,
|
||||||
formatComponentName,
|
formatComponentName,
|
||||||
} from './component'
|
} from './component'
|
||||||
import { isFunction, isString } from '@vue/shared'
|
import { isFunction, isString } from '@vue/shared'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
|
||||||
import { isRef, pauseTracking, resetTracking, toRaw } from '@vue/reactivity'
|
import { isRef, pauseTracking, resetTracking, toRaw } from '@vue/reactivity'
|
||||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||||
import { type VNode, isVNode } from './vnode'
|
import { type VNode, isVNode } from './vnode'
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
type App,
|
type App,
|
||||||
|
type Component,
|
||||||
type ConcreteComponent,
|
type ConcreteComponent,
|
||||||
type CreateAppFunction,
|
type CreateAppFunction,
|
||||||
type DefineComponent,
|
type DefineComponent,
|
||||||
|
@ -144,7 +145,7 @@ export const createApp = ((...args) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}) as CreateAppFunction<Element>
|
}) as CreateAppFunction<Element, Component>
|
||||||
|
|
||||||
export const createSSRApp = ((...args) => {
|
export const createSSRApp = ((...args) => {
|
||||||
const app = ensureHydrationRenderer().createApp(...args)
|
const app = ensureHydrationRenderer().createApp(...args)
|
||||||
|
|
|
@ -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.
|
|
|
@ -1 +0,0 @@
|
||||||
# @vue/runtime-shared
|
|
|
@ -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')
|
|
||||||
}
|
|
|
@ -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:*"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { toHandlers } from './toHandlers'
|
|
||||||
export { type Data } from './typeUtils'
|
|
|
@ -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<string, any>,
|
|
||||||
preserveCaseIfNecessary?: boolean,
|
|
||||||
): Record<string, any> {
|
|
||||||
const ret: Record<string, any> = {}
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export type Data = Record<string, unknown>
|
|
|
@ -6,8 +6,8 @@ import {
|
||||||
type SetupFn,
|
type SetupFn,
|
||||||
createVaporApp,
|
createVaporApp,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
} from '../src'
|
} from '../src/_old'
|
||||||
import type { RawProps } from '../src/componentProps'
|
import type { RawProps } from '../src/_old/componentProps'
|
||||||
|
|
||||||
export interface RenderContext {
|
export interface RenderContext {
|
||||||
component: Component
|
component: Component
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ref } from '@vue/reactivity'
|
import { ref } from '@vue/reactivity'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
import { createFor, createSelector, nextTick, renderEffect } from '../src'
|
import { createFor, createSelector, nextTick, renderEffect } from '../src/_old'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ import {
|
||||||
resolveComponent,
|
resolveComponent,
|
||||||
resolveDirective,
|
resolveDirective,
|
||||||
withDirectives,
|
withDirectives,
|
||||||
} from '../src'
|
} from '../src/_old'
|
||||||
import { warn } from '../src/warning'
|
import { warn } from '../src/_old/warning'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { ref, shallowRef } from '@vue/reactivity'
|
import { ref, shallowRef } from '@vue/reactivity'
|
||||||
import { createComponent } from '../src/apiCreateComponent'
|
import { createComponent } from '../src/_old/apiCreateComponent'
|
||||||
import { setRef } from '../src/dom/templateRef'
|
import { setRef } from '../src/dom/templateRef'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
import {
|
import {
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
} from '../src/component'
|
} from '../src/_old/component'
|
||||||
import { defineComponent } from '../src/apiDefineComponent'
|
import { defineComponent } from '../src/_old/apiDefineComponent'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
describe('api: expose', () => {
|
describe('api: expose', () => {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
ref,
|
ref,
|
||||||
renderEffect,
|
renderEffect,
|
||||||
setText,
|
setText,
|
||||||
} from '../src'
|
} from '../src/_old'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
const define = makeRender<any>()
|
const define = makeRender<any>()
|
||||||
|
|
|
@ -24,7 +24,7 @@ import {
|
||||||
renderEffect,
|
renderEffect,
|
||||||
setText,
|
setText,
|
||||||
template,
|
template,
|
||||||
} from '../src'
|
} from '../src/_old'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
import { ITERATE_KEY } from '@vue/reactivity'
|
import { ITERATE_KEY } from '@vue/reactivity'
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
setInheritAttrs,
|
setInheritAttrs,
|
||||||
template,
|
template,
|
||||||
watchEffect,
|
watchEffect,
|
||||||
} from '../src'
|
} from '../src/_old'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { SetupContext } from '../src/component'
|
import type { SetupContext } from '../src/_old/component'
|
||||||
import {
|
import {
|
||||||
createComponent,
|
createComponent,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
|
@ -6,7 +6,7 @@ import {
|
||||||
template,
|
template,
|
||||||
useAttrs,
|
useAttrs,
|
||||||
useSlots,
|
useSlots,
|
||||||
} from '../src'
|
} from '../src/_old'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
const define = makeRender<any>()
|
const define = makeRender<any>()
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
ref,
|
ref,
|
||||||
watchEffect,
|
watchEffect,
|
||||||
watchSyncEffect,
|
watchSyncEffect,
|
||||||
} from '../src'
|
} from '../src/_old'
|
||||||
|
|
||||||
describe('watchEffect and onWatcherCleanup', () => {
|
describe('watchEffect and onWatcherCleanup', () => {
|
||||||
test('basic', async () => {
|
test('basic', async () => {
|
||||||
|
|
|
@ -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 { describe, expect } from 'vitest'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
setText,
|
setText,
|
||||||
template,
|
template,
|
||||||
watchEffect,
|
watchEffect,
|
||||||
} from '../src'
|
} from '../src/_old'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
const define = makeRender<any>()
|
const define = makeRender<any>()
|
||||||
|
|
|
@ -9,8 +9,8 @@ import {
|
||||||
defineComponent,
|
defineComponent,
|
||||||
nextTick,
|
nextTick,
|
||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
} from '../src'
|
} from '../src/_old'
|
||||||
import { isEmitListener } from '../src/componentEmits'
|
import { isEmitListener } from '../src/_old/componentEmits'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
toRefs,
|
toRefs,
|
||||||
watch,
|
watch,
|
||||||
watchEffect,
|
watchEffect,
|
||||||
} from '../src'
|
} from '../src/_old'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
const define = makeRender<any>()
|
const define = makeRender<any>()
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
setText,
|
setText,
|
||||||
template,
|
template,
|
||||||
withDestructure,
|
withDestructure,
|
||||||
} from '../src'
|
} from '../src/_old'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
const define = makeRender<any>()
|
const define = makeRender<any>()
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
vModelDynamic,
|
vModelDynamic,
|
||||||
vModelSelect,
|
vModelSelect,
|
||||||
withDirectives,
|
withDirectives,
|
||||||
} from '../../src'
|
} from '../../src/_old'
|
||||||
import { makeRender } from '../_utils'
|
import { makeRender } from '../_utils'
|
||||||
import { nextTick } from '@vue/runtime-dom'
|
import { nextTick } from '@vue/runtime-dom'
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
template,
|
template,
|
||||||
vShow,
|
vShow,
|
||||||
withDirectives,
|
withDirectives,
|
||||||
} from '../../src'
|
} from '../../src/_old'
|
||||||
import { nextTick, ref } from 'vue'
|
import { nextTick, ref } from 'vue'
|
||||||
import { describe, expect, test } from 'vitest'
|
import { describe, expect, test } from 'vitest'
|
||||||
import { makeRender } from '../_utils'
|
import { makeRender } from '../_utils'
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { setStyle } from '../../src/dom/style'
|
||||||
import {
|
import {
|
||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
setCurrentInstance,
|
setCurrentInstance,
|
||||||
} from '../../src/component'
|
} from '../../src/_old/component'
|
||||||
import { getMetadata, recordPropMetadata } from '../../src/componentMetadata'
|
import { getMetadata, recordPropMetadata } from '../../src/componentMetadata'
|
||||||
import { getCurrentScope } from '@vue/reactivity'
|
import { getCurrentScope } from '@vue/reactivity'
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
setText,
|
setText,
|
||||||
template,
|
template,
|
||||||
watchEffect,
|
watchEffect,
|
||||||
} from '../../src'
|
} from '../../src/_old'
|
||||||
import { makeRender } from '../_utils'
|
import { makeRender } from '../_utils'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
|
|
@ -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 { type RefEl, setRef } from '../src/dom/templateRef'
|
||||||
import { onErrorCaptured, onMounted } from '../src/apiLifecycle'
|
import { onErrorCaptured, onMounted } from '../src/_old/apiLifecycle'
|
||||||
import { createComponent } from '../src/apiCreateComponent'
|
import { createComponent } from '../src/_old/apiCreateComponent'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
import { template } from '../src/dom/template'
|
import { template } from '../src/dom/template'
|
||||||
import { watch, watchEffect } from '../src/apiWatch'
|
import { watch, watchEffect } from '../src/_old/apiWatch'
|
||||||
import { nextTick } from '../src/scheduler'
|
import { nextTick } from '../src/_old/scheduler'
|
||||||
import { ref } from '@vue/reactivity'
|
import { ref } from '@vue/reactivity'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
template,
|
template,
|
||||||
triggerRef,
|
triggerRef,
|
||||||
withDestructure,
|
withDestructure,
|
||||||
} from '../src'
|
} from '../src/_old'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
createVaporApp,
|
createVaporApp,
|
||||||
resolveComponent,
|
resolveComponent,
|
||||||
resolveDirective,
|
resolveDirective,
|
||||||
} from '@vue/runtime-vapor'
|
} from 'packages/runtime-vapor/src/_old'
|
||||||
import { makeRender } from '../_utils'
|
import { makeRender } from '../_utils'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
|
|
@ -8,10 +8,10 @@ import {
|
||||||
setText,
|
setText,
|
||||||
template,
|
template,
|
||||||
withDirectives,
|
withDirectives,
|
||||||
} from '../src'
|
} from '../src/_old'
|
||||||
import type { Mock } from 'vitest'
|
import type { Mock } from 'vitest'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
import { unmountComponent } from '../src/apiRender'
|
import { unmountComponent } from '../src/_old/apiRender'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,11 @@ import {
|
||||||
watchEffect,
|
watchEffect,
|
||||||
watchPostEffect,
|
watchPostEffect,
|
||||||
watchSyncEffect,
|
watchSyncEffect,
|
||||||
} from '../src'
|
} from '../src/_old'
|
||||||
import {
|
import {
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
currentInstance,
|
currentInstance,
|
||||||
} from '../src/component'
|
} from '../src/_old/component'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
const define = makeRender<any>()
|
const define = makeRender<any>()
|
||||||
|
|
|
@ -36,7 +36,9 @@
|
||||||
"homepage": "https://github.com/vuejs/vue-vapor/tree/dev/packages/runtime-vapor#readme",
|
"homepage": "https://github.com/vuejs/vue-vapor/tree/dev/packages/runtime-vapor#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "workspace:*",
|
"@vue/shared": "workspace:*",
|
||||||
"@vue/reactivity": "workspace:*",
|
"@vue/reactivity": "workspace:*"
|
||||||
"@vue/runtime-shared": "workspace:*"
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/runtime-dom": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
_old
|
|
@ -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<ObjectVaporComponent, 'setup'> & {
|
|
||||||
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<any> | null, ProxyHandler<any>]
|
|
||||||
/**
|
|
||||||
* 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<string, any>
|
|
||||||
attrs: Record<string, any>
|
|
||||||
exposed: Record<string, any> | null
|
|
||||||
|
|
||||||
emitted: Record<string, boolean> | null
|
|
||||||
propsDefaults: Record<string, any> | 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<E = EmitsOptions> {
|
|
||||||
attrs: Record<string, any>
|
|
||||||
// emit: EmitFn<E>
|
|
||||||
// slots: Readonly<StaticSlots>
|
|
||||||
expose: (exposed?: Record<string, any>) => void
|
|
||||||
|
|
||||||
constructor(instance: VaporComponentInstance) {
|
|
||||||
this.attrs = instance.attrs
|
|
||||||
// this.emit = instance.emit as EmitFn<E>
|
|
||||||
// this.slots = instance.slots
|
|
||||||
this.expose = (exposed = {}) => {
|
|
||||||
instance.exposed = exposed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<string, any>, 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]()
|
|
||||||
}
|
|
|
@ -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 = any> = T | (() => T)
|
|
||||||
|
|
||||||
type DynamicPropsSource = PropSource<Record<string, any>>
|
|
||||||
|
|
||||||
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<string, any>) => unknown,
|
|
||||||
instance: VaporComponentInstance,
|
|
||||||
) {
|
|
||||||
return factory.call(null, instance.props)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO optimization: maybe convert functions into computeds
|
|
||||||
export function resolveSource(source: PropSource): Record<string, any> {
|
|
||||||
return isFunction(source) ? source() : source
|
|
||||||
}
|
|
||||||
|
|
||||||
const passThrough = (val: any) => val
|
|
||||||
|
|
||||||
export function getDynamicPropsHandlers(
|
|
||||||
comp: VaporComponent,
|
|
||||||
instance: VaporComponentInstance,
|
|
||||||
): [ProxyHandler<RawProps> | null, ProxyHandler<RawProps>] {
|
|
||||||
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<RawProps>)
|
|
||||||
: 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<RawProps>
|
|
||||||
|
|
||||||
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])
|
|
||||||
}
|
|
|
@ -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'
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { normalizeContainer } from '../apiRender'
|
import { normalizeContainer } from './_old/apiRender'
|
||||||
import { insert } from '../dom/element'
|
import { insert } from './dom/element'
|
||||||
import { type VaporComponent, createComponent } from './component'
|
import { type VaporComponent, createComponent } from './component'
|
||||||
import {
|
import {
|
||||||
type AppMountFn,
|
type AppMountFn,
|
||||||
|
@ -8,7 +8,7 @@ import {
|
||||||
createAppAPI,
|
createAppAPI,
|
||||||
} from '@vue/runtime-core'
|
} from '@vue/runtime-core'
|
||||||
|
|
||||||
let _createApp: CreateAppFunction<ParentNode>
|
let _createApp: CreateAppFunction<ParentNode, VaporComponent>
|
||||||
|
|
||||||
const mountApp: AppMountFn<ParentNode> = (app, container) => {
|
const mountApp: AppMountFn<ParentNode> = (app, container) => {
|
||||||
// clear content before mounting
|
// clear content before mounting
|
||||||
|
@ -24,7 +24,10 @@ const unmountApp: AppUnmountFn = app => {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createVaporApp(comp: VaporComponent): any {
|
export const createVaporApp: CreateAppFunction<
|
||||||
|
ParentNode,
|
||||||
|
VaporComponent
|
||||||
|
> = comp => {
|
||||||
if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp)
|
if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp)
|
||||||
const app = _createApp(comp)
|
const app = _createApp(comp)
|
||||||
const mount = app.mount
|
const mount = app.mount
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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<any>,
|
|
||||||
key: ShallowRef<any>,
|
|
||||||
index: ShallowRef<number | undefined>,
|
|
||||||
]
|
|
||||||
key: any
|
|
||||||
}
|
|
||||||
|
|
||||||
type Source = any[] | Record<any, any> | number | Set<any> | Map<any, any>
|
|
||||||
|
|
||||||
/*! #__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<newIndex, oldIndex>
|
|
||||||
// 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<any, any> | number | Set<any> | Map<any, any>,
|
|
||||||
getSlot: (item: any, key: any, index?: number) => DynamicSlot,
|
|
||||||
): DynamicSlot[] {
|
|
||||||
const sourceLength = getLength(source)
|
|
||||||
const slots = new Array<DynamicSlot>(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<any>).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<any>)
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
import {
|
|
||||||
type MaybeRefOrGetter,
|
|
||||||
type ShallowRef,
|
|
||||||
onScopeDispose,
|
|
||||||
shallowRef,
|
|
||||||
toValue,
|
|
||||||
} from '@vue/reactivity'
|
|
||||||
import { watchEffect } from './apiWatch'
|
|
||||||
|
|
||||||
export function createSelector<T, U extends T>(
|
|
||||||
source: MaybeRefOrGetter<T>,
|
|
||||||
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<boolean | undefined> & { _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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 = any[]> = Options extends unknown[]
|
|
||||||
? (app: App, ...options: Options) => any
|
|
||||||
: (app: App, options: Options) => any
|
|
||||||
|
|
||||||
export type ObjectPlugin<Options = any[]> = {
|
|
||||||
install: PluginInstallFunction<Options>
|
|
||||||
}
|
|
||||||
export type FunctionPlugin<Options = any[]> = PluginInstallFunction<Options> &
|
|
||||||
Partial<ObjectPlugin<Options>>
|
|
||||||
|
|
||||||
export type Plugin<Options = any[]> =
|
|
||||||
| FunctionPlugin<Options>
|
|
||||||
| ObjectPlugin<Options>
|
|
||||||
|
|
||||||
export interface App {
|
|
||||||
version: string
|
|
||||||
config: AppConfig
|
|
||||||
|
|
||||||
use<Options extends unknown[]>(
|
|
||||||
plugin: Plugin<Options>,
|
|
||||||
...options: Options
|
|
||||||
): this
|
|
||||||
use<Options>(plugin: Plugin<Options>, options: Options): this
|
|
||||||
|
|
||||||
component(name: string): Component | undefined
|
|
||||||
component<T extends Component>(name: string, component: T): this
|
|
||||||
directive<T = any, V = any>(name: string): Directive<T, V> | undefined
|
|
||||||
directive<T = any, V = any>(name: string, directive: Directive<T, V>): this
|
|
||||||
|
|
||||||
mount(
|
|
||||||
rootContainer: ParentNode | string,
|
|
||||||
isHydrate?: boolean,
|
|
||||||
): ComponentInternalInstance
|
|
||||||
unmount(): void
|
|
||||||
provide<T>(key: string | InjectionKey<T>, value: T): App
|
|
||||||
runWithContext<T>(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<string, any>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AppContext {
|
|
||||||
app: App // for devtools
|
|
||||||
config: AppConfig
|
|
||||||
mixins: never[] // for devtools, but no longer supported
|
|
||||||
provides: Record<string | symbol, any>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolved component registry, only for components with mixins or extends
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
components: Record<string, Component>
|
|
||||||
/**
|
|
||||||
* Resolved directive registry, only for components with mixins or extends
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
directives: Record<string, Directive>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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 {}
|
|
|
@ -1,6 +0,0 @@
|
||||||
import type { Component } from './component'
|
|
||||||
|
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
|
||||||
export function defineComponent(comp: Component): Component {
|
|
||||||
return comp
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
import { isFunction } from '@vue/shared'
|
|
||||||
import { currentInstance } from './component'
|
|
||||||
import { currentApp } from './apiCreateVaporApp'
|
|
||||||
import { warn } from './warning'
|
|
||||||
|
|
||||||
export interface InjectionKey<T> extends Symbol {}
|
|
||||||
|
|
||||||
export function provide<T, K = InjectionKey<T> | string | number>(
|
|
||||||
key: K,
|
|
||||||
value: K extends InjectionKey<infer V> ? 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<T>(key: InjectionKey<T> | string): T | undefined
|
|
||||||
export function inject<T>(
|
|
||||||
key: InjectionKey<T> | string,
|
|
||||||
defaultValue: T,
|
|
||||||
treatDefaultAsFactory?: false,
|
|
||||||
): T
|
|
||||||
export function inject<T>(
|
|
||||||
key: InjectionKey<T> | string,
|
|
||||||
defaultValue: T | (() => T),
|
|
||||||
treatDefaultAsFactory: true,
|
|
||||||
): T
|
|
||||||
export function inject(
|
|
||||||
key: InjectionKey<any> | 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)
|
|
||||||
}
|
|
|
@ -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 =
|
|
||||||
<T extends Function = () => any>(lifecycle: VaporLifecycleHooks) =>
|
|
||||||
(hook: T, target: ComponentInternalInstance | null = currentInstance) =>
|
|
||||||
injectHook(lifecycle, (...args: unknown[]) => hook(...args), target)
|
|
||||||
type CreateHook<T = any> = (
|
|
||||||
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<DebuggerHook>(
|
|
||||||
VaporLifecycleHooks.RENDER_TRIGGERED,
|
|
||||||
)
|
|
||||||
export const onRenderTracked: CreateHook = createHook<DebuggerHook>(
|
|
||||||
VaporLifecycleHooks.RENDER_TRACKED,
|
|
||||||
)
|
|
||||||
|
|
||||||
export type ErrorCapturedHook<TError = unknown> = (
|
|
||||||
err: TError,
|
|
||||||
instance: ComponentInternalInstance | null,
|
|
||||||
info: string,
|
|
||||||
) => boolean | void
|
|
||||||
|
|
||||||
export function onErrorCaptured<TError = Error>(
|
|
||||||
hook: ErrorCapturedHook<TError>,
|
|
||||||
target: ComponentInternalInstance | null = currentInstance,
|
|
||||||
): void {
|
|
||||||
injectHook(VaporLifecycleHooks.ERROR_CAPTURED, hook, target)
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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<T = any> = Ref<T> | ComputedRef<T> | (() => T)
|
|
||||||
|
|
||||||
export type WatchCallback<V = any, OV = any> = (
|
|
||||||
value: V,
|
|
||||||
oldValue: OV,
|
|
||||||
onCleanup: OnCleanup,
|
|
||||||
) => any
|
|
||||||
|
|
||||||
type MapSources<T, Immediate> = {
|
|
||||||
[K in keyof T]: T[K] extends WatchSource<infer V>
|
|
||||||
? 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<Immediate = boolean> 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<unknown> | object)[]
|
|
||||||
|
|
||||||
// overload: single source + cb
|
|
||||||
export function watch<T, Immediate extends Readonly<boolean> = false>(
|
|
||||||
source: WatchSource<T>,
|
|
||||||
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
|
|
||||||
options?: WatchOptions<Immediate>,
|
|
||||||
): WatchStopHandle
|
|
||||||
|
|
||||||
// overload: array of multiple sources + cb
|
|
||||||
export function watch<
|
|
||||||
T extends MultiWatchSources,
|
|
||||||
Immediate extends Readonly<boolean> = false,
|
|
||||||
>(
|
|
||||||
sources: [...T],
|
|
||||||
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
|
||||||
options?: WatchOptions<Immediate>,
|
|
||||||
): 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<MultiWatchSources>,
|
|
||||||
Immediate extends Readonly<boolean> = false,
|
|
||||||
>(
|
|
||||||
source: T,
|
|
||||||
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
|
||||||
options?: WatchOptions<Immediate>,
|
|
||||||
): WatchStopHandle
|
|
||||||
|
|
||||||
// overload: watching reactive object w/ cb
|
|
||||||
export function watch<
|
|
||||||
T extends object,
|
|
||||||
Immediate extends Readonly<boolean> = false,
|
|
||||||
>(
|
|
||||||
source: T,
|
|
||||||
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
|
|
||||||
options?: WatchOptions<Immediate>,
|
|
||||||
): WatchStopHandle
|
|
||||||
|
|
||||||
// implementation
|
|
||||||
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
|
|
||||||
source: T | WatchSource<T>,
|
|
||||||
cb: any,
|
|
||||||
options?: WatchOptions<Immediate>,
|
|
||||||
): 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
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { isArray } from '@vue/shared'
|
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` : ``)
|
export const fragmentKey: unique symbol = Symbol(__DEV__ ? `fragmentKey` : ``)
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
import {
|
||||||
|
type ComponentInternalOptions,
|
||||||
type ComponentPropsOptions,
|
type ComponentPropsOptions,
|
||||||
type NormalizedPropsOptions,
|
EffectScope,
|
||||||
type NormalizedRawProps,
|
|
||||||
type RawProps,
|
|
||||||
initProps,
|
|
||||||
normalizePropsOptions,
|
|
||||||
} from './componentProps'
|
|
||||||
import {
|
|
||||||
type EmitFn,
|
|
||||||
type EmitsOptions,
|
type EmitsOptions,
|
||||||
|
type GenericAppContext,
|
||||||
|
type GenericComponentInstance,
|
||||||
|
type LifecycleHook,
|
||||||
|
type NormalizedPropsOptions,
|
||||||
type ObjectEmitsOptions,
|
type ObjectEmitsOptions,
|
||||||
emit,
|
nextUid,
|
||||||
normalizeEmitsOptions,
|
popWarningContext,
|
||||||
} from './componentEmits'
|
pushWarningContext,
|
||||||
import { type RawSlots, type StaticSlots, initSlots } from './componentSlots'
|
} from '@vue/runtime-core'
|
||||||
import { VaporLifecycleHooks } from './enums'
|
import type { Block } from './block'
|
||||||
import { warn } from './warning'
|
import { pauseTracking, resetTracking } from '@vue/reactivity'
|
||||||
|
import { EMPTY_OBJ, isFunction } from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
type AppConfig,
|
type RawProps,
|
||||||
type AppContext,
|
getDynamicPropsHandlers,
|
||||||
createAppContext,
|
initStaticProps,
|
||||||
} from './apiCreateVaporApp'
|
} from './componentProps'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
import { setDynamicProp } from './dom/prop'
|
||||||
import type { ComponentInstance } from './apiCreateComponentSimple'
|
import { renderEffect } from './renderEffect'
|
||||||
|
|
||||||
export type Component = FunctionalComponent | ObjectComponent
|
export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent
|
||||||
|
|
||||||
type SharedInternalOptions = {
|
export type VaporSetupFn = (
|
||||||
__propsOptions?: NormalizedPropsOptions
|
|
||||||
__propsHandlers?: [ProxyHandler<any>, ProxyHandler<any>]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SetupFn = (
|
|
||||||
props: any,
|
props: any,
|
||||||
ctx: SetupContext,
|
ctx: SetupContext,
|
||||||
) => Block | Data | undefined
|
) => Block | Record<string, any> | undefined
|
||||||
|
|
||||||
export type FunctionalComponent = SetupFn &
|
export type FunctionalVaporComponent = VaporSetupFn &
|
||||||
Omit<ObjectComponent, 'setup'> & {
|
Omit<ObjectVaporComponent, 'setup'> & {
|
||||||
displayName?: string
|
displayName?: string
|
||||||
} & SharedInternalOptions
|
} & SharedInternalOptions
|
||||||
|
|
||||||
export class SetupContext<E = EmitsOptions> {
|
export interface ObjectVaporComponent
|
||||||
attrs: Data
|
|
||||||
emit: EmitFn<E>
|
|
||||||
slots: Readonly<StaticSlots>
|
|
||||||
expose: (exposed?: Record<string, any>) => void
|
|
||||||
|
|
||||||
constructor(instance: ComponentInstance) {
|
|
||||||
this.attrs = instance.attrs
|
|
||||||
this.emit = instance.emit as EmitFn<E>
|
|
||||||
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<string, any>) => {
|
|
||||||
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
|
|
||||||
extends ComponentInternalOptions,
|
extends ComponentInternalOptions,
|
||||||
SharedInternalOptions {
|
SharedInternalOptions {
|
||||||
setup?: SetupFn
|
setup?: VaporSetupFn
|
||||||
inheritAttrs?: boolean
|
inheritAttrs?: boolean
|
||||||
props?: ComponentPropsOptions
|
props?: ComponentPropsOptions
|
||||||
emits?: EmitsOptions
|
emits?: EmitsOptions
|
||||||
|
@ -116,311 +48,176 @@ export interface ObjectComponent
|
||||||
vapor?: boolean
|
vapor?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: can't mark this whole interface internal because some public interfaces
|
interface SharedInternalOptions {
|
||||||
// extend it.
|
|
||||||
export interface ComponentInternalOptions {
|
|
||||||
/**
|
/**
|
||||||
* @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<any> | null, ProxyHandler<any>]
|
||||||
/**
|
/**
|
||||||
* @internal
|
* Cached normalized emits options.
|
||||||
*/
|
*/
|
||||||
__hmrId?: string
|
__emitsOptions?: ObjectEmitsOptions
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LifecycleHook<TFn = Function> = TFn[] | null
|
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] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export let currentInstance: ComponentInternalInstance | null = null
|
const instance = new VaporComponentInstance(component, rawProps)
|
||||||
|
|
||||||
export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
|
pauseTracking()
|
||||||
currentInstance
|
let prevInstance = currentInstance
|
||||||
|
|
||||||
export const setCurrentInstance = (instance: ComponentInternalInstance) => {
|
|
||||||
const prev = currentInstance
|
|
||||||
currentInstance = instance
|
currentInstance = instance
|
||||||
return (): void => {
|
instance.scope.on()
|
||||||
currentInstance = prev
|
|
||||||
}
|
if (__DEV__) {
|
||||||
|
pushWarningContext(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unsetCurrentInstance = (): void => {
|
const setupFn = isFunction(component) ? component : component.setup
|
||||||
currentInstance && currentInstance.scope.off()
|
const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null
|
||||||
currentInstance = 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])
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyAppContext = createAppContext()
|
if (__DEV__) {
|
||||||
|
popWarningContext()
|
||||||
|
}
|
||||||
|
|
||||||
let uid = 0
|
instance.scope.off()
|
||||||
export class ComponentInternalInstance {
|
currentInstance = prevInstance
|
||||||
vapor = true
|
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
|
uid: number
|
||||||
appContext: AppContext
|
type: VaporComponent
|
||||||
|
parent: GenericComponentInstance | null
|
||||||
|
appContext: GenericAppContext
|
||||||
|
|
||||||
type: Component
|
block: Block
|
||||||
block: Block | null
|
|
||||||
container: ParentNode
|
|
||||||
parent: ComponentInternalInstance | null
|
|
||||||
root: ComponentInternalInstance
|
|
||||||
|
|
||||||
provides: Data
|
|
||||||
scope: EffectScope
|
scope: EffectScope
|
||||||
comps: Set<ComponentInternalInstance>
|
rawProps: RawProps | undefined
|
||||||
scopeIds: string[]
|
props: Record<string, any>
|
||||||
|
attrs: Record<string, any>
|
||||||
|
exposed: Record<string, any> | null
|
||||||
|
|
||||||
rawProps: NormalizedRawProps
|
|
||||||
propsOptions: NormalizedPropsOptions
|
|
||||||
emitsOptions: ObjectEmitsOptions | null
|
|
||||||
|
|
||||||
// state
|
|
||||||
setupState: Data
|
|
||||||
setupContext: SetupContext | null
|
|
||||||
props: Data
|
|
||||||
emit: EmitFn
|
|
||||||
emitted: Record<string, boolean> | null
|
emitted: Record<string, boolean> | null
|
||||||
attrs: Data
|
propsDefaults: Record<string, any> | null
|
||||||
/**
|
|
||||||
* - `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<string, any>
|
|
||||||
|
|
||||||
attrsProxy?: Data
|
// for useTemplateRef()
|
||||||
slotsProxy?: StaticSlots
|
refs: Record<string, any>
|
||||||
|
// for provide / inject
|
||||||
|
provides: Record<string, any>
|
||||||
|
|
||||||
|
hasFallthrough: boolean
|
||||||
|
|
||||||
// lifecycle
|
|
||||||
isMounted: boolean
|
isMounted: boolean
|
||||||
isUnmounted: boolean
|
isUnmounted: boolean
|
||||||
isUpdating: boolean
|
isDeactivated: boolean
|
||||||
// TODO: registory of provides, lifecycles, ...
|
// LifecycleHooks.ERROR_CAPTURED
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
ec: LifecycleHook
|
ec: LifecycleHook
|
||||||
|
|
||||||
constructor(
|
// dev only
|
||||||
component: Component,
|
propsOptions?: NormalizedPropsOptions
|
||||||
rawProps: RawProps | null,
|
emitsOptions?: ObjectEmitsOptions | null
|
||||||
slots: RawSlots | null,
|
|
||||||
once: boolean = false,
|
constructor(comp: VaporComponent, rawProps?: RawProps) {
|
||||||
// application root node only
|
this.uid = nextUid()
|
||||||
appContext?: AppContext,
|
this.type = comp
|
||||||
) {
|
this.parent = currentInstance
|
||||||
this.uid = uid++
|
this.appContext = currentInstance
|
||||||
const parent = (this.parent = currentInstance)
|
? currentInstance.appContext
|
||||||
this.root = parent ? parent.root : this
|
: emptyContext
|
||||||
const _appContext = (this.appContext =
|
|
||||||
(parent ? parent.appContext : appContext) || emptyAppContext)
|
this.block = null! // to be set
|
||||||
this.block = null
|
|
||||||
this.container = null!
|
|
||||||
this.root = null!
|
|
||||||
this.scope = new EffectScope(true)
|
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.rawProps = rawProps
|
||||||
this.setupState = EMPTY_OBJ
|
this.provides = this.refs = EMPTY_OBJ
|
||||||
this.setupContext = null
|
this.emitted = this.ec = this.exposed = null
|
||||||
this.props = EMPTY_OBJ
|
this.isMounted = this.isUnmounted = this.isDeactivated = false
|
||||||
this.emit = emit.bind(null, this)
|
|
||||||
this.emitted = null
|
|
||||||
this.attrs = EMPTY_OBJ
|
|
||||||
this.slots = EMPTY_OBJ
|
|
||||||
this.refs = EMPTY_OBJ
|
|
||||||
|
|
||||||
// lifecycle
|
// init props
|
||||||
this.isMounted = false
|
this.propsDefaults = null
|
||||||
this.isUnmounted = false
|
this.hasFallthrough = false
|
||||||
this.isUpdating = false
|
if (rawProps && rawProps.$) {
|
||||||
this[VaporLifecycleHooks.BEFORE_MOUNT] = null
|
// has dynamic props, use proxy
|
||||||
this[VaporLifecycleHooks.MOUNTED] = null
|
const handlers = getDynamicPropsHandlers(comp, this)
|
||||||
this[VaporLifecycleHooks.BEFORE_UPDATE] = null
|
this.props = comp.props ? new Proxy(rawProps, handlers[0]!) : EMPTY_OBJ
|
||||||
this[VaporLifecycleHooks.UPDATED] = null
|
this.attrs = new Proxy(rawProps, handlers[1])
|
||||||
this[VaporLifecycleHooks.BEFORE_UNMOUNT] = null
|
this.hasFallthrough = true
|
||||||
this[VaporLifecycleHooks.UNMOUNTED] = null
|
} else {
|
||||||
this[VaporLifecycleHooks.RENDER_TRACKED] = null
|
this.props = {}
|
||||||
this[VaporLifecycleHooks.RENDER_TRIGGERED] = null
|
this.attrs = {}
|
||||||
this[VaporLifecycleHooks.ACTIVATED] = null
|
this.hasFallthrough = initStaticProps(comp, rawProps, this)
|
||||||
this[VaporLifecycleHooks.DEACTIVATED] = null
|
}
|
||||||
this[VaporLifecycleHooks.ERROR_CAPTURED] = null
|
|
||||||
|
|
||||||
initProps(this, rawProps, !isFunction(component), once)
|
// TODO validate props
|
||||||
initSlots(this, slots)
|
// TODO init slots
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isVaporComponent(
|
export function isVaporComponent(
|
||||||
val: unknown,
|
value: unknown,
|
||||||
): val is ComponentInternalInstance {
|
): value is VaporComponentInstance {
|
||||||
return val instanceof ComponentInternalInstance
|
return value instanceof VaporComponentInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateComponentName(
|
export class SetupContext<E = EmitsOptions> {
|
||||||
name: string,
|
attrs: Record<string, any>
|
||||||
{ isNativeTag }: AppConfig,
|
// emit: EmitFn<E>
|
||||||
): void {
|
// slots: Readonly<StaticSlots>
|
||||||
if (isBuiltInTag(name) || isNativeTag(name)) {
|
expose: (exposed?: Record<string, any>) => void
|
||||||
warn(
|
|
||||||
'Do not use built-in or reserved HTML elements as component id: ' + name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
constructor(instance: VaporComponentInstance) {
|
||||||
* Dev-only
|
this.attrs = instance.attrs
|
||||||
*/
|
// this.emit = instance.emit as EmitFn<E>
|
||||||
export function getAttrsProxy(instance: ComponentInternalInstance): Data {
|
// this.slots = instance.slots
|
||||||
return (
|
this.expose = (exposed = {}) => {
|
||||||
instance.attrsProxy ||
|
instance.exposed = exposed
|
||||||
(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]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!name && instance && instance.parent) {
|
|
||||||
// try to infer the name based on reverse resolution
|
|
||||||
const inferFromRegistry = (registry: Record<string, any> | 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, '')
|
|
||||||
|
|
|
@ -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<string>()
|
|
||||||
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<string, string> | 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
|
|
||||||
}
|
|
|
@ -1,185 +1,70 @@
|
||||||
import {
|
import {
|
||||||
type UnionToIntersection,
|
type EmitFn,
|
||||||
camelize,
|
type ObjectEmitsOptions,
|
||||||
extend,
|
baseEmit,
|
||||||
hasOwn,
|
} from '@vue/runtime-core'
|
||||||
hyphenate,
|
import {
|
||||||
isArray,
|
type VaporComponent,
|
||||||
isFunction,
|
type VaporComponentInstance,
|
||||||
isOn,
|
currentInstance,
|
||||||
isString,
|
} from './component'
|
||||||
looseToNumber,
|
import { EMPTY_OBJ, NOOP, hasOwn, isArray } from '@vue/shared'
|
||||||
toHandlerKey,
|
import { resolveSource } from './componentProps'
|
||||||
} 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<infer V>
|
|
||||||
? (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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The logic from core isn't too reusable so it's better to duplicate here
|
||||||
|
*/
|
||||||
export function normalizeEmitsOptions(
|
export function normalizeEmitsOptions(
|
||||||
comp: Component,
|
comp: VaporComponent,
|
||||||
): ObjectEmitsOptions | null {
|
): ObjectEmitsOptions | null {
|
||||||
// TODO: caching?
|
const cached = comp.__emitsOptions
|
||||||
|
if (cached) return cached
|
||||||
|
|
||||||
const raw = comp.emits
|
const raw = comp.emits
|
||||||
if (!raw) return null
|
if (!raw) return null
|
||||||
|
|
||||||
let normalized: ObjectEmitsOptions = {}
|
let normalized: ObjectEmitsOptions
|
||||||
if (isArray(raw)) {
|
if (isArray(raw)) {
|
||||||
raw.forEach(key => (normalized[key] = null))
|
normalized = {}
|
||||||
|
for (const key in raw) normalized[key] = null
|
||||||
} else {
|
} else {
|
||||||
extend(normalized, raw)
|
normalized = raw
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalized
|
return (comp.__emitsOptions = normalized)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if an incoming prop key is a declared emit event listener.
|
export function useEmit(): EmitFn {
|
||||||
// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are
|
if (!currentInstance) {
|
||||||
// both considered matched listeners.
|
// TODO warn
|
||||||
export function isEmitListener(
|
return NOOP
|
||||||
options: ObjectEmitsOptions | null,
|
} else {
|
||||||
key: string,
|
return emit.bind(null, currentInstance)
|
||||||
): boolean {
|
}
|
||||||
if (!options || !isOn(key)) {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
key = key.slice(2).replace(/Once$/, '')
|
export function emit(
|
||||||
return (
|
instance: VaporComponentInstance,
|
||||||
hasOwn(options, key[0].toLowerCase() + key.slice(1)) ||
|
event: string,
|
||||||
hasOwn(options, hyphenate(key)) ||
|
...rawArgs: any[]
|
||||||
hasOwn(options, key)
|
): void {
|
||||||
|
baseEmit(
|
||||||
|
instance,
|
||||||
|
instance.rawProps || EMPTY_OBJ,
|
||||||
|
propGetter,
|
||||||
|
event,
|
||||||
|
...rawArgs,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function propGetter(rawProps: Record<string, any>, 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])
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,392 +1,231 @@
|
||||||
|
import { EMPTY_ARR, NO, camelize, hasOwn, isFunction } from '@vue/shared'
|
||||||
|
import type { VaporComponent, VaporComponentInstance } from './component'
|
||||||
import {
|
import {
|
||||||
EMPTY_ARR,
|
type NormalizedPropsOptions,
|
||||||
EMPTY_OBJ,
|
baseNormalizePropsOptions,
|
||||||
camelize,
|
isEmitListener,
|
||||||
extend,
|
resolvePropValue,
|
||||||
hasOwn,
|
} from '@vue/runtime-core'
|
||||||
hyphenate,
|
import { normalizeEmitsOptions } from './componentEmits'
|
||||||
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'
|
|
||||||
|
|
||||||
export type ComponentPropsOptions<P = Data> =
|
export interface RawProps {
|
||||||
| ComponentObjectPropsOptions<P>
|
[key: string]: PropSource
|
||||||
| string[]
|
$?: DynamicPropsSource[]
|
||||||
|
|
||||||
export type ComponentObjectPropsOptions<P = Data> = {
|
|
||||||
[K in keyof P]: Prop<P[K]> | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Prop<T, D = T> = PropOptions<T, D> | PropType<T>
|
type PropSource<T = any> = T | (() => T)
|
||||||
|
|
||||||
type DefaultFactory<T> = (props: Data) => T | null | undefined
|
type DynamicPropsSource = PropSource<Record<string, any>>
|
||||||
|
|
||||||
export interface PropOptions<T = any, D = T> {
|
export function initStaticProps(
|
||||||
type?: PropType<T> | true | null
|
comp: VaporComponent,
|
||||||
required?: boolean
|
rawProps: RawProps | undefined,
|
||||||
default?: D | DefaultFactory<D> | null | undefined | object
|
instance: VaporComponentInstance,
|
||||||
validator?(value: unknown, props: Data): boolean
|
): boolean {
|
||||||
/**
|
let hasAttrs = false
|
||||||
* @internal
|
const { props, attrs } = instance
|
||||||
*/
|
const [propsOptions, needCastKeys] = normalizePropsOptions(comp)
|
||||||
skipFactory?: boolean
|
const emitsOptions = normalizeEmitsOptions(comp)
|
||||||
}
|
|
||||||
|
|
||||||
export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
|
// for dev emit check
|
||||||
|
|
||||||
type PropConstructor<T = any> =
|
|
||||||
| { new (...args: any[]): T & {} }
|
|
||||||
| { (): T }
|
|
||||||
| PropMethod<T>
|
|
||||||
|
|
||||||
type PropMethod<T, TConstructor = any> = [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<string, NormalizedProp>
|
|
||||||
export type NormalizedPropsOptions =
|
|
||||||
| [props: NormalizedProps, needCastKeys: string[]]
|
|
||||||
| []
|
|
||||||
|
|
||||||
export type StaticProps = Record<string, () => unknown>
|
|
||||||
type DynamicProps = () => Data
|
|
||||||
export type NormalizedRawProps = Array<StaticProps | DynamicProps>
|
|
||||||
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<Data>({}))
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
} 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validation
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
validateProps(rawProps, props, options || {})
|
instance.propsOptions = normalizePropsOptions(comp)
|
||||||
|
instance.emitsOptions = emitsOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasDynamicProps) {
|
for (const key in rawProps) {
|
||||||
firstEffect(instance, () => patchAttrs(instance, true))
|
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 {
|
} else {
|
||||||
patchAttrs(instance)
|
props[normalizedKey] = needCast
|
||||||
|
? resolvePropValue(
|
||||||
|
propsOptions,
|
||||||
|
normalizedKey,
|
||||||
|
source,
|
||||||
|
instance,
|
||||||
|
resolveDefault,
|
||||||
|
)
|
||||||
|
: source
|
||||||
}
|
}
|
||||||
|
} else if (!isEmitListener(emitsOptions, key)) {
|
||||||
if (isStateful) {
|
if (isFunction(source)) {
|
||||||
instance.props = /* TODO isSSR ? props : */ shallowReactive(props)
|
Object.defineProperty(attrs, key, {
|
||||||
|
enumerable: true,
|
||||||
|
get: source,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
// functional w/ optional props, props === attrs
|
attrs[normalizedKey] = source
|
||||||
instance.props = instance.propsOptions === EMPTY_ARR ? attrs : props
|
|
||||||
}
|
}
|
||||||
|
hasAttrs = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const key in propsOptions) {
|
||||||
|
if (!(key in props)) {
|
||||||
|
props[key] = resolvePropValue(
|
||||||
|
propsOptions,
|
||||||
|
key,
|
||||||
|
undefined,
|
||||||
|
instance,
|
||||||
|
resolveDefault,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasAttrs
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerProp(
|
function resolveDefault(
|
||||||
instance: ComponentInternalInstance,
|
factory: (props: Record<string, any>) => unknown,
|
||||||
once: boolean,
|
instance: VaporComponentInstance,
|
||||||
props: Data,
|
|
||||||
rawKey: string,
|
|
||||||
getter?: (() => unknown) | (() => DynamicPropResult),
|
|
||||||
isDynamic?: boolean,
|
|
||||||
isAbsent?: boolean,
|
|
||||||
) {
|
) {
|
||||||
const key = camelize(rawKey)
|
return factory.call(null, instance.props)
|
||||||
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 {
|
// TODO optimization: maybe convert functions into computeds
|
||||||
if (!rawProps) return []
|
export function resolveSource(source: PropSource): Record<string, any> {
|
||||||
if (!isArray(rawProps)) return [rawProps]
|
return isFunction(source) ? source() : source
|
||||||
return rawProps
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function walkRawProps(
|
const passThrough = (val: any) => val
|
||||||
rawProps: NormalizedRawProps,
|
|
||||||
cb: (key: string, value: any, getter?: boolean) => void,
|
export function getDynamicPropsHandlers(
|
||||||
): void {
|
comp: VaporComponent,
|
||||||
for (const props of Array.from(rawProps).reverse()) {
|
instance: VaporComponentInstance,
|
||||||
if (isFunction(props)) {
|
): [ProxyHandler<RawProps> | null, ProxyHandler<RawProps>] {
|
||||||
const resolved = props()
|
if (comp.__propsHandlers) {
|
||||||
for (const rawKey in resolved) {
|
return comp.__propsHandlers
|
||||||
cb(rawKey, resolved[rawKey])
|
|
||||||
}
|
}
|
||||||
} else {
|
let normalizedKeys: string[] | undefined
|
||||||
for (const rawKey in props) {
|
const propsOptions = normalizePropsOptions(comp)[0]
|
||||||
cb(rawKey, props[rawKey], true)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRawKey(obj: Data, key: string) {
|
const propsHandlers = propsOptions
|
||||||
return Object.keys(obj).find(k => camelize(k) === key)
|
? ({
|
||||||
|
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<RawProps>)
|
||||||
|
: 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DynamicPropResult = [value: unknown, absent: boolean]
|
const attrsHandlers = {
|
||||||
export function getDynamicPropValue(
|
get: (target, key: string) => getProp(target, key, false),
|
||||||
rawProps: NormalizedRawProps,
|
has: hasAttr,
|
||||||
key: string,
|
getOwnPropertyDescriptor(target, key: string) {
|
||||||
): DynamicPropResult {
|
if (hasAttr(target, key)) {
|
||||||
for (const props of Array.from(rawProps).reverse()) {
|
return {
|
||||||
if (isFunction(props)) {
|
configurable: true,
|
||||||
const resolved = props()
|
enumerable: true,
|
||||||
const rawKey = getRawKey(resolved, key)
|
get: () => getProp(target, key, false),
|
||||||
if (rawKey) return [resolved[rawKey], false]
|
|
||||||
} else {
|
|
||||||
const rawKey = getRawKey(props, key)
|
|
||||||
if (rawKey) return [props[rawKey](), false]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [undefined, true]
|
},
|
||||||
|
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<RawProps>
|
||||||
|
|
||||||
|
return (comp.__propsHandlers = [propsHandlers, attrsHandlers])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolvePropValue(
|
function normalizePropsOptions(comp: VaporComponent): NormalizedPropsOptions {
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// boolean casting
|
|
||||||
if (opt[BooleanFlags.shouldCast]) {
|
|
||||||
if (isAbsent && !hasDefault) {
|
|
||||||
value = false
|
|
||||||
} else if (
|
|
||||||
opt[BooleanFlags.shouldCastTrue] &&
|
|
||||||
(value === '' || value === hyphenate(key))
|
|
||||||
) {
|
|
||||||
value = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizePropsOptions(comp: Component): NormalizedPropsOptions {
|
|
||||||
const cached = comp.__propsOptions
|
const cached = comp.__propsOptions
|
||||||
if (cached) return cached
|
if (cached) return cached
|
||||||
|
|
||||||
const raw = comp.props
|
const raw = comp.props
|
||||||
const normalized: NormalizedProps | undefined = {}
|
if (!raw) return EMPTY_ARR as []
|
||||||
|
|
||||||
|
const normalized: NormalizedPropsOptions[0] = {}
|
||||||
const needCastKeys: NormalizedPropsOptions[1] = []
|
const needCastKeys: NormalizedPropsOptions[1] = []
|
||||||
|
baseNormalizePropsOptions(raw, normalized, needCastKeys)
|
||||||
|
|
||||||
if (!raw) {
|
return (comp.__propsOptions = [normalized, needCastKeys])
|
||||||
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<any>): string {
|
|
||||||
const match = ctor && ctor.toString().match(/^\s*(function|class) (\w+)/)
|
|
||||||
return match ? match[2] : ctor === null ? 'null' : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSameType(a: Prop<any>, b: Prop<any>): boolean {
|
|
||||||
return getType(a) === getType(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTypeIndex(
|
|
||||||
type: Prop<any>,
|
|
||||||
expectedTypes: PropType<any> | 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 + '".')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<T extends any = any> = (
|
|
||||||
...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
|
|
||||||
) => Block
|
|
||||||
|
|
||||||
export type StaticSlots = Record<string, Slot>
|
|
||||||
export type DynamicSlot = { name: string; fn: Slot }
|
|
||||||
export type DynamicSlotFn = () => DynamicSlot | DynamicSlot[] | undefined
|
|
||||||
export type NormalizedRawSlots = Array<StaticSlots | DynamicSlotFn>
|
|
||||||
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<string>[] = []
|
|
||||||
rawSlots.forEach((slots, index) => {
|
|
||||||
const isDynamicSlot = isDynamicSlotFn(slots)
|
|
||||||
if (isDynamicSlot) {
|
|
||||||
firstEffect(instance, () => {
|
|
||||||
const recordNames = keys[index] || (keys[index] = new Set())
|
|
||||||
let dynamicSlot: ReturnType<DynamicSlotFn>
|
|
||||||
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<string>) {
|
|
||||||
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<Block>()
|
|
||||||
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<Data[]>([]) : undefined
|
|
||||||
const result = shallowReactive<Data>({})
|
|
||||||
|
|
||||||
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<Data>({}))
|
|
||||||
: 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]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
import {
|
|
||||||
type ShallowUnwrapRef,
|
|
||||||
proxyRefs,
|
|
||||||
shallowReactive,
|
|
||||||
} from '@vue/reactivity'
|
|
||||||
import { renderEffect } from './renderEffect'
|
|
||||||
|
|
||||||
export function withDestructure<T extends any[], R>(
|
|
||||||
assign: (data: ShallowUnwrapRef<T>) => any[],
|
|
||||||
block: (ctx: any[]) => R,
|
|
||||||
): (data: T) => R {
|
|
||||||
return (data: T) => {
|
|
||||||
const ctx = shallowReactive<any[]>([])
|
|
||||||
renderEffect(() => {
|
|
||||||
const res = assign(proxyRefs(data))
|
|
||||||
const len = res.length
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
ctx[i] = res[i]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return block(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<string, string | Symbol>
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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<M extends string = string> = Record<M, boolean>
|
|
||||||
|
|
||||||
export interface DirectiveBinding<T = any, V = any, M extends string = string> {
|
|
||||||
instance: ComponentInternalInstance
|
|
||||||
source: () => V
|
|
||||||
arg?: string
|
|
||||||
modifiers?: DirectiveModifiers<M>
|
|
||||||
dir: Directive<T, V, M>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DirectiveBindingsMap = Map<Node, DirectiveBinding[]>
|
|
||||||
|
|
||||||
export type Directive<T = any, V = any, M extends string = string> = (
|
|
||||||
node: T,
|
|
||||||
binding: DirectiveBinding<T, V, M>,
|
|
||||||
) => 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<T extends ComponentInternalInstance | Node>(
|
|
||||||
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]
|
|
||||||
}
|
|
|
@ -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<HTMLElement, AssignerFn>()
|
|
||||||
const assigningMap = new WeakMap<HTMLElement, boolean>()
|
|
||||||
|
|
||||||
// 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<HTMLInputElement> = (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<HTMLSelectElement, any, 'number'> = (
|
|
||||||
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(
|
|
||||||
`<select multiple v-model> expects an Array or Set value for its binding, ` +
|
|
||||||
`but got ${Object.prototype.toString.call(value).slice(8, -1)}.`,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0, l = el.options.length; i < l; i++) {
|
|
||||||
const option = el.options[i]
|
|
||||||
const optionValue = getValue(option)
|
|
||||||
if (isMultiple) {
|
|
||||||
if (isArrayValue) {
|
|
||||||
const optionType = typeof optionValue
|
|
||||||
// fast path for string / number values
|
|
||||||
if (optionType === 'string' || optionType === 'number') {
|
|
||||||
option.selected = value.includes(
|
|
||||||
number ? looseToNumber(optionValue) : optionValue,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
option.selected = looseIndexOf(value, optionValue) > -1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
option.selected = value.has(optionValue)
|
|
||||||
}
|
|
||||||
} else if (looseEqual(getValue(option), value)) {
|
|
||||||
if (el.selectedIndex !== i) el.selectedIndex = i
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isMultiple && el.selectedIndex !== -1) {
|
|
||||||
el.selectedIndex = -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieve raw value set via :value bindings
|
|
||||||
function getValue(el: HTMLOptionElement | HTMLInputElement) {
|
|
||||||
const metadata = getMetadata(el)
|
|
||||||
return (metadata && metadata[MetadataKind.prop].value) || el.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieve raw value for true-value and false-value set via :true-value or :false-value bindings
|
|
||||||
function getCheckboxValue(el: HTMLInputElement, checked: boolean) {
|
|
||||||
const metadata = getMetadata(el)
|
|
||||||
const props = metadata && metadata[MetadataKind.prop]
|
|
||||||
const key = checked ? 'true-value' : 'false-value'
|
|
||||||
if (props && key in props) {
|
|
||||||
return props[key]
|
|
||||||
}
|
|
||||||
if (el.hasAttribute(key)) {
|
|
||||||
return el.getAttribute(key)
|
|
||||||
}
|
|
||||||
return checked
|
|
||||||
}
|
|
||||||
|
|
||||||
export const vModelCheckbox: Directive<HTMLInputElement> = (el, { source }) => {
|
|
||||||
onBeforeMount(() => {
|
|
||||||
assignFnMap.set(el, getModelAssigner(el))
|
|
||||||
|
|
||||||
addEventListener(el, 'change', () => {
|
|
||||||
const modelValue = source()
|
|
||||||
const elementValue = getValue(el)
|
|
||||||
const checked = el.checked
|
|
||||||
const assigner = assignFnMap.get(el)!
|
|
||||||
if (isArray(modelValue)) {
|
|
||||||
const index = looseIndexOf(modelValue, elementValue)
|
|
||||||
const found = index !== -1
|
|
||||||
if (checked && !found) {
|
|
||||||
assigner(modelValue.concat(elementValue))
|
|
||||||
} else if (!checked && found) {
|
|
||||||
const filtered = [...modelValue]
|
|
||||||
filtered.splice(index, 1)
|
|
||||||
assigner(filtered)
|
|
||||||
}
|
|
||||||
} else if (isSet(modelValue)) {
|
|
||||||
const cloned = new Set(modelValue)
|
|
||||||
if (checked) {
|
|
||||||
cloned.add(elementValue)
|
|
||||||
} else {
|
|
||||||
cloned.delete(elementValue)
|
|
||||||
}
|
|
||||||
assigner(cloned)
|
|
||||||
} else {
|
|
||||||
assigner(getCheckboxValue(el, checked))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
setChecked()
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUpdate(() => {
|
|
||||||
assignFnMap.set(el, getModelAssigner(el))
|
|
||||||
setChecked()
|
|
||||||
})
|
|
||||||
|
|
||||||
function setChecked() {
|
|
||||||
const value = source()
|
|
||||||
if (isArray(value)) {
|
|
||||||
el.checked = looseIndexOf(value, getValue(el)) > -1
|
|
||||||
} else if (isSet(value)) {
|
|
||||||
el.checked = value.has(getValue(el))
|
|
||||||
} else {
|
|
||||||
el.checked = looseEqual(value, getCheckboxValue(el, true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const vModelDynamic: Directive<
|
|
||||||
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
|
|
||||||
> = (el, binding) => {
|
|
||||||
const type = el.getAttribute('type')
|
|
||||||
const modelToUse = resolveDynamicModel(el.tagName, type)
|
|
||||||
modelToUse(el, binding)
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveDynamicModel(tagName: string, type: string | null): Directive {
|
|
||||||
switch (tagName) {
|
|
||||||
case 'SELECT':
|
|
||||||
return vModelSelect
|
|
||||||
case 'TEXTAREA':
|
|
||||||
return vModelText
|
|
||||||
default:
|
|
||||||
switch (type) {
|
|
||||||
case 'checkbox':
|
|
||||||
return vModelCheckbox
|
|
||||||
case 'radio':
|
|
||||||
return vModelRadio
|
|
||||||
default:
|
|
||||||
return vModelText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
import type { Directive } from '../directives'
|
|
||||||
import { renderEffect } from '../renderEffect'
|
|
||||||
|
|
||||||
export const vShowOriginalDisplay: unique symbol = Symbol('_vod')
|
|
||||||
export const vShowHidden: unique symbol = Symbol('_vsh')
|
|
||||||
|
|
||||||
export interface VShowElement extends HTMLElement {
|
|
||||||
// _vod = vue original display
|
|
||||||
[vShowOriginalDisplay]: string
|
|
||||||
[vShowHidden]: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const vShow: Directive<VShowElement> = (el, { source }) => {
|
|
||||||
el[vShowOriginalDisplay] = el.style.display === 'none' ? '' : el.style.display
|
|
||||||
renderEffect(() => setDisplay(el, source()))
|
|
||||||
}
|
|
||||||
|
|
||||||
function setDisplay(el: VShowElement, value: unknown): void {
|
|
||||||
el.style.display = value ? el[vShowOriginalDisplay] : 'none'
|
|
||||||
el[vShowHidden] = !value
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ import { isArray } from '@vue/shared'
|
||||||
import { renderEffect } from '../renderEffect'
|
import { renderEffect } from '../renderEffect'
|
||||||
import { setText } from './prop'
|
import { setText } from './prop'
|
||||||
import { type Block, normalizeBlock } from '../block'
|
import { type Block, normalizeBlock } from '../block'
|
||||||
import { isVaporComponent } from '../_new/component'
|
import { isVaporComponent } from '../component'
|
||||||
|
|
||||||
// export function insert(
|
// export function insert(
|
||||||
// block: Block,
|
// block: Block,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
recordEventMetadata,
|
recordEventMetadata,
|
||||||
} from '../componentMetadata'
|
} from '../componentMetadata'
|
||||||
import { withKeys, withModifiers } from '@vue/runtime-dom'
|
import { withKeys, withModifiers } from '@vue/runtime-dom'
|
||||||
import { queuePostFlushCb } from '../scheduler'
|
import { queuePostFlushCb } from '@vue/runtime-dom'
|
||||||
|
|
||||||
export function addEventListener(
|
export function addEventListener(
|
||||||
el: Element,
|
el: Element,
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
shouldSetAsAttr,
|
shouldSetAsAttr,
|
||||||
toDisplayString,
|
toDisplayString,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { warn } from '../warning'
|
import { warn } from '../_old/warning'
|
||||||
import { setStyle } from './style'
|
import { setStyle } from './style'
|
||||||
import {
|
import {
|
||||||
MetadataKind,
|
MetadataKind,
|
||||||
|
@ -20,8 +20,7 @@ import {
|
||||||
recordPropMetadata,
|
recordPropMetadata,
|
||||||
} from '../componentMetadata'
|
} from '../componentMetadata'
|
||||||
import { on } from './event'
|
import { on } from './event'
|
||||||
import type { Data } from '@vue/runtime-shared'
|
import { currentInstance } from '../_old/component'
|
||||||
import { currentInstance } from '../component'
|
|
||||||
|
|
||||||
export function mergeInheritAttr(key: string, value: any): unknown {
|
export function mergeInheritAttr(key: string, value: any): unknown {
|
||||||
const instance = currentInstance!
|
const instance = currentInstance!
|
||||||
|
@ -199,6 +198,8 @@ export function mergeProp(
|
||||||
return incoming
|
return incoming
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Data = Record<string, any>
|
||||||
|
|
||||||
export function mergeProps(...args: Data[]): Data {
|
export function mergeProps(...args: Data[]): Data {
|
||||||
const ret: Data = {}
|
const ret: Data = {}
|
||||||
for (let i = 0; i < args.length; i++) {
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
isString,
|
isString,
|
||||||
normalizeStyle,
|
normalizeStyle,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { warn } from '../warning'
|
import { warn } from '../_old/warning'
|
||||||
import { recordPropMetadata } from '../componentMetadata'
|
import { recordPropMetadata } from '../componentMetadata'
|
||||||
import { mergeInheritAttr } from './prop'
|
import { mergeInheritAttr } from './prop'
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ import {
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
currentInstance,
|
currentInstance,
|
||||||
isVaporComponent,
|
isVaporComponent,
|
||||||
} from '../component'
|
} from '../_old/component'
|
||||||
import { VaporErrorCodes, callWithErrorHandling } from '../errorHandling'
|
import { VaporErrorCodes, callWithErrorHandling } from '../_old/errorHandling'
|
||||||
import {
|
import {
|
||||||
EMPTY_OBJ,
|
EMPTY_OBJ,
|
||||||
hasOwn,
|
hasOwn,
|
||||||
|
@ -13,8 +13,8 @@ import {
|
||||||
isString,
|
isString,
|
||||||
remove,
|
remove,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { warn } from '../warning'
|
import { warn } from '../_old/warning'
|
||||||
import { type SchedulerJob, queuePostFlushCb } from '../scheduler'
|
import { type SchedulerJob, queuePostFlushCb } from '../_old/scheduler'
|
||||||
|
|
||||||
export type NodeRef = string | Ref | ((ref: Element) => void)
|
export type NodeRef = string | Ref | ((ref: Element) => void)
|
||||||
export type RefEl = Element | ComponentInternalInstance
|
export type RefEl = Element | ComponentInternalInstance
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
export enum VaporLifecycleHooks {
|
|
||||||
BEFORE_MOUNT = 'bm',
|
|
||||||
MOUNTED = 'm',
|
|
||||||
BEFORE_UPDATE = 'bu',
|
|
||||||
UPDATED = 'u',
|
|
||||||
BEFORE_UNMOUNT = 'bum',
|
|
||||||
UNMOUNTED = 'um',
|
|
||||||
DEACTIVATED = 'da',
|
|
||||||
ACTIVATED = 'a',
|
|
||||||
RENDER_TRIGGERED = 'rtg',
|
|
||||||
RENDER_TRACKED = 'rtc',
|
|
||||||
ERROR_CAPTURED = 'ec',
|
|
||||||
// SERVER_PREFETCH = 'sp',
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
// These codes originate from a file of the same name in runtime-core,
|
|
||||||
// duplicated during Vapor's early development to ensure its independence.
|
|
||||||
// The ultimate aim is to uncouple this replicated code and
|
|
||||||
// facilitate its shared use between two runtimes.
|
|
||||||
|
|
||||||
import type { ComponentInternalInstance } from './component'
|
|
||||||
import { isFunction, isPromise } from '@vue/shared'
|
|
||||||
import { warn } from './warning'
|
|
||||||
import { VaporLifecycleHooks } from './enums'
|
|
||||||
import { WatchErrorCodes, pauseTracking, resetTracking } from '@vue/reactivity'
|
|
||||||
|
|
||||||
// contexts where user provided function may be executed, in addition to
|
|
||||||
// lifecycle hooks.
|
|
||||||
export enum VaporErrorCodes {
|
|
||||||
SETUP_FUNCTION,
|
|
||||||
RENDER_FUNCTION,
|
|
||||||
// The error codes for the watch have been transferred to the reactivity
|
|
||||||
// package along with baseWatch to maintain code compatibility. Hence,
|
|
||||||
// it is essential to keep these values unchanged.
|
|
||||||
// WATCH_GETTER,
|
|
||||||
// WATCH_CALLBACK,
|
|
||||||
// WATCH_CLEANUP,
|
|
||||||
NATIVE_EVENT_HANDLER = 5,
|
|
||||||
COMPONENT_EVENT_HANDLER,
|
|
||||||
VNODE_HOOK,
|
|
||||||
DIRECTIVE_HOOK,
|
|
||||||
TRANSITION_HOOK,
|
|
||||||
APP_ERROR_HANDLER,
|
|
||||||
APP_WARN_HANDLER,
|
|
||||||
FUNCTION_REF,
|
|
||||||
ASYNC_COMPONENT_LOADER,
|
|
||||||
SCHEDULER,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ErrorTypes = VaporLifecycleHooks | VaporErrorCodes | WatchErrorCodes
|
|
||||||
|
|
||||||
export const ErrorTypeStrings: Record<ErrorTypes, string> = {
|
|
||||||
// [VaporLifecycleHooks.SERVER_PREFETCH]: 'serverPrefetch hook',
|
|
||||||
[VaporLifecycleHooks.BEFORE_MOUNT]: 'beforeMount hook',
|
|
||||||
[VaporLifecycleHooks.MOUNTED]: 'mounted hook',
|
|
||||||
[VaporLifecycleHooks.BEFORE_UPDATE]: 'beforeUpdate hook',
|
|
||||||
[VaporLifecycleHooks.UPDATED]: 'updated',
|
|
||||||
[VaporLifecycleHooks.BEFORE_UNMOUNT]: 'beforeUnmount hook',
|
|
||||||
[VaporLifecycleHooks.UNMOUNTED]: 'unmounted hook',
|
|
||||||
[VaporLifecycleHooks.ACTIVATED]: 'activated hook',
|
|
||||||
[VaporLifecycleHooks.DEACTIVATED]: 'deactivated hook',
|
|
||||||
[VaporLifecycleHooks.ERROR_CAPTURED]: 'errorCaptured hook',
|
|
||||||
[VaporLifecycleHooks.RENDER_TRACKED]: 'renderTracked hook',
|
|
||||||
[VaporLifecycleHooks.RENDER_TRIGGERED]: 'renderTriggered hook',
|
|
||||||
[VaporErrorCodes.SETUP_FUNCTION]: 'setup function',
|
|
||||||
[VaporErrorCodes.RENDER_FUNCTION]: 'render function',
|
|
||||||
[WatchErrorCodes.WATCH_GETTER]: 'watcher getter',
|
|
||||||
[WatchErrorCodes.WATCH_CALLBACK]: 'watcher callback',
|
|
||||||
[WatchErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function',
|
|
||||||
[VaporErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
|
|
||||||
[VaporErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
|
|
||||||
[VaporErrorCodes.VNODE_HOOK]: 'vnode hook',
|
|
||||||
[VaporErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
|
|
||||||
[VaporErrorCodes.TRANSITION_HOOK]: 'transition hook',
|
|
||||||
[VaporErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
|
|
||||||
[VaporErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
|
|
||||||
[VaporErrorCodes.FUNCTION_REF]: 'ref function',
|
|
||||||
[VaporErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
|
|
||||||
[VaporErrorCodes.SCHEDULER]:
|
|
||||||
'scheduler flush. This is likely a Vue internals bug. ' +
|
|
||||||
'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/core',
|
|
||||||
}
|
|
||||||
|
|
||||||
export function callWithErrorHandling(
|
|
||||||
fn: Function,
|
|
||||||
instance: ComponentInternalInstance | null,
|
|
||||||
type: ErrorTypes,
|
|
||||||
args?: unknown[],
|
|
||||||
): any {
|
|
||||||
let res
|
|
||||||
try {
|
|
||||||
res = args ? fn(...args) : fn()
|
|
||||||
} catch (err) {
|
|
||||||
handleError(err, instance, type)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
export function callWithAsyncErrorHandling<F extends Function | Function[]>(
|
|
||||||
fn: F,
|
|
||||||
instance: ComponentInternalInstance | null,
|
|
||||||
type: ErrorTypes,
|
|
||||||
args?: unknown[],
|
|
||||||
): F extends Function ? any : any[] {
|
|
||||||
if (isFunction(fn)) {
|
|
||||||
const res = callWithErrorHandling(fn, instance, type, args)
|
|
||||||
if (res && isPromise(res)) {
|
|
||||||
res.catch(err => {
|
|
||||||
handleError(err, instance, type)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
const values = []
|
|
||||||
for (let i = 0; i < fn.length; i++) {
|
|
||||||
values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleError(
|
|
||||||
err: unknown,
|
|
||||||
instance: ComponentInternalInstance | null,
|
|
||||||
type: ErrorTypes,
|
|
||||||
throwInDev = true,
|
|
||||||
): void {
|
|
||||||
if (instance) {
|
|
||||||
let cur = instance.parent
|
|
||||||
// in production the hook receives only the error code
|
|
||||||
const errorInfo = __DEV__
|
|
||||||
? ErrorTypeStrings[type]
|
|
||||||
: `https://vuejs.org/errors/#runtime-${type}`
|
|
||||||
while (cur) {
|
|
||||||
const errorCapturedHooks = 'ec' in cur ? cur.ec : null
|
|
||||||
if (errorCapturedHooks) {
|
|
||||||
for (let i = 0; i < errorCapturedHooks.length; i++) {
|
|
||||||
if (errorCapturedHooks[i](err, instance, errorInfo) === false) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cur = cur.parent
|
|
||||||
}
|
|
||||||
|
|
||||||
// app-level handling
|
|
||||||
const appErrorHandler = instance.appContext.config.errorHandler
|
|
||||||
if (appErrorHandler) {
|
|
||||||
pauseTracking()
|
|
||||||
callWithErrorHandling(
|
|
||||||
appErrorHandler,
|
|
||||||
null,
|
|
||||||
VaporErrorCodes.APP_ERROR_HANDLER,
|
|
||||||
[err, instance, errorInfo],
|
|
||||||
)
|
|
||||||
resetTracking()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logError(err, type, throwInDev)
|
|
||||||
}
|
|
||||||
|
|
||||||
function logError(err: unknown, type: ErrorTypes, throwInDev = true) {
|
|
||||||
if (__DEV__) {
|
|
||||||
const info = ErrorTypeStrings[type]
|
|
||||||
warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
|
|
||||||
// crash in dev by default so it's more noticeable
|
|
||||||
if (throwInDev) {
|
|
||||||
throw err
|
|
||||||
} else if (!__TEST__) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// recover in prod to reduce the impact on end-user
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
import { camelize, capitalize, isString } from '@vue/shared'
|
|
||||||
import { warn } from '../warning'
|
|
||||||
import type { Directive } from '../directives'
|
|
||||||
import { type Component, currentInstance } from '../component'
|
|
||||||
import { getComponentName } from '../component'
|
|
||||||
|
|
||||||
const COMPONENTS = 'components'
|
|
||||||
const DIRECTIVES = 'directives'
|
|
||||||
|
|
||||||
export type AssetTypes = typeof COMPONENTS | typeof DIRECTIVES
|
|
||||||
|
|
||||||
export function resolveComponent(name: string): string | Component {
|
|
||||||
return resolveAsset(COMPONENTS, name, true) || name
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveDirective(name: string): Directive | undefined {
|
|
||||||
return resolveAsset(DIRECTIVES, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* overload 1: components
|
|
||||||
*/
|
|
||||||
function resolveAsset(
|
|
||||||
type: typeof COMPONENTS,
|
|
||||||
name: string,
|
|
||||||
warnMissing?: boolean,
|
|
||||||
): Component | undefined
|
|
||||||
// overload 2: directives
|
|
||||||
function resolveAsset(
|
|
||||||
type: typeof DIRECTIVES,
|
|
||||||
name: string,
|
|
||||||
): Directive | undefined
|
|
||||||
// implementation
|
|
||||||
function resolveAsset(type: AssetTypes, name: string, warnMissing = true) {
|
|
||||||
const instance = currentInstance
|
|
||||||
if (instance) {
|
|
||||||
const Component = instance.type
|
|
||||||
|
|
||||||
// explicit self name has highest priority
|
|
||||||
if (type === COMPONENTS) {
|
|
||||||
const selfName = getComponentName(Component)
|
|
||||||
if (
|
|
||||||
selfName &&
|
|
||||||
(selfName === name ||
|
|
||||||
selfName === camelize(name) ||
|
|
||||||
selfName === capitalize(camelize(name)))
|
|
||||||
) {
|
|
||||||
return Component
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const res =
|
|
||||||
// global registration
|
|
||||||
resolve(instance.appContext[type], name)
|
|
||||||
|
|
||||||
if (__DEV__ && warnMissing && !res) {
|
|
||||||
const extra =
|
|
||||||
type === COMPONENTS
|
|
||||||
? `\nIf this is a native custom element, make sure to exclude it from ` +
|
|
||||||
`component resolution via compilerOptions.isCustomElement.`
|
|
||||||
: ``
|
|
||||||
warn(`Failed to resolve ${type.slice(0, -1)}: ${name}${extra}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
} else if (__DEV__) {
|
|
||||||
warn(
|
|
||||||
`resolve${capitalize(type.slice(0, -1))} ` +
|
|
||||||
`can only be used in render() or setup().`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolve(registry: Record<string, any> | undefined, name: string) {
|
|
||||||
return (
|
|
||||||
registry &&
|
|
||||||
(registry[name] ||
|
|
||||||
registry[camelize(name)] ||
|
|
||||||
registry[capitalize(camelize(name))])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
export function resolveDynamicComponent(
|
|
||||||
component: string | Component,
|
|
||||||
): string | Component {
|
|
||||||
if (isString(component)) {
|
|
||||||
return resolveAsset(COMPONENTS, component, false) || component
|
|
||||||
} else {
|
|
||||||
return component
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { toHandlers as _toHandlers } from '@vue/runtime-shared'
|
|
||||||
import { warn } from '../warning'
|
|
||||||
import { NOOP } from '@vue/shared'
|
|
||||||
|
|
||||||
export const toHandlers = (
|
|
||||||
obj: Record<string, any>,
|
|
||||||
preserveCaseIfNecessary?: boolean | undefined,
|
|
||||||
): Record<string, any> =>
|
|
||||||
_toHandlers(__DEV__ ? warn : NOOP, obj, preserveCaseIfNecessary)
|
|
|
@ -1,110 +1,9 @@
|
||||||
// Core API ------------------------------------------------------------------
|
export { createComponent } from './component'
|
||||||
|
|
||||||
export const version: string = __VERSION__
|
|
||||||
export {
|
|
||||||
// core
|
|
||||||
TrackOpTypes,
|
|
||||||
TriggerOpTypes,
|
|
||||||
reactive,
|
|
||||||
ref,
|
|
||||||
readonly,
|
|
||||||
computed,
|
|
||||||
// utilities
|
|
||||||
unref,
|
|
||||||
proxyRefs,
|
|
||||||
isRef,
|
|
||||||
toRef,
|
|
||||||
toValue,
|
|
||||||
toRefs,
|
|
||||||
isProxy,
|
|
||||||
isReactive,
|
|
||||||
isReadonly,
|
|
||||||
isShallow,
|
|
||||||
// advanced
|
|
||||||
customRef,
|
|
||||||
triggerRef,
|
|
||||||
shallowRef,
|
|
||||||
shallowReactive,
|
|
||||||
shallowReadonly,
|
|
||||||
markRaw,
|
|
||||||
toRaw,
|
|
||||||
// effect
|
|
||||||
stop,
|
|
||||||
ReactiveEffect,
|
|
||||||
onEffectCleanup,
|
|
||||||
// effect scope
|
|
||||||
effectScope,
|
|
||||||
EffectScope,
|
|
||||||
getCurrentScope,
|
|
||||||
onScopeDispose,
|
|
||||||
// baseWatch
|
|
||||||
onWatcherCleanup,
|
|
||||||
getCurrentWatcher,
|
|
||||||
} from '@vue/reactivity'
|
|
||||||
export type {
|
|
||||||
Ref,
|
|
||||||
MaybeRef,
|
|
||||||
MaybeRefOrGetter,
|
|
||||||
ToRef,
|
|
||||||
ToRefs,
|
|
||||||
UnwrapRef,
|
|
||||||
ShallowRef,
|
|
||||||
ShallowUnwrapRef,
|
|
||||||
CustomRefFactory,
|
|
||||||
ReactiveFlags,
|
|
||||||
DeepReadonly,
|
|
||||||
ShallowReactive,
|
|
||||||
UnwrapNestedRefs,
|
|
||||||
ComputedRef,
|
|
||||||
WritableComputedRef,
|
|
||||||
WritableComputedOptions,
|
|
||||||
ComputedGetter,
|
|
||||||
ComputedSetter,
|
|
||||||
ReactiveEffectRunner,
|
|
||||||
ReactiveEffectOptions,
|
|
||||||
EffectScheduler,
|
|
||||||
DebuggerOptions,
|
|
||||||
DebuggerEvent,
|
|
||||||
DebuggerEventExtraInfo,
|
|
||||||
Raw,
|
|
||||||
Reactive,
|
|
||||||
} from '@vue/reactivity'
|
|
||||||
|
|
||||||
import { NOOP } from '@vue/shared'
|
|
||||||
import { warn as _warn } from './warning'
|
|
||||||
export const warn = (__DEV__ ? _warn : NOOP) as typeof _warn
|
|
||||||
|
|
||||||
export { nextTick } from './scheduler'
|
|
||||||
export {
|
|
||||||
getCurrentInstance,
|
|
||||||
ComponentInternalInstance,
|
|
||||||
type Component as Component,
|
|
||||||
type ObjectComponent,
|
|
||||||
type FunctionalComponent,
|
|
||||||
type SetupFn,
|
|
||||||
} from './component'
|
|
||||||
export { createSlot } from './componentSlots'
|
|
||||||
export { renderEffect } from './renderEffect'
|
export { renderEffect } from './renderEffect'
|
||||||
export {
|
export { createVaporApp } from './apiCreateApp'
|
||||||
watch,
|
export { useEmit } from './componentEmits'
|
||||||
watchEffect,
|
|
||||||
watchPostEffect,
|
|
||||||
watchSyncEffect,
|
|
||||||
type WatchEffect,
|
|
||||||
type WatchOptions,
|
|
||||||
type WatchOptionsBase,
|
|
||||||
type WatchCallback,
|
|
||||||
type WatchSource,
|
|
||||||
type WatchStopHandle,
|
|
||||||
} from './apiWatch'
|
|
||||||
export {
|
|
||||||
withDirectives,
|
|
||||||
type Directive,
|
|
||||||
type DirectiveBinding,
|
|
||||||
type DirectiveArguments,
|
|
||||||
type DirectiveModifiers,
|
|
||||||
} from './directives'
|
|
||||||
|
|
||||||
|
// DOM
|
||||||
export { template, children, next } from './dom/template'
|
export { template, children, next } from './dom/template'
|
||||||
export { insert, prepend, remove, createTextNode } from './dom/element'
|
export { insert, prepend, remove, createTextNode } from './dom/element'
|
||||||
export { setStyle } from './dom/style'
|
export { setStyle } from './dom/style'
|
||||||
|
@ -120,75 +19,3 @@ export {
|
||||||
} from './dom/prop'
|
} from './dom/prop'
|
||||||
export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event'
|
export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event'
|
||||||
export { setRef } from './dom/templateRef'
|
export { setRef } from './dom/templateRef'
|
||||||
|
|
||||||
export { defineComponent } from './apiDefineComponent'
|
|
||||||
export {
|
|
||||||
type InjectionKey,
|
|
||||||
inject,
|
|
||||||
provide,
|
|
||||||
hasInjectionContext,
|
|
||||||
} from './apiInject'
|
|
||||||
export {
|
|
||||||
onBeforeMount,
|
|
||||||
onMounted,
|
|
||||||
onBeforeUpdate,
|
|
||||||
onUpdated,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onUnmounted,
|
|
||||||
// onActivated,
|
|
||||||
// onDeactivated,
|
|
||||||
onRenderTracked,
|
|
||||||
onRenderTriggered,
|
|
||||||
onErrorCaptured,
|
|
||||||
// onServerPrefetch,
|
|
||||||
} from './apiLifecycle'
|
|
||||||
export { useAttrs, useSlots } from './apiSetupHelpers'
|
|
||||||
export {
|
|
||||||
createVaporApp,
|
|
||||||
type App,
|
|
||||||
type AppConfig,
|
|
||||||
type AppContext,
|
|
||||||
type Plugin,
|
|
||||||
type ObjectPlugin,
|
|
||||||
type FunctionPlugin,
|
|
||||||
} from './apiCreateVaporApp'
|
|
||||||
export { createBranch, createIf } from './apiCreateIf'
|
|
||||||
export { createFor, createForSlots } from './apiCreateFor'
|
|
||||||
export { createComponent } from './apiCreateComponent'
|
|
||||||
export { createSelector } from './apiCreateSelector'
|
|
||||||
export { setInheritAttrs } from './componentAttrs'
|
|
||||||
|
|
||||||
export {
|
|
||||||
resolveComponent,
|
|
||||||
resolveDirective,
|
|
||||||
resolveDynamicComponent,
|
|
||||||
} from './helpers/resolveAssets'
|
|
||||||
export { toHandlers } from './helpers/toHandlers'
|
|
||||||
|
|
||||||
export { withDestructure } from './destructure'
|
|
||||||
|
|
||||||
// **Internal** DOM-only runtime directive helpers
|
|
||||||
export {
|
|
||||||
vModelText,
|
|
||||||
vModelCheckbox,
|
|
||||||
vModelRadio,
|
|
||||||
vModelSelect,
|
|
||||||
vModelDynamic,
|
|
||||||
} from './directives/vModel'
|
|
||||||
export { vShow } from './directives/vShow'
|
|
||||||
|
|
||||||
// For devtools
|
|
||||||
import {
|
|
||||||
type DevtoolsHook,
|
|
||||||
devtools as _devtools,
|
|
||||||
setDevtoolsHook as _setDevtoolsHook,
|
|
||||||
} from './devtools'
|
|
||||||
|
|
||||||
export const devtools = (
|
|
||||||
__DEV__ || __ESM_BUNDLER__ ? _devtools : undefined
|
|
||||||
) as DevtoolsHook
|
|
||||||
export const setDevtoolsHook = (
|
|
||||||
__DEV__ || __ESM_BUNDLER__ ? _setDevtoolsHook : NOOP
|
|
||||||
) as typeof _setDevtoolsHook
|
|
||||||
|
|
||||||
export * from './_new'
|
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
/* eslint-disable no-restricted-globals */
|
|
||||||
import {
|
|
||||||
type ComponentInternalInstance,
|
|
||||||
formatComponentName,
|
|
||||||
} from './component'
|
|
||||||
import { devtoolsPerfEnd, devtoolsPerfStart } from './devtools'
|
|
||||||
|
|
||||||
let supported: boolean
|
|
||||||
let perf: Performance
|
|
||||||
|
|
||||||
export function startMeasure(
|
|
||||||
instance: ComponentInternalInstance,
|
|
||||||
type: string,
|
|
||||||
): void {
|
|
||||||
if (instance.appContext.config.performance && isSupported()) {
|
|
||||||
perf.mark(`vue-${type}-${instance.uid}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
|
||||||
devtoolsPerfStart(instance, type, isSupported() ? perf.now() : Date.now())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function endMeasure(
|
|
||||||
instance: ComponentInternalInstance,
|
|
||||||
type: string,
|
|
||||||
): void {
|
|
||||||
if (instance.appContext.config.performance && isSupported()) {
|
|
||||||
const startTag = `vue-${type}-${instance.uid}`
|
|
||||||
const endTag = startTag + `:end`
|
|
||||||
perf.mark(endTag)
|
|
||||||
perf.measure(
|
|
||||||
`<${formatComponentName(instance, instance.type)}> ${type}`,
|
|
||||||
startTag,
|
|
||||||
endTag,
|
|
||||||
)
|
|
||||||
perf.clearMarks(startTag)
|
|
||||||
perf.clearMarks(endTag)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
|
||||||
devtoolsPerfEnd(instance, type, isSupported() ? perf.now() : Date.now())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSupported() {
|
|
||||||
if (supported !== undefined) {
|
|
||||||
return supported
|
|
||||||
}
|
|
||||||
if (typeof window !== 'undefined' && window.performance) {
|
|
||||||
supported = true
|
|
||||||
perf = window.performance
|
|
||||||
} else {
|
|
||||||
supported = false
|
|
||||||
}
|
|
||||||
return supported
|
|
||||||
}
|
|
|
@ -1,95 +1,19 @@
|
||||||
import { EffectFlags, ReactiveEffect, getCurrentScope } from '@vue/reactivity'
|
import { ReactiveEffect } from '@vue/reactivity'
|
||||||
import { invokeArrayFns } from '@vue/shared'
|
import { type SchedulerJob, queueJob } from '@vue/runtime-core'
|
||||||
import {
|
import { currentInstance } from './component'
|
||||||
type ComponentInternalInstance,
|
|
||||||
getCurrentInstance,
|
|
||||||
setCurrentInstance,
|
|
||||||
} from './component'
|
|
||||||
import {
|
|
||||||
type SchedulerJob,
|
|
||||||
VaporSchedulerJobFlags,
|
|
||||||
queueJob,
|
|
||||||
queuePostFlushCb,
|
|
||||||
} from './scheduler'
|
|
||||||
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
|
||||||
|
|
||||||
export function renderEffect(cb: () => void): void {
|
export function renderEffect(fn: () => void): void {
|
||||||
const instance = getCurrentInstance()
|
const updateFn = () => {
|
||||||
const scope = getCurrentScope()
|
fn()
|
||||||
|
|
||||||
if (scope) {
|
|
||||||
const baseCb = cb
|
|
||||||
cb = () => scope.run(baseCb)
|
|
||||||
}
|
}
|
||||||
|
const effect = new ReactiveEffect(updateFn)
|
||||||
if (instance) {
|
const job: SchedulerJob = effect.runIfDirty.bind(effect)
|
||||||
const baseCb = cb
|
job.i = currentInstance as any
|
||||||
cb = () => {
|
job.id = currentInstance!.uid
|
||||||
const reset = setCurrentInstance(instance)
|
|
||||||
baseCb()
|
|
||||||
reset()
|
|
||||||
}
|
|
||||||
job.id = instance.uid
|
|
||||||
}
|
|
||||||
|
|
||||||
const effect = new ReactiveEffect(() =>
|
|
||||||
callWithAsyncErrorHandling(cb, instance, VaporErrorCodes.RENDER_FUNCTION),
|
|
||||||
)
|
|
||||||
|
|
||||||
effect.scheduler = () => queueJob(job)
|
|
||||||
if (__DEV__ && instance) {
|
|
||||||
effect.onTrack = instance.rtc
|
|
||||||
? e => invokeArrayFns(instance.rtc!, e)
|
|
||||||
: void 0
|
|
||||||
effect.onTrigger = instance.rtg
|
|
||||||
? e => invokeArrayFns(instance.rtg!, e)
|
|
||||||
: void 0
|
|
||||||
}
|
|
||||||
effect.run()
|
|
||||||
|
|
||||||
function job() {
|
|
||||||
if (!(effect.flags & EffectFlags.ACTIVE) || !effect.dirty) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const reset = instance && setCurrentInstance(instance)
|
|
||||||
|
|
||||||
if (instance && instance.isMounted && !instance.isUpdating) {
|
|
||||||
instance.isUpdating = true
|
|
||||||
|
|
||||||
const { bu, u } = instance
|
|
||||||
// beforeUpdate hook
|
|
||||||
if (bu) {
|
|
||||||
invokeArrayFns(bu)
|
|
||||||
}
|
|
||||||
|
|
||||||
effect.run()
|
|
||||||
|
|
||||||
queuePostFlushCb(() => {
|
|
||||||
instance.isUpdating = false
|
|
||||||
const reset = setCurrentInstance(instance)
|
|
||||||
// updated hook
|
|
||||||
if (u) {
|
|
||||||
queuePostFlushCb(u)
|
|
||||||
}
|
|
||||||
reset()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
effect.run()
|
|
||||||
}
|
|
||||||
|
|
||||||
reset && reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function firstEffect(
|
|
||||||
instance: ComponentInternalInstance,
|
|
||||||
fn: () => void,
|
|
||||||
): void {
|
|
||||||
const effect = new ReactiveEffect(fn)
|
|
||||||
const job: SchedulerJob = () => effect.run()
|
|
||||||
job.flags! |= VaporSchedulerJobFlags.PRE
|
|
||||||
job.id = instance.uid
|
|
||||||
effect.scheduler = () => queueJob(job)
|
effect.scheduler = () => queueJob(job)
|
||||||
effect.run()
|
effect.run()
|
||||||
|
|
||||||
|
// TODO lifecycle
|
||||||
|
// TODO recurse handling
|
||||||
|
// TODO measure
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,227 +0,0 @@
|
||||||
import type { WatchScheduler } from '@vue/reactivity'
|
|
||||||
import type { ComponentInternalInstance } from './component'
|
|
||||||
import { isArray } from '@vue/shared'
|
|
||||||
|
|
||||||
export enum VaporSchedulerJobFlags {
|
|
||||||
QUEUED = 1 << 0,
|
|
||||||
PRE = 1 << 1,
|
|
||||||
/**
|
|
||||||
* Indicates whether the effect is allowed to recursively trigger itself
|
|
||||||
* when managed by the scheduler.
|
|
||||||
*
|
|
||||||
* By default, a job cannot trigger itself because some built-in method calls,
|
|
||||||
* e.g. Array.prototype.push actually performs reads as well (#1740) which
|
|
||||||
* can lead to confusing infinite loops.
|
|
||||||
* The allowed cases are component update functions and watch callbacks.
|
|
||||||
* Component update functions may update child component props, which in turn
|
|
||||||
* trigger flush: "pre" watch callbacks that mutates state that the parent
|
|
||||||
* relies on (#1801). Watch callbacks doesn't track its dependencies so if it
|
|
||||||
* triggers itself again, it's likely intentional and it is the user's
|
|
||||||
* responsibility to perform recursive state mutation that eventually
|
|
||||||
* stabilizes (#1727).
|
|
||||||
*/
|
|
||||||
ALLOW_RECURSE = 1 << 2,
|
|
||||||
DISPOSED = 1 << 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SchedulerJob extends Function {
|
|
||||||
id?: number
|
|
||||||
/**
|
|
||||||
* flags can technically be undefined, but it can still be used in bitwise
|
|
||||||
* operations just like 0.
|
|
||||||
*/
|
|
||||||
flags?: VaporSchedulerJobFlags
|
|
||||||
/**
|
|
||||||
* Attached by renderer.ts when setting up a component's render effect
|
|
||||||
* Used to obtain component information when reporting max recursive updates.
|
|
||||||
*/
|
|
||||||
i?: ComponentInternalInstance
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SchedulerJobs = SchedulerJob | SchedulerJob[]
|
|
||||||
export type QueueEffect = (
|
|
||||||
cb: SchedulerJobs,
|
|
||||||
suspense: ComponentInternalInstance | null,
|
|
||||||
) => void
|
|
||||||
|
|
||||||
let isFlushing = false
|
|
||||||
let isFlushPending = false
|
|
||||||
|
|
||||||
// TODO: The queues in Vapor need to be merged with the queues in Core.
|
|
||||||
// this is a temporary solution, the ultimate goal is to support
|
|
||||||
// the mixed use of vapor components and default components.
|
|
||||||
const queue: SchedulerJob[] = []
|
|
||||||
let flushIndex = 0
|
|
||||||
|
|
||||||
// TODO: The queues in Vapor need to be merged with the queues in Core.
|
|
||||||
// this is a temporary solution, the ultimate goal is to support
|
|
||||||
// the mixed use of vapor components and default components.
|
|
||||||
const pendingPostFlushCbs: SchedulerJob[] = []
|
|
||||||
let activePostFlushCbs: SchedulerJob[] | null = null
|
|
||||||
let postFlushIndex = 0
|
|
||||||
|
|
||||||
const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
|
|
||||||
let currentFlushPromise: Promise<void> | null = null
|
|
||||||
|
|
||||||
export function queueJob(job: SchedulerJob): void {
|
|
||||||
let lastOne: SchedulerJob | undefined
|
|
||||||
if (!(job.flags! & VaporSchedulerJobFlags.QUEUED)) {
|
|
||||||
if (job.id == null) {
|
|
||||||
queue.push(job)
|
|
||||||
} else if (
|
|
||||||
// fast path when the job id is larger than the tail
|
|
||||||
!(job.flags! & VaporSchedulerJobFlags.PRE) &&
|
|
||||||
job.id >= (((lastOne = queue[queue.length - 1]) && lastOne.id) || 0)
|
|
||||||
) {
|
|
||||||
queue.push(job)
|
|
||||||
} else {
|
|
||||||
queue.splice(findInsertionIndex(job.id), 0, job)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(job.flags! & VaporSchedulerJobFlags.ALLOW_RECURSE)) {
|
|
||||||
job.flags! |= VaporSchedulerJobFlags.QUEUED
|
|
||||||
}
|
|
||||||
queueFlush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queuePostFlushCb(cb: SchedulerJobs): void {
|
|
||||||
if (!isArray(cb)) {
|
|
||||||
if (!(cb.flags! & VaporSchedulerJobFlags.QUEUED)) {
|
|
||||||
pendingPostFlushCbs.push(cb)
|
|
||||||
if (!(cb.flags! & VaporSchedulerJobFlags.ALLOW_RECURSE)) {
|
|
||||||
cb.flags! |= VaporSchedulerJobFlags.QUEUED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if cb is an array, it is a component lifecycle hook which can only be
|
|
||||||
// triggered by a job, which is already deduped in the main queue, so
|
|
||||||
// we can skip duplicate check here to improve perf
|
|
||||||
pendingPostFlushCbs.push(...cb)
|
|
||||||
}
|
|
||||||
queueFlush()
|
|
||||||
}
|
|
||||||
|
|
||||||
function queueFlush() {
|
|
||||||
if (!isFlushing && !isFlushPending) {
|
|
||||||
isFlushPending = true
|
|
||||||
currentFlushPromise = resolvedPromise.then(flushJobs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function flushPostFlushCbs(): void {
|
|
||||||
if (!pendingPostFlushCbs.length) return
|
|
||||||
|
|
||||||
const deduped = [...new Set(pendingPostFlushCbs)]
|
|
||||||
pendingPostFlushCbs.length = 0
|
|
||||||
|
|
||||||
// #1947 already has active queue, nested flushPostFlushCbs call
|
|
||||||
if (activePostFlushCbs) {
|
|
||||||
activePostFlushCbs.push(...deduped)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
activePostFlushCbs = deduped
|
|
||||||
|
|
||||||
activePostFlushCbs.sort((a, b) => getId(a) - getId(b))
|
|
||||||
|
|
||||||
for (
|
|
||||||
postFlushIndex = 0;
|
|
||||||
postFlushIndex < activePostFlushCbs.length;
|
|
||||||
postFlushIndex++
|
|
||||||
) {
|
|
||||||
activePostFlushCbs[postFlushIndex]()
|
|
||||||
activePostFlushCbs[postFlushIndex].flags! &= ~VaporSchedulerJobFlags.QUEUED
|
|
||||||
}
|
|
||||||
activePostFlushCbs = null
|
|
||||||
postFlushIndex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: dev mode and checkRecursiveUpdates
|
|
||||||
function flushJobs() {
|
|
||||||
if (__BENCHMARK__) performance.mark('flushJobs-start')
|
|
||||||
isFlushPending = false
|
|
||||||
isFlushing = true
|
|
||||||
|
|
||||||
// Sort queue before flush.
|
|
||||||
// This ensures that:
|
|
||||||
// 1. Components are updated from parent to child. (because parent is always
|
|
||||||
// created before the child so its render effect will have smaller
|
|
||||||
// priority number)
|
|
||||||
// 2. If a component is unmounted during a parent component's update,
|
|
||||||
// its update can be skipped.
|
|
||||||
queue.sort(comparator)
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (let i = 0; i < queue!.length; i++) {
|
|
||||||
queue[i]()
|
|
||||||
queue[i].flags! &= ~VaporSchedulerJobFlags.QUEUED
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
flushIndex = 0
|
|
||||||
queue.length = 0
|
|
||||||
|
|
||||||
flushPostFlushCbs()
|
|
||||||
|
|
||||||
isFlushing = false
|
|
||||||
currentFlushPromise = null
|
|
||||||
// some postFlushCb queued jobs!
|
|
||||||
// keep flushing until it drains.
|
|
||||||
if (queue.length || pendingPostFlushCbs.length) {
|
|
||||||
flushJobs()
|
|
||||||
}
|
|
||||||
if (__BENCHMARK__) performance.mark('flushJobs-end')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function nextTick<T = void, R = void>(
|
|
||||||
this: T,
|
|
||||||
fn?: (this: T) => R,
|
|
||||||
): Promise<Awaited<R>> {
|
|
||||||
const p = currentFlushPromise || resolvedPromise
|
|
||||||
return fn ? p.then(this ? fn.bind(this) : fn) : p
|
|
||||||
}
|
|
||||||
|
|
||||||
// #2768
|
|
||||||
// Use binary-search to find a suitable position in the queue,
|
|
||||||
// so that the queue maintains the increasing order of job's id,
|
|
||||||
// which can prevent the job from being skipped and also can avoid repeated patching.
|
|
||||||
function findInsertionIndex(id: number) {
|
|
||||||
// the start index should be `flushIndex + 1`
|
|
||||||
let start = flushIndex + 1
|
|
||||||
let end = queue.length
|
|
||||||
|
|
||||||
while (start < end) {
|
|
||||||
const middle = (start + end) >>> 1
|
|
||||||
const middleJob = queue[middle]
|
|
||||||
const middleJobId = getId(middleJob)
|
|
||||||
if (
|
|
||||||
middleJobId < id ||
|
|
||||||
(middleJobId === id && middleJob.flags! & VaporSchedulerJobFlags.PRE)
|
|
||||||
) {
|
|
||||||
start = middle + 1
|
|
||||||
} else {
|
|
||||||
end = middle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return start
|
|
||||||
}
|
|
||||||
|
|
||||||
const getId = (job: SchedulerJob): number =>
|
|
||||||
job.id == null ? Infinity : job.id
|
|
||||||
|
|
||||||
const comparator = (a: SchedulerJob, b: SchedulerJob): number => {
|
|
||||||
const diff = getId(a) - getId(b)
|
|
||||||
if (diff === 0) {
|
|
||||||
const isAPre = a.flags! & VaporSchedulerJobFlags.PRE
|
|
||||||
const isBPre = b.flags! & VaporSchedulerJobFlags.PRE
|
|
||||||
if (isAPre && !isBPre) return -1
|
|
||||||
if (isBPre && !isAPre) return 1
|
|
||||||
}
|
|
||||||
return diff
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SchedulerFactory = (
|
|
||||||
instance: ComponentInternalInstance | null,
|
|
||||||
) => WatchScheduler
|
|
|
@ -1,153 +0,0 @@
|
||||||
import {
|
|
||||||
type ComponentInternalInstance,
|
|
||||||
currentInstance,
|
|
||||||
formatComponentName,
|
|
||||||
} from './component'
|
|
||||||
import { isFunction, isString } from '@vue/shared'
|
|
||||||
import { isRef, pauseTracking, resetTracking, toRaw } from '@vue/reactivity'
|
|
||||||
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
|
|
||||||
import type { NormalizedRawProps } from './componentProps'
|
|
||||||
|
|
||||||
type TraceEntry = {
|
|
||||||
instance: ComponentInternalInstance
|
|
||||||
recurseCount: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type ComponentTraceStack = TraceEntry[]
|
|
||||||
|
|
||||||
export function warn(msg: string, ...args: any[]): void {
|
|
||||||
// avoid props formatting or warn handler tracking deps that might be mutated
|
|
||||||
// during patch, leading to infinite recursion.
|
|
||||||
pauseTracking()
|
|
||||||
|
|
||||||
const instance = currentInstance
|
|
||||||
const appWarnHandler = instance && instance.appContext.config.warnHandler
|
|
||||||
const trace = getComponentTrace()
|
|
||||||
|
|
||||||
if (appWarnHandler) {
|
|
||||||
callWithErrorHandling(
|
|
||||||
appWarnHandler,
|
|
||||||
instance,
|
|
||||||
VaporErrorCodes.APP_WARN_HANDLER,
|
|
||||||
[
|
|
||||||
msg +
|
|
||||||
args
|
|
||||||
.map(a => (a.toString && a.toString()) ?? JSON.stringify(a))
|
|
||||||
.join(''),
|
|
||||||
instance,
|
|
||||||
trace
|
|
||||||
.map(
|
|
||||||
({ instance }) =>
|
|
||||||
`at <${formatComponentName(instance, instance.type)}>`,
|
|
||||||
)
|
|
||||||
.join('\n'),
|
|
||||||
trace,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
const warnArgs = [`[Vue warn]: ${msg}`, ...args]
|
|
||||||
/* istanbul ignore if */
|
|
||||||
if (
|
|
||||||
trace.length &&
|
|
||||||
// avoid spamming console during tests
|
|
||||||
!__TEST__
|
|
||||||
) {
|
|
||||||
warnArgs.push(`\n`, ...formatTrace(trace))
|
|
||||||
}
|
|
||||||
console.warn(...warnArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
resetTracking()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getComponentTrace(): ComponentTraceStack {
|
|
||||||
let instance = currentInstance
|
|
||||||
if (!instance) return []
|
|
||||||
|
|
||||||
// we can't just use the stack because it will be incomplete during updates
|
|
||||||
// that did not start from the root. Re-construct the parent chain using
|
|
||||||
// instance parent pointers.
|
|
||||||
const stack: ComponentTraceStack = []
|
|
||||||
|
|
||||||
while (instance) {
|
|
||||||
const last = stack[0]
|
|
||||||
if (last && last.instance === instance) {
|
|
||||||
last.recurseCount++
|
|
||||||
} else {
|
|
||||||
stack.push({
|
|
||||||
instance,
|
|
||||||
recurseCount: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
instance = instance.parent
|
|
||||||
}
|
|
||||||
|
|
||||||
return stack
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatTrace(trace: ComponentTraceStack): any[] {
|
|
||||||
const logs: any[] = []
|
|
||||||
trace.forEach((entry, i) => {
|
|
||||||
logs.push(...(i === 0 ? [] : [`\n`]), ...formatTraceEntry(entry))
|
|
||||||
})
|
|
||||||
return logs
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatTraceEntry({ instance, recurseCount }: TraceEntry): any[] {
|
|
||||||
const postfix =
|
|
||||||
recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``
|
|
||||||
const isRoot = instance ? instance.parent == null : false
|
|
||||||
const open = ` at <${formatComponentName(instance, instance.type, isRoot)}`
|
|
||||||
const close = `>` + postfix
|
|
||||||
return instance.rawProps.length
|
|
||||||
? [open, ...formatProps(instance.rawProps), close]
|
|
||||||
: [open + close]
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatProps(rawProps: NormalizedRawProps): any[] {
|
|
||||||
const fullProps: Record<string, any> = {}
|
|
||||||
for (const props of rawProps) {
|
|
||||||
if (isFunction(props)) {
|
|
||||||
const propsObj = props()
|
|
||||||
for (const key in propsObj) {
|
|
||||||
fullProps[key] = propsObj[key]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const key in props) {
|
|
||||||
fullProps[key] = props[key]()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const res: any[] = []
|
|
||||||
Object.keys(fullProps)
|
|
||||||
.slice(0, 3)
|
|
||||||
.forEach(key => res.push(...formatProp(key, fullProps[key])))
|
|
||||||
|
|
||||||
if (fullProps.length > 3) {
|
|
||||||
res.push(` ...`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatProp(key: string, value: unknown, raw?: boolean): any {
|
|
||||||
if (isString(value)) {
|
|
||||||
value = JSON.stringify(value)
|
|
||||||
return raw ? value : [`${key}=${value}`]
|
|
||||||
} else if (
|
|
||||||
typeof value === 'number' ||
|
|
||||||
typeof value === 'boolean' ||
|
|
||||||
value == null
|
|
||||||
) {
|
|
||||||
return raw ? value : [`${key}=${value}`]
|
|
||||||
} else if (isRef(value)) {
|
|
||||||
value = formatProp(key, toRaw(value.value), true)
|
|
||||||
return raw ? value : [`${key}=Ref<`, value, `>`]
|
|
||||||
} else if (isFunction(value)) {
|
|
||||||
return [`${key}=fn${value.name ? `<${value.name}>` : ``}`]
|
|
||||||
} else {
|
|
||||||
value = toRaw(value)
|
|
||||||
return raw ? value : [`${key}=`, value]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,4 +12,4 @@ if (__DEV__ && __BROWSER__) {
|
||||||
initCustomFormatter()
|
initCustomFormatter()
|
||||||
}
|
}
|
||||||
|
|
||||||
export * from '@vue/runtime-vapor'
|
export * from '@vue/runtime-vapor/src'
|
||||||
|
|
|
@ -399,9 +399,6 @@ importers:
|
||||||
'@vue/reactivity':
|
'@vue/reactivity':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../reactivity
|
version: link:../reactivity
|
||||||
'@vue/runtime-shared':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../runtime-shared
|
|
||||||
'@vue/shared':
|
'@vue/shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../shared
|
version: link:../shared
|
||||||
|
@ -425,12 +422,6 @@ importers:
|
||||||
specifier: ^2.0.7
|
specifier: ^2.0.7
|
||||||
version: 2.0.7
|
version: 2.0.7
|
||||||
|
|
||||||
packages/runtime-shared:
|
|
||||||
dependencies:
|
|
||||||
'@vue/shared':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../shared
|
|
||||||
|
|
||||||
packages/runtime-test:
|
packages/runtime-test:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/runtime-core':
|
'@vue/runtime-core':
|
||||||
|
@ -445,9 +436,9 @@ importers:
|
||||||
'@vue/reactivity':
|
'@vue/reactivity':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../reactivity
|
version: link:../reactivity
|
||||||
'@vue/runtime-shared':
|
'@vue/runtime-dom':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../runtime-shared
|
version: link:../runtime-dom
|
||||||
'@vue/shared':
|
'@vue/shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../shared
|
version: link:../shared
|
||||||
|
|
|
@ -39,5 +39,8 @@
|
||||||
"scripts/*",
|
"scripts/*",
|
||||||
"rollup.*.js"
|
"rollup.*.js"
|
||||||
],
|
],
|
||||||
"exclude": ["packages-private/sfc-playground/src/vue-dev-proxy*"]
|
"exclude": [
|
||||||
|
"packages-private/sfc-playground/src/vue-dev-proxy*",
|
||||||
|
"packages/runtime-vapor/src/_old"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue