refactor: remove runtime-shared

This commit is contained in:
Evan You 2024-12-04 13:50:54 +08:00
parent 4fe05bdd74
commit c73ee16345
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
98 changed files with 545 additions and 5318 deletions

View File

@ -43,7 +43,7 @@ export interface BaseIRNode {
type: IRNodeTypes
}
export type VaporHelper = keyof typeof import('@vue/runtime-vapor')
export type VaporHelper = keyof typeof import('packages/runtime-vapor/src')
export interface BlockIRNode extends BaseIRNode {
type: IRNodeTypes.BLOCK

View File

@ -47,7 +47,6 @@
"homepage": "https://github.com/vuejs/vue-vapor/tree/main/packages/runtime-core#readme",
"dependencies": {
"@vue/shared": "workspace:*",
"@vue/reactivity": "workspace:*",
"@vue/runtime-shared": "workspace:*"
"@vue/reactivity": "workspace:*"
}
}

View File

@ -1,6 +1,7 @@
import {
type Component,
type ConcreteComponent,
type Data,
type GenericComponent,
type GenericComponentInstance,
getComponentPublicInstance,
@ -22,7 +23,6 @@ import { warn } from './warning'
import type { VNode } from './vnode'
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
import { NO, extend, isFunction, isObject } from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import { version } from '.'
import { installAppCompatProperties } from './compat/global'
import type { NormalizedPropsOptions } from './componentProps'
@ -256,8 +256,8 @@ export function createAppContext(): AppContext {
}
}
export type CreateAppFunction<HostElement> = (
rootComponent: GenericComponent,
export type CreateAppFunction<HostElement, Comp = Component> = (
rootComponent: Comp,
rootProps?: Data | null,
) => App<HostElement>
@ -275,13 +275,13 @@ export type AppUnmountFn = (app: App) => void
/**
* @internal
*/
export function createAppAPI<HostElement>(
export function createAppAPI<HostElement, Comp = Component>(
// render: RootRenderFunction<HostElement>,
// hydrate?: RootHydrateFunction,
mount: AppMountFn<HostElement>,
unmount: AppUnmountFn,
render?: RootRenderFunction,
): CreateAppFunction<HostElement> {
): CreateAppFunction<HostElement, Comp> {
return function createApp(rootComponent, rootProps = null) {
if (!isFunction(rootComponent)) {
rootComponent = extend({}, rootComponent)

View File

@ -462,7 +462,7 @@ function installCompatMount(
* function simulates that behavior.
*/
app._createRoot = options => {
const component = app._component
const component = app._component as Component
const vnode = createVNode(component, options.propsData || null)
vnode.appContext = context

View File

@ -1,7 +1,6 @@
import { isArray } from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import { inject } from '../apiInject'
import type { ComponentInternalInstance } from '../component'
import type { ComponentInternalInstance, Data } from '../component'
import {
type ComponentOptions,
resolveMergedOptions,

View File

@ -10,11 +10,11 @@ import {
normalizeStyle,
toHandlerKey,
} from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import type {
Component,
ComponentInternalInstance,
ComponentOptions,
Data,
InternalRenderFunction,
} from '../component'
import { currentRenderingInstance } from '../componentRenderContext'

View File

@ -7,13 +7,12 @@ import {
isReservedProp,
normalizeClass,
} from '@vue/shared'
import type { ComponentInternalInstance } from '../component'
import type { ComponentInternalInstance, Data } from '../component'
import type { Slot } from '../componentSlots'
import { createSlots } from '../helpers/createSlots'
import { renderSlot } from '../helpers/renderSlot'
import { toHandlers } from '../helpers/toHandlers'
import { type VNode, mergeProps } from '../vnode'
import type { Data } from '@vue/runtime-shared'
function toObject(arr: Array<any>): Object {
const res = {}

View File

@ -73,7 +73,6 @@ import {
isObject,
isPromise,
} from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import type { SuspenseBoundary } from './components/Suspense'
import type { CompilerOptions } from '@vue/compiler-core'
import { markAttrsAccessed } from './componentRenderUtils'
@ -98,6 +97,8 @@ import { markAsyncBoundary } from './helpers/useId'
import { isAsyncWrapper } from './apiAsyncComponent'
import type { RendererElement } from './renderer'
export type Data = Record<string, unknown>
/**
* Public utility type for extracting the instance type of a component.
* Works with all valid component definition types. This is intended to replace
@ -509,7 +510,7 @@ export interface ComponentInternalInstance extends GenericComponentInstance {
* setup related
* @internal
*/
setupState: Data | null
setupState: Data
/**
* devtools access to additional info
* @internal

View File

@ -3,6 +3,7 @@ import {
type ComponentInternalInstance,
type ComponentInternalOptions,
type ConcreteComponent,
type Data,
type InternalRenderFunction,
type SetupContext,
currentInstance,
@ -18,7 +19,6 @@ import {
isPromise,
isString,
} from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import { type Ref, getCurrentScope, isRef, traverse } from '@vue/reactivity'
import { computed } from './apiComputed'
import {

View File

@ -24,12 +24,12 @@ import {
makeMap,
toRawType,
} from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import { warn } from './warning'
import {
type ComponentInternalInstance,
type ComponentOptions,
type ConcreteComponent,
type Data,
type GenericComponentInstance,
setCurrentInstance,
} from './component'

View File

@ -1,6 +1,7 @@
import {
type Component,
type ComponentInternalInstance,
type Data,
getComponentPublicInstance,
isStatefulComponent,
} from './component'
@ -23,7 +24,6 @@ import {
isGloballyAllowed,
isString,
} from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import {
ReactiveFlags,
type ShallowUnwrapRef,

View File

@ -1,5 +1,6 @@
import {
type ComponentInternalInstance,
type Data,
type FunctionalComponent,
getComponentName,
} from './component'
@ -15,7 +16,6 @@ import {
} from './vnode'
import { ErrorCodes, handleError } from './errorHandling'
import { PatchFlags, ShapeFlags, isModelListener, isOn } from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import { warn } from './warning'
import { isHmrUpdating } from './hmr'
import type { NormalizedProps } from './componentProps'

View File

@ -13,10 +13,10 @@ return withDirectives(h(comp), [
import type { VNode } from './vnode'
import { EMPTY_OBJ, isBuiltInDirective, isFunction } from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import { warn } from './warning'
import {
type ComponentInternalInstance,
type Data,
getComponentPublicInstance,
} from './component'
import { currentRenderingInstance } from './componentRenderContext'

View File

@ -14,9 +14,9 @@ import {
openBlock,
} from '../vnode'
import { PatchFlags, SlotFlags, isSymbol } from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import { warn } from '../warning'
import { isAsyncWrapper } from '../apiAsyncComponent'
import type { Data } from '../component'
/**
* Compiler runtime helper for rendering `<slot/>`

View File

@ -1,8 +1,25 @@
import { toHandlers as _toHandlers } from '@vue/runtime-shared'
import { isObject, toHandlerKey } from '@vue/shared'
import { warn } from '../warning'
import { NOOP } from '@vue/shared'
export const toHandlers: (
/**
* For prefixing keys in v-on="obj" with "on"
* @private
*/
export function toHandlers(
obj: Record<string, any>,
preserveCaseIfNecessary?: boolean | undefined,
) => Record<string, any> = _toHandlers.bind(undefined, __DEV__ ? warn : NOOP)
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
}

View File

@ -17,6 +17,7 @@ import {
import {
type ComponentInternalInstance,
type ComponentOptions,
type Data,
type LifecycleHook,
createComponentInstance,
setupComponent,
@ -39,7 +40,6 @@ import {
isArray,
isReservedProp,
} from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import {
type SchedulerJob,
SchedulerJobFlags,

View File

@ -12,12 +12,12 @@ import {
normalizeClass,
normalizeStyle,
} from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import {
type ClassComponent,
type Component,
type ComponentInternalInstance,
type ConcreteComponent,
type Data,
isClassComponent,
} from './component'
import type { RawSlots } from './componentSlots'

View File

@ -1,10 +1,10 @@
import {
type ComponentInternalInstance,
type Data,
type GenericComponentInstance,
formatComponentName,
} from './component'
import { isFunction, isString } from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import { isRef, pauseTracking, resetTracking, toRaw } from '@vue/reactivity'
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
import { type VNode, isVNode } from './vnode'

View File

@ -1,5 +1,6 @@
import {
type App,
type Component,
type ConcreteComponent,
type CreateAppFunction,
type DefineComponent,
@ -144,7 +145,7 @@ export const createApp = ((...args) => {
}
return app
}) as CreateAppFunction<Element>
}) as CreateAppFunction<Element, Component>
export const createSSRApp = ((...args) => {
const app = ensureHydrationRenderer().createApp(...args)

View File

@ -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.

View File

@ -1 +0,0 @@
# @vue/runtime-shared

View File

@ -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')
}

View File

@ -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:*"
}
}

View File

@ -1,2 +0,0 @@
export { toHandlers } from './toHandlers'
export { type Data } from './typeUtils'

View File

@ -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
}

View File

@ -1 +0,0 @@
export type Data = Record<string, unknown>

View File

@ -6,8 +6,8 @@ import {
type SetupFn,
createVaporApp,
defineComponent,
} from '../src'
import type { RawProps } from '../src/componentProps'
} from '../src/_old'
import type { RawProps } from '../src/_old/componentProps'
export interface RenderContext {
component: Component

View File

@ -1,6 +1,6 @@
import { ref } from '@vue/reactivity'
import { makeRender } from './_utils'
import { createFor, createSelector, nextTick, renderEffect } from '../src'
import { createFor, createSelector, nextTick, renderEffect } from '../src/_old'
const define = makeRender()

View File

@ -11,8 +11,8 @@ import {
resolveComponent,
resolveDirective,
withDirectives,
} from '../src'
import { warn } from '../src/warning'
} from '../src/_old'
import { warn } from '../src/_old/warning'
import { makeRender } from './_utils'
const define = makeRender()

View File

@ -1,12 +1,12 @@
import { ref, shallowRef } from '@vue/reactivity'
import { createComponent } from '../src/apiCreateComponent'
import { createComponent } from '../src/_old/apiCreateComponent'
import { setRef } from '../src/dom/templateRef'
import { makeRender } from './_utils'
import {
type ComponentInternalInstance,
getCurrentInstance,
} from '../src/component'
import { defineComponent } from '../src/apiDefineComponent'
} from '../src/_old/component'
import { defineComponent } from '../src/_old/apiDefineComponent'
const define = makeRender()
describe('api: expose', () => {

View File

@ -13,7 +13,7 @@ import {
ref,
renderEffect,
setText,
} from '../src'
} from '../src/_old'
import { makeRender } from './_utils'
const define = makeRender<any>()

View File

@ -24,7 +24,7 @@ import {
renderEffect,
setText,
template,
} from '../src'
} from '../src/_old'
import { makeRender } from './_utils'
import { ITERATE_KEY } from '@vue/reactivity'

View File

@ -14,7 +14,7 @@ import {
setInheritAttrs,
template,
watchEffect,
} from '../src'
} from '../src/_old'
import { makeRender } from './_utils'
const define = makeRender()

View File

@ -1,4 +1,4 @@
import type { SetupContext } from '../src/component'
import type { SetupContext } from '../src/_old/component'
import {
createComponent,
defineComponent,
@ -6,7 +6,7 @@ import {
template,
useAttrs,
useSlots,
} from '../src'
} from '../src/_old'
import { makeRender } from './_utils'
const define = makeRender<any>()

View File

@ -6,7 +6,7 @@ import {
ref,
watchEffect,
watchSyncEffect,
} from '../src'
} from '../src/_old'
describe('watchEffect and onWatcherCleanup', () => {
test('basic', async () => {

View File

@ -1,4 +1,4 @@
import { ref, setText, template, watchEffect } from '../src'
import { ref, setText, template, watchEffect } from '../src/_old'
import { describe, expect } from 'vitest'
import { makeRender } from './_utils'

View File

@ -7,7 +7,7 @@ import {
setText,
template,
watchEffect,
} from '../src'
} from '../src/_old'
import { makeRender } from './_utils'
const define = makeRender<any>()

View File

@ -9,8 +9,8 @@ import {
defineComponent,
nextTick,
onBeforeUnmount,
} from '../src'
import { isEmitListener } from '../src/componentEmits'
} from '../src/_old'
import { isEmitListener } from '../src/_old/componentEmits'
import { makeRender } from './_utils'
const define = makeRender()

View File

@ -11,7 +11,7 @@ import {
toRefs,
watch,
watchEffect,
} from '../src'
} from '../src/_old'
import { makeRender } from './_utils'
const define = makeRender<any>()

View File

@ -15,7 +15,7 @@ import {
setText,
template,
withDestructure,
} from '../src'
} from '../src/_old'
import { makeRender } from './_utils'
const define = makeRender<any>()

View File

@ -9,7 +9,7 @@ import {
vModelDynamic,
vModelSelect,
withDirectives,
} from '../../src'
} from '../../src/_old'
import { makeRender } from '../_utils'
import { nextTick } from '@vue/runtime-dom'

View File

@ -5,7 +5,7 @@ import {
template,
vShow,
withDirectives,
} from '../../src'
} from '../../src/_old'
import { nextTick, ref } from 'vue'
import { describe, expect, test } from 'vitest'
import { makeRender } from '../_utils'

View File

@ -13,7 +13,7 @@ import { setStyle } from '../../src/dom/style'
import {
ComponentInternalInstance,
setCurrentInstance,
} from '../../src/component'
} from '../../src/_old/component'
import { getMetadata, recordPropMetadata } from '../../src/componentMetadata'
import { getCurrentScope } from '@vue/reactivity'

View File

@ -12,7 +12,7 @@ import {
setText,
template,
watchEffect,
} from '../../src'
} from '../../src/_old'
import { makeRender } from '../_utils'
const define = makeRender()

View File

@ -1,11 +1,11 @@
import type { Component } from '../src/component'
import type { Component } from '../src/_old/component'
import { type RefEl, setRef } from '../src/dom/templateRef'
import { onErrorCaptured, onMounted } from '../src/apiLifecycle'
import { createComponent } from '../src/apiCreateComponent'
import { onErrorCaptured, onMounted } from '../src/_old/apiLifecycle'
import { createComponent } from '../src/_old/apiCreateComponent'
import { makeRender } from './_utils'
import { template } from '../src/dom/template'
import { watch, watchEffect } from '../src/apiWatch'
import { nextTick } from '../src/scheduler'
import { watch, watchEffect } from '../src/_old/apiWatch'
import { nextTick } from '../src/_old/scheduler'
import { ref } from '@vue/reactivity'
const define = makeRender()

View File

@ -7,7 +7,7 @@ import {
template,
triggerRef,
withDestructure,
} from '../src'
} from '../src/_old'
import { makeRender } from './_utils'
const define = makeRender()

View File

@ -4,7 +4,7 @@ import {
createVaporApp,
resolveComponent,
resolveDirective,
} from '@vue/runtime-vapor'
} from 'packages/runtime-vapor/src/_old'
import { makeRender } from '../_utils'
const define = makeRender()

View File

@ -8,10 +8,10 @@ import {
setText,
template,
withDirectives,
} from '../src'
} from '../src/_old'
import type { Mock } from 'vitest'
import { makeRender } from './_utils'
import { unmountComponent } from '../src/apiRender'
import { unmountComponent } from '../src/_old/apiRender'
const define = makeRender()

View File

@ -11,11 +11,11 @@ import {
watchEffect,
watchPostEffect,
watchSyncEffect,
} from '../src'
} from '../src/_old'
import {
type ComponentInternalInstance,
currentInstance,
} from '../src/component'
} from '../src/_old/component'
import { makeRender } from './_utils'
const define = makeRender<any>()

View File

@ -36,7 +36,9 @@
"homepage": "https://github.com/vuejs/vue-vapor/tree/dev/packages/runtime-vapor#readme",
"dependencies": {
"@vue/shared": "workspace:*",
"@vue/reactivity": "workspace:*",
"@vue/runtime-shared": "workspace:*"
"@vue/reactivity": "workspace:*"
},
"peerDependencies": {
"@vue/runtime-dom": "workspace:*"
}
}

1
packages/runtime-vapor/src/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_old

View File

@ -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
}
}
}

View File

@ -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]()
}

View File

@ -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])
}

View File

@ -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'

View File

@ -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
}

View File

@ -1,5 +1,5 @@
import { normalizeContainer } from '../apiRender'
import { insert } from '../dom/element'
import { normalizeContainer } from './_old/apiRender'
import { insert } from './dom/element'
import { type VaporComponent, createComponent } from './component'
import {
type AppMountFn,
@ -8,7 +8,7 @@ import {
createAppAPI,
} from '@vue/runtime-core'
let _createApp: CreateAppFunction<ParentNode>
let _createApp: CreateAppFunction<ParentNode, VaporComponent>
const mountApp: AppMountFn<ParentNode> = (app, container) => {
// clear content before mounting
@ -24,7 +24,10 @@ const unmountApp: AppUnmountFn = app => {
// TODO
}
export function createVaporApp(comp: VaporComponent): any {
export const createVaporApp: CreateAppFunction<
ParentNode,
VaporComponent
> = comp => {
if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp)
const app = _createApp(comp)
const mount = app.mount

View File

@ -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
}

View File

@ -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
}

View File

@ -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,
)
}

View File

@ -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)
}
}

View File

@ -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 {}

View File

@ -1,6 +0,0 @@
import type { Component } from './component'
/*! #__NO_SIDE_EFFECTS__ */
export function defineComponent(comp: Component): Component {
return comp
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -1,5 +1,5 @@
import { isArray } from '@vue/shared'
import { type VaporComponentInstance, isVaporComponent } from './_new/component'
import { type VaporComponentInstance, isVaporComponent } from './component'
export const fragmentKey: unique symbol = Symbol(__DEV__ ? `fragmentKey` : ``)

View File

@ -1,112 +1,44 @@
import { EffectScope, isRef } from '@vue/reactivity'
import { EMPTY_OBJ, isArray, isBuiltInTag, isFunction } from '@vue/shared'
import type { Block } from './block'
import {
type ComponentInternalOptions,
type ComponentPropsOptions,
type NormalizedPropsOptions,
type NormalizedRawProps,
type RawProps,
initProps,
normalizePropsOptions,
} from './componentProps'
import {
type EmitFn,
EffectScope,
type EmitsOptions,
type GenericAppContext,
type GenericComponentInstance,
type LifecycleHook,
type NormalizedPropsOptions,
type ObjectEmitsOptions,
emit,
normalizeEmitsOptions,
} from './componentEmits'
import { type RawSlots, type StaticSlots, initSlots } from './componentSlots'
import { VaporLifecycleHooks } from './enums'
import { warn } from './warning'
nextUid,
popWarningContext,
pushWarningContext,
} from '@vue/runtime-core'
import type { Block } from './block'
import { pauseTracking, resetTracking } from '@vue/reactivity'
import { EMPTY_OBJ, isFunction } from '@vue/shared'
import {
type AppConfig,
type AppContext,
createAppContext,
} from './apiCreateVaporApp'
import type { Data } from '@vue/runtime-shared'
import type { ComponentInstance } from './apiCreateComponentSimple'
type RawProps,
getDynamicPropsHandlers,
initStaticProps,
} from './componentProps'
import { setDynamicProp } from './dom/prop'
import { renderEffect } from './renderEffect'
export type Component = FunctionalComponent | ObjectComponent
export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent
type SharedInternalOptions = {
__propsOptions?: NormalizedPropsOptions
__propsHandlers?: [ProxyHandler<any>, ProxyHandler<any>]
}
export type SetupFn = (
export type VaporSetupFn = (
props: any,
ctx: SetupContext,
) => Block | Data | undefined
) => Block | Record<string, any> | undefined
export type FunctionalComponent = SetupFn &
Omit<ObjectComponent, 'setup'> & {
export type FunctionalVaporComponent = VaporSetupFn &
Omit<ObjectVaporComponent, 'setup'> & {
displayName?: string
} & SharedInternalOptions
export class SetupContext<E = EmitsOptions> {
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
export interface ObjectVaporComponent
extends ComponentInternalOptions,
SharedInternalOptions {
setup?: SetupFn
setup?: VaporSetupFn
inheritAttrs?: boolean
props?: ComponentPropsOptions
emits?: EmitsOptions
@ -116,311 +48,176 @@ export interface ObjectComponent
vapor?: boolean
}
// Note: can't mark this whole interface internal because some public interfaces
// extend it.
export interface ComponentInternalOptions {
interface SharedInternalOptions {
/**
* @internal
* Cached normalized props options.
* In vapor mode there are no mixins so normalized options can be cached
* directly on the component
*/
__scopeId?: string
__propsOptions?: NormalizedPropsOptions
/**
* @internal
* Cached normalized props proxy handlers.
*/
__cssModules?: Data
__propsHandlers?: [ProxyHandler<any> | null, ProxyHandler<any>]
/**
* @internal
* Cached normalized emits options.
*/
__hmrId?: string
/**
* Compat build only, for bailing out of certain compatibility behavior
*/
__isBuiltIn?: boolean
/**
* This one should be exposed so that devtools can make use of it
*/
__file?: string
/**
* name inferred from filename
*/
__name?: string
__emitsOptions?: ObjectEmitsOptions
}
type LifecycleHook<TFn = Function> = TFn[] | null
export let currentInstance: ComponentInternalInstance | null = null
export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
currentInstance
export const setCurrentInstance = (instance: ComponentInternalInstance) => {
const prev = currentInstance
currentInstance = instance
return (): void => {
currentInstance = prev
export function createComponent(
component: VaporComponent,
rawProps?: RawProps,
isSingleRoot?: boolean,
): VaporComponentInstance {
// check if we are the single root of the parent
// if yes, inject parent attrs as dynamic props source
if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) {
if (rawProps) {
;(rawProps.$ || (rawProps.$ = [])).push(currentInstance.attrs)
} else {
rawProps = { $: [currentInstance.attrs] }
}
}
const instance = new VaporComponentInstance(component, rawProps)
pauseTracking()
let prevInstance = currentInstance
currentInstance = instance
instance.scope.on()
if (__DEV__) {
pushWarningContext(instance)
}
const setupFn = isFunction(component) ? component : component.setup
const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null
instance.block = setupFn!(
instance.props,
// @ts-expect-error
setupContext,
) as Block // TODO handle return object
// single root, inherit attrs
if (
instance.hasFallthrough &&
component.inheritAttrs !== false &&
instance.block instanceof Element &&
Object.keys(instance.attrs).length
) {
renderEffect(() => {
for (const key in instance.attrs) {
setDynamicProp(instance.block as Element, key, instance.attrs[key])
}
})
}
if (__DEV__) {
popWarningContext()
}
instance.scope.off()
currentInstance = prevInstance
resetTracking()
return instance
}
export const unsetCurrentInstance = (): void => {
currentInstance && currentInstance.scope.off()
currentInstance = null
export let currentInstance: VaporComponentInstance | null = null
const emptyContext: GenericAppContext = {
app: null as any,
config: {},
provides: /*@__PURE__*/ Object.create(null),
}
const emptyAppContext = createAppContext()
let uid = 0
export class ComponentInternalInstance {
vapor = true
export class VaporComponentInstance implements GenericComponentInstance {
uid: number
appContext: AppContext
type: VaporComponent
parent: GenericComponentInstance | null
appContext: GenericAppContext
type: Component
block: Block | null
container: ParentNode
parent: ComponentInternalInstance | null
root: ComponentInternalInstance
provides: Data
block: Block
scope: EffectScope
comps: Set<ComponentInternalInstance>
scopeIds: string[]
rawProps: RawProps | undefined
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
attrs: Data
/**
* - `undefined` : no props
* - `false` : all props are static
* - `string[]` : list of props are dynamic
* - `true` : all props as dynamic
*/
dynamicAttrs?: string[] | boolean
slots: StaticSlots
refs: Data
// exposed properties via expose()
exposed?: Record<string, any>
propsDefaults: Record<string, any> | null
attrsProxy?: Data
slotsProxy?: StaticSlots
// for useTemplateRef()
refs: Record<string, any>
// for provide / inject
provides: Record<string, any>
hasFallthrough: boolean
// lifecycle
isMounted: boolean
isUnmounted: boolean
isUpdating: boolean
// TODO: registory of provides, lifecycles, ...
/**
* @internal
*/
// [VaporLifecycleHooks.BEFORE_MOUNT]: LifecycleHook;
bm: LifecycleHook
/**
* @internal
*/
// [VaporLifecycleHooks.MOUNTED]: LifecycleHook;
m: LifecycleHook
/**
* @internal
*/
// [VaporLifecycleHooks.BEFORE_UPDATE]: LifecycleHook;
bu: LifecycleHook
/**
* @internal
*/
// [VaporLifecycleHooks.UPDATED]: LifecycleHook;
u: LifecycleHook
/**
* @internal
*/
// [VaporLifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;
bum: LifecycleHook
/**
* @internal
*/
// [VaporLifecycleHooks.UNMOUNTED]: LifecycleHook;
um: LifecycleHook
/**
* @internal
*/
// [VaporLifecycleHooks.RENDER_TRACKED]: LifecycleHook;
rtc: LifecycleHook
/**
* @internal
*/
// [VaporLifecycleHooks.RENDER_TRIGGERED]: LifecycleHook;
rtg: LifecycleHook
/**
* @internal
*/
// [VaporLifecycleHooks.ACTIVATED]: LifecycleHook;
a: LifecycleHook
/**
* @internal
*/
// [VaporLifecycleHooks.DEACTIVATED]: LifecycleHook;
da: LifecycleHook
/**
* @internal
*/
// [VaporLifecycleHooks.ERROR_CAPTURED]: LifecycleHook
isDeactivated: boolean
// LifecycleHooks.ERROR_CAPTURED
ec: LifecycleHook
constructor(
component: Component,
rawProps: RawProps | null,
slots: RawSlots | null,
once: boolean = false,
// application root node only
appContext?: AppContext,
) {
this.uid = uid++
const parent = (this.parent = currentInstance)
this.root = parent ? parent.root : this
const _appContext = (this.appContext =
(parent ? parent.appContext : appContext) || emptyAppContext)
this.block = null
this.container = null!
this.root = null!
// dev only
propsOptions?: NormalizedPropsOptions
emitsOptions?: ObjectEmitsOptions | null
constructor(comp: VaporComponent, rawProps?: RawProps) {
this.uid = nextUid()
this.type = comp
this.parent = currentInstance
this.appContext = currentInstance
? currentInstance.appContext
: emptyContext
this.block = null! // to be set
this.scope = new EffectScope(true)
this.provides = parent
? parent.provides
: Object.create(_appContext.provides)
this.type = component
this.comps = new Set()
this.scopeIds = []
this.rawProps = null!
this.propsOptions = normalizePropsOptions(component)
this.emitsOptions = normalizeEmitsOptions(component)
// state
this.setupState = EMPTY_OBJ
this.setupContext = null
this.props = EMPTY_OBJ
this.emit = emit.bind(null, this)
this.emitted = null
this.attrs = EMPTY_OBJ
this.slots = EMPTY_OBJ
this.refs = EMPTY_OBJ
this.rawProps = rawProps
this.provides = this.refs = EMPTY_OBJ
this.emitted = this.ec = this.exposed = null
this.isMounted = this.isUnmounted = this.isDeactivated = false
// lifecycle
this.isMounted = false
this.isUnmounted = false
this.isUpdating = false
this[VaporLifecycleHooks.BEFORE_MOUNT] = null
this[VaporLifecycleHooks.MOUNTED] = null
this[VaporLifecycleHooks.BEFORE_UPDATE] = null
this[VaporLifecycleHooks.UPDATED] = null
this[VaporLifecycleHooks.BEFORE_UNMOUNT] = null
this[VaporLifecycleHooks.UNMOUNTED] = null
this[VaporLifecycleHooks.RENDER_TRACKED] = null
this[VaporLifecycleHooks.RENDER_TRIGGERED] = null
this[VaporLifecycleHooks.ACTIVATED] = null
this[VaporLifecycleHooks.DEACTIVATED] = null
this[VaporLifecycleHooks.ERROR_CAPTURED] = null
// init props
this.propsDefaults = null
this.hasFallthrough = false
if (rawProps && rawProps.$) {
// has dynamic props, use proxy
const handlers = getDynamicPropsHandlers(comp, this)
this.props = comp.props ? new Proxy(rawProps, handlers[0]!) : EMPTY_OBJ
this.attrs = new Proxy(rawProps, handlers[1])
this.hasFallthrough = true
} else {
this.props = {}
this.attrs = {}
this.hasFallthrough = initStaticProps(comp, rawProps, this)
}
initProps(this, rawProps, !isFunction(component), once)
initSlots(this, slots)
// TODO validate props
// TODO init slots
}
}
export function isVaporComponent(
val: unknown,
): val is ComponentInternalInstance {
return val instanceof ComponentInternalInstance
value: unknown,
): value is VaporComponentInstance {
return value instanceof VaporComponentInstance
}
export function validateComponentName(
name: string,
{ isNativeTag }: AppConfig,
): void {
if (isBuiltInTag(name) || isNativeTag(name)) {
warn(
'Do not use built-in or reserved HTML elements as component id: ' + name,
)
}
}
export class SetupContext<E = EmitsOptions> {
attrs: Record<string, any>
// emit: EmitFn<E>
// slots: Readonly<StaticSlots>
expose: (exposed?: Record<string, any>) => void
/**
* Dev-only
*/
export function getAttrsProxy(instance: ComponentInternalInstance): Data {
return (
instance.attrsProxy ||
(instance.attrsProxy = new Proxy(instance.attrs, {
get(target, key: string) {
return target[key]
},
set() {
warn(`setupContext.attrs is readonly.`)
return false
},
deleteProperty() {
warn(`setupContext.attrs is readonly.`)
return false
},
}))
)
}
/**
* Dev-only
*/
export function getSlotsProxy(
instance: ComponentInternalInstance,
): StaticSlots {
return (
instance.slotsProxy ||
(instance.slotsProxy = new Proxy(instance.slots, {
get(target, key: string) {
return target[key]
},
}))
)
}
export function getComponentName(
Component: Component,
): string | false | undefined {
return isFunction(Component)
? Component.displayName || Component.name
: Component.name || Component.__name
}
export function formatComponentName(
instance: ComponentInternalInstance | null,
Component: Component,
isRoot = false,
): string {
let name = getComponentName(Component)
if (!name && Component.__file) {
const match = Component.__file.match(/([^/\\]+)\.\w+$/)
if (match) {
name = match[1]
constructor(instance: VaporComponentInstance) {
this.attrs = instance.attrs
// this.emit = instance.emit as EmitFn<E>
// this.slots = instance.slots
this.expose = (exposed = {}) => {
instance.exposed = exposed
}
}
if (!name && instance && instance.parent) {
// try to infer the name based on reverse resolution
const inferFromRegistry = (registry: Record<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, '')

View File

@ -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
}

View File

@ -1,185 +1,70 @@
import {
type UnionToIntersection,
camelize,
extend,
hasOwn,
hyphenate,
isArray,
isFunction,
isOn,
isString,
looseToNumber,
toHandlerKey,
} from '@vue/shared'
import type { Component, ComponentInternalInstance } from './component'
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
import { type StaticProps, getDynamicPropValue } from './componentProps'
import { warn } from './warning'
export type ObjectEmitsOptions = Record<
string,
((...args: any[]) => any) | null // TODO: call validation?
>
export type EmitsOptions = ObjectEmitsOptions | string[]
export type EmitFn<
Options = ObjectEmitsOptions,
Event extends keyof Options = keyof Options,
> =
Options extends Array<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,
)
}
}
type EmitFn,
type ObjectEmitsOptions,
baseEmit,
} from '@vue/runtime-core'
import {
type VaporComponent,
type VaporComponentInstance,
currentInstance,
} from './component'
import { EMPTY_OBJ, NOOP, hasOwn, isArray } from '@vue/shared'
import { resolveSource } from './componentProps'
/**
* The logic from core isn't too reusable so it's better to duplicate here
*/
export function normalizeEmitsOptions(
comp: Component,
comp: VaporComponent,
): ObjectEmitsOptions | null {
// TODO: caching?
const cached = comp.__emitsOptions
if (cached) return cached
const raw = comp.emits
if (!raw) return null
let normalized: ObjectEmitsOptions = {}
let normalized: ObjectEmitsOptions
if (isArray(raw)) {
raw.forEach(key => (normalized[key] = null))
normalized = {}
for (const key in raw) normalized[key] = null
} else {
extend(normalized, raw)
normalized = raw
}
return normalized
return (comp.__emitsOptions = normalized)
}
// Check if an incoming prop key is a declared emit event listener.
// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are
// both considered matched listeners.
export function isEmitListener(
options: ObjectEmitsOptions | null,
key: string,
): boolean {
if (!options || !isOn(key)) {
return false
export function useEmit(): EmitFn {
if (!currentInstance) {
// TODO warn
return NOOP
} else {
return emit.bind(null, currentInstance)
}
}
key = key.slice(2).replace(/Once$/, '')
return (
hasOwn(options, key[0].toLowerCase() + key.slice(1)) ||
hasOwn(options, hyphenate(key)) ||
hasOwn(options, key)
export function emit(
instance: VaporComponentInstance,
event: string,
...rawArgs: any[]
): void {
baseEmit(
instance,
instance.rawProps || EMPTY_OBJ,
propGetter,
event,
...rawArgs,
)
}
function propGetter(rawProps: Record<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])
}

View File

@ -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
}

View File

@ -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))
}
}

View File

@ -1,392 +1,231 @@
import { EMPTY_ARR, NO, camelize, hasOwn, isFunction } from '@vue/shared'
import type { VaporComponent, VaporComponentInstance } from './component'
import {
EMPTY_ARR,
EMPTY_OBJ,
camelize,
extend,
hasOwn,
hyphenate,
isArray,
isFunction,
} from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import { shallowReactive } from '@vue/reactivity'
import { warn } from './warning'
import type { Component, ComponentInternalInstance } from './component'
import { patchAttrs } from './componentAttrs'
import { firstEffect } from './renderEffect'
type NormalizedPropsOptions,
baseNormalizePropsOptions,
isEmitListener,
resolvePropValue,
} from '@vue/runtime-core'
import { normalizeEmitsOptions } from './componentEmits'
export type ComponentPropsOptions<P = Data> =
| ComponentObjectPropsOptions<P>
| string[]
export type ComponentObjectPropsOptions<P = Data> = {
[K in keyof P]: Prop<P[K]> | null
export interface RawProps {
[key: string]: PropSource
$?: DynamicPropsSource[]
}
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> {
type?: PropType<T> | true | null
required?: boolean
default?: D | DefaultFactory<D> | null | undefined | object
validator?(value: unknown, props: Data): boolean
/**
* @internal
*/
skipFactory?: boolean
}
export function initStaticProps(
comp: VaporComponent,
rawProps: RawProps | undefined,
instance: VaporComponentInstance,
): boolean {
let hasAttrs = false
const { props, attrs } = instance
const [propsOptions, needCastKeys] = normalizePropsOptions(comp)
const emitsOptions = normalizeEmitsOptions(comp)
export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
// for dev emit check
if (__DEV__) {
instance.propsOptions = normalizePropsOptions(comp)
instance.emitsOptions = emitsOptions
}
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)
for (const key in rawProps) {
const normalizedKey = camelize(key)
const needCast = needCastKeys && needCastKeys.includes(normalizedKey)
const source = rawProps[key]
if (propsOptions && normalizedKey in propsOptions) {
if (isFunction(source)) {
Object.defineProperty(props, normalizedKey, {
enumerable: true,
get: needCast
? () =>
resolvePropValue(
propsOptions,
normalizedKey,
source(),
instance,
resolveDefault,
)
: source,
})
} else {
props[normalizedKey] = needCast
? resolvePropValue(
propsOptions,
normalizedKey,
source,
instance,
resolveDefault,
)
: source
}
} else {
const staticProps = rawProps[0] as StaticProps
for (const key in options) {
const rawKey = staticProps && getRawKey(staticProps, key)
if (rawKey) {
registerProp(instance, once, props, key, staticProps[rawKey])
} else {
registerProp(instance, once, props, key, undefined, false, true)
} else if (!isEmitListener(emitsOptions, key)) {
if (isFunction(source)) {
Object.defineProperty(attrs, key, {
enumerable: true,
get: source,
})
} else {
attrs[normalizedKey] = source
}
hasAttrs = true
}
}
for (const key in propsOptions) {
if (!(key in props)) {
props[key] = resolvePropValue(
propsOptions,
key,
undefined,
instance,
resolveDefault,
true,
)
}
}
return hasAttrs
}
function resolveDefault(
factory: (props: Record<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)
}
// validation
if (__DEV__) {
validateProps(rawProps, props, options || {})
}
const propsHandlers = propsOptions
? ({
get: (target, key: string) => getProp(target, key, true),
has: (_, key: string) => isProp(key),
getOwnPropertyDescriptor(target, key: string) {
if (isProp(key)) {
return {
configurable: true,
enumerable: true,
get: () => getProp(target, key, true),
}
}
},
ownKeys: () =>
normalizedKeys || (normalizedKeys = Object.keys(propsOptions)),
set: NO,
deleteProperty: NO,
} satisfies ProxyHandler<RawProps>)
: null
if (hasDynamicProps) {
firstEffect(instance, () => patchAttrs(instance, true))
} else {
patchAttrs(instance)
}
if (isStateful) {
instance.props = /* TODO isSSR ? props : */ shallowReactive(props)
} else {
// functional w/ optional props, props === attrs
instance.props = instance.propsOptions === EMPTY_ARR ? attrs : props
}
}
function registerProp(
instance: ComponentInternalInstance,
once: boolean,
props: Data,
rawKey: string,
getter?: (() => unknown) | (() => DynamicPropResult),
isDynamic?: boolean,
isAbsent?: boolean,
) {
const key = camelize(rawKey)
if (key in props) return
const [options, needCastKeys] = instance.propsOptions
const needCast = needCastKeys && needCastKeys.includes(key)
const withCast = (value: unknown, absent?: boolean) =>
resolvePropValue(options!, props, key, value, absent)
if (isAbsent) {
props[key] = needCast ? withCast(undefined, true) : undefined
} else {
const get: () => unknown = isDynamic
? needCast
? () => withCast(...(getter!() as DynamicPropResult))
: () => (getter!() as DynamicPropResult)[0]
: needCast
? () => withCast(getter!())
: getter!
const descriptor: PropertyDescriptor = once ? { value: get() } : { get }
descriptor.enumerable = true
Object.defineProperty(props, key, descriptor)
}
}
export function normalizeRawProps(rawProps: RawProps): NormalizedRawProps {
if (!rawProps) return []
if (!isArray(rawProps)) return [rawProps]
return rawProps
}
export function walkRawProps(
rawProps: NormalizedRawProps,
cb: (key: string, value: any, getter?: boolean) => void,
): void {
for (const props of Array.from(rawProps).reverse()) {
if (isFunction(props)) {
const resolved = props()
for (const rawKey in resolved) {
cb(rawKey, resolved[rawKey])
}
} else {
for (const rawKey in props) {
cb(rawKey, props[rawKey], true)
const hasAttr = (target: RawProps, key: string) => {
if (key === '$' || isProp(key) || isEmitListener(emitsOptions, key))
return false
if (target.$) {
let i = target.$.length
while (i--) {
if (hasOwn(resolveSource(target.$[i]), key)) {
return true
}
}
}
return hasOwn(target, key)
}
}
function getRawKey(obj: Data, key: string) {
return Object.keys(obj).find(k => camelize(k) === key)
}
type DynamicPropResult = [value: unknown, absent: boolean]
export function getDynamicPropValue(
rawProps: NormalizedRawProps,
key: string,
): DynamicPropResult {
for (const props of Array.from(rawProps).reverse()) {
if (isFunction(props)) {
const resolved = props()
const rawKey = getRawKey(resolved, key)
if (rawKey) return [resolved[rawKey], false]
} else {
const rawKey = getRawKey(props, key)
if (rawKey) return [props[rawKey](), false]
}
}
return [undefined, true]
}
export function resolvePropValue(
options: NormalizedProps,
props: Data,
key: string,
value: unknown,
isAbsent?: boolean,
): unknown {
const opt = options[key]
if (opt != null) {
const hasDefault = hasOwn(opt, 'default')
// default values
if (hasDefault && value === undefined) {
const defaultValue = opt.default
if (
opt.type !== Function &&
!opt.skipFactory &&
isFunction(defaultValue)
) {
value = defaultValue.call(null, props)
} else {
value = defaultValue
const attrsHandlers = {
get: (target, key: string) => getProp(target, key, false),
has: hasAttr,
getOwnPropertyDescriptor(target, key: string) {
if (hasAttr(target, key)) {
return {
configurable: true,
enumerable: true,
get: () => getProp(target, key, false),
}
}
}
// boolean casting
if (opt[BooleanFlags.shouldCast]) {
if (isAbsent && !hasDefault) {
value = false
} else if (
opt[BooleanFlags.shouldCastTrue] &&
(value === '' || value === hyphenate(key))
) {
value = true
},
ownKeys(target) {
const keys = Object.keys(target)
if (target.$) {
let i = target.$.length
while (i--) {
keys.push(...Object.keys(resolveSource(target.$[i])))
}
}
}
}
return value
return keys.filter(key => hasAttr(target, key))
},
set: NO,
deleteProperty: NO,
} satisfies ProxyHandler<RawProps>
return (comp.__propsHandlers = [propsHandlers, attrsHandlers])
}
export function normalizePropsOptions(comp: Component): NormalizedPropsOptions {
function normalizePropsOptions(comp: VaporComponent): NormalizedPropsOptions {
const cached = comp.__propsOptions
if (cached) return cached
const raw = comp.props
const normalized: NormalizedProps | undefined = {}
if (!raw) return EMPTY_ARR as []
const normalized: NormalizedPropsOptions[0] = {}
const needCastKeys: NormalizedPropsOptions[1] = []
baseNormalizePropsOptions(raw, normalized, needCastKeys)
if (!raw) {
return EMPTY_ARR as []
}
if (isArray(raw)) {
for (let i = 0; i < raw.length; i++) {
const normalizedKey = camelize(raw[i])
if (validatePropName(normalizedKey)) {
normalized[normalizedKey] = EMPTY_OBJ
}
}
} else {
for (const key in raw) {
const normalizedKey = camelize(key)
if (validatePropName(normalizedKey)) {
const opt = raw[key]
const prop: NormalizedProp = (normalized[normalizedKey] =
isArray(opt) || isFunction(opt) ? { type: opt } : extend({}, opt))
if (prop) {
const booleanIndex = getTypeIndex(Boolean, prop.type)
const stringIndex = getTypeIndex(String, prop.type)
prop[BooleanFlags.shouldCast] = booleanIndex > -1
prop[BooleanFlags.shouldCastTrue] =
stringIndex < 0 || booleanIndex < stringIndex
// if the prop needs boolean casting or default value
if (booleanIndex > -1 || hasOwn(prop, 'default')) {
needCastKeys.push(normalizedKey)
}
}
}
}
}
const res: NormalizedPropsOptions = (comp.__propsOptions = [
normalized,
needCastKeys,
])
return res
}
function validatePropName(key: string) {
if (key[0] !== '$') {
return true
} else if (__DEV__) {
warn(`Invalid prop name: "${key}" is a reserved property.`)
}
return false
}
function getType(ctor: Prop<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 + '".')
}
return (comp.__propsOptions = [normalized, needCastKeys])
}

View File

@ -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]
}
}
}

View File

@ -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)
}
}

View File

@ -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,
)
}

View File

@ -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]
}

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -2,7 +2,7 @@ import { isArray } from '@vue/shared'
import { renderEffect } from '../renderEffect'
import { setText } from './prop'
import { type Block, normalizeBlock } from '../block'
import { isVaporComponent } from '../_new/component'
import { isVaporComponent } from '../component'
// export function insert(
// block: Block,

View File

@ -9,7 +9,7 @@ import {
recordEventMetadata,
} from '../componentMetadata'
import { withKeys, withModifiers } from '@vue/runtime-dom'
import { queuePostFlushCb } from '../scheduler'
import { queuePostFlushCb } from '@vue/runtime-dom'
export function addEventListener(
el: Element,

View File

@ -12,7 +12,7 @@ import {
shouldSetAsAttr,
toDisplayString,
} from '@vue/shared'
import { warn } from '../warning'
import { warn } from '../_old/warning'
import { setStyle } from './style'
import {
MetadataKind,
@ -20,8 +20,7 @@ import {
recordPropMetadata,
} from '../componentMetadata'
import { on } from './event'
import type { Data } from '@vue/runtime-shared'
import { currentInstance } from '../component'
import { currentInstance } from '../_old/component'
export function mergeInheritAttr(key: string, value: any): unknown {
const instance = currentInstance!
@ -199,6 +198,8 @@ export function mergeProp(
return incoming
}
type Data = Record<string, any>
export function mergeProps(...args: Data[]): Data {
const ret: Data = {}
for (let i = 0; i < args.length; i++) {

View File

@ -6,7 +6,7 @@ import {
isString,
normalizeStyle,
} from '@vue/shared'
import { warn } from '../warning'
import { warn } from '../_old/warning'
import { recordPropMetadata } from '../componentMetadata'
import { mergeInheritAttr } from './prop'

View File

@ -3,8 +3,8 @@ import {
type ComponentInternalInstance,
currentInstance,
isVaporComponent,
} from '../component'
import { VaporErrorCodes, callWithErrorHandling } from '../errorHandling'
} from '../_old/component'
import { VaporErrorCodes, callWithErrorHandling } from '../_old/errorHandling'
import {
EMPTY_OBJ,
hasOwn,
@ -13,8 +13,8 @@ import {
isString,
remove,
} from '@vue/shared'
import { warn } from '../warning'
import { type SchedulerJob, queuePostFlushCb } from '../scheduler'
import { warn } from '../_old/warning'
import { type SchedulerJob, queuePostFlushCb } from '../_old/scheduler'
export type NodeRef = string | Ref | ((ref: Element) => void)
export type RefEl = Element | ComponentInternalInstance

View File

@ -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',
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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)

View File

@ -1,110 +1,9 @@
// Core API ------------------------------------------------------------------
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 { createComponent } from './component'
export { renderEffect } from './renderEffect'
export {
watch,
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'
export { createVaporApp } from './apiCreateApp'
export { useEmit } from './componentEmits'
// DOM
export { template, children, next } from './dom/template'
export { insert, prepend, remove, createTextNode } from './dom/element'
export { setStyle } from './dom/style'
@ -120,75 +19,3 @@ export {
} from './dom/prop'
export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event'
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'

View File

@ -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
}

View File

@ -1,95 +1,19 @@
import { EffectFlags, ReactiveEffect, getCurrentScope } from '@vue/reactivity'
import { invokeArrayFns } from '@vue/shared'
import {
type ComponentInternalInstance,
getCurrentInstance,
setCurrentInstance,
} from './component'
import {
type SchedulerJob,
VaporSchedulerJobFlags,
queueJob,
queuePostFlushCb,
} from './scheduler'
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
import { ReactiveEffect } from '@vue/reactivity'
import { type SchedulerJob, queueJob } from '@vue/runtime-core'
import { currentInstance } from './component'
export function renderEffect(cb: () => void): void {
const instance = getCurrentInstance()
const scope = getCurrentScope()
if (scope) {
const baseCb = cb
cb = () => scope.run(baseCb)
export function renderEffect(fn: () => void): void {
const updateFn = () => {
fn()
}
if (instance) {
const baseCb = cb
cb = () => {
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
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
}

View File

@ -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

View File

@ -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]
}
}

View File

@ -12,4 +12,4 @@ if (__DEV__ && __BROWSER__) {
initCustomFormatter()
}
export * from '@vue/runtime-vapor'
export * from '@vue/runtime-vapor/src'

View File

@ -399,9 +399,6 @@ importers:
'@vue/reactivity':
specifier: workspace:*
version: link:../reactivity
'@vue/runtime-shared':
specifier: workspace:*
version: link:../runtime-shared
'@vue/shared':
specifier: workspace:*
version: link:../shared
@ -425,12 +422,6 @@ importers:
specifier: ^2.0.7
version: 2.0.7
packages/runtime-shared:
dependencies:
'@vue/shared':
specifier: workspace:*
version: link:../shared
packages/runtime-test:
dependencies:
'@vue/runtime-core':
@ -445,9 +436,9 @@ importers:
'@vue/reactivity':
specifier: workspace:*
version: link:../reactivity
'@vue/runtime-shared':
'@vue/runtime-dom':
specifier: workspace:*
version: link:../runtime-shared
version: link:../runtime-dom
'@vue/shared':
specifier: workspace:*
version: link:../shared

View File

@ -39,5 +39,8 @@
"scripts/*",
"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"
]
}