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
"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:*"
}
}
import {
type Component,
type ConcreteComponent,
+ type Data,
type GenericComponent,
type GenericComponentInstance,
getComponentPublicInstance,
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'
}
}
-export type CreateAppFunction<HostElement> = (
- rootComponent: GenericComponent,
+export type CreateAppFunction<HostElement, Comp = Component> = (
+ rootComponent: Comp,
rootProps?: Data | null,
) => App<HostElement>
/**
* @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)
* 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
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,
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'
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 = {}
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'
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
* setup related
* @internal
*/
- setupState: Data | null
+ setupState: Data
/**
* devtools access to additional info
* @internal
type ComponentInternalInstance,
type ComponentInternalOptions,
type ConcreteComponent,
+ type Data,
type InternalRenderFunction,
type SetupContext,
currentInstance,
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 {
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'
import {
type Component,
type ComponentInternalInstance,
+ type Data,
getComponentPublicInstance,
isStatefulComponent,
} from './component'
isGloballyAllowed,
isString,
} from '@vue/shared'
-import type { Data } from '@vue/runtime-shared'
import {
ReactiveFlags,
type ShallowUnwrapRef,
import {
type ComponentInternalInstance,
+ type Data,
type FunctionalComponent,
getComponentName,
} from './component'
} 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'
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'
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/>`
-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
+}
import {
type ComponentInternalInstance,
type ComponentOptions,
+ type Data,
type LifecycleHook,
createComponentInstance,
setupComponent,
isArray,
isReservedProp,
} from '@vue/shared'
-import type { Data } from '@vue/runtime-shared'
import {
type SchedulerJob,
SchedulerJobFlags,
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'
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'
import {
type App,
+ type Component,
type ConcreteComponent,
type CreateAppFunction,
type DefineComponent,
}
return app
-}) as CreateAppFunction<Element>
+}) as CreateAppFunction<Element, Component>
export const createSSRApp = ((...args) => {
const app = ensureHydrationRenderer().createApp(...args)
+++ /dev/null
-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.
+++ /dev/null
-# @vue/runtime-shared
+++ /dev/null
-'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')
-}
+++ /dev/null
-{
- "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:*"
- }
-}
+++ /dev/null
-export { toHandlers } from './toHandlers'
-export { type Data } from './typeUtils'
+++ /dev/null
-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
-}
+++ /dev/null
-export type Data = Record<string, unknown>
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
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()
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()
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', () => {
ref,
renderEffect,
setText,
-} from '../src'
+} from '../src/_old'
import { makeRender } from './_utils'
const define = makeRender<any>()
renderEffect,
setText,
template,
-} from '../src'
+} from '../src/_old'
import { makeRender } from './_utils'
import { ITERATE_KEY } from '@vue/reactivity'
setInheritAttrs,
template,
watchEffect,
-} from '../src'
+} from '../src/_old'
import { makeRender } from './_utils'
const define = makeRender()
-import type { SetupContext } from '../src/component'
+import type { SetupContext } from '../src/_old/component'
import {
createComponent,
defineComponent,
template,
useAttrs,
useSlots,
-} from '../src'
+} from '../src/_old'
import { makeRender } from './_utils'
const define = makeRender<any>()
ref,
watchEffect,
watchSyncEffect,
-} from '../src'
+} from '../src/_old'
describe('watchEffect and onWatcherCleanup', () => {
test('basic', async () => {
-import { ref, setText, template, watchEffect } from '../src'
+import { ref, setText, template, watchEffect } from '../src/_old'
import { describe, expect } from 'vitest'
import { makeRender } from './_utils'
setText,
template,
watchEffect,
-} from '../src'
+} from '../src/_old'
import { makeRender } from './_utils'
const define = makeRender<any>()
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()
toRefs,
watch,
watchEffect,
-} from '../src'
+} from '../src/_old'
import { makeRender } from './_utils'
const define = makeRender<any>()
setText,
template,
withDestructure,
-} from '../src'
+} from '../src/_old'
import { makeRender } from './_utils'
const define = makeRender<any>()
vModelDynamic,
vModelSelect,
withDirectives,
-} from '../../src'
+} from '../../src/_old'
import { makeRender } from '../_utils'
import { nextTick } from '@vue/runtime-dom'
template,
vShow,
withDirectives,
-} from '../../src'
+} from '../../src/_old'
import { nextTick, ref } from 'vue'
import { describe, expect, test } from 'vitest'
import { makeRender } from '../_utils'
import {
ComponentInternalInstance,
setCurrentInstance,
-} from '../../src/component'
+} from '../../src/_old/component'
import { getMetadata, recordPropMetadata } from '../../src/componentMetadata'
import { getCurrentScope } from '@vue/reactivity'
setText,
template,
watchEffect,
-} from '../../src'
+} from '../../src/_old'
import { makeRender } from '../_utils'
const define = makeRender()
-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()
template,
triggerRef,
withDestructure,
-} from '../src'
+} from '../src/_old'
import { makeRender } from './_utils'
const define = makeRender()
createVaporApp,
resolveComponent,
resolveDirective,
-} from '@vue/runtime-vapor'
+} from 'packages/runtime-vapor/src/_old'
import { makeRender } from '../_utils'
const define = makeRender()
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()
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>()
"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:*"
}
}
+++ /dev/null
-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
- }
- }
-}
+++ /dev/null
-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]()
-}
+++ /dev/null
-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])
-}
+++ /dev/null
-export { createComponent as createComponentSimple } from './component'
-export { renderEffect as renderEffectSimple } from './renderEffect'
-export { createVaporApp as createVaporAppSimple } from './apiCreateApp'
-export { useEmit } from './componentEmits'
+++ /dev/null
-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
-}
-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,
createAppAPI,
} from '@vue/runtime-core'
-let _createApp: CreateAppFunction<ParentNode>
+let _createApp: CreateAppFunction<ParentNode, VaporComponent>
const mountApp: AppMountFn<ParentNode> = (app, container) => {
// clear content before mounting
// 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
+++ /dev/null
-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
-}
+++ /dev/null
-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
-}
+++ /dev/null
-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,
- )
-}
+++ /dev/null
-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)
- }
-}
+++ /dev/null
-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 {}
+++ /dev/null
-import type { Component } from './component'
-
-/*! #__NO_SIDE_EFFECTS__ */
-export function defineComponent(comp: Component): Component {
- return comp
-}
+++ /dev/null
-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)
-}
+++ /dev/null
-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)
-}
+++ /dev/null
-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()
-}
+++ /dev/null
-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))
-}
+++ /dev/null
-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
-}
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` : ``)
-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'
-
-export type Component = FunctionalComponent | ObjectComponent
+ type RawProps,
+ getDynamicPropsHandlers,
+ initStaticProps,
+} from './componentProps'
+import { setDynamicProp } from './dom/prop'
+import { renderEffect } from './renderEffect'
-type SharedInternalOptions = {
- __propsOptions?: NormalizedPropsOptions
- __propsHandlers?: [ProxyHandler<any>, ProxyHandler<any>]
-}
+export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent
-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
vapor?: boolean
}
-// Note: can't mark this whole interface internal because some public interfaces
-// extend it.
-export interface ComponentInternalOptions {
- /**
- * @internal
- */
- __scopeId?: string
- /**
- * @internal
- */
- __cssModules?: Data
- /**
- * @internal
- */
- __hmrId?: string
+interface SharedInternalOptions {
/**
- * Compat build only, for bailing out of certain compatibility behavior
+ * Cached normalized props options.
+ * In vapor mode there are no mixins so normalized options can be cached
+ * directly on the component
*/
- __isBuiltIn?: boolean
+ __propsOptions?: NormalizedPropsOptions
/**
- * This one should be exposed so that devtools can make use of it
+ * Cached normalized props proxy handlers.
*/
- __file?: string
+ __propsHandlers?: [ProxyHandler<any> | null, ProxyHandler<any>]
/**
- * name inferred from filename
+ * Cached normalized emits options.
*/
- __name?: string
+ __emitsOptions?: ObjectEmitsOptions
}
-type LifecycleHook<TFn = Function> = TFn[] | null
-
-export let currentInstance: ComponentInternalInstance | null = null
+export function createComponent(
+ component: VaporComponent,
+ rawProps?: RawProps,
+ isSingleRoot?: boolean,
+): VaporComponentInstance {
+ // check if we are the single root of the parent
+ // if yes, inject parent attrs as dynamic props source
+ if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) {
+ if (rawProps) {
+ ;(rawProps.$ || (rawProps.$ = [])).push(currentInstance.attrs)
+ } else {
+ rawProps = { $: [currentInstance.attrs] }
+ }
+ }
-export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
- currentInstance
+ const instance = new VaporComponentInstance(component, rawProps)
-export const setCurrentInstance = (instance: ComponentInternalInstance) => {
- const prev = currentInstance
+ pauseTracking()
+ let prevInstance = currentInstance
currentInstance = instance
- return (): void => {
- currentInstance = prev
+ 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])
+ }
+ })
}
-}
-export const unsetCurrentInstance = (): void => {
- currentInstance && currentInstance.scope.off()
- currentInstance = null
+ if (__DEV__) {
+ popWarningContext()
+ }
+
+ instance.scope.off()
+ currentInstance = prevInstance
+ resetTracking()
+ return instance
}
-const emptyAppContext = createAppContext()
+export let currentInstance: VaporComponentInstance | null = null
-let uid = 0
-export class ComponentInternalInstance {
- vapor = true
+const emptyContext: GenericAppContext = {
+ app: null as any,
+ config: {},
+ provides: /*@__PURE__*/ Object.create(null),
+}
+export class VaporComponentInstance implements GenericComponentInstance {
uid: number
- appContext: AppContext
-
- type: Component
- block: Block | null
- container: ParentNode
- parent: ComponentInternalInstance | null
- root: ComponentInternalInstance
+ type: VaporComponent
+ parent: GenericComponentInstance | null
+ appContext: GenericAppContext
- provides: Data
+ block: Block
scope: EffectScope
- comps: Set<ComponentInternalInstance>
- scopeIds: string[]
-
- rawProps: NormalizedRawProps
- propsOptions: NormalizedPropsOptions
- emitsOptions: ObjectEmitsOptions | null
+ rawProps: RawProps | undefined
+ props: Record<string, any>
+ attrs: Record<string, any>
+ exposed: Record<string, any> | 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!
- 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
+ // dev only
+ propsOptions?: NormalizedPropsOptions
+ emitsOptions?: ObjectEmitsOptions | null
- // 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
+ constructor(comp: VaporComponent, rawProps?: RawProps) {
+ this.uid = nextUid()
+ this.type = comp
+ this.parent = currentInstance
+ this.appContext = currentInstance
+ ? currentInstance.appContext
+ : emptyContext
- initProps(this, rawProps, !isFunction(component), once)
- initSlots(this, slots)
- }
-}
+ this.block = null! // to be set
+ this.scope = new EffectScope(true)
-export function isVaporComponent(
- val: unknown,
-): val is ComponentInternalInstance {
- return val instanceof ComponentInternalInstance
-}
+ 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)
+ }
-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,
- )
+ // TODO validate props
+ // TODO init slots
}
}
-/**
- * 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 isVaporComponent(
+ value: unknown,
+): value is VaporComponentInstance {
+ return value instanceof VaporComponentInstance
}
-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]
- }
- }
+export class SetupContext<E = EmitsOptions> {
+ attrs: Record<string, any>
+ // emit: EmitFn<E>
+ // slots: Readonly<StaticSlots>
+ expose: (exposed?: Record<string, any>) => void
- 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
- }
- }
+ constructor(instance: VaporComponentInstance) {
+ this.attrs = instance.attrs
+ // this.emit = instance.emit as EmitFn<E>
+ // this.slots = instance.slots
+ this.expose = (exposed = {}) => {
+ instance.exposed = exposed
}
- 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, '')
+++ /dev/null
-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
-}
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])
+}
+++ /dev/null
-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
-}
+++ /dev/null
-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))
- }
-}
+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 type Prop<T, D = T> = PropOptions<T, D> | PropType<T>
-
-type DefaultFactory<T> = (props: Data) => T | null | undefined
-
-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 interface RawProps {
+ [key: string]: PropSource
+ $?: DynamicPropsSource[]
}
-export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
-
-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[]]
- | []
+type PropSource<T = any> = T | (() => T)
-export type StaticProps = Record<string, () => unknown>
-type DynamicProps = () => Data
-export type NormalizedRawProps = Array<StaticProps | DynamicProps>
-export type RawProps = NormalizedRawProps | StaticProps | DynamicProps | null
+type DynamicPropsSource = PropSource<Record<string, any>>
-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)
+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)
- if (options) {
- if (hasDynamicProps) {
- for (const key in options) {
- const getter = () =>
- getDynamicPropValue(rawProps as NormalizedRawProps, key)
- registerProp(instance, once, props, key, getter, true)
+ // 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 {
- 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
}
}
-
- // validation
- if (__DEV__) {
- validateProps(rawProps, props, options || {})
- }
-
- 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
+ for (const key in propsOptions) {
+ if (!(key in props)) {
+ props[key] = resolvePropValue(
+ propsOptions,
+ key,
+ undefined,
+ instance,
+ resolveDefault,
+ true,
+ )
+ }
}
+ return hasAttrs
}
-function registerProp(
- instance: ComponentInternalInstance,
- once: boolean,
- props: Data,
- rawKey: string,
- getter?: (() => unknown) | (() => DynamicPropResult),
- isDynamic?: boolean,
- isAbsent?: boolean,
+function resolveDefault(
+ factory: (props: Record<string, any>) => unknown,
+ instance: VaporComponentInstance,
) {
- 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)
- }
+ return factory.call(null, instance.props)
}
-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)
- }
- }
- }
+// TODO optimization: maybe convert functions into computeds
+export function resolveSource(source: PropSource): Record<string, any> {
+ return isFunction(source) ? source() : source
}
-function getRawKey(obj: Data, key: string) {
- return Object.keys(obj).find(k => camelize(k) === key)
-}
+const passThrough = (val: any) => val
-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]
- }
+export function getDynamicPropsHandlers(
+ comp: VaporComponent,
+ instance: VaporComponentInstance,
+): [ProxyHandler<RawProps> | null, ProxyHandler<RawProps>] {
+ if (comp.__propsHandlers) {
+ return comp.__propsHandlers
}
- return [undefined, true]
-}
+ let normalizedKeys: string[] | undefined
+ const propsOptions = normalizePropsOptions(comp)[0]
+ const emitsOptions = normalizeEmitsOptions(comp)
+ const isProp = propsOptions ? (key: string) => hasOwn(propsOptions, key) : NO
-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 getProp = (target: RawProps, key: string, asProp: boolean) => {
+ if (key === '$') return
+ if (asProp) {
+ if (!isProp(key)) return
+ } else if (isProp(key) || isEmitListener(emitsOptions, key)) {
+ return
}
- // boolean casting
- if (opt[BooleanFlags.shouldCast]) {
- if (isAbsent && !hasDefault) {
- value = false
- } else if (
- opt[BooleanFlags.shouldCastTrue] &&
- (value === '' || value === hyphenate(key))
- ) {
- value = true
- }
+ 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]))
}
- }
- return value
-}
-
-export function normalizePropsOptions(comp: Component): NormalizedPropsOptions {
- const cached = comp.__propsOptions
- if (cached) return cached
-
- const raw = comp.props
- const normalized: NormalizedProps | undefined = {}
- const needCastKeys: NormalizedPropsOptions[1] = []
-
- 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
+ if (target.$) {
+ let i = target.$.length
+ let source
+ while (i--) {
+ source = resolveSource(target.$[i])
+ if (hasOwn(source, key)) {
+ return castProp(source[key])
+ }
}
}
- } 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)
+ 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
}
}
}
- }
-
- 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)
-}
+ 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>
-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
+ return (comp.__propsHandlers = [propsHandlers, attrsHandlers])
}
-/**
- * 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))
- }
+function normalizePropsOptions(comp: VaporComponent): NormalizedPropsOptions {
+ const cached = comp.__propsOptions
+ if (cached) return cached
- for (const key in options) {
- const opt = options[key]
- if (opt != null)
- validateProp(
- key,
- props[key],
- opt,
- props,
- !presentKeys.some(k => camelize(k) === key),
- )
- }
-}
+ const raw = comp.props
+ if (!raw) return EMPTY_ARR as []
-/**
- * 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
- // }
- // }
+ const normalized: NormalizedPropsOptions[0] = {}
+ const needCastKeys: NormalizedPropsOptions[1] = []
+ baseNormalizePropsOptions(raw, normalized, needCastKeys)
- // custom validator
- if (validator && !validator(value, props)) {
- warn('Invalid prop: custom validator check failed for prop "' + name + '".')
- }
+ return (comp.__propsOptions = [normalized, needCastKeys])
}
+++ /dev/null
-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]
- }
- }
-}
+++ /dev/null
-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)
- }
-}
+++ /dev/null
-/* 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,
- )
-}
+++ /dev/null
-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]
-}
+++ /dev/null
-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
- }
- }
-}
+++ /dev/null
-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
-}
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,
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,
shouldSetAsAttr,
toDisplayString,
} from '@vue/shared'
-import { warn } from '../warning'
+import { warn } from '../_old/warning'
import { setStyle } from './style'
import {
MetadataKind,
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!
return incoming
}
+type Data = Record<string, any>
+
export function mergeProps(...args: Data[]): Data {
const ret: Data = {}
for (let i = 0; i < args.length; i++) {
isString,
normalizeStyle,
} from '@vue/shared'
-import { warn } from '../warning'
+import { warn } from '../_old/warning'
import { recordPropMetadata } from '../componentMetadata'
import { mergeInheritAttr } from './prop'
type ComponentInternalInstance,
currentInstance,
isVaporComponent,
-} from '../component'
-import { VaporErrorCodes, callWithErrorHandling } from '../errorHandling'
+} from '../_old/component'
+import { VaporErrorCodes, callWithErrorHandling } from '../_old/errorHandling'
import {
EMPTY_OBJ,
hasOwn,
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
+++ /dev/null
-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',
-}
+++ /dev/null
-// 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)
- }
-}
+++ /dev/null
-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
- }
-}
+++ /dev/null
-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)
-// 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'
} 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'
+++ /dev/null
-/* 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
-}
-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)
- }
-
- if (instance) {
- const baseCb = cb
- cb = () => {
- const reset = setCurrentInstance(instance)
- baseCb()
- reset()
- }
- job.id = instance.uid
+export function renderEffect(fn: () => void): void {
+ const updateFn = () => {
+ fn()
}
-
- const effect = new ReactiveEffect(() =>
- callWithAsyncErrorHandling(cb, instance, VaporErrorCodes.RENDER_FUNCTION),
- )
-
+ 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)
- if (__DEV__ && instance) {
- effect.onTrack = instance.rtc
- ? e => invokeArrayFns(instance.rtc!, e)
- : void 0
- effect.onTrigger = instance.rtg
- ? e => invokeArrayFns(instance.rtg!, e)
- : void 0
- }
effect.run()
- function job() {
- if (!(effect.flags & EffectFlags.ACTIVE) || !effect.dirty) {
- return
- }
-
- const reset = instance && setCurrentInstance(instance)
-
- if (instance && instance.isMounted && !instance.isUpdating) {
- instance.isUpdating = true
-
- const { bu, u } = instance
- // beforeUpdate hook
- if (bu) {
- invokeArrayFns(bu)
- }
-
- effect.run()
-
- queuePostFlushCb(() => {
- instance.isUpdating = false
- const reset = setCurrentInstance(instance)
- // updated hook
- if (u) {
- queuePostFlushCb(u)
- }
- reset()
- })
- } else {
- effect.run()
- }
-
- reset && reset()
- }
-}
-
-export function firstEffect(
- instance: ComponentInternalInstance,
- fn: () => void,
-): void {
- const effect = new ReactiveEffect(fn)
- const job: SchedulerJob = () => effect.run()
- job.flags! |= VaporSchedulerJobFlags.PRE
- job.id = instance.uid
- effect.scheduler = () => queueJob(job)
- effect.run()
+ // TODO lifecycle
+ // TODO recurse handling
+ // TODO measure
}
+++ /dev/null
-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
+++ /dev/null
-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]
- }
-}
initCustomFormatter()
}
-export * from '@vue/runtime-vapor'
+export * from '@vue/runtime-vapor/src'
'@vue/reactivity':
specifier: workspace:*
version: link:../reactivity
- '@vue/runtime-shared':
- specifier: workspace:*
- version: link:../runtime-shared
'@vue/shared':
specifier: workspace:*
version: link:../shared
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':
'@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
"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"
+ ]
}