refactor: remove runtime-shared

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,25 @@
import { toHandlers as _toHandlers } from '@vue/runtime-shared' import { isObject, toHandlerKey } from '@vue/shared'
import { warn } from '../warning' import { warn } from '../warning'
import { NOOP } from '@vue/shared'
export const toHandlers: ( /**
* For prefixing keys in v-on="obj" with "on"
* @private
*/
export function toHandlers(
obj: Record<string, any>, obj: Record<string, any>,
preserveCaseIfNecessary?: boolean | undefined, preserveCaseIfNecessary?: boolean,
) => Record<string, any> = _toHandlers.bind(undefined, __DEV__ ? warn : NOOP) ): Record<string, any> {
const ret: Record<string, any> = {}
if (__DEV__ && !isObject(obj)) {
warn(`v-on with no argument expects an object value.`)
return ret
}
for (const key in obj) {
ret[
preserveCaseIfNecessary && /[A-Z]/.test(key)
? `on:${key}`
: toHandlerKey(key)
] = obj[key]
}
return ret
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2018-present, Yuxi (Evan) You
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

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

View File

@ -1,7 +0,0 @@
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./dist/runtime-shared.cjs.prod.js')
} else {
module.exports = require('./dist/runtime-shared.cjs.js')
}

View File

@ -1,50 +0,0 @@
{
"name": "@vue/runtime-shared",
"version": "3.0.0-vapor",
"description": "@vue/runtime-shared",
"main": "index.js",
"module": "dist/runtime-shared.esm-bundler.js",
"types": "dist/runtime-shared.d.ts",
"files": [
"index.js",
"dist"
],
"exports": {
".": {
"types": "./dist/runtime-shared.d.ts",
"node": {
"production": "./dist/runtime-shared.cjs.prod.js",
"development": "./dist/runtime-shared.cjs.js",
"default": "./index.js"
},
"module": "./dist/runtime-shared.esm-bundler.js",
"import": "./dist/runtime-shared.esm-bundler.js",
"require": "./index.js"
},
"./*": "./*"
},
"sideEffects": false,
"buildOptions": {
"formats": [
"esm-bundler",
"cjs"
]
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue-vapor.git",
"directory": "packages/runtime-shared"
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vue-vapor/issues"
},
"homepage": "https://github.com/vuejs/vue-vapor/tree/main/packages/runtime-shared#readme",
"dependencies": {
"@vue/shared": "workspace:*"
}
}

View File

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

View File

@ -1,25 +0,0 @@
import { isObject, toHandlerKey } from '@vue/shared'
/**
* For prefixing keys in v-on="obj" with "on"
* @private
*/
export function toHandlers(
warn: (msg: string) => void,
obj: Record<string, any>,
preserveCaseIfNecessary?: boolean,
): Record<string, any> {
const ret: Record<string, any> = {}
if (__DEV__ && !isObject(obj)) {
warn(`v-on with no argument expects an object value.`)
return ret
}
for (const key in obj) {
ret[
preserveCaseIfNecessary && /[A-Z]/.test(key)
? `on:${key}`
: toHandlerKey(key)
] = obj[key]
}
return ret
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

@ -0,0 +1 @@
_old

View File

@ -1,224 +0,0 @@
import {
type ComponentInternalOptions,
type ComponentPropsOptions,
EffectScope,
type EmitsOptions,
type GenericAppContext,
type GenericComponentInstance,
type LifecycleHook,
type NormalizedPropsOptions,
type ObjectEmitsOptions,
nextUid,
popWarningContext,
pushWarningContext,
} from '@vue/runtime-core'
import type { Block } from '../block'
import type { Data } from '@vue/runtime-shared'
import { pauseTracking, resetTracking } from '@vue/reactivity'
import { EMPTY_OBJ, isFunction } from '@vue/shared'
import {
type RawProps,
getDynamicPropsHandlers,
initStaticProps,
} from './componentProps'
import { setDynamicProp } from '../dom/prop'
import { renderEffect } from './renderEffect'
export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent
export type VaporSetupFn = (
props: any,
ctx: SetupContext,
) => Block | Data | undefined
export type FunctionalVaporComponent = VaporSetupFn &
Omit<ObjectVaporComponent, 'setup'> & {
displayName?: string
} & SharedInternalOptions
export interface ObjectVaporComponent
extends ComponentInternalOptions,
SharedInternalOptions {
setup?: VaporSetupFn
inheritAttrs?: boolean
props?: ComponentPropsOptions
emits?: EmitsOptions
render?(ctx: any): Block
name?: string
vapor?: boolean
}
interface SharedInternalOptions {
/**
* Cached normalized props options.
* In vapor mode there are no mixins so normalized options can be cached
* directly on the component
*/
__propsOptions?: NormalizedPropsOptions
/**
* Cached normalized props proxy handlers.
*/
__propsHandlers?: [ProxyHandler<any> | null, ProxyHandler<any>]
/**
* Cached normalized emits options.
*/
__emitsOptions?: ObjectEmitsOptions
}
export function createComponent(
component: VaporComponent,
rawProps?: RawProps,
isSingleRoot?: boolean,
): VaporComponentInstance {
// check if we are the single root of the parent
// if yes, inject parent attrs as dynamic props source
if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) {
if (rawProps) {
;(rawProps.$ || (rawProps.$ = [])).push(currentInstance.attrs)
} else {
rawProps = { $: [currentInstance.attrs] }
}
}
const instance = new VaporComponentInstance(component, rawProps)
pauseTracking()
let prevInstance = currentInstance
currentInstance = instance
instance.scope.on()
if (__DEV__) {
pushWarningContext(instance)
}
const setupFn = isFunction(component) ? component : component.setup
const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null
instance.block = setupFn!(
instance.props,
// @ts-expect-error
setupContext,
) as Block // TODO handle return object
// single root, inherit attrs
if (
instance.hasFallthrough &&
component.inheritAttrs !== false &&
instance.block instanceof Element &&
Object.keys(instance.attrs).length
) {
renderEffect(() => {
for (const key in instance.attrs) {
setDynamicProp(instance.block as Element, key, instance.attrs[key])
}
})
}
if (__DEV__) {
popWarningContext()
}
instance.scope.off()
currentInstance = prevInstance
resetTracking()
return instance
}
export let currentInstance: VaporComponentInstance | null = null
const emptyContext: GenericAppContext = {
app: null as any,
config: {},
provides: /*@__PURE__*/ Object.create(null),
}
export class VaporComponentInstance implements GenericComponentInstance {
uid: number
type: VaporComponent
parent: GenericComponentInstance | null
appContext: GenericAppContext
block: Block
scope: EffectScope
rawProps: RawProps | undefined
props: Record<string, any>
attrs: Record<string, any>
exposed: Record<string, any> | null
emitted: Record<string, boolean> | null
propsDefaults: Record<string, any> | null
// for useTemplateRef()
refs: Data
// for provide / inject
provides: Data
hasFallthrough: boolean
isMounted: boolean
isUnmounted: boolean
isDeactivated: boolean
// LifecycleHooks.ERROR_CAPTURED
ec: LifecycleHook
// dev only
propsOptions?: NormalizedPropsOptions
emitsOptions?: ObjectEmitsOptions | null
constructor(comp: VaporComponent, rawProps?: RawProps) {
this.uid = nextUid()
this.type = comp
this.parent = currentInstance
this.appContext = currentInstance
? currentInstance.appContext
: emptyContext
this.block = null! // to be set
this.scope = new EffectScope(true)
this.rawProps = rawProps
this.provides = this.refs = EMPTY_OBJ
this.emitted = this.ec = this.exposed = null
this.isMounted = this.isUnmounted = this.isDeactivated = false
// init props
this.propsDefaults = null
this.hasFallthrough = false
if (rawProps && rawProps.$) {
// has dynamic props, use proxy
const handlers = getDynamicPropsHandlers(comp, this)
this.props = comp.props ? new Proxy(rawProps, handlers[0]!) : EMPTY_OBJ
this.attrs = new Proxy(rawProps, handlers[1])
this.hasFallthrough = true
} else {
this.props = {}
this.attrs = {}
this.hasFallthrough = initStaticProps(comp, rawProps, this)
}
// TODO validate props
// TODO init slots
}
}
export function isVaporComponent(
value: unknown,
): value is VaporComponentInstance {
return value instanceof VaporComponentInstance
}
export class SetupContext<E = EmitsOptions> {
attrs: Record<string, any>
// emit: EmitFn<E>
// slots: Readonly<StaticSlots>
expose: (exposed?: Record<string, any>) => void
constructor(instance: VaporComponentInstance) {
this.attrs = instance.attrs
// this.emit = instance.emit as EmitFn<E>
// this.slots = instance.slots
this.expose = (exposed = {}) => {
instance.exposed = exposed
}
}
}

View File

@ -1,70 +0,0 @@
import {
type EmitFn,
type ObjectEmitsOptions,
baseEmit,
} from '@vue/runtime-core'
import {
type VaporComponent,
type VaporComponentInstance,
currentInstance,
} from './component'
import { EMPTY_OBJ, NOOP, hasOwn, isArray } from '@vue/shared'
import { resolveSource } from './componentProps'
/**
* The logic from core isn't too reusable so it's better to duplicate here
*/
export function normalizeEmitsOptions(
comp: VaporComponent,
): ObjectEmitsOptions | null {
const cached = comp.__emitsOptions
if (cached) return cached
const raw = comp.emits
if (!raw) return null
let normalized: ObjectEmitsOptions
if (isArray(raw)) {
normalized = {}
for (const key in raw) normalized[key] = null
} else {
normalized = raw
}
return (comp.__emitsOptions = normalized)
}
export function useEmit(): EmitFn {
if (!currentInstance) {
// TODO warn
return NOOP
} else {
return emit.bind(null, currentInstance)
}
}
export function emit(
instance: VaporComponentInstance,
event: string,
...rawArgs: any[]
): void {
baseEmit(
instance,
instance.rawProps || EMPTY_OBJ,
propGetter,
event,
...rawArgs,
)
}
function propGetter(rawProps: Record<string, any>, key: string) {
const dynamicSources = rawProps.$
if (dynamicSources) {
let i = dynamicSources.length
while (i--) {
const source = resolveSource(dynamicSources[i])
if (hasOwn(source, key)) return source[key]
}
}
return rawProps[key] && rawProps[key]()
}

View File

@ -1,231 +0,0 @@
import { EMPTY_ARR, NO, camelize, hasOwn, isFunction } from '@vue/shared'
import type { VaporComponent, VaporComponentInstance } from './component'
import {
type NormalizedPropsOptions,
baseNormalizePropsOptions,
isEmitListener,
resolvePropValue,
} from '@vue/runtime-core'
import { normalizeEmitsOptions } from './componentEmits'
export interface RawProps {
[key: string]: PropSource
$?: DynamicPropsSource[]
}
type PropSource<T = any> = T | (() => T)
type DynamicPropsSource = PropSource<Record<string, any>>
export function initStaticProps(
comp: VaporComponent,
rawProps: RawProps | undefined,
instance: VaporComponentInstance,
): boolean {
let hasAttrs = false
const { props, attrs } = instance
const [propsOptions, needCastKeys] = normalizePropsOptions(comp)
const emitsOptions = normalizeEmitsOptions(comp)
// for dev emit check
if (__DEV__) {
instance.propsOptions = normalizePropsOptions(comp)
instance.emitsOptions = emitsOptions
}
for (const key in rawProps) {
const normalizedKey = camelize(key)
const needCast = needCastKeys && needCastKeys.includes(normalizedKey)
const source = rawProps[key]
if (propsOptions && normalizedKey in propsOptions) {
if (isFunction(source)) {
Object.defineProperty(props, normalizedKey, {
enumerable: true,
get: needCast
? () =>
resolvePropValue(
propsOptions,
normalizedKey,
source(),
instance,
resolveDefault,
)
: source,
})
} else {
props[normalizedKey] = needCast
? resolvePropValue(
propsOptions,
normalizedKey,
source,
instance,
resolveDefault,
)
: source
}
} else if (!isEmitListener(emitsOptions, key)) {
if (isFunction(source)) {
Object.defineProperty(attrs, key, {
enumerable: true,
get: source,
})
} else {
attrs[normalizedKey] = source
}
hasAttrs = true
}
}
for (const key in propsOptions) {
if (!(key in props)) {
props[key] = resolvePropValue(
propsOptions,
key,
undefined,
instance,
resolveDefault,
true,
)
}
}
return hasAttrs
}
function resolveDefault(
factory: (props: Record<string, any>) => unknown,
instance: VaporComponentInstance,
) {
return factory.call(null, instance.props)
}
// TODO optimization: maybe convert functions into computeds
export function resolveSource(source: PropSource): Record<string, any> {
return isFunction(source) ? source() : source
}
const passThrough = (val: any) => val
export function getDynamicPropsHandlers(
comp: VaporComponent,
instance: VaporComponentInstance,
): [ProxyHandler<RawProps> | null, ProxyHandler<RawProps>] {
if (comp.__propsHandlers) {
return comp.__propsHandlers
}
let normalizedKeys: string[] | undefined
const propsOptions = normalizePropsOptions(comp)[0]
const emitsOptions = normalizeEmitsOptions(comp)
const isProp = propsOptions ? (key: string) => hasOwn(propsOptions, key) : NO
const getProp = (target: RawProps, key: string, asProp: boolean) => {
if (key === '$') return
if (asProp) {
if (!isProp(key)) return
} else if (isProp(key) || isEmitListener(emitsOptions, key)) {
return
}
const castProp = propsOptions
? (value: any, isAbsent = false) =>
asProp
? resolvePropValue(
propsOptions,
key as string,
value,
instance,
resolveDefault,
isAbsent,
)
: value
: passThrough
if (key in target) {
return castProp(resolveSource(target[key as string]))
}
if (target.$) {
let i = target.$.length
let source
while (i--) {
source = resolveSource(target.$[i])
if (hasOwn(source, key)) {
return castProp(source[key])
}
}
}
return castProp(undefined, true)
}
const propsHandlers = propsOptions
? ({
get: (target, key: string) => getProp(target, key, true),
has: (_, key: string) => isProp(key),
getOwnPropertyDescriptor(target, key: string) {
if (isProp(key)) {
return {
configurable: true,
enumerable: true,
get: () => getProp(target, key, true),
}
}
},
ownKeys: () =>
normalizedKeys || (normalizedKeys = Object.keys(propsOptions)),
set: NO,
deleteProperty: NO,
} satisfies ProxyHandler<RawProps>)
: null
const hasAttr = (target: RawProps, key: string) => {
if (key === '$' || isProp(key) || isEmitListener(emitsOptions, key))
return false
if (target.$) {
let i = target.$.length
while (i--) {
if (hasOwn(resolveSource(target.$[i]), key)) {
return true
}
}
}
return hasOwn(target, key)
}
const attrsHandlers = {
get: (target, key: string) => getProp(target, key, false),
has: hasAttr,
getOwnPropertyDescriptor(target, key: string) {
if (hasAttr(target, key)) {
return {
configurable: true,
enumerable: true,
get: () => getProp(target, key, false),
}
}
},
ownKeys(target) {
const keys = Object.keys(target)
if (target.$) {
let i = target.$.length
while (i--) {
keys.push(...Object.keys(resolveSource(target.$[i])))
}
}
return keys.filter(key => hasAttr(target, key))
},
set: NO,
deleteProperty: NO,
} satisfies ProxyHandler<RawProps>
return (comp.__propsHandlers = [propsHandlers, attrsHandlers])
}
function normalizePropsOptions(comp: VaporComponent): NormalizedPropsOptions {
const cached = comp.__propsOptions
if (cached) return cached
const raw = comp.props
if (!raw) return EMPTY_ARR as []
const normalized: NormalizedPropsOptions[0] = {}
const needCastKeys: NormalizedPropsOptions[1] = []
baseNormalizePropsOptions(raw, normalized, needCastKeys)
return (comp.__propsOptions = [normalized, needCastKeys])
}

View File

@ -1,4 +0,0 @@
export { createComponent as createComponentSimple } from './component'
export { renderEffect as renderEffectSimple } from './renderEffect'
export { createVaporApp as createVaporAppSimple } from './apiCreateApp'
export { useEmit } from './componentEmits'

View File

@ -1,19 +0,0 @@
import { ReactiveEffect } from '@vue/reactivity'
import { type SchedulerJob, queueJob } from '@vue/runtime-core'
import { currentInstance } from './component'
export function renderEffect(fn: () => void): void {
const updateFn = () => {
fn()
}
const effect = new ReactiveEffect(updateFn)
const job: SchedulerJob = effect.runIfDirty.bind(effect)
job.i = currentInstance as any
job.id = currentInstance!.uid
effect.scheduler = () => queueJob(job)
effect.run()
// TODO lifecycle
// TODO recurse handling
// TODO measure
}

View File

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

View File

@ -1,47 +0,0 @@
import {
type Component,
ComponentInternalInstance,
currentInstance,
} from './component'
import { setupComponent } from './apiRender'
import type { RawProps } from './componentProps'
import type { RawSlots } from './componentSlots'
import { withAttrs } from './componentAttrs'
import { isString } from '@vue/shared'
import { fallbackComponent } from './componentFallback'
export function createComponent(
comp: Component | string,
rawProps: RawProps | null = null,
slots: RawSlots | null = null,
singleRoot: boolean = false,
once: boolean = false,
): ComponentInternalInstance | HTMLElement {
const current = currentInstance!
if (isString(comp)) {
return fallbackComponent(comp, rawProps, slots, current, singleRoot)
}
const instance = new ComponentInternalInstance(
comp,
singleRoot ? withAttrs(rawProps) : rawProps,
slots,
once,
)
if (singleRoot) {
instance.scopeIds.push(...current.scopeIds)
}
const scopeId = current.type.__scopeId
if (scopeId) {
instance.scopeIds.push(scopeId)
}
setupComponent(instance)
// register sub-component with current component for lifecycle management
current.comps.add(instance)
return instance
}

View File

@ -1,431 +0,0 @@
import {
type EffectScope,
type ShallowRef,
effectScope,
shallowRef,
} from '@vue/reactivity'
import { isArray, isObject, isString } from '@vue/shared'
import {
createComment,
createTextNode,
insert,
remove as removeBlock,
} from './dom/element'
import { type Block, type Fragment, fragmentKey } from './block'
import { warn } from './warning'
import { currentInstance, isVaporComponent } from './component'
import type { DynamicSlot } from './componentSlots'
import { renderEffect } from './renderEffect'
interface ForBlock extends Fragment {
scope: EffectScope
state: [
item: ShallowRef<any>,
key: ShallowRef<any>,
index: ShallowRef<number | undefined>,
]
key: any
}
type Source = any[] | Record<any, any> | number | Set<any> | Map<any, any>
/*! #__NO_SIDE_EFFECTS__ */
export const createFor = (
src: () => Source,
renderItem: (block: ForBlock['state']) => Block,
getKey?: (item: any, key: any, index?: number) => any,
container?: ParentNode,
hydrationNode?: Node,
once?: boolean,
): Fragment => {
let isMounted = false
let oldBlocks: ForBlock[] = []
let newBlocks: ForBlock[]
let parent: ParentNode | undefined | null
const parentAnchor = container
? undefined
: __DEV__
? createComment('for')
: createTextNode()
const ref: Fragment = {
nodes: oldBlocks,
[fragmentKey]: true,
}
const instance = currentInstance!
if (__DEV__ && !instance) {
warn('createFor() can only be used inside setup()')
}
once ? renderList() : renderEffect(renderList)
return ref
function renderList() {
const source = src()
const newLength = getLength(source)
const oldLength = oldBlocks.length
newBlocks = new Array(newLength)
if (!isMounted) {
isMounted = true
mountList(source)
} else {
parent = parent || container || parentAnchor!.parentNode
if (!oldLength) {
// fast path for all new
mountList(source)
} else if (!newLength) {
// fast path for all removed
if (container) {
container.textContent = ''
for (let i = 0; i < oldLength; i++) {
oldBlocks[i].scope.stop()
}
} else {
// fast path for clearing
for (let i = 0; i < oldLength; i++) {
unmount(oldBlocks[i])
}
}
} else if (!getKey) {
// unkeyed fast path
const commonLength = Math.min(newLength, oldLength)
for (let i = 0; i < commonLength; i++) {
const [item] = getItem(source, i)
update((newBlocks[i] = oldBlocks[i]), item)
}
mountList(source, oldLength)
for (let i = newLength; i < oldLength; i++) {
unmount(oldBlocks[i])
}
} else {
let i = 0
let e1 = oldLength - 1 // prev ending index
let e2 = newLength - 1 // next ending index
// 1. sync from start
// (a b) c
// (a b) d e
while (i <= e1 && i <= e2) {
if (tryPatchIndex(source, i)) {
i++
} else {
break
}
}
// 2. sync from end
// a (b c)
// d e (b c)
while (i <= e1 && i <= e2) {
if (tryPatchIndex(source, i)) {
e1--
e2--
} else {
break
}
}
// 3. common sequence + mount
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
if (i > e1) {
if (i <= e2) {
const nextPos = e2 + 1
const anchor =
nextPos < newLength
? normalizeAnchor(newBlocks[nextPos].nodes)
: parentAnchor
while (i <= e2) {
mount(source, i, anchor)
i++
}
}
}
// 4. common sequence + unmount
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1
// a (b c)
// (b c)
// i = 0, e1 = 0, e2 = -1
else if (i > e2) {
while (i <= e1) {
unmount(oldBlocks[i])
i++
}
}
// 5. unknown sequence
// [i ... e1 + 1]: a b [c d e] f g
// [i ... e2 + 1]: a b [e d c h] f g
// i = 2, e1 = 4, e2 = 5
else {
const s1 = i // prev starting index
const s2 = i // next starting index
// 5.1 build key:index map for newChildren
const keyToNewIndexMap = new Map()
for (i = s2; i <= e2; i++) {
keyToNewIndexMap.set(getKey(...getItem(source, i)), i)
}
// 5.2 loop through old children left to be patched and try to patch
// matching nodes & remove nodes that are no longer present
let j
let patched = 0
const toBePatched = e2 - s2 + 1
let moved = false
// used to track whether any node has moved
let maxNewIndexSoFar = 0
// works as Map<newIndex, oldIndex>
// Note that oldIndex is offset by +1
// and oldIndex = 0 is a special value indicating the new node has
// no corresponding old node.
// used for determining longest stable subsequence
const newIndexToOldIndexMap = new Array(toBePatched).fill(0)
for (i = s1; i <= e1; i++) {
const prevBlock = oldBlocks[i]
if (patched >= toBePatched) {
// all new children have been patched so this can only be a removal
unmount(prevBlock)
} else {
const newIndex = keyToNewIndexMap.get(prevBlock.key)
if (newIndex == null) {
unmount(prevBlock)
} else {
newIndexToOldIndexMap[newIndex - s2] = i + 1
if (newIndex >= maxNewIndexSoFar) {
maxNewIndexSoFar = newIndex
} else {
moved = true
}
update(
(newBlocks[newIndex] = prevBlock),
...getItem(source, newIndex),
)
patched++
}
}
}
// 5.3 move and mount
// generate longest stable subsequence only when nodes have moved
const increasingNewIndexSequence = moved
? getSequence(newIndexToOldIndexMap)
: []
j = increasingNewIndexSequence.length - 1
// looping backwards so that we can use last patched node as anchor
for (i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i
const anchor =
nextIndex + 1 < newLength
? normalizeAnchor(newBlocks[nextIndex + 1].nodes)
: parentAnchor
if (newIndexToOldIndexMap[i] === 0) {
// mount new
mount(source, nextIndex, anchor)
} else if (moved) {
// move if:
// There is no stable subsequence (e.g. a reverse)
// OR current node is not among the stable sequence
if (j < 0 || i !== increasingNewIndexSequence[j]) {
insert(newBlocks[nextIndex].nodes, parent!, anchor)
} else {
j--
}
}
}
}
}
}
ref.nodes = [(oldBlocks = newBlocks)]
if (parentAnchor) {
ref.nodes.push(parentAnchor)
}
}
function mount(
source: any,
idx: number,
anchor: Node | undefined = parentAnchor,
): ForBlock {
const scope = effectScope()
const [item, key, index] = getItem(source, idx)
const state = [
shallowRef(item),
shallowRef(key),
shallowRef(index),
] as ForBlock['state']
const block: ForBlock = (newBlocks[idx] = {
nodes: null!, // set later
scope,
state,
key: getKey && getKey(item, key, index),
[fragmentKey]: true,
})
block.nodes = scope.run(() => renderItem(state))!
if (parent) insert(block.nodes, parent, anchor)
return block
}
function mountList(source: any, offset = 0) {
for (let i = offset; i < getLength(source); i++) {
mount(source, i)
}
}
function tryPatchIndex(source: any, idx: number) {
const block = oldBlocks[idx]
const [item, key, index] = getItem(source, idx)
if (block.key === getKey!(item, key, index)) {
update((newBlocks[idx] = block), item)
return true
}
}
function update(
block: ForBlock,
newItem: any,
newKey = block.state[1].value,
newIndex = block.state[2].value,
) {
const [item, key, index] = block.state
let needsUpdate =
newItem !== item.value || newKey !== key.value || newIndex !== index.value
if (needsUpdate) updateState(block, newItem, newKey, newIndex)
}
function unmount({ nodes, scope }: ForBlock) {
removeBlock(nodes, parent!)
scope.stop()
}
}
function updateState(
block: ForBlock,
newItem: any,
newKey: any,
newIndex: number | undefined,
) {
const [item, key, index] = block.state
item.value = newItem
key.value = newKey
index.value = newIndex
}
export function createForSlots(
source: any[] | Record<any, any> | number | Set<any> | Map<any, any>,
getSlot: (item: any, key: any, index?: number) => DynamicSlot,
): DynamicSlot[] {
const sourceLength = getLength(source)
const slots = new Array<DynamicSlot>(sourceLength)
for (let i = 0; i < sourceLength; i++) {
const [item, key, index] = getItem(source, i)
slots[i] = getSlot(item, key, index)
}
return slots
}
function getLength(source: any): number {
if (isArray(source) || isString(source)) {
return source.length
} else if (typeof source === 'number') {
if (__DEV__ && !Number.isInteger(source)) {
warn(`The v-for range expect an integer value but got ${source}.`)
}
return source
} else if (isObject(source)) {
if (source[Symbol.iterator as any]) {
return Array.from(source as Iterable<any>).length
} else {
return Object.keys(source).length
}
}
return 0
}
function getItem(
source: any,
idx: number,
): [item: any, key: any, index?: number] {
if (isArray(source) || isString(source)) {
return [source[idx], idx, undefined]
} else if (typeof source === 'number') {
return [idx + 1, idx, undefined]
} else if (isObject(source)) {
if (source[Symbol.iterator as any]) {
source = Array.from(source as Iterable<any>)
return [source[idx], idx, undefined]
} else {
const key = Object.keys(source)[idx]
return [source[key], key, idx]
}
}
return null!
}
function normalizeAnchor(node: Block): Node {
if (node instanceof Node) {
return node
} else if (isArray(node)) {
return normalizeAnchor(node[0])
} else if (isVaporComponent(node)) {
return normalizeAnchor(node.block!)
} else {
return normalizeAnchor(node.nodes!)
}
}
// https://en.wikipedia.org/wiki/Longest_increasing_subsequence
function getSequence(arr: number[]): number[] {
const p = arr.slice()
const result = [0]
let i, j, u, v, c
const len = arr.length
for (i = 0; i < len; i++) {
const arrI = arr[i]
if (arrI !== 0) {
j = result[result.length - 1]
if (arr[j] < arrI) {
p[i] = j
result.push(i)
continue
}
u = 0
v = result.length - 1
while (u < v) {
c = (u + v) >> 1
if (arr[result[c]] < arrI) {
u = c + 1
} else {
v = c
}
}
if (arrI < arr[result[u]]) {
if (u > 0) {
p[i] = result[u - 1]
}
result[u] = i
}
}
}
u = result.length
v = result[u - 1]
while (u-- > 0) {
result[u] = v
v = p[v]
}
return result
}

View File

@ -1,83 +0,0 @@
import { renderEffect } from './renderEffect'
import { type Block, type Fragment, fragmentKey } from './block'
import { type EffectScope, effectScope, shallowReactive } from '@vue/reactivity'
import { createComment, createTextNode, insert, remove } from './dom/element'
type BlockFn = () => Block
/*! #__NO_SIDE_EFFECTS__ */
export function createBranch(
expression: () => any,
render: (value: any) => BlockFn | undefined,
once?: boolean,
commentLabel?: string,
// hydrationNode?: Node,
): Fragment {
let newValue: any
let oldValue: any
let branch: BlockFn | undefined
let block: Block | undefined
let scope: EffectScope | undefined
const anchor = __DEV__
? createComment(commentLabel || 'dynamic')
: createTextNode()
const fragment: Fragment = shallowReactive({
nodes: [],
anchor,
[fragmentKey]: true,
})
// TODO: SSR
// if (isHydrating) {
// parent = hydrationNode!.parentNode
// setCurrentHydrationNode(hydrationNode!)
// }
if (once) {
doChange()
} else {
renderEffect(() => doChange())
}
// TODO: SSR
// if (isHydrating) {
// parent!.insertBefore(anchor, currentHydrationNode)
// }
return fragment
function doChange() {
if ((newValue = expression()) !== oldValue) {
const parent = anchor.parentNode
if (block) {
scope!.stop()
remove(block, parent!)
}
oldValue = newValue
if ((branch = render(newValue))) {
scope = effectScope()
fragment.nodes = block = scope.run(branch)!
parent && insert(block, parent, anchor)
} else {
scope = block = undefined
fragment.nodes = []
}
}
}
}
/*! #__NO_SIDE_EFFECTS__ */
export function createIf(
condition: () => any,
b1: BlockFn,
b2?: BlockFn,
once?: boolean,
// hydrationNode?: Node,
): Fragment {
return createBranch(
() => !!condition(),
value => (value ? b1 : b2),
once,
__DEV__ ? 'if' : undefined,
)
}

View File

@ -1,42 +0,0 @@
import {
type MaybeRefOrGetter,
type ShallowRef,
onScopeDispose,
shallowRef,
toValue,
} from '@vue/reactivity'
import { watchEffect } from './apiWatch'
export function createSelector<T, U extends T>(
source: MaybeRefOrGetter<T>,
fn: (key: U, value: T) => boolean = (key, value) => key === value,
): (key: U) => boolean {
let subs = new Map()
let val: T
let oldVal: U
watchEffect(() => {
val = toValue(source)
const keys = [...subs.keys()]
for (let i = 0, len = keys.length; i < len; i++) {
const key = keys[i]
if (fn(key, val)) {
const o = subs.get(key)
o.value = true
} else if (oldVal !== undefined && fn(key, oldVal)) {
const o = subs.get(key)
o.value = false
}
}
oldVal = val as U
})
return key => {
let l: ShallowRef<boolean | undefined> & { _count?: number }
if (!(l = subs.get(key))) subs.set(key, (l = shallowRef()))
l.value
l._count ? l._count++ : (l._count = 1)
onScopeDispose(() => (l._count! > 1 ? l._count!-- : subs.delete(key)))
return l.value !== undefined ? l.value : fn(key, val)
}
}

View File

@ -1,326 +0,0 @@
import { NO, getGlobalThis, isFunction, isObject } from '@vue/shared'
import {
type Component,
ComponentInternalInstance,
validateComponentName,
} from './component'
import { warn } from './warning'
import { version } from '.'
import {
normalizeContainer,
render,
setupComponent,
unmountComponent,
} from './apiRender'
import type { InjectionKey } from './apiInject'
import type { RawProps } from './componentProps'
import { type Directive, validateDirectiveName } from './directives'
import { devtoolsInitApp, setDevtoolsHook } from './devtools'
let uid = 0
export function createVaporApp(
rootComponent: Component,
rootProps: RawProps | null = null,
): App {
if (rootProps != null && !isObject(rootProps) && !isFunction(rootProps)) {
__DEV__ &&
warn(`root props passed to app.mount() must be an object or function.`)
rootProps = null
}
const target = getGlobalThis()
target.__VUE__ = true
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__, target)
}
const context = createAppContext()
const installedPlugins = new WeakSet()
let instance: ComponentInternalInstance
const app: App = (context.app = {
_uid: uid++,
_component: rootComponent,
_props: rootProps,
_container: null,
_context: context,
_instance: null,
version,
get config() {
return context.config
},
set config(v) {
if (__DEV__) {
warn(
`app.config cannot be replaced. Modify individual options instead.`,
)
}
},
use(plugin: Plugin, ...options: any[]) {
if (installedPlugins.has(plugin)) {
__DEV__ && warn(`Plugin has already been applied to target app.`)
} else if (plugin && isFunction(plugin.install)) {
installedPlugins.add(plugin)
plugin.install(app, ...options)
} else if (isFunction(plugin)) {
installedPlugins.add(plugin)
plugin(app, ...options)
} else if (__DEV__) {
warn(
`A plugin must either be a function or an object with an "install" ` +
`function.`,
)
}
return app
},
component(name: string, component?: Component): any {
if (__DEV__) {
validateComponentName(name, context.config)
}
if (!component) {
return context.components[name]
}
if (__DEV__ && context.components[name]) {
warn(`Component "${name}" has already been registered in target app.`)
}
context.components[name] = component
return app
},
directive(name: string, directive?: Directive) {
if (__DEV__) {
validateDirectiveName(name)
}
if (!directive) {
return context.directives[name] as any
}
if (__DEV__ && context.directives[name]) {
warn(`Directive "${name}" has already been registered in target app.`)
}
context.directives[name] = directive
return app
},
mount(container): any {
if (!instance) {
container = normalizeContainer(container)
// #5571
if (__DEV__ && (container as any).__vue_app__) {
warn(
`There is already an app instance mounted on the host container.\n` +
` If you want to mount another app on the same host container,` +
` you need to unmount the previous app by calling \`app.unmount()\` first.`,
)
}
// clear content before mounting
if (container.nodeType === 1 /* Node.ELEMENT_NODE */) {
container.textContent = ''
}
instance = new ComponentInternalInstance(
rootComponent,
rootProps,
null,
false,
context,
)
setupComponent(instance)
render(instance, container)
app._container = container
// for devtools and telemetry
;(container as any).__vue_app__ = app
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = instance
devtoolsInitApp(app, version)
}
if (container instanceof Element) {
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
}
return instance
} else if (__DEV__) {
warn(
`App has already been mounted.\n` +
`If you want to remount the same app, move your app creation logic ` +
`into a factory function and create fresh app instances for each ` +
`mount - e.g. \`const createMyApp = () => createApp(App)\``,
)
}
},
unmount() {
if (instance) {
unmountComponent(instance)
delete (app._container as any).__vue_app__
} else if (__DEV__) {
warn(`Cannot unmount an app that is not mounted.`)
}
},
provide(key, value) {
if (__DEV__ && (key as string | symbol) in context.provides) {
warn(
`App already provides property with key "${String(key)}". ` +
`It will be overwritten with the new value.`,
)
}
context.provides[key as string | symbol] = value
return app
},
runWithContext(fn) {
const lastApp = currentApp
currentApp = app
try {
return fn()
} finally {
currentApp = lastApp
}
},
})
return app
}
export function createAppContext(): AppContext {
return {
app: null as any,
mixins: [],
config: {
isNativeTag: NO,
performance: false,
errorHandler: undefined,
warnHandler: undefined,
globalProperties: {},
},
provides: Object.create(null),
components: {},
directives: {},
}
}
type PluginInstallFunction<Options = any[]> = Options extends unknown[]
? (app: App, ...options: Options) => any
: (app: App, options: Options) => any
export type ObjectPlugin<Options = any[]> = {
install: PluginInstallFunction<Options>
}
export type FunctionPlugin<Options = any[]> = PluginInstallFunction<Options> &
Partial<ObjectPlugin<Options>>
export type Plugin<Options = any[]> =
| FunctionPlugin<Options>
| ObjectPlugin<Options>
export interface App {
version: string
config: AppConfig
use<Options extends unknown[]>(
plugin: Plugin<Options>,
...options: Options
): this
use<Options>(plugin: Plugin<Options>, options: Options): this
component(name: string): Component | undefined
component<T extends Component>(name: string, component: T): this
directive<T = any, V = any>(name: string): Directive<T, V> | undefined
directive<T = any, V = any>(name: string, directive: Directive<T, V>): this
mount(
rootContainer: ParentNode | string,
isHydrate?: boolean,
): ComponentInternalInstance
unmount(): void
provide<T>(key: string | InjectionKey<T>, value: T): App
runWithContext<T>(fn: () => T): T
// internal, but we need to expose these for the server-renderer and devtools
_uid: number
_component: Component
_props: RawProps | null
_container: ParentNode | null
_context: AppContext
_instance: ComponentInternalInstance | null
}
export interface AppConfig {
// @private
readonly isNativeTag: (tag: string) => boolean
performance: boolean
errorHandler?: (
err: unknown,
instance: ComponentInternalInstance | null,
info: string,
) => void
warnHandler?: (
msg: string,
instance: ComponentInternalInstance | null,
trace: string,
) => void
globalProperties: ComponentCustomProperties & Record<string, any>
}
export interface AppContext {
app: App // for devtools
config: AppConfig
mixins: never[] // for devtools, but no longer supported
provides: Record<string | symbol, any>
/**
* Resolved component registry, only for components with mixins or extends
* @internal
*/
components: Record<string, Component>
/**
* Resolved directive registry, only for components with mixins or extends
* @internal
*/
directives: Record<string, Directive>
}
/**
* @internal Used to identify the current app when using `inject()` within
* `app.runWithContext()`.
*/
export let currentApp: App | null = null
/**
* Custom properties added to component instances in any way and can be accessed through `this`
*
* @example
* Here is an example of adding a property `$router` to every component instance:
* ```ts
* import { createApp } from 'vue'
* import { Router, createRouter } from 'vue-router'
*
* declare module '@vue/runtime-core' {
* interface ComponentCustomProperties {
* $router: Router
* }
* }
*
* // effectively adding the router to every component instance
* const app = createApp({})
* const router = createRouter()
* app.config.globalProperties.$router = router
*
* const vm = app.mount('#app')
* // we can access the router from the instance
* vm.$router.push('/')
* ```
*/
export interface ComponentCustomProperties {}

View File

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

View File

@ -1,84 +0,0 @@
import { isFunction } from '@vue/shared'
import { currentInstance } from './component'
import { currentApp } from './apiCreateVaporApp'
import { warn } from './warning'
export interface InjectionKey<T> extends Symbol {}
export function provide<T, K = InjectionKey<T> | string | number>(
key: K,
value: K extends InjectionKey<infer V> ? V : T,
): void {
if (!currentInstance) {
if (__DEV__) {
warn(`provide() can only be used inside setup().`)
}
} else {
let provides = currentInstance.provides
// by default an instance inherits its parent's provides object
// but when it needs to provide values of its own, it creates its
// own provides object using parent provides object as prototype.
// this way in `inject` we can simply look up injections from direct
// parent and let the prototype chain do the work.
const parentProvides =
currentInstance.parent && currentInstance.parent.provides
if (parentProvides === provides) {
provides = currentInstance.provides = Object.create(parentProvides)
}
// TS doesn't allow symbol as index type
provides[key as string] = value
}
}
export function inject<T>(key: InjectionKey<T> | string): T | undefined
export function inject<T>(
key: InjectionKey<T> | string,
defaultValue: T,
treatDefaultAsFactory?: false,
): T
export function inject<T>(
key: InjectionKey<T> | string,
defaultValue: T | (() => T),
treatDefaultAsFactory: true,
): T
export function inject(
key: InjectionKey<any> | string,
defaultValue?: unknown,
treatDefaultAsFactory = false,
) {
const instance = currentInstance
// also support looking up from app-level provides w/ `app.runWithContext()`
if (instance || currentApp) {
// #2400
// to support `app.use` plugins,
// fallback to appContext's `provides` if the instance is at root
const provides = instance
? instance.parent == null
? instance.appContext && instance.appContext.provides
: instance.parent.provides
: currentApp!._context.provides
if (provides && (key as string | symbol) in provides) {
// TS doesn't allow symbol as index type
return provides[key as string]
} else if (arguments.length > 1) {
return treatDefaultAsFactory && isFunction(defaultValue)
? defaultValue.call(instance && instance)
: defaultValue
} else if (__DEV__) {
warn(`injection "${String(key)}" not found.`)
}
} else if (__DEV__) {
warn(`inject() can only be used inside setup() or functional components.`)
}
}
/**
* Returns true if `inject()` can be used without warning about being called in the wrong place (e.g. outside of
* setup()). This is used by libraries that want to use `inject()` internally without triggering a warning to the end
* user. One example is `useRoute()` in `vue-router`.
*/
export function hasInjectionContext(): boolean {
return !!(currentInstance || currentApp)
}

View File

@ -1,99 +0,0 @@
import {
type ComponentInternalInstance,
currentInstance,
setCurrentInstance,
} from './component'
import { warn } from './warning'
import {
type DebuggerEvent,
pauseTracking,
resetTracking,
} from '@vue/reactivity'
import { ErrorTypeStrings, callWithAsyncErrorHandling } from './errorHandling'
import { toHandlerKey } from '@vue/shared'
import { VaporLifecycleHooks } from './enums'
const injectHook = (
type: VaporLifecycleHooks,
hook: Function & { __weh?: Function },
target: ComponentInternalInstance | null = currentInstance,
prepend: boolean = false,
) => {
if (target) {
const hooks = target[type] || (target[type] = [])
const wrappedHook =
hook.__weh ||
(hook.__weh = (...args: unknown[]) => {
if (target.isUnmounted) {
return
}
pauseTracking()
const reset = setCurrentInstance(target)
const res = target.scope.run(() =>
callWithAsyncErrorHandling(hook, target, type, args),
)
reset()
resetTracking()
return res
})
if (prepend) {
hooks.unshift(wrappedHook)
} else {
hooks.push(wrappedHook)
}
return wrappedHook
} else if (__DEV__) {
const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, ''))
warn(
`${apiName} is called when there is no active component instance to be ` +
`associated with. ` +
`Lifecycle injection APIs can only be used during execution of setup().` +
(__FEATURE_SUSPENSE__
? ` If you are using async setup(), make sure to register lifecycle ` +
`hooks before the first await statement.`
: ``),
)
}
}
const createHook =
<T extends Function = () => any>(lifecycle: VaporLifecycleHooks) =>
(hook: T, target: ComponentInternalInstance | null = currentInstance) =>
injectHook(lifecycle, (...args: unknown[]) => hook(...args), target)
type CreateHook<T = any> = (
hook: T,
target?: ComponentInternalInstance | null,
) => void
export const onBeforeMount: CreateHook = createHook(
VaporLifecycleHooks.BEFORE_MOUNT,
)
export const onMounted: CreateHook = createHook(VaporLifecycleHooks.MOUNTED)
export const onBeforeUpdate: CreateHook = createHook(
VaporLifecycleHooks.BEFORE_UPDATE,
)
export const onUpdated: CreateHook = createHook(VaporLifecycleHooks.UPDATED)
export const onBeforeUnmount: CreateHook = createHook(
VaporLifecycleHooks.BEFORE_UNMOUNT,
)
export const onUnmounted: CreateHook = createHook(VaporLifecycleHooks.UNMOUNTED)
export type DebuggerHook = (e: DebuggerEvent) => void
export const onRenderTriggered: CreateHook = createHook<DebuggerHook>(
VaporLifecycleHooks.RENDER_TRIGGERED,
)
export const onRenderTracked: CreateHook = createHook<DebuggerHook>(
VaporLifecycleHooks.RENDER_TRACKED,
)
export type ErrorCapturedHook<TError = unknown> = (
err: TError,
instance: ComponentInternalInstance | null,
info: string,
) => boolean | void
export function onErrorCaptured<TError = Error>(
hook: ErrorCapturedHook<TError>,
target: ComponentInternalInstance | null = currentInstance,
): void {
injectHook(VaporLifecycleHooks.ERROR_CAPTURED, hook, target)
}

View File

@ -1,181 +0,0 @@
import {
type ComponentInternalInstance,
createSetupContext,
getAttrsProxy,
getSlotsProxy,
isVaporComponent,
setCurrentInstance,
validateComponentName,
} from './component'
import { insert, querySelector } from './dom/element'
import { flushPostFlushCbs, queuePostFlushCb } from './scheduler'
import { invokeLifecycle } from './componentLifecycle'
import { VaporLifecycleHooks } from './enums'
import {
pauseTracking,
proxyRefs,
resetTracking,
shallowReadonly,
} from '@vue/reactivity'
import { isArray, isFunction, isObject } from '@vue/shared'
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
import { endMeasure, startMeasure } from './profiling'
import { devtoolsComponentAdded } from './devtools'
import { fallThroughAttrs } from './componentAttrs'
import { type Block, findFirstRootElement, fragmentKey } from './block'
export function setupComponent(instance: ComponentInternalInstance): void {
if (__DEV__) {
startMeasure(instance, `init`)
}
const reset = setCurrentInstance(instance)
instance.scope.run(function componentSetupFn() {
const { type: component, props } = instance
if (__DEV__) {
if (component.name) {
validateComponentName(component.name, instance.appContext.config)
}
}
const setupFn = isFunction(component) ? component : component.setup
let stateOrNode: Block | undefined
if (setupFn) {
const setupContext = (instance.setupContext =
setupFn && setupFn.length > 1 ? createSetupContext(instance) : null)
pauseTracking()
stateOrNode = callWithErrorHandling(
setupFn,
instance,
VaporErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(props) : props, setupContext],
)
resetTracking()
}
let block: Block | undefined
// Skip the type check for production since this is only for Dev HMR
if (__DEV__) {
if (
stateOrNode &&
(stateOrNode instanceof Node ||
isVaporComponent(stateOrNode) ||
isArray(stateOrNode) ||
fragmentKey in stateOrNode)
) {
block = stateOrNode
} else if (isObject(stateOrNode)) {
instance.setupState = proxyRefs(stateOrNode)
}
if (!block && component.render) {
pauseTracking()
block = callWithErrorHandling(
component.render,
instance,
VaporErrorCodes.RENDER_FUNCTION,
[
instance.setupState, // _ctx
shallowReadonly(props), // $props
instance.emit, // $emit
getAttrsProxy(instance), // $attrs
getSlotsProxy(instance), // $slots
],
)
resetTracking()
}
} else {
block = stateOrNode
}
if (!block) {
// TODO: warn no template
block = []
}
instance.block = block
const rootElement = findFirstRootElement(instance)
if (rootElement) {
fallThroughAttrs(instance, rootElement)
// attach scopeId
for (const id of instance.scopeIds) {
rootElement.setAttribute(id, '')
}
}
return block
})
reset()
if (__DEV__) {
endMeasure(instance, `init`)
}
}
export function render(
instance: ComponentInternalInstance,
container: string | ParentNode,
): void {
mountComponent(instance, (container = normalizeContainer(container)))
flushPostFlushCbs()
}
export function normalizeContainer(container: string | ParentNode): ParentNode {
return typeof container === 'string'
? (querySelector(container) as ParentNode)
: container
}
function mountComponent(
instance: ComponentInternalInstance,
container: ParentNode,
) {
instance.container = container
if (__DEV__) {
startMeasure(instance, 'mount')
}
// hook: beforeMount
invokeLifecycle(instance, VaporLifecycleHooks.BEFORE_MOUNT)
insert(instance.block!, instance.container)
// hook: mounted
invokeLifecycle(
instance,
VaporLifecycleHooks.MOUNTED,
instance => (instance.isMounted = true),
true,
)
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsComponentAdded(instance)
}
if (__DEV__) {
endMeasure(instance, 'mount')
}
return instance
}
export function unmountComponent(instance: ComponentInternalInstance): void {
const { container, scope } = instance
// hook: beforeUnmount
invokeLifecycle(instance, VaporLifecycleHooks.BEFORE_UNMOUNT)
scope.stop()
container.textContent = ''
// hook: unmounted
invokeLifecycle(
instance,
VaporLifecycleHooks.UNMOUNTED,
instance => queuePostFlushCb(() => (instance.isUnmounted = true)),
true,
)
flushPostFlushCbs()
}

View File

@ -1,22 +0,0 @@
import {
type SetupContext,
createSetupContext,
getCurrentInstance,
} from './component'
import { warn } from './warning'
export function useSlots(): SetupContext['slots'] {
return getContext().slots
}
export function useAttrs(): SetupContext['attrs'] {
return getContext().attrs
}
function getContext(): SetupContext {
const i = getCurrentInstance()!
if (__DEV__ && !i) {
warn(`useContext() called without active instance.`)
}
return i.setupContext || (i.setupContext = createSetupContext(i))
}

View File

@ -1,230 +0,0 @@
import {
type WatchOptions as BaseWatchOptions,
type ComputedRef,
type DebuggerOptions,
type Ref,
watch as baseWatch,
} from '@vue/reactivity'
import { EMPTY_OBJ, extend, isFunction } from '@vue/shared'
import { currentInstance } from './component'
import {
type SchedulerJob,
VaporSchedulerJobFlags,
queueJob,
queuePostFlushCb,
} from './scheduler'
import { callWithAsyncErrorHandling } from './errorHandling'
import { warn } from './warning'
export type WatchEffect = (onCleanup: OnCleanup) => void
export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
export type WatchCallback<V = any, OV = any> = (
value: V,
oldValue: OV,
onCleanup: OnCleanup,
) => any
type MapSources<T, Immediate> = {
[K in keyof T]: T[K] extends WatchSource<infer V>
? Immediate extends true
? V | undefined
: V
: T[K] extends object
? Immediate extends true
? T[K] | undefined
: T[K]
: never
}
type OnCleanup = (cleanupFn: () => void) => void
export interface WatchOptionsBase extends DebuggerOptions {
flush?: 'pre' | 'post' | 'sync'
}
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
immediate?: Immediate
deep?: boolean
once?: boolean
}
export type WatchStopHandle = () => void
// Simple effect.
export function watchEffect(
effect: WatchEffect,
options?: WatchOptionsBase,
): WatchStopHandle {
return doWatch(effect, null, options)
}
export function watchPostEffect(
effect: WatchEffect,
options?: DebuggerOptions,
): WatchStopHandle {
return doWatch(
effect,
null,
__DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
)
}
export function watchSyncEffect(
effect: WatchEffect,
options?: DebuggerOptions,
): WatchStopHandle {
return doWatch(
effect,
null,
__DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' },
)
}
type MultiWatchSources = (WatchSource<unknown> | object)[]
// overload: single source + cb
export function watch<T, Immediate extends Readonly<boolean> = false>(
source: WatchSource<T>,
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
// overload: array of multiple sources + cb
export function watch<
T extends MultiWatchSources,
Immediate extends Readonly<boolean> = false,
>(
sources: [...T],
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
// overload: multiple sources w/ `as const`
// watch([foo, bar] as const, () => {})
// somehow [...T] breaks when the type is readonly
export function watch<
T extends Readonly<MultiWatchSources>,
Immediate extends Readonly<boolean> = false,
>(
source: T,
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
// overload: watching reactive object w/ cb
export function watch<
T extends object,
Immediate extends Readonly<boolean> = false,
>(
source: T,
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
options?: WatchOptions<Immediate>,
): WatchStopHandle
// implementation
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
source: T | WatchSource<T>,
cb: any,
options?: WatchOptions<Immediate>,
): WatchStopHandle {
if (__DEV__ && !isFunction(cb)) {
warn(
`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
`Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
`supports \`watch(source, cb, options?) signature.`,
)
}
return doWatch(source as any, cb, options)
}
function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
options: WatchOptions = EMPTY_OBJ,
): WatchStopHandle {
const { immediate, deep, flush, once } = options
if (__DEV__ && !cb) {
if (immediate !== undefined) {
warn(
`watch() "immediate" option is only respected when using the ` +
`watch(source, callback, options?) signature.`,
)
}
if (deep !== undefined) {
warn(
`watch() "deep" option is only respected when using the ` +
`watch(source, callback, options?) signature.`,
)
}
if (once !== undefined) {
warn(
`watch() "once" option is only respected when using the ` +
`watch(source, callback, options?) signature.`,
)
}
}
const baseWatchOptions: BaseWatchOptions = extend({}, options)
if (__DEV__) baseWatchOptions.onWarn = warn
let ssrCleanup: (() => void)[] | undefined
// TODO: SSR
// if (__SSR__ && isInSSRComponentSetup) {
// if (flush === 'sync') {
// const ctx = useSSRContext()!
// ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
// } else if (!cb || immediate) {
// // immediately watch or watchEffect
// extendOptions.once = true
// } else {
// // watch(source, cb)
// return NOOP
// }
// }
const instance = currentInstance
baseWatchOptions.call = (fn, type, args) =>
callWithAsyncErrorHandling(fn, instance, type, args)
// scheduler
let isPre = false
if (flush === 'post') {
baseWatchOptions.scheduler = job => {
queuePostFlushCb(job)
}
} else if (flush !== 'sync') {
// default: 'pre'
isPre = true
baseWatchOptions.scheduler = (job, isFirstRun) => {
if (isFirstRun) {
job()
} else {
queueJob(job)
}
}
}
baseWatchOptions.augmentJob = (job: SchedulerJob) => {
// important: mark the job as a watcher callback so that scheduler knows
// it is allowed to self-trigger (#1727)
if (cb) {
job.flags! |= VaporSchedulerJobFlags.ALLOW_RECURSE
}
if (isPre) {
job.flags! |= VaporSchedulerJobFlags.PRE
if (instance) {
job.id = instance.uid
;(job as SchedulerJob).i = instance
}
}
}
const watchHandle = baseWatch(source, cb, baseWatchOptions)
if (__SSR__ && ssrCleanup) ssrCleanup.push(watchHandle)
return watchHandle
}

View File

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

View File

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

View File

@ -1,142 +0,0 @@
import { camelize, isArray, normalizeClass, normalizeStyle } from '@vue/shared'
import { type ComponentInternalInstance, currentInstance } from './component'
import { isEmitListener } from './componentEmits'
import { type RawProps, walkRawProps } from './componentProps'
import { renderEffect } from './renderEffect'
import { mergeProp, setDynamicProp } from './dom/prop'
export function patchAttrs(
instance: ComponentInternalInstance,
hasDynamicProps?: boolean,
): void {
const {
attrs,
rawProps,
propsOptions: [options],
} = instance
if (!rawProps.length) return
const keys = new Set<string>()
const classes: any[] = []
const styles: any[] = []
walkRawProps(rawProps, registerAttr)
for (const key in attrs) {
if (!keys.has(key)) {
delete attrs[key]
}
}
setClassOrStyle(classes, 'class', normalizeClass)
setClassOrStyle(styles, 'style', normalizeStyle)
function setClassOrStyle(
values: any[],
field: 'class' | 'style',
normalize: (value: any) => any,
) {
if (values.length) {
if (hasDynamicProps) {
Object.defineProperty(attrs, field, {
get() {
return normalize(values.map(value => value()))
},
enumerable: true,
configurable: true,
})
} else {
attrs[field] = normalizeClass(values)
}
}
}
function registerAttr(key: string, value: any, getter?: boolean) {
if (
(!options || !(camelize(key) in options)) &&
!isEmitListener(instance.emitsOptions, key) &&
(key === 'class' || key === 'style' || !keys.has(key))
) {
keys.add(key)
if (key === 'class' || key === 'style') {
;(key === 'class' ? classes : styles).push(
hasDynamicProps
? getter
? value
: () => value
: getter
? value()
: value,
)
} else if (getter) {
Object.defineProperty(attrs, key, {
get: value,
enumerable: true,
configurable: true,
})
} else {
attrs[key] = value
}
}
}
}
export function withAttrs(props: RawProps): RawProps {
const instance = currentInstance!
if (instance.type.inheritAttrs === false) return props
const attrsGetter = () => instance.attrs
if (!props) return [attrsGetter]
if (isArray(props)) {
return [attrsGetter, ...props]
}
return [attrsGetter, props]
}
export function fallThroughAttrs(
instance: ComponentInternalInstance,
element: Element,
): void {
const {
type: { inheritAttrs },
dynamicAttrs,
} = instance
if (
inheritAttrs === false ||
dynamicAttrs === true // all props as dynamic
)
return
const hasStaticAttrs = dynamicAttrs || dynamicAttrs === false
let initial: Record<string, string> | undefined
if (hasStaticAttrs) {
// attrs in static template
initial = {}
for (let i = 0; i < element.attributes.length; i++) {
const attr = element.attributes[i]
if (dynamicAttrs && dynamicAttrs.includes(attr.name)) continue
initial[attr.name] = attr.value
}
}
renderEffect(() => {
for (const key in instance.attrs) {
if (dynamicAttrs && dynamicAttrs.includes(key)) continue
let value: unknown
if (hasStaticAttrs && key in initial!) {
value = mergeProp(key, instance.attrs[key], initial![key])
} else {
value = instance.attrs[key]
}
setDynamicProp(element, key, value)
}
})
}
export function setInheritAttrs(dynamicAttrs?: string[] | boolean): void {
const instance = currentInstance!
if (instance.type.inheritAttrs === false) return
instance.dynamicAttrs = dynamicAttrs
}

View File

@ -1,185 +1,70 @@
import { import {
type UnionToIntersection, type EmitFn,
camelize, type ObjectEmitsOptions,
extend, baseEmit,
hasOwn, } from '@vue/runtime-core'
hyphenate, import {
isArray, type VaporComponent,
isFunction, type VaporComponentInstance,
isOn, currentInstance,
isString, } from './component'
looseToNumber, import { EMPTY_OBJ, NOOP, hasOwn, isArray } from '@vue/shared'
toHandlerKey, import { resolveSource } from './componentProps'
} from '@vue/shared'
import type { Component, ComponentInternalInstance } from './component'
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
import { type StaticProps, getDynamicPropValue } from './componentProps'
import { warn } from './warning'
export type ObjectEmitsOptions = Record<
string,
((...args: any[]) => any) | null // TODO: call validation?
>
export type EmitsOptions = ObjectEmitsOptions | string[]
export type EmitFn<
Options = ObjectEmitsOptions,
Event extends keyof Options = keyof Options,
> =
Options extends Array<infer V>
? (event: V, ...args: any[]) => void
: {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function
? (event: string, ...args: any[]) => void
: UnionToIntersection<
{
[key in Event]: Options[key] extends (...args: infer Args) => any
? (event: key, ...args: Args) => void
: (event: key, ...args: any[]) => void
}[Event]
>
export function emit(
instance: ComponentInternalInstance,
event: string,
...rawArgs: any[]
): void {
if (instance.isUnmounted) return
if (__DEV__) {
const {
emitsOptions,
propsOptions: [propsOptions],
} = instance
if (emitsOptions) {
if (!(event in emitsOptions)) {
if (!propsOptions || !(toHandlerKey(event) in propsOptions)) {
warn(
`Component emitted event "${event}" but it is neither declared in ` +
`the emits option nor as an "${toHandlerKey(event)}" prop.`,
)
}
} else {
const validator = emitsOptions[event]
if (isFunction(validator)) {
const isValid = validator(...rawArgs)
if (!isValid) {
warn(
`Invalid event arguments: event validation failed for event "${event}".`,
)
}
}
}
}
}
const { rawProps } = instance
const hasDynamicProps = rawProps.some(isFunction)
let handlerName: string
let handler: any
let onceHandler: any
const isModelListener = event.startsWith('update:')
const modelArg = isModelListener && event.slice(7)
let modifiers: any
// has v-bind or :[eventName]
if (hasDynamicProps) {
tryGet(key => getDynamicPropValue(rawProps, key)[0])
} else {
const staticProps = rawProps[0] as StaticProps
tryGet(key => staticProps[key] && staticProps[key]())
}
function tryGet(getter: (key: string) => any) {
handler =
getter((handlerName = toHandlerKey(event))) ||
// also try camelCase event handler (#2249)
getter((handlerName = toHandlerKey(camelize(event))))
// for v-model update:xxx events, also trigger kebab-case equivalent
// for props passed via kebab-case
if (!handler && isModelListener) {
handler = getter((handlerName = toHandlerKey(hyphenate(event))))
}
onceHandler = getter(`${handlerName}Once`)
modifiers =
modelArg &&
getter(`${modelArg === 'modelValue' ? 'model' : modelArg}Modifiers`)
}
// for v-model update:xxx events, apply modifiers on args
let args = rawArgs
if (modifiers) {
const { number, trim } = modifiers
if (trim) {
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
}
if (number) {
args = rawArgs.map(looseToNumber)
}
}
// TODO: warn
if (handler) {
callWithAsyncErrorHandling(
handler,
instance,
VaporErrorCodes.COMPONENT_EVENT_HANDLER,
args,
)
}
if (onceHandler) {
if (!instance.emitted) {
instance.emitted = {}
} else if (instance.emitted[handlerName!]) {
return
}
instance.emitted[handlerName!] = true
callWithAsyncErrorHandling(
onceHandler,
instance,
VaporErrorCodes.COMPONENT_EVENT_HANDLER,
args,
)
}
}
/**
* The logic from core isn't too reusable so it's better to duplicate here
*/
export function normalizeEmitsOptions( export function normalizeEmitsOptions(
comp: Component, comp: VaporComponent,
): ObjectEmitsOptions | null { ): ObjectEmitsOptions | null {
// TODO: caching? const cached = comp.__emitsOptions
if (cached) return cached
const raw = comp.emits const raw = comp.emits
if (!raw) return null if (!raw) return null
let normalized: ObjectEmitsOptions = {} let normalized: ObjectEmitsOptions
if (isArray(raw)) { if (isArray(raw)) {
raw.forEach(key => (normalized[key] = null)) normalized = {}
for (const key in raw) normalized[key] = null
} else { } else {
extend(normalized, raw) normalized = raw
} }
return normalized return (comp.__emitsOptions = normalized)
} }
// Check if an incoming prop key is a declared emit event listener. export function useEmit(): EmitFn {
// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are if (!currentInstance) {
// both considered matched listeners. // TODO warn
export function isEmitListener( return NOOP
options: ObjectEmitsOptions | null, } else {
key: string, return emit.bind(null, currentInstance)
): boolean { }
if (!options || !isOn(key)) {
return false
} }
key = key.slice(2).replace(/Once$/, '') export function emit(
return ( instance: VaporComponentInstance,
hasOwn(options, key[0].toLowerCase() + key.slice(1)) || event: string,
hasOwn(options, hyphenate(key)) || ...rawArgs: any[]
hasOwn(options, key) ): void {
baseEmit(
instance,
instance.rawProps || EMPTY_OBJ,
propGetter,
event,
...rawArgs,
) )
} }
function propGetter(rawProps: Record<string, any>, key: string) {
const dynamicSources = rawProps.$
if (dynamicSources) {
let i = dynamicSources.length
while (i--) {
const source = resolveSource(dynamicSources[i])
if (hasOwn(source, key)) return source[key]
}
}
return rawProps[key] && resolveSource(rawProps[key])
}

View File

@ -1,69 +0,0 @@
import type { ComponentInternalInstance } from './component'
import {
type NormalizedRawProps,
type RawProps,
normalizeRawProps,
walkRawProps,
} from './componentProps'
import { type RawSlots, isDynamicSlotFn } from './componentSlots'
import { renderEffect } from './renderEffect'
import { setClass, setDynamicProp } from './dom/prop'
import { setStyle } from './dom/style'
import { normalizeBlock } from './block'
export function fallbackComponent(
comp: string,
rawProps: RawProps | null,
slots: RawSlots | null,
instance: ComponentInternalInstance,
singleRoot: boolean = false,
): HTMLElement {
// eslint-disable-next-line no-restricted-globals
const el = document.createElement(comp)
if (rawProps || Object.keys(instance.attrs).length) {
rawProps = [() => instance.attrs, ...normalizeRawProps(rawProps)]
renderEffect(() => {
let classes: unknown[] | undefined
let styles: unknown[] | undefined
walkRawProps(
rawProps as NormalizedRawProps,
(key, valueOrGetter, getter) => {
const value = getter ? valueOrGetter() : valueOrGetter
if (key === 'class') (classes ||= []).push(value)
else if (key === 'style') (styles ||= []).push(value)
else setDynamicProp(el, key, value)
},
)
if (classes) setClass(el, classes)
if (styles) setStyle(el, styles)
})
}
if (slots) {
if (!Array.isArray(slots)) slots = [slots]
for (let i = 0; i < slots.length; i++) {
const slot = slots[i]
if (!isDynamicSlotFn(slot) && slot.default) {
const block = slot.default && slot.default()
if (block) el.append(...normalizeBlock(block))
}
}
}
if (singleRoot) {
instance.dynamicAttrs = true
for (let i = 0; i < instance.scopeIds.length; i++) {
const id = instance.scopeIds[i]
el.setAttribute(id, '')
}
}
const scopeId = instance.type.__scopeId
if (scopeId) el.setAttribute(scopeId, '')
return el
}

View File

@ -1,30 +0,0 @@
import { invokeArrayFns } from '@vue/shared'
import type { VaporLifecycleHooks } from './enums'
import { type ComponentInternalInstance, setCurrentInstance } from './component'
import { queuePostFlushCb } from './scheduler'
export function invokeLifecycle(
instance: ComponentInternalInstance,
lifecycle: VaporLifecycleHooks,
cb?: (instance: ComponentInternalInstance) => void,
post?: boolean,
): void {
invokeArrayFns(post ? [invokeSub, invokeCurrent] : [invokeCurrent, invokeSub])
function invokeCurrent() {
cb && cb(instance)
const hooks = instance[lifecycle]
if (hooks) {
const fn = () => {
const reset = setCurrentInstance(instance)
instance.scope.run(() => invokeArrayFns(hooks))
reset()
}
post ? queuePostFlushCb(fn) : fn()
}
}
function invokeSub() {
instance.comps.forEach(comp => invokeLifecycle(comp, lifecycle, cb, post))
}
}

View File

@ -1,392 +1,231 @@
import { EMPTY_ARR, NO, camelize, hasOwn, isFunction } from '@vue/shared'
import type { VaporComponent, VaporComponentInstance } from './component'
import { import {
EMPTY_ARR, type NormalizedPropsOptions,
EMPTY_OBJ, baseNormalizePropsOptions,
camelize, isEmitListener,
extend, resolvePropValue,
hasOwn, } from '@vue/runtime-core'
hyphenate, import { normalizeEmitsOptions } from './componentEmits'
isArray,
isFunction,
} from '@vue/shared'
import type { Data } from '@vue/runtime-shared'
import { shallowReactive } from '@vue/reactivity'
import { warn } from './warning'
import type { Component, ComponentInternalInstance } from './component'
import { patchAttrs } from './componentAttrs'
import { firstEffect } from './renderEffect'
export type ComponentPropsOptions<P = Data> = export interface RawProps {
| ComponentObjectPropsOptions<P> [key: string]: PropSource
| string[] $?: DynamicPropsSource[]
export type ComponentObjectPropsOptions<P = Data> = {
[K in keyof P]: Prop<P[K]> | null
} }
export type Prop<T, D = T> = PropOptions<T, D> | PropType<T> type PropSource<T = any> = T | (() => T)
type DefaultFactory<T> = (props: Data) => T | null | undefined type DynamicPropsSource = PropSource<Record<string, any>>
export interface PropOptions<T = any, D = T> { export function initStaticProps(
type?: PropType<T> | true | null comp: VaporComponent,
required?: boolean rawProps: RawProps | undefined,
default?: D | DefaultFactory<D> | null | undefined | object instance: VaporComponentInstance,
validator?(value: unknown, props: Data): boolean ): boolean {
/** let hasAttrs = false
* @internal const { props, attrs } = instance
*/ const [propsOptions, needCastKeys] = normalizePropsOptions(comp)
skipFactory?: boolean const emitsOptions = normalizeEmitsOptions(comp)
}
export type PropType<T> = PropConstructor<T> | PropConstructor<T>[] // for dev emit check
type PropConstructor<T = any> =
| { new (...args: any[]): T & {} }
| { (): T }
| PropMethod<T>
type PropMethod<T, TConstructor = any> = [T] extends [
((...args: any) => any) | undefined,
] // if is function with args, allowing non-required functions
? { new (): TConstructor; (): T; readonly prototype: TConstructor } // Create Function like constructor
: never
enum BooleanFlags {
shouldCast,
shouldCastTrue,
}
type NormalizedProp =
| null
| (PropOptions & {
[BooleanFlags.shouldCast]?: boolean
[BooleanFlags.shouldCastTrue]?: boolean
})
export type NormalizedProps = Record<string, NormalizedProp>
export type NormalizedPropsOptions =
| [props: NormalizedProps, needCastKeys: string[]]
| []
export type StaticProps = Record<string, () => unknown>
type DynamicProps = () => Data
export type NormalizedRawProps = Array<StaticProps | DynamicProps>
export type RawProps = NormalizedRawProps | StaticProps | DynamicProps | null
export function initProps(
instance: ComponentInternalInstance,
rawProps: RawProps,
isStateful: boolean,
once: boolean,
): void {
instance.rawProps = rawProps = normalizeRawProps(rawProps)
const props: Data = {}
const attrs = (instance.attrs = shallowReactive<Data>({}))
const [options] = instance.propsOptions
// has v-bind or :[eventName]
const hasDynamicProps = rawProps.some(isFunction)
if (options) {
if (hasDynamicProps) {
for (const key in options) {
const getter = () =>
getDynamicPropValue(rawProps as NormalizedRawProps, key)
registerProp(instance, once, props, key, getter, true)
}
} else {
const staticProps = rawProps[0] as StaticProps
for (const key in options) {
const rawKey = staticProps && getRawKey(staticProps, key)
if (rawKey) {
registerProp(instance, once, props, key, staticProps[rawKey])
} else {
registerProp(instance, once, props, key, undefined, false, true)
}
}
}
}
// validation
if (__DEV__) { if (__DEV__) {
validateProps(rawProps, props, options || {}) instance.propsOptions = normalizePropsOptions(comp)
instance.emitsOptions = emitsOptions
} }
if (hasDynamicProps) { for (const key in rawProps) {
firstEffect(instance, () => patchAttrs(instance, true)) const normalizedKey = camelize(key)
const needCast = needCastKeys && needCastKeys.includes(normalizedKey)
const source = rawProps[key]
if (propsOptions && normalizedKey in propsOptions) {
if (isFunction(source)) {
Object.defineProperty(props, normalizedKey, {
enumerable: true,
get: needCast
? () =>
resolvePropValue(
propsOptions,
normalizedKey,
source(),
instance,
resolveDefault,
)
: source,
})
} else { } else {
patchAttrs(instance) props[normalizedKey] = needCast
? resolvePropValue(
propsOptions,
normalizedKey,
source,
instance,
resolveDefault,
)
: source
} }
} else if (!isEmitListener(emitsOptions, key)) {
if (isStateful) { if (isFunction(source)) {
instance.props = /* TODO isSSR ? props : */ shallowReactive(props) Object.defineProperty(attrs, key, {
enumerable: true,
get: source,
})
} else { } else {
// functional w/ optional props, props === attrs attrs[normalizedKey] = source
instance.props = instance.propsOptions === EMPTY_ARR ? attrs : props
} }
hasAttrs = true
}
}
for (const key in propsOptions) {
if (!(key in props)) {
props[key] = resolvePropValue(
propsOptions,
key,
undefined,
instance,
resolveDefault,
true,
)
}
}
return hasAttrs
} }
function registerProp( function resolveDefault(
instance: ComponentInternalInstance, factory: (props: Record<string, any>) => unknown,
once: boolean, instance: VaporComponentInstance,
props: Data,
rawKey: string,
getter?: (() => unknown) | (() => DynamicPropResult),
isDynamic?: boolean,
isAbsent?: boolean,
) { ) {
const key = camelize(rawKey) return factory.call(null, instance.props)
if (key in props) return
const [options, needCastKeys] = instance.propsOptions
const needCast = needCastKeys && needCastKeys.includes(key)
const withCast = (value: unknown, absent?: boolean) =>
resolvePropValue(options!, props, key, value, absent)
if (isAbsent) {
props[key] = needCast ? withCast(undefined, true) : undefined
} else {
const get: () => unknown = isDynamic
? needCast
? () => withCast(...(getter!() as DynamicPropResult))
: () => (getter!() as DynamicPropResult)[0]
: needCast
? () => withCast(getter!())
: getter!
const descriptor: PropertyDescriptor = once ? { value: get() } : { get }
descriptor.enumerable = true
Object.defineProperty(props, key, descriptor)
}
} }
export function normalizeRawProps(rawProps: RawProps): NormalizedRawProps { // TODO optimization: maybe convert functions into computeds
if (!rawProps) return [] export function resolveSource(source: PropSource): Record<string, any> {
if (!isArray(rawProps)) return [rawProps] return isFunction(source) ? source() : source
return rawProps
} }
export function walkRawProps( const passThrough = (val: any) => val
rawProps: NormalizedRawProps,
cb: (key: string, value: any, getter?: boolean) => void, export function getDynamicPropsHandlers(
): void { comp: VaporComponent,
for (const props of Array.from(rawProps).reverse()) { instance: VaporComponentInstance,
if (isFunction(props)) { ): [ProxyHandler<RawProps> | null, ProxyHandler<RawProps>] {
const resolved = props() if (comp.__propsHandlers) {
for (const rawKey in resolved) { return comp.__propsHandlers
cb(rawKey, resolved[rawKey])
} }
} else { let normalizedKeys: string[] | undefined
for (const rawKey in props) { const propsOptions = normalizePropsOptions(comp)[0]
cb(rawKey, props[rawKey], true) const emitsOptions = normalizeEmitsOptions(comp)
const isProp = propsOptions ? (key: string) => hasOwn(propsOptions, key) : NO
const getProp = (target: RawProps, key: string, asProp: boolean) => {
if (key === '$') return
if (asProp) {
if (!isProp(key)) return
} else if (isProp(key) || isEmitListener(emitsOptions, key)) {
return
}
const castProp = propsOptions
? (value: any, isAbsent = false) =>
asProp
? resolvePropValue(
propsOptions,
key as string,
value,
instance,
resolveDefault,
isAbsent,
)
: value
: passThrough
if (key in target) {
return castProp(resolveSource(target[key as string]))
}
if (target.$) {
let i = target.$.length
let source
while (i--) {
source = resolveSource(target.$[i])
if (hasOwn(source, key)) {
return castProp(source[key])
} }
} }
} }
return castProp(undefined, true)
} }
function getRawKey(obj: Data, key: string) { const propsHandlers = propsOptions
return Object.keys(obj).find(k => camelize(k) === key) ? ({
get: (target, key: string) => getProp(target, key, true),
has: (_, key: string) => isProp(key),
getOwnPropertyDescriptor(target, key: string) {
if (isProp(key)) {
return {
configurable: true,
enumerable: true,
get: () => getProp(target, key, true),
}
}
},
ownKeys: () =>
normalizedKeys || (normalizedKeys = Object.keys(propsOptions)),
set: NO,
deleteProperty: NO,
} satisfies ProxyHandler<RawProps>)
: null
const hasAttr = (target: RawProps, key: string) => {
if (key === '$' || isProp(key) || isEmitListener(emitsOptions, key))
return false
if (target.$) {
let i = target.$.length
while (i--) {
if (hasOwn(resolveSource(target.$[i]), key)) {
return true
}
}
}
return hasOwn(target, key)
} }
type DynamicPropResult = [value: unknown, absent: boolean] const attrsHandlers = {
export function getDynamicPropValue( get: (target, key: string) => getProp(target, key, false),
rawProps: NormalizedRawProps, has: hasAttr,
key: string, getOwnPropertyDescriptor(target, key: string) {
): DynamicPropResult { if (hasAttr(target, key)) {
for (const props of Array.from(rawProps).reverse()) { return {
if (isFunction(props)) { configurable: true,
const resolved = props() enumerable: true,
const rawKey = getRawKey(resolved, key) get: () => getProp(target, key, false),
if (rawKey) return [resolved[rawKey], false]
} else {
const rawKey = getRawKey(props, key)
if (rawKey) return [props[rawKey](), false]
} }
} }
return [undefined, true] },
ownKeys(target) {
const keys = Object.keys(target)
if (target.$) {
let i = target.$.length
while (i--) {
keys.push(...Object.keys(resolveSource(target.$[i])))
}
}
return keys.filter(key => hasAttr(target, key))
},
set: NO,
deleteProperty: NO,
} satisfies ProxyHandler<RawProps>
return (comp.__propsHandlers = [propsHandlers, attrsHandlers])
} }
export function resolvePropValue( function normalizePropsOptions(comp: VaporComponent): NormalizedPropsOptions {
options: NormalizedProps,
props: Data,
key: string,
value: unknown,
isAbsent?: boolean,
): unknown {
const opt = options[key]
if (opt != null) {
const hasDefault = hasOwn(opt, 'default')
// default values
if (hasDefault && value === undefined) {
const defaultValue = opt.default
if (
opt.type !== Function &&
!opt.skipFactory &&
isFunction(defaultValue)
) {
value = defaultValue.call(null, props)
} else {
value = defaultValue
}
}
// boolean casting
if (opt[BooleanFlags.shouldCast]) {
if (isAbsent && !hasDefault) {
value = false
} else if (
opt[BooleanFlags.shouldCastTrue] &&
(value === '' || value === hyphenate(key))
) {
value = true
}
}
}
return value
}
export function normalizePropsOptions(comp: Component): NormalizedPropsOptions {
const cached = comp.__propsOptions const cached = comp.__propsOptions
if (cached) return cached if (cached) return cached
const raw = comp.props const raw = comp.props
const normalized: NormalizedProps | undefined = {} if (!raw) return EMPTY_ARR as []
const normalized: NormalizedPropsOptions[0] = {}
const needCastKeys: NormalizedPropsOptions[1] = [] const needCastKeys: NormalizedPropsOptions[1] = []
baseNormalizePropsOptions(raw, normalized, needCastKeys)
if (!raw) { return (comp.__propsOptions = [normalized, needCastKeys])
return EMPTY_ARR as []
}
if (isArray(raw)) {
for (let i = 0; i < raw.length; i++) {
const normalizedKey = camelize(raw[i])
if (validatePropName(normalizedKey)) {
normalized[normalizedKey] = EMPTY_OBJ
}
}
} else {
for (const key in raw) {
const normalizedKey = camelize(key)
if (validatePropName(normalizedKey)) {
const opt = raw[key]
const prop: NormalizedProp = (normalized[normalizedKey] =
isArray(opt) || isFunction(opt) ? { type: opt } : extend({}, opt))
if (prop) {
const booleanIndex = getTypeIndex(Boolean, prop.type)
const stringIndex = getTypeIndex(String, prop.type)
prop[BooleanFlags.shouldCast] = booleanIndex > -1
prop[BooleanFlags.shouldCastTrue] =
stringIndex < 0 || booleanIndex < stringIndex
// if the prop needs boolean casting or default value
if (booleanIndex > -1 || hasOwn(prop, 'default')) {
needCastKeys.push(normalizedKey)
}
}
}
}
}
const res: NormalizedPropsOptions = (comp.__propsOptions = [
normalized,
needCastKeys,
])
return res
}
function validatePropName(key: string) {
if (key[0] !== '$') {
return true
} else if (__DEV__) {
warn(`Invalid prop name: "${key}" is a reserved property.`)
}
return false
}
function getType(ctor: Prop<any>): string {
const match = ctor && ctor.toString().match(/^\s*(function|class) (\w+)/)
return match ? match[2] : ctor === null ? 'null' : ''
}
function isSameType(a: Prop<any>, b: Prop<any>): boolean {
return getType(a) === getType(b)
}
function getTypeIndex(
type: Prop<any>,
expectedTypes: PropType<any> | void | null | true,
): number {
if (isArray(expectedTypes)) {
return expectedTypes.findIndex(t => isSameType(t, type))
} else if (isFunction(expectedTypes)) {
return isSameType(expectedTypes, type) ? 0 : -1
}
return -1
}
/**
* dev only
*/
function validateProps(
rawProps: NormalizedRawProps,
props: Data,
options: NormalizedProps,
) {
const presentKeys: string[] = []
for (const props of rawProps) {
presentKeys.push(...Object.keys(isFunction(props) ? props() : props))
}
for (const key in options) {
const opt = options[key]
if (opt != null)
validateProp(
key,
props[key],
opt,
props,
!presentKeys.some(k => camelize(k) === key),
)
}
}
/**
* dev only
*/
function validateProp(
name: string,
value: unknown,
option: PropOptions,
props: Data,
isAbsent: boolean,
) {
const { required, validator } = option
// required!
if (required && isAbsent) {
warn('Missing required prop: "' + name + '"')
return
}
// missing but optional
if (value == null && !required) {
return
}
// NOTE: type check is not supported in vapor
// // type check
// if (type != null && type !== true) {
// let isValid = false
// const types = isArray(type) ? type : [type]
// const expectedTypes = []
// // value is valid as long as one of the specified types match
// for (let i = 0; i < types.length && !isValid; i++) {
// const { valid, expectedType } = assertType(value, types[i])
// expectedTypes.push(expectedType || '')
// isValid = valid
// }
// if (!isValid) {
// warn(getInvalidTypeMessage(name, value, expectedTypes))
// return
// }
// }
// custom validator
if (validator && !validator(value, props)) {
warn('Invalid prop: custom validator check failed for prop "' + name + '".')
}
} }

View File

@ -1,267 +0,0 @@
import { type IfAny, isArray, isFunction } from '@vue/shared'
import {
type EffectScope,
effectScope,
isReactive,
shallowReactive,
shallowRef,
} from '@vue/reactivity'
import {
type ComponentInternalInstance,
currentInstance,
setCurrentInstance,
} from './component'
import { type Block, type Fragment, fragmentKey, isValidBlock } from './block'
import { firstEffect, renderEffect } from './renderEffect'
import { createComment, createTextNode, insert, remove } from './dom/element'
import type { NormalizedRawProps } from './componentProps'
import type { Data } from '@vue/runtime-shared'
import { mergeProps } from './dom/prop'
// TODO: SSR
export type Slot<T extends any = any> = (
...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
) => Block
export type StaticSlots = Record<string, Slot>
export type DynamicSlot = { name: string; fn: Slot }
export type DynamicSlotFn = () => DynamicSlot | DynamicSlot[] | undefined
export type NormalizedRawSlots = Array<StaticSlots | DynamicSlotFn>
export type RawSlots = NormalizedRawSlots | StaticSlots | null
export const isDynamicSlotFn = isFunction as (
val: StaticSlots | DynamicSlotFn,
) => val is DynamicSlotFn
export function initSlots(
instance: ComponentInternalInstance,
rawSlots: RawSlots | null = null,
): void {
if (!rawSlots) return
if (!isArray(rawSlots)) rawSlots = [rawSlots]
if (!rawSlots.some(slot => isDynamicSlotFn(slot))) {
instance.slots = {}
// with ctx
const slots = rawSlots[0] as StaticSlots
for (const name in slots) {
registerSlot(name, slots[name])
}
return
}
instance.slots = shallowReactive({})
const keys: Set<string>[] = []
rawSlots.forEach((slots, index) => {
const isDynamicSlot = isDynamicSlotFn(slots)
if (isDynamicSlot) {
firstEffect(instance, () => {
const recordNames = keys[index] || (keys[index] = new Set())
let dynamicSlot: ReturnType<DynamicSlotFn>
if (isDynamicSlotFn(slots)) {
dynamicSlot = slots()
if (isArray(dynamicSlot)) {
for (const slot of dynamicSlot) {
registerSlot(slot.name, slot.fn, recordNames)
}
} else if (dynamicSlot) {
registerSlot(dynamicSlot.name, dynamicSlot.fn, recordNames)
}
} else {
}
for (const name of recordNames) {
if (
!(isArray(dynamicSlot)
? dynamicSlot.some(s => s.name === name)
: dynamicSlot && dynamicSlot.name === name)
) {
recordNames.delete(name)
delete instance.slots[name]
}
}
})
} else {
for (const name in slots) {
registerSlot(name, slots[name])
}
}
})
function registerSlot(name: string, fn: Slot, recordNames?: Set<string>) {
instance.slots[name] = withCtx(fn)
recordNames && recordNames.add(name)
}
function withCtx(fn: Slot): Slot {
return (...args: any[]) => {
const reset = setCurrentInstance(instance.parent!)
try {
return fn(...(args as any))
} finally {
reset()
}
}
}
}
export function createSlot(
name: string | (() => string),
binds?: NormalizedRawProps,
fallback?: Slot,
): Block {
const { slots } = currentInstance!
const slotBlock = shallowRef<Block>()
let slotBranch: Slot | undefined
let slotScope: EffectScope | undefined
let fallbackBlock: Block | undefined
let fallbackBranch: Slot | undefined
let fallbackScope: EffectScope | undefined
const normalizeBinds = binds && normalizeSlotProps(binds)
const isDynamicName = isFunction(name)
// fast path for static slots & without fallback
if (!isDynamicName && !isReactive(slots) && !fallback) {
if ((slotBranch = slots[name])) {
return slotBranch(normalizeBinds)
} else {
return []
}
}
const anchor = __DEV__ ? createComment('slot') : createTextNode()
const fragment: Fragment = {
nodes: [],
anchor,
[fragmentKey]: true,
}
// TODO lifecycle hooks
renderEffect(() => {
const parent = anchor.parentNode
if (
!slotBlock.value || // not initied
fallbackScope || // in fallback slot
isValidBlock(slotBlock.value) // slot block is valid
) {
renderSlot(parent)
} else {
renderFallback(parent)
}
})
return fragment
function renderSlot(parent: ParentNode | null) {
// from fallback to slot
const fromFallback = fallbackScope
if (fromFallback) {
// clean fallback slot
fallbackScope!.stop()
remove(fallbackBlock!, parent!)
fallbackScope = fallbackBlock = undefined
}
const slotName = isFunction(name) ? name() : name
const branch = slots[slotName]!
if (branch) {
// init slot scope and block or switch branch
if (!slotScope || slotBranch !== branch) {
// clean previous slot
if (slotScope && !fromFallback) {
slotScope.stop()
remove(slotBlock.value!, parent!)
}
slotBranch = branch
slotScope = effectScope()
slotBlock.value = slotScope.run(() => slotBranch!(normalizeBinds))
}
// if slot block is valid, render it
if (slotBlock.value && isValidBlock(slotBlock.value)) {
fragment.nodes = slotBlock.value
parent && insert(slotBlock.value, parent, anchor)
} else {
renderFallback(parent)
}
} else {
renderFallback(parent)
}
}
function renderFallback(parent: ParentNode | null) {
// if slot branch is initied, remove it from DOM, but keep the scope
if (slotBranch) {
remove(slotBlock.value!, parent!)
}
fallbackBranch ||= fallback
if (fallbackBranch) {
fallbackScope = effectScope()
fragment.nodes = fallbackBlock = fallbackScope.run(() =>
fallbackBranch!(normalizeBinds),
)!
parent && insert(fallbackBlock, parent, anchor)
} else {
fragment.nodes = []
}
}
}
function normalizeSlotProps(rawPropsList: NormalizedRawProps) {
const { length } = rawPropsList
const mergings = length > 1 ? shallowReactive<Data[]>([]) : undefined
const result = shallowReactive<Data>({})
for (let i = 0; i < length; i++) {
const rawProps = rawPropsList[i]
if (isFunction(rawProps)) {
// dynamic props
renderEffect(() => {
const props = rawProps()
if (mergings) {
mergings[i] = props
} else {
setDynamicProps(props)
}
})
} else {
// static props
const props = mergings
? (mergings[i] = shallowReactive<Data>({}))
: result
for (const key in rawProps) {
const valueSource = rawProps[key]
renderEffect(() => {
props[key] = valueSource()
})
}
}
}
if (mergings) {
renderEffect(() => {
setDynamicProps(mergeProps(...mergings))
})
}
return result
function setDynamicProps(props: Data) {
const otherExistingKeys = new Set(Object.keys(result))
for (const key in props) {
result[key] = props[key]
otherExistingKeys.delete(key)
}
// delete other stale props
for (const key of otherExistingKeys) {
delete result[key]
}
}
}

View File

@ -1,23 +0,0 @@
import {
type ShallowUnwrapRef,
proxyRefs,
shallowReactive,
} from '@vue/reactivity'
import { renderEffect } from './renderEffect'
export function withDestructure<T extends any[], R>(
assign: (data: ShallowUnwrapRef<T>) => any[],
block: (ctx: any[]) => R,
): (data: T) => R {
return (data: T) => {
const ctx = shallowReactive<any[]>([])
renderEffect(() => {
const res = assign(proxyRefs(data))
const len = res.length
for (let i = 0; i < len; i++) {
ctx[i] = res[i]
}
})
return block(ctx)
}
}

View File

@ -1,168 +0,0 @@
/* eslint-disable no-restricted-globals */
import type { App } from './apiCreateVaporApp'
import type { ComponentInternalInstance } from './component'
interface AppRecord {
id: number
app: App
version: string
types: Record<string, string | Symbol>
}
enum DevtoolsHooks {
APP_INIT = 'app:init',
APP_UNMOUNT = 'app:unmount',
COMPONENT_UPDATED = 'component:updated',
COMPONENT_ADDED = 'component:added',
COMPONENT_REMOVED = 'component:removed',
COMPONENT_EMIT = 'component:emit',
PERFORMANCE_START = 'perf:start',
PERFORMANCE_END = 'perf:end',
}
export interface DevtoolsHook {
enabled?: boolean
emit: (event: string, ...payload: any[]) => void
on: (event: string, handler: Function) => void
once: (event: string, handler: Function) => void
off: (event: string, handler: Function) => void
appRecords: AppRecord[]
/**
* Added at https://github.com/vuejs/devtools/commit/f2ad51eea789006ab66942e5a27c0f0986a257f9
* Returns whether the arg was buffered or not
*/
cleanupBuffer?: (matchArg: unknown) => boolean
}
export let devtools: DevtoolsHook
let buffer: { event: string; args: any[] }[] = []
let devtoolsNotInstalled = false
function emit(event: string, ...args: any[]) {
if (devtools) {
devtools.emit(event, ...args)
} else if (!devtoolsNotInstalled) {
buffer.push({ event, args })
}
}
export function setDevtoolsHook(hook: DevtoolsHook, target: any): void {
devtools = hook
if (devtools) {
devtools.enabled = true
buffer.forEach(({ event, args }) => devtools.emit(event, ...args))
buffer = []
} else if (
// handle late devtools injection - only do this if we are in an actual
// browser environment to avoid the timer handle stalling test runner exit
// (#4815)
typeof window !== 'undefined' &&
// some envs mock window but not fully
window.HTMLElement &&
// also exclude jsdom
// eslint-disable-next-line no-restricted-syntax
!window.navigator?.userAgent?.includes('jsdom')
) {
const replay = (target.__VUE_DEVTOOLS_HOOK_REPLAY__ =
target.__VUE_DEVTOOLS_HOOK_REPLAY__ || [])
replay.push((newHook: DevtoolsHook) => {
setDevtoolsHook(newHook, target)
})
// clear buffer after 3s - the user probably doesn't have devtools installed
// at all, and keeping the buffer will cause memory leaks (#4738)
setTimeout(() => {
if (!devtools) {
target.__VUE_DEVTOOLS_HOOK_REPLAY__ = null
devtoolsNotInstalled = true
buffer = []
}
}, 3000)
} else {
// non-browser env, assume not installed
devtoolsNotInstalled = true
buffer = []
}
}
export function devtoolsInitApp(app: App, version: string): void {
emit(DevtoolsHooks.APP_INIT, app, version, {})
}
export function devtoolsUnmountApp(app: App): void {
emit(DevtoolsHooks.APP_UNMOUNT, app)
}
export const devtoolsComponentAdded: DevtoolsComponentHook =
/*#__PURE__*/ createDevtoolsComponentHook(DevtoolsHooks.COMPONENT_ADDED)
export const devtoolsComponentUpdated: DevtoolsComponentHook =
/*#__PURE__*/ createDevtoolsComponentHook(DevtoolsHooks.COMPONENT_UPDATED)
const _devtoolsComponentRemoved = /*#__PURE__*/ createDevtoolsComponentHook(
DevtoolsHooks.COMPONENT_REMOVED,
)
export const devtoolsComponentRemoved = (
component: ComponentInternalInstance,
): void => {
if (
devtools &&
typeof devtools.cleanupBuffer === 'function' &&
// remove the component if it wasn't buffered
!devtools.cleanupBuffer(component)
) {
_devtoolsComponentRemoved(component)
}
}
type DevtoolsComponentHook = (component: ComponentInternalInstance) => void
/*! #__NO_SIDE_EFFECTS__ */
function createDevtoolsComponentHook(
hook: DevtoolsHooks,
): DevtoolsComponentHook {
return (component: ComponentInternalInstance) => {
emit(
hook,
component.appContext.app,
component.uid,
component.parent ? component.parent.uid : undefined,
component,
)
}
}
export const devtoolsPerfStart: DevtoolsPerformanceHook =
/*#__PURE__*/ createDevtoolsPerformanceHook(DevtoolsHooks.PERFORMANCE_START)
export const devtoolsPerfEnd: DevtoolsPerformanceHook =
/*#__PURE__*/ createDevtoolsPerformanceHook(DevtoolsHooks.PERFORMANCE_END)
type DevtoolsPerformanceHook = (
component: ComponentInternalInstance,
type: string,
time: number,
) => void
function createDevtoolsPerformanceHook(
hook: DevtoolsHooks,
): DevtoolsPerformanceHook {
return (component: ComponentInternalInstance, type: string, time: number) => {
emit(hook, component.appContext.app, component.uid, component, type, time)
}
}
export function devtoolsComponentEmit(
component: ComponentInternalInstance,
event: string,
params: any[],
): void {
emit(
DevtoolsHooks.COMPONENT_EMIT,
component.appContext.app,
component,
event,
params,
)
}

View File

@ -1,106 +0,0 @@
import { isBuiltInDirective } from '@vue/shared'
import {
type ComponentInternalInstance,
currentInstance,
isVaporComponent,
} from './component'
import { warn } from './warning'
import { normalizeBlock } from './block'
import { getCurrentScope } from '@vue/reactivity'
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
export interface DirectiveBinding<T = any, V = any, M extends string = string> {
instance: ComponentInternalInstance
source: () => V
arg?: string
modifiers?: DirectiveModifiers<M>
dir: Directive<T, V, M>
}
export type DirectiveBindingsMap = Map<Node, DirectiveBinding[]>
export type Directive<T = any, V = any, M extends string = string> = (
node: T,
binding: DirectiveBinding<T, V, M>,
) => void
export function validateDirectiveName(name: string): void {
if (isBuiltInDirective(name)) {
warn('Do not use built-in directive ids as custom directive id: ' + name)
}
}
export type DirectiveArguments = Array<
| [Directive | undefined]
| [Directive | undefined, () => any]
| [Directive | undefined, () => any, argument: string]
| [
Directive | undefined,
value: () => any,
argument: string,
modifiers: DirectiveModifiers,
]
>
export function withDirectives<T extends ComponentInternalInstance | Node>(
nodeOrComponent: T,
directives: DirectiveArguments,
): T {
if (!currentInstance) {
__DEV__ && warn(`withDirectives can only be used inside render functions.`)
return nodeOrComponent
}
let node: Node
if (isVaporComponent(nodeOrComponent)) {
const root = getComponentNode(nodeOrComponent)
if (!root) return nodeOrComponent
node = root
} else {
node = nodeOrComponent
}
const instance = currentInstance!
const parentScope = getCurrentScope()
if (__DEV__ && !parentScope) {
warn(`Directives should be used inside of RenderEffectScope.`)
}
for (const directive of directives) {
let [dir, source = () => undefined, arg, modifiers] = directive
if (!dir) continue
const binding: DirectiveBinding = {
dir,
source,
instance,
arg,
modifiers,
}
callWithAsyncErrorHandling(dir, instance, VaporErrorCodes.DIRECTIVE_HOOK, [
node,
binding,
])
}
return nodeOrComponent
}
function getComponentNode(component: ComponentInternalInstance) {
if (!component.block) return
const nodes = normalizeBlock(component.block)
if (nodes.length !== 1) {
warn(
`Runtime directive used on component with non-element root node. ` +
`The directives will not function as intended.`,
)
return
}
return nodes[0]
}

View File

@ -1,313 +0,0 @@
import {
invokeArrayFns,
isArray,
isSet,
looseEqual,
looseIndexOf,
looseToNumber,
} from '@vue/shared'
import type { Directive } from '../directives'
import { addEventListener } from '../dom/event'
import { nextTick } from '../scheduler'
import { warn } from '../warning'
import { MetadataKind, getMetadata } from '../componentMetadata'
import {
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onMounted,
} from '../apiLifecycle'
import { renderEffect } from '../renderEffect'
type AssignerFn = (value: any) => void
function getModelAssigner(el: Element): AssignerFn {
const metadata = getMetadata(el)
const fn = metadata[MetadataKind.event]['update:modelValue'] || []
return value => invokeArrayFns(fn, value)
}
function onCompositionStart(e: Event) {
;(e.target as any).composing = true
}
function onCompositionEnd(e: Event) {
const target = e.target as any
if (target.composing) {
target.composing = false
target.dispatchEvent(new Event('input'))
}
}
const assignFnMap = new WeakMap<HTMLElement, AssignerFn>()
const assigningMap = new WeakMap<HTMLElement, boolean>()
// We are exporting the v-model runtime directly as vnode hooks so that it can
// be tree-shaken in case v-model is never used.
export const vModelText: Directive<
HTMLInputElement | HTMLTextAreaElement,
any,
'lazy' | 'trim' | 'number'
> = (el, { source, modifiers: { lazy, trim, number } = {} }) => {
onBeforeMount(() => {
const assigner = getModelAssigner(el)
assignFnMap.set(el, assigner)
const metadata = getMetadata(el)
const castToNumber = number || metadata[MetadataKind.prop].type === 'number'
addEventListener(el, lazy ? 'change' : 'input', e => {
if ((e.target as any).composing) return
let domValue: string | number = el.value
if (trim) {
domValue = domValue.trim()
}
if (castToNumber) {
domValue = looseToNumber(domValue)
}
assigner(domValue)
})
if (trim) {
addEventListener(el, 'change', () => {
el.value = el.value.trim()
})
}
if (!lazy) {
addEventListener(el, 'compositionstart', onCompositionStart)
addEventListener(el, 'compositionend', onCompositionEnd)
// Safari < 10.2 & UIWebView doesn't fire compositionend when
// switching focus before confirming composition choice
// this also fixes the issue where some browsers e.g. iOS Chrome
// fires "change" instead of "input" on autocomplete.
addEventListener(el, 'change', onCompositionEnd)
}
})
onMounted(() => {
const value = source()
el.value = value == null ? '' : value
})
renderEffect(() => {
const value = source()
assignFnMap.set(el, getModelAssigner(el))
// avoid clearing unresolved text. #2302
if ((el as any).composing) return
const elValue =
number || el.type === 'number' ? looseToNumber(el.value) : el.value
const newValue = value == null ? '' : value
if (elValue === newValue) {
return
}
// eslint-disable-next-line no-restricted-globals
if (document.activeElement === el && el.type !== 'range') {
if (lazy) {
return
}
if (trim && el.value.trim() === newValue) {
return
}
}
el.value = newValue
})
}
export const vModelRadio: Directive<HTMLInputElement> = (el, { source }) => {
onBeforeMount(() => {
el.checked = looseEqual(source(), getValue(el))
assignFnMap.set(el, getModelAssigner(el))
addEventListener(el, 'change', () => {
assignFnMap.get(el)!(getValue(el))
})
})
renderEffect(() => {
const value = source()
assignFnMap.set(el, getModelAssigner(el))
el.checked = looseEqual(value, getValue(el))
})
}
export const vModelSelect: Directive<HTMLSelectElement, any, 'number'> = (
el,
{ source, modifiers: { number = false } = {} },
) => {
onBeforeMount(() => {
const value = source()
const isSetModel = isSet(value)
addEventListener(el, 'change', () => {
const selectedVal = Array.prototype.filter
.call(el.options, (o: HTMLOptionElement) => o.selected)
.map((o: HTMLOptionElement) =>
number ? looseToNumber(getValue(o)) : getValue(o),
)
assignFnMap.get(el)!(
el.multiple
? isSetModel
? new Set(selectedVal)
: selectedVal
: selectedVal[0],
)
assigningMap.set(el, true)
nextTick(() => {
assigningMap.set(el, false)
})
})
assignFnMap.set(el, getModelAssigner(el))
setSelected(el, value, number)
})
onBeforeUnmount(() => {
assignFnMap.set(el, getModelAssigner(el))
})
renderEffect(() => {
if (!assigningMap.get(el)) {
setSelected(el, source(), number)
}
})
}
function setSelected(el: HTMLSelectElement, value: any, number: boolean) {
const isMultiple = el.multiple
const isArrayValue = isArray(value)
if (isMultiple && !isArrayValue && !isSet(value)) {
__DEV__ &&
warn(
`<select multiple v-model> expects an Array or Set value for its binding, ` +
`but got ${Object.prototype.toString.call(value).slice(8, -1)}.`,
)
return
}
for (let i = 0, l = el.options.length; i < l; i++) {
const option = el.options[i]
const optionValue = getValue(option)
if (isMultiple) {
if (isArrayValue) {
const optionType = typeof optionValue
// fast path for string / number values
if (optionType === 'string' || optionType === 'number') {
option.selected = value.includes(
number ? looseToNumber(optionValue) : optionValue,
)
} else {
option.selected = looseIndexOf(value, optionValue) > -1
}
} else {
option.selected = value.has(optionValue)
}
} else if (looseEqual(getValue(option), value)) {
if (el.selectedIndex !== i) el.selectedIndex = i
return
}
}
if (!isMultiple && el.selectedIndex !== -1) {
el.selectedIndex = -1
}
}
// retrieve raw value set via :value bindings
function getValue(el: HTMLOptionElement | HTMLInputElement) {
const metadata = getMetadata(el)
return (metadata && metadata[MetadataKind.prop].value) || el.value
}
// retrieve raw value for true-value and false-value set via :true-value or :false-value bindings
function getCheckboxValue(el: HTMLInputElement, checked: boolean) {
const metadata = getMetadata(el)
const props = metadata && metadata[MetadataKind.prop]
const key = checked ? 'true-value' : 'false-value'
if (props && key in props) {
return props[key]
}
if (el.hasAttribute(key)) {
return el.getAttribute(key)
}
return checked
}
export const vModelCheckbox: Directive<HTMLInputElement> = (el, { source }) => {
onBeforeMount(() => {
assignFnMap.set(el, getModelAssigner(el))
addEventListener(el, 'change', () => {
const modelValue = source()
const elementValue = getValue(el)
const checked = el.checked
const assigner = assignFnMap.get(el)!
if (isArray(modelValue)) {
const index = looseIndexOf(modelValue, elementValue)
const found = index !== -1
if (checked && !found) {
assigner(modelValue.concat(elementValue))
} else if (!checked && found) {
const filtered = [...modelValue]
filtered.splice(index, 1)
assigner(filtered)
}
} else if (isSet(modelValue)) {
const cloned = new Set(modelValue)
if (checked) {
cloned.add(elementValue)
} else {
cloned.delete(elementValue)
}
assigner(cloned)
} else {
assigner(getCheckboxValue(el, checked))
}
})
})
onMounted(() => {
setChecked()
})
onBeforeUpdate(() => {
assignFnMap.set(el, getModelAssigner(el))
setChecked()
})
function setChecked() {
const value = source()
if (isArray(value)) {
el.checked = looseIndexOf(value, getValue(el)) > -1
} else if (isSet(value)) {
el.checked = value.has(getValue(el))
} else {
el.checked = looseEqual(value, getCheckboxValue(el, true))
}
}
}
export const vModelDynamic: Directive<
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
> = (el, binding) => {
const type = el.getAttribute('type')
const modelToUse = resolveDynamicModel(el.tagName, type)
modelToUse(el, binding)
}
function resolveDynamicModel(tagName: string, type: string | null): Directive {
switch (tagName) {
case 'SELECT':
return vModelSelect
case 'TEXTAREA':
return vModelText
default:
switch (type) {
case 'checkbox':
return vModelCheckbox
case 'radio':
return vModelRadio
default:
return vModelText
}
}
}

View File

@ -1,21 +0,0 @@
import type { Directive } from '../directives'
import { renderEffect } from '../renderEffect'
export const vShowOriginalDisplay: unique symbol = Symbol('_vod')
export const vShowHidden: unique symbol = Symbol('_vsh')
export interface VShowElement extends HTMLElement {
// _vod = vue original display
[vShowOriginalDisplay]: string
[vShowHidden]: boolean
}
export const vShow: Directive<VShowElement> = (el, { source }) => {
el[vShowOriginalDisplay] = el.style.display === 'none' ? '' : el.style.display
renderEffect(() => setDisplay(el, source()))
}
function setDisplay(el: VShowElement, value: unknown): void {
el.style.display = value ? el[vShowOriginalDisplay] : 'none'
el[vShowHidden] = !value
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,14 +0,0 @@
export enum VaporLifecycleHooks {
BEFORE_MOUNT = 'bm',
MOUNTED = 'm',
BEFORE_UPDATE = 'bu',
UPDATED = 'u',
BEFORE_UNMOUNT = 'bum',
UNMOUNTED = 'um',
DEACTIVATED = 'da',
ACTIVATED = 'a',
RENDER_TRIGGERED = 'rtg',
RENDER_TRACKED = 'rtc',
ERROR_CAPTURED = 'ec',
// SERVER_PREFETCH = 'sp',
}

View File

@ -1,162 +0,0 @@
// These codes originate from a file of the same name in runtime-core,
// duplicated during Vapor's early development to ensure its independence.
// The ultimate aim is to uncouple this replicated code and
// facilitate its shared use between two runtimes.
import type { ComponentInternalInstance } from './component'
import { isFunction, isPromise } from '@vue/shared'
import { warn } from './warning'
import { VaporLifecycleHooks } from './enums'
import { WatchErrorCodes, pauseTracking, resetTracking } from '@vue/reactivity'
// contexts where user provided function may be executed, in addition to
// lifecycle hooks.
export enum VaporErrorCodes {
SETUP_FUNCTION,
RENDER_FUNCTION,
// The error codes for the watch have been transferred to the reactivity
// package along with baseWatch to maintain code compatibility. Hence,
// it is essential to keep these values unchanged.
// WATCH_GETTER,
// WATCH_CALLBACK,
// WATCH_CLEANUP,
NATIVE_EVENT_HANDLER = 5,
COMPONENT_EVENT_HANDLER,
VNODE_HOOK,
DIRECTIVE_HOOK,
TRANSITION_HOOK,
APP_ERROR_HANDLER,
APP_WARN_HANDLER,
FUNCTION_REF,
ASYNC_COMPONENT_LOADER,
SCHEDULER,
}
export type ErrorTypes = VaporLifecycleHooks | VaporErrorCodes | WatchErrorCodes
export const ErrorTypeStrings: Record<ErrorTypes, string> = {
// [VaporLifecycleHooks.SERVER_PREFETCH]: 'serverPrefetch hook',
[VaporLifecycleHooks.BEFORE_MOUNT]: 'beforeMount hook',
[VaporLifecycleHooks.MOUNTED]: 'mounted hook',
[VaporLifecycleHooks.BEFORE_UPDATE]: 'beforeUpdate hook',
[VaporLifecycleHooks.UPDATED]: 'updated',
[VaporLifecycleHooks.BEFORE_UNMOUNT]: 'beforeUnmount hook',
[VaporLifecycleHooks.UNMOUNTED]: 'unmounted hook',
[VaporLifecycleHooks.ACTIVATED]: 'activated hook',
[VaporLifecycleHooks.DEACTIVATED]: 'deactivated hook',
[VaporLifecycleHooks.ERROR_CAPTURED]: 'errorCaptured hook',
[VaporLifecycleHooks.RENDER_TRACKED]: 'renderTracked hook',
[VaporLifecycleHooks.RENDER_TRIGGERED]: 'renderTriggered hook',
[VaporErrorCodes.SETUP_FUNCTION]: 'setup function',
[VaporErrorCodes.RENDER_FUNCTION]: 'render function',
[WatchErrorCodes.WATCH_GETTER]: 'watcher getter',
[WatchErrorCodes.WATCH_CALLBACK]: 'watcher callback',
[WatchErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function',
[VaporErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
[VaporErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
[VaporErrorCodes.VNODE_HOOK]: 'vnode hook',
[VaporErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
[VaporErrorCodes.TRANSITION_HOOK]: 'transition hook',
[VaporErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
[VaporErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
[VaporErrorCodes.FUNCTION_REF]: 'ref function',
[VaporErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
[VaporErrorCodes.SCHEDULER]:
'scheduler flush. This is likely a Vue internals bug. ' +
'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/core',
}
export function callWithErrorHandling(
fn: Function,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[],
): any {
let res
try {
res = args ? fn(...args) : fn()
} catch (err) {
handleError(err, instance, type)
}
return res
}
export function callWithAsyncErrorHandling<F extends Function | Function[]>(
fn: F,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[],
): F extends Function ? any : any[] {
if (isFunction(fn)) {
const res = callWithErrorHandling(fn, instance, type, args)
if (res && isPromise(res)) {
res.catch(err => {
handleError(err, instance, type)
})
}
return res
}
const values = []
for (let i = 0; i < fn.length; i++) {
values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
}
return values
}
export function handleError(
err: unknown,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
throwInDev = true,
): void {
if (instance) {
let cur = instance.parent
// in production the hook receives only the error code
const errorInfo = __DEV__
? ErrorTypeStrings[type]
: `https://vuejs.org/errors/#runtime-${type}`
while (cur) {
const errorCapturedHooks = 'ec' in cur ? cur.ec : null
if (errorCapturedHooks) {
for (let i = 0; i < errorCapturedHooks.length; i++) {
if (errorCapturedHooks[i](err, instance, errorInfo) === false) {
return
}
}
}
cur = cur.parent
}
// app-level handling
const appErrorHandler = instance.appContext.config.errorHandler
if (appErrorHandler) {
pauseTracking()
callWithErrorHandling(
appErrorHandler,
null,
VaporErrorCodes.APP_ERROR_HANDLER,
[err, instance, errorInfo],
)
resetTracking()
return
}
}
logError(err, type, throwInDev)
}
function logError(err: unknown, type: ErrorTypes, throwInDev = true) {
if (__DEV__) {
const info = ErrorTypeStrings[type]
warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
// crash in dev by default so it's more noticeable
if (throwInDev) {
throw err
} else if (!__TEST__) {
console.error(err)
}
} else {
// recover in prod to reduce the impact on end-user
console.error(err)
}
}

View File

@ -1,95 +0,0 @@
import { camelize, capitalize, isString } from '@vue/shared'
import { warn } from '../warning'
import type { Directive } from '../directives'
import { type Component, currentInstance } from '../component'
import { getComponentName } from '../component'
const COMPONENTS = 'components'
const DIRECTIVES = 'directives'
export type AssetTypes = typeof COMPONENTS | typeof DIRECTIVES
export function resolveComponent(name: string): string | Component {
return resolveAsset(COMPONENTS, name, true) || name
}
export function resolveDirective(name: string): Directive | undefined {
return resolveAsset(DIRECTIVES, name)
}
/**
* @private
* overload 1: components
*/
function resolveAsset(
type: typeof COMPONENTS,
name: string,
warnMissing?: boolean,
): Component | undefined
// overload 2: directives
function resolveAsset(
type: typeof DIRECTIVES,
name: string,
): Directive | undefined
// implementation
function resolveAsset(type: AssetTypes, name: string, warnMissing = true) {
const instance = currentInstance
if (instance) {
const Component = instance.type
// explicit self name has highest priority
if (type === COMPONENTS) {
const selfName = getComponentName(Component)
if (
selfName &&
(selfName === name ||
selfName === camelize(name) ||
selfName === capitalize(camelize(name)))
) {
return Component
}
}
const res =
// global registration
resolve(instance.appContext[type], name)
if (__DEV__ && warnMissing && !res) {
const extra =
type === COMPONENTS
? `\nIf this is a native custom element, make sure to exclude it from ` +
`component resolution via compilerOptions.isCustomElement.`
: ``
warn(`Failed to resolve ${type.slice(0, -1)}: ${name}${extra}`)
}
return res
} else if (__DEV__) {
warn(
`resolve${capitalize(type.slice(0, -1))} ` +
`can only be used in render() or setup().`,
)
}
}
function resolve(registry: Record<string, any> | undefined, name: string) {
return (
registry &&
(registry[name] ||
registry[camelize(name)] ||
registry[capitalize(camelize(name))])
)
}
/**
* @private
*/
export function resolveDynamicComponent(
component: string | Component,
): string | Component {
if (isString(component)) {
return resolveAsset(COMPONENTS, component, false) || component
} else {
return component
}
}

View File

@ -1,9 +0,0 @@
import { toHandlers as _toHandlers } from '@vue/runtime-shared'
import { warn } from '../warning'
import { NOOP } from '@vue/shared'
export const toHandlers = (
obj: Record<string, any>,
preserveCaseIfNecessary?: boolean | undefined,
): Record<string, any> =>
_toHandlers(__DEV__ ? warn : NOOP, obj, preserveCaseIfNecessary)

View File

@ -1,110 +1,9 @@
// Core API ------------------------------------------------------------------ export { createComponent } from './component'
export const version: string = __VERSION__
export {
// core
TrackOpTypes,
TriggerOpTypes,
reactive,
ref,
readonly,
computed,
// utilities
unref,
proxyRefs,
isRef,
toRef,
toValue,
toRefs,
isProxy,
isReactive,
isReadonly,
isShallow,
// advanced
customRef,
triggerRef,
shallowRef,
shallowReactive,
shallowReadonly,
markRaw,
toRaw,
// effect
stop,
ReactiveEffect,
onEffectCleanup,
// effect scope
effectScope,
EffectScope,
getCurrentScope,
onScopeDispose,
// baseWatch
onWatcherCleanup,
getCurrentWatcher,
} from '@vue/reactivity'
export type {
Ref,
MaybeRef,
MaybeRefOrGetter,
ToRef,
ToRefs,
UnwrapRef,
ShallowRef,
ShallowUnwrapRef,
CustomRefFactory,
ReactiveFlags,
DeepReadonly,
ShallowReactive,
UnwrapNestedRefs,
ComputedRef,
WritableComputedRef,
WritableComputedOptions,
ComputedGetter,
ComputedSetter,
ReactiveEffectRunner,
ReactiveEffectOptions,
EffectScheduler,
DebuggerOptions,
DebuggerEvent,
DebuggerEventExtraInfo,
Raw,
Reactive,
} from '@vue/reactivity'
import { NOOP } from '@vue/shared'
import { warn as _warn } from './warning'
export const warn = (__DEV__ ? _warn : NOOP) as typeof _warn
export { nextTick } from './scheduler'
export {
getCurrentInstance,
ComponentInternalInstance,
type Component as Component,
type ObjectComponent,
type FunctionalComponent,
type SetupFn,
} from './component'
export { createSlot } from './componentSlots'
export { renderEffect } from './renderEffect' export { renderEffect } from './renderEffect'
export { export { createVaporApp } from './apiCreateApp'
watch, export { useEmit } from './componentEmits'
watchEffect,
watchPostEffect,
watchSyncEffect,
type WatchEffect,
type WatchOptions,
type WatchOptionsBase,
type WatchCallback,
type WatchSource,
type WatchStopHandle,
} from './apiWatch'
export {
withDirectives,
type Directive,
type DirectiveBinding,
type DirectiveArguments,
type DirectiveModifiers,
} from './directives'
// DOM
export { template, children, next } from './dom/template' export { template, children, next } from './dom/template'
export { insert, prepend, remove, createTextNode } from './dom/element' export { insert, prepend, remove, createTextNode } from './dom/element'
export { setStyle } from './dom/style' export { setStyle } from './dom/style'
@ -120,75 +19,3 @@ export {
} from './dom/prop' } from './dom/prop'
export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event' export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event'
export { setRef } from './dom/templateRef' export { setRef } from './dom/templateRef'
export { defineComponent } from './apiDefineComponent'
export {
type InjectionKey,
inject,
provide,
hasInjectionContext,
} from './apiInject'
export {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
// onActivated,
// onDeactivated,
onRenderTracked,
onRenderTriggered,
onErrorCaptured,
// onServerPrefetch,
} from './apiLifecycle'
export { useAttrs, useSlots } from './apiSetupHelpers'
export {
createVaporApp,
type App,
type AppConfig,
type AppContext,
type Plugin,
type ObjectPlugin,
type FunctionPlugin,
} from './apiCreateVaporApp'
export { createBranch, createIf } from './apiCreateIf'
export { createFor, createForSlots } from './apiCreateFor'
export { createComponent } from './apiCreateComponent'
export { createSelector } from './apiCreateSelector'
export { setInheritAttrs } from './componentAttrs'
export {
resolveComponent,
resolveDirective,
resolveDynamicComponent,
} from './helpers/resolveAssets'
export { toHandlers } from './helpers/toHandlers'
export { withDestructure } from './destructure'
// **Internal** DOM-only runtime directive helpers
export {
vModelText,
vModelCheckbox,
vModelRadio,
vModelSelect,
vModelDynamic,
} from './directives/vModel'
export { vShow } from './directives/vShow'
// For devtools
import {
type DevtoolsHook,
devtools as _devtools,
setDevtoolsHook as _setDevtoolsHook,
} from './devtools'
export const devtools = (
__DEV__ || __ESM_BUNDLER__ ? _devtools : undefined
) as DevtoolsHook
export const setDevtoolsHook = (
__DEV__ || __ESM_BUNDLER__ ? _setDevtoolsHook : NOOP
) as typeof _setDevtoolsHook
export * from './_new'

View File

@ -1,57 +0,0 @@
/* eslint-disable no-restricted-globals */
import {
type ComponentInternalInstance,
formatComponentName,
} from './component'
import { devtoolsPerfEnd, devtoolsPerfStart } from './devtools'
let supported: boolean
let perf: Performance
export function startMeasure(
instance: ComponentInternalInstance,
type: string,
): void {
if (instance.appContext.config.performance && isSupported()) {
perf.mark(`vue-${type}-${instance.uid}`)
}
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsPerfStart(instance, type, isSupported() ? perf.now() : Date.now())
}
}
export function endMeasure(
instance: ComponentInternalInstance,
type: string,
): void {
if (instance.appContext.config.performance && isSupported()) {
const startTag = `vue-${type}-${instance.uid}`
const endTag = startTag + `:end`
perf.mark(endTag)
perf.measure(
`<${formatComponentName(instance, instance.type)}> ${type}`,
startTag,
endTag,
)
perf.clearMarks(startTag)
perf.clearMarks(endTag)
}
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsPerfEnd(instance, type, isSupported() ? perf.now() : Date.now())
}
}
function isSupported() {
if (supported !== undefined) {
return supported
}
if (typeof window !== 'undefined' && window.performance) {
supported = true
perf = window.performance
} else {
supported = false
}
return supported
}

View File

@ -1,95 +1,19 @@
import { EffectFlags, ReactiveEffect, getCurrentScope } from '@vue/reactivity' import { ReactiveEffect } from '@vue/reactivity'
import { invokeArrayFns } from '@vue/shared' import { type SchedulerJob, queueJob } from '@vue/runtime-core'
import { import { currentInstance } from './component'
type ComponentInternalInstance,
getCurrentInstance,
setCurrentInstance,
} from './component'
import {
type SchedulerJob,
VaporSchedulerJobFlags,
queueJob,
queuePostFlushCb,
} from './scheduler'
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
export function renderEffect(cb: () => void): void { export function renderEffect(fn: () => void): void {
const instance = getCurrentInstance() const updateFn = () => {
const scope = getCurrentScope() fn()
if (scope) {
const baseCb = cb
cb = () => scope.run(baseCb)
} }
const effect = new ReactiveEffect(updateFn)
if (instance) { const job: SchedulerJob = effect.runIfDirty.bind(effect)
const baseCb = cb job.i = currentInstance as any
cb = () => { job.id = currentInstance!.uid
const reset = setCurrentInstance(instance)
baseCb()
reset()
}
job.id = instance.uid
}
const effect = new ReactiveEffect(() =>
callWithAsyncErrorHandling(cb, instance, VaporErrorCodes.RENDER_FUNCTION),
)
effect.scheduler = () => queueJob(job)
if (__DEV__ && instance) {
effect.onTrack = instance.rtc
? e => invokeArrayFns(instance.rtc!, e)
: void 0
effect.onTrigger = instance.rtg
? e => invokeArrayFns(instance.rtg!, e)
: void 0
}
effect.run()
function job() {
if (!(effect.flags & EffectFlags.ACTIVE) || !effect.dirty) {
return
}
const reset = instance && setCurrentInstance(instance)
if (instance && instance.isMounted && !instance.isUpdating) {
instance.isUpdating = true
const { bu, u } = instance
// beforeUpdate hook
if (bu) {
invokeArrayFns(bu)
}
effect.run()
queuePostFlushCb(() => {
instance.isUpdating = false
const reset = setCurrentInstance(instance)
// updated hook
if (u) {
queuePostFlushCb(u)
}
reset()
})
} else {
effect.run()
}
reset && reset()
}
}
export function firstEffect(
instance: ComponentInternalInstance,
fn: () => void,
): void {
const effect = new ReactiveEffect(fn)
const job: SchedulerJob = () => effect.run()
job.flags! |= VaporSchedulerJobFlags.PRE
job.id = instance.uid
effect.scheduler = () => queueJob(job) effect.scheduler = () => queueJob(job)
effect.run() effect.run()
// TODO lifecycle
// TODO recurse handling
// TODO measure
} }

View File

@ -1,227 +0,0 @@
import type { WatchScheduler } from '@vue/reactivity'
import type { ComponentInternalInstance } from './component'
import { isArray } from '@vue/shared'
export enum VaporSchedulerJobFlags {
QUEUED = 1 << 0,
PRE = 1 << 1,
/**
* Indicates whether the effect is allowed to recursively trigger itself
* when managed by the scheduler.
*
* By default, a job cannot trigger itself because some built-in method calls,
* e.g. Array.prototype.push actually performs reads as well (#1740) which
* can lead to confusing infinite loops.
* The allowed cases are component update functions and watch callbacks.
* Component update functions may update child component props, which in turn
* trigger flush: "pre" watch callbacks that mutates state that the parent
* relies on (#1801). Watch callbacks doesn't track its dependencies so if it
* triggers itself again, it's likely intentional and it is the user's
* responsibility to perform recursive state mutation that eventually
* stabilizes (#1727).
*/
ALLOW_RECURSE = 1 << 2,
DISPOSED = 1 << 3,
}
export interface SchedulerJob extends Function {
id?: number
/**
* flags can technically be undefined, but it can still be used in bitwise
* operations just like 0.
*/
flags?: VaporSchedulerJobFlags
/**
* Attached by renderer.ts when setting up a component's render effect
* Used to obtain component information when reporting max recursive updates.
*/
i?: ComponentInternalInstance
}
export type SchedulerJobs = SchedulerJob | SchedulerJob[]
export type QueueEffect = (
cb: SchedulerJobs,
suspense: ComponentInternalInstance | null,
) => void
let isFlushing = false
let isFlushPending = false
// TODO: The queues in Vapor need to be merged with the queues in Core.
// this is a temporary solution, the ultimate goal is to support
// the mixed use of vapor components and default components.
const queue: SchedulerJob[] = []
let flushIndex = 0
// TODO: The queues in Vapor need to be merged with the queues in Core.
// this is a temporary solution, the ultimate goal is to support
// the mixed use of vapor components and default components.
const pendingPostFlushCbs: SchedulerJob[] = []
let activePostFlushCbs: SchedulerJob[] | null = null
let postFlushIndex = 0
const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
let currentFlushPromise: Promise<void> | null = null
export function queueJob(job: SchedulerJob): void {
let lastOne: SchedulerJob | undefined
if (!(job.flags! & VaporSchedulerJobFlags.QUEUED)) {
if (job.id == null) {
queue.push(job)
} else if (
// fast path when the job id is larger than the tail
!(job.flags! & VaporSchedulerJobFlags.PRE) &&
job.id >= (((lastOne = queue[queue.length - 1]) && lastOne.id) || 0)
) {
queue.push(job)
} else {
queue.splice(findInsertionIndex(job.id), 0, job)
}
if (!(job.flags! & VaporSchedulerJobFlags.ALLOW_RECURSE)) {
job.flags! |= VaporSchedulerJobFlags.QUEUED
}
queueFlush()
}
}
export function queuePostFlushCb(cb: SchedulerJobs): void {
if (!isArray(cb)) {
if (!(cb.flags! & VaporSchedulerJobFlags.QUEUED)) {
pendingPostFlushCbs.push(cb)
if (!(cb.flags! & VaporSchedulerJobFlags.ALLOW_RECURSE)) {
cb.flags! |= VaporSchedulerJobFlags.QUEUED
}
}
} else {
// if cb is an array, it is a component lifecycle hook which can only be
// triggered by a job, which is already deduped in the main queue, so
// we can skip duplicate check here to improve perf
pendingPostFlushCbs.push(...cb)
}
queueFlush()
}
function queueFlush() {
if (!isFlushing && !isFlushPending) {
isFlushPending = true
currentFlushPromise = resolvedPromise.then(flushJobs)
}
}
export function flushPostFlushCbs(): void {
if (!pendingPostFlushCbs.length) return
const deduped = [...new Set(pendingPostFlushCbs)]
pendingPostFlushCbs.length = 0
// #1947 already has active queue, nested flushPostFlushCbs call
if (activePostFlushCbs) {
activePostFlushCbs.push(...deduped)
return
}
activePostFlushCbs = deduped
activePostFlushCbs.sort((a, b) => getId(a) - getId(b))
for (
postFlushIndex = 0;
postFlushIndex < activePostFlushCbs.length;
postFlushIndex++
) {
activePostFlushCbs[postFlushIndex]()
activePostFlushCbs[postFlushIndex].flags! &= ~VaporSchedulerJobFlags.QUEUED
}
activePostFlushCbs = null
postFlushIndex = 0
}
// TODO: dev mode and checkRecursiveUpdates
function flushJobs() {
if (__BENCHMARK__) performance.mark('flushJobs-start')
isFlushPending = false
isFlushing = true
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child so its render effect will have smaller
// priority number)
// 2. If a component is unmounted during a parent component's update,
// its update can be skipped.
queue.sort(comparator)
try {
for (let i = 0; i < queue!.length; i++) {
queue[i]()
queue[i].flags! &= ~VaporSchedulerJobFlags.QUEUED
}
} finally {
flushIndex = 0
queue.length = 0
flushPostFlushCbs()
isFlushing = false
currentFlushPromise = null
// some postFlushCb queued jobs!
// keep flushing until it drains.
if (queue.length || pendingPostFlushCbs.length) {
flushJobs()
}
if (__BENCHMARK__) performance.mark('flushJobs-end')
}
}
export function nextTick<T = void, R = void>(
this: T,
fn?: (this: T) => R,
): Promise<Awaited<R>> {
const p = currentFlushPromise || resolvedPromise
return fn ? p.then(this ? fn.bind(this) : fn) : p
}
// #2768
// Use binary-search to find a suitable position in the queue,
// so that the queue maintains the increasing order of job's id,
// which can prevent the job from being skipped and also can avoid repeated patching.
function findInsertionIndex(id: number) {
// the start index should be `flushIndex + 1`
let start = flushIndex + 1
let end = queue.length
while (start < end) {
const middle = (start + end) >>> 1
const middleJob = queue[middle]
const middleJobId = getId(middleJob)
if (
middleJobId < id ||
(middleJobId === id && middleJob.flags! & VaporSchedulerJobFlags.PRE)
) {
start = middle + 1
} else {
end = middle
}
}
return start
}
const getId = (job: SchedulerJob): number =>
job.id == null ? Infinity : job.id
const comparator = (a: SchedulerJob, b: SchedulerJob): number => {
const diff = getId(a) - getId(b)
if (diff === 0) {
const isAPre = a.flags! & VaporSchedulerJobFlags.PRE
const isBPre = b.flags! & VaporSchedulerJobFlags.PRE
if (isAPre && !isBPre) return -1
if (isBPre && !isAPre) return 1
}
return diff
}
export type SchedulerFactory = (
instance: ComponentInternalInstance | null,
) => WatchScheduler

View File

@ -1,153 +0,0 @@
import {
type ComponentInternalInstance,
currentInstance,
formatComponentName,
} from './component'
import { isFunction, isString } from '@vue/shared'
import { isRef, pauseTracking, resetTracking, toRaw } from '@vue/reactivity'
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
import type { NormalizedRawProps } from './componentProps'
type TraceEntry = {
instance: ComponentInternalInstance
recurseCount: number
}
type ComponentTraceStack = TraceEntry[]
export function warn(msg: string, ...args: any[]): void {
// avoid props formatting or warn handler tracking deps that might be mutated
// during patch, leading to infinite recursion.
pauseTracking()
const instance = currentInstance
const appWarnHandler = instance && instance.appContext.config.warnHandler
const trace = getComponentTrace()
if (appWarnHandler) {
callWithErrorHandling(
appWarnHandler,
instance,
VaporErrorCodes.APP_WARN_HANDLER,
[
msg +
args
.map(a => (a.toString && a.toString()) ?? JSON.stringify(a))
.join(''),
instance,
trace
.map(
({ instance }) =>
`at <${formatComponentName(instance, instance.type)}>`,
)
.join('\n'),
trace,
],
)
} else {
const warnArgs = [`[Vue warn]: ${msg}`, ...args]
/* istanbul ignore if */
if (
trace.length &&
// avoid spamming console during tests
!__TEST__
) {
warnArgs.push(`\n`, ...formatTrace(trace))
}
console.warn(...warnArgs)
}
resetTracking()
}
export function getComponentTrace(): ComponentTraceStack {
let instance = currentInstance
if (!instance) return []
// we can't just use the stack because it will be incomplete during updates
// that did not start from the root. Re-construct the parent chain using
// instance parent pointers.
const stack: ComponentTraceStack = []
while (instance) {
const last = stack[0]
if (last && last.instance === instance) {
last.recurseCount++
} else {
stack.push({
instance,
recurseCount: 0,
})
}
instance = instance.parent
}
return stack
}
function formatTrace(trace: ComponentTraceStack): any[] {
const logs: any[] = []
trace.forEach((entry, i) => {
logs.push(...(i === 0 ? [] : [`\n`]), ...formatTraceEntry(entry))
})
return logs
}
function formatTraceEntry({ instance, recurseCount }: TraceEntry): any[] {
const postfix =
recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``
const isRoot = instance ? instance.parent == null : false
const open = ` at <${formatComponentName(instance, instance.type, isRoot)}`
const close = `>` + postfix
return instance.rawProps.length
? [open, ...formatProps(instance.rawProps), close]
: [open + close]
}
function formatProps(rawProps: NormalizedRawProps): any[] {
const fullProps: Record<string, any> = {}
for (const props of rawProps) {
if (isFunction(props)) {
const propsObj = props()
for (const key in propsObj) {
fullProps[key] = propsObj[key]
}
} else {
for (const key in props) {
fullProps[key] = props[key]()
}
}
}
const res: any[] = []
Object.keys(fullProps)
.slice(0, 3)
.forEach(key => res.push(...formatProp(key, fullProps[key])))
if (fullProps.length > 3) {
res.push(` ...`)
}
return res
}
function formatProp(key: string, value: unknown, raw?: boolean): any {
if (isString(value)) {
value = JSON.stringify(value)
return raw ? value : [`${key}=${value}`]
} else if (
typeof value === 'number' ||
typeof value === 'boolean' ||
value == null
) {
return raw ? value : [`${key}=${value}`]
} else if (isRef(value)) {
value = formatProp(key, toRaw(value.value), true)
return raw ? value : [`${key}=Ref<`, value, `>`]
} else if (isFunction(value)) {
return [`${key}=fn${value.name ? `<${value.name}>` : ``}`]
} else {
value = toRaw(value)
return raw ? value : [`${key}=`, value]
}
}

View File

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

View File

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

View File

@ -39,5 +39,8 @@
"scripts/*", "scripts/*",
"rollup.*.js" "rollup.*.js"
], ],
"exclude": ["packages-private/sfc-playground/src/vue-dev-proxy*"] "exclude": [
"packages-private/sfc-playground/src/vue-dev-proxy*",
"packages/runtime-vapor/src/_old"
]
} }