mirror of https://github.com/vuejs/core.git
refactor: remove runtime-shared
This commit is contained in:
parent
4fe05bdd74
commit
c73ee16345
|
@ -43,7 +43,7 @@ export interface BaseIRNode {
|
|||
type: IRNodeTypes
|
||||
}
|
||||
|
||||
export type VaporHelper = keyof typeof import('@vue/runtime-vapor')
|
||||
export type VaporHelper = keyof typeof import('packages/runtime-vapor/src')
|
||||
|
||||
export interface BlockIRNode extends BaseIRNode {
|
||||
type: IRNodeTypes.BLOCK
|
||||
|
|
|
@ -47,7 +47,6 @@
|
|||
"homepage": "https://github.com/vuejs/vue-vapor/tree/main/packages/runtime-core#readme",
|
||||
"dependencies": {
|
||||
"@vue/shared": "workspace:*",
|
||||
"@vue/reactivity": "workspace:*",
|
||||
"@vue/runtime-shared": "workspace:*"
|
||||
"@vue/reactivity": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
type Component,
|
||||
type ConcreteComponent,
|
||||
type Data,
|
||||
type GenericComponent,
|
||||
type GenericComponentInstance,
|
||||
getComponentPublicInstance,
|
||||
|
@ -22,7 +23,6 @@ import { warn } from './warning'
|
|||
import type { VNode } from './vnode'
|
||||
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
|
||||
import { NO, extend, isFunction, isObject } from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import { version } from '.'
|
||||
import { installAppCompatProperties } from './compat/global'
|
||||
import type { NormalizedPropsOptions } from './componentProps'
|
||||
|
@ -256,8 +256,8 @@ export function createAppContext(): AppContext {
|
|||
}
|
||||
}
|
||||
|
||||
export type CreateAppFunction<HostElement> = (
|
||||
rootComponent: GenericComponent,
|
||||
export type CreateAppFunction<HostElement, Comp = Component> = (
|
||||
rootComponent: Comp,
|
||||
rootProps?: Data | null,
|
||||
) => App<HostElement>
|
||||
|
||||
|
@ -275,13 +275,13 @@ export type AppUnmountFn = (app: App) => void
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function createAppAPI<HostElement>(
|
||||
export function createAppAPI<HostElement, Comp = Component>(
|
||||
// render: RootRenderFunction<HostElement>,
|
||||
// hydrate?: RootHydrateFunction,
|
||||
mount: AppMountFn<HostElement>,
|
||||
unmount: AppUnmountFn,
|
||||
render?: RootRenderFunction,
|
||||
): CreateAppFunction<HostElement> {
|
||||
): CreateAppFunction<HostElement, Comp> {
|
||||
return function createApp(rootComponent, rootProps = null) {
|
||||
if (!isFunction(rootComponent)) {
|
||||
rootComponent = extend({}, rootComponent)
|
||||
|
|
|
@ -462,7 +462,7 @@ function installCompatMount(
|
|||
* function simulates that behavior.
|
||||
*/
|
||||
app._createRoot = options => {
|
||||
const component = app._component
|
||||
const component = app._component as Component
|
||||
const vnode = createVNode(component, options.propsData || null)
|
||||
vnode.appContext = context
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { isArray } from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import { inject } from '../apiInject'
|
||||
import type { ComponentInternalInstance } from '../component'
|
||||
import type { ComponentInternalInstance, Data } from '../component'
|
||||
import {
|
||||
type ComponentOptions,
|
||||
resolveMergedOptions,
|
||||
|
|
|
@ -10,11 +10,11 @@ import {
|
|||
normalizeStyle,
|
||||
toHandlerKey,
|
||||
} from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import type {
|
||||
Component,
|
||||
ComponentInternalInstance,
|
||||
ComponentOptions,
|
||||
Data,
|
||||
InternalRenderFunction,
|
||||
} from '../component'
|
||||
import { currentRenderingInstance } from '../componentRenderContext'
|
||||
|
|
|
@ -7,13 +7,12 @@ import {
|
|||
isReservedProp,
|
||||
normalizeClass,
|
||||
} from '@vue/shared'
|
||||
import type { ComponentInternalInstance } from '../component'
|
||||
import type { ComponentInternalInstance, Data } from '../component'
|
||||
import type { Slot } from '../componentSlots'
|
||||
import { createSlots } from '../helpers/createSlots'
|
||||
import { renderSlot } from '../helpers/renderSlot'
|
||||
import { toHandlers } from '../helpers/toHandlers'
|
||||
import { type VNode, mergeProps } from '../vnode'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
|
||||
function toObject(arr: Array<any>): Object {
|
||||
const res = {}
|
||||
|
|
|
@ -73,7 +73,6 @@ import {
|
|||
isObject,
|
||||
isPromise,
|
||||
} from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import type { SuspenseBoundary } from './components/Suspense'
|
||||
import type { CompilerOptions } from '@vue/compiler-core'
|
||||
import { markAttrsAccessed } from './componentRenderUtils'
|
||||
|
@ -98,6 +97,8 @@ import { markAsyncBoundary } from './helpers/useId'
|
|||
import { isAsyncWrapper } from './apiAsyncComponent'
|
||||
import type { RendererElement } from './renderer'
|
||||
|
||||
export type Data = Record<string, unknown>
|
||||
|
||||
/**
|
||||
* Public utility type for extracting the instance type of a component.
|
||||
* Works with all valid component definition types. This is intended to replace
|
||||
|
@ -509,7 +510,7 @@ export interface ComponentInternalInstance extends GenericComponentInstance {
|
|||
* setup related
|
||||
* @internal
|
||||
*/
|
||||
setupState: Data | null
|
||||
setupState: Data
|
||||
/**
|
||||
* devtools access to additional info
|
||||
* @internal
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
type ComponentInternalInstance,
|
||||
type ComponentInternalOptions,
|
||||
type ConcreteComponent,
|
||||
type Data,
|
||||
type InternalRenderFunction,
|
||||
type SetupContext,
|
||||
currentInstance,
|
||||
|
@ -18,7 +19,6 @@ import {
|
|||
isPromise,
|
||||
isString,
|
||||
} from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import { type Ref, getCurrentScope, isRef, traverse } from '@vue/reactivity'
|
||||
import { computed } from './apiComputed'
|
||||
import {
|
||||
|
|
|
@ -24,12 +24,12 @@ import {
|
|||
makeMap,
|
||||
toRawType,
|
||||
} from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import { warn } from './warning'
|
||||
import {
|
||||
type ComponentInternalInstance,
|
||||
type ComponentOptions,
|
||||
type ConcreteComponent,
|
||||
type Data,
|
||||
type GenericComponentInstance,
|
||||
setCurrentInstance,
|
||||
} from './component'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
type Component,
|
||||
type ComponentInternalInstance,
|
||||
type Data,
|
||||
getComponentPublicInstance,
|
||||
isStatefulComponent,
|
||||
} from './component'
|
||||
|
@ -23,7 +24,6 @@ import {
|
|||
isGloballyAllowed,
|
||||
isString,
|
||||
} from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import {
|
||||
ReactiveFlags,
|
||||
type ShallowUnwrapRef,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
type ComponentInternalInstance,
|
||||
type Data,
|
||||
type FunctionalComponent,
|
||||
getComponentName,
|
||||
} from './component'
|
||||
|
@ -15,7 +16,6 @@ import {
|
|||
} from './vnode'
|
||||
import { ErrorCodes, handleError } from './errorHandling'
|
||||
import { PatchFlags, ShapeFlags, isModelListener, isOn } from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import { warn } from './warning'
|
||||
import { isHmrUpdating } from './hmr'
|
||||
import type { NormalizedProps } from './componentProps'
|
||||
|
|
|
@ -13,10 +13,10 @@ return withDirectives(h(comp), [
|
|||
|
||||
import type { VNode } from './vnode'
|
||||
import { EMPTY_OBJ, isBuiltInDirective, isFunction } from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import { warn } from './warning'
|
||||
import {
|
||||
type ComponentInternalInstance,
|
||||
type Data,
|
||||
getComponentPublicInstance,
|
||||
} from './component'
|
||||
import { currentRenderingInstance } from './componentRenderContext'
|
||||
|
|
|
@ -14,9 +14,9 @@ import {
|
|||
openBlock,
|
||||
} from '../vnode'
|
||||
import { PatchFlags, SlotFlags, isSymbol } from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import { warn } from '../warning'
|
||||
import { isAsyncWrapper } from '../apiAsyncComponent'
|
||||
import type { Data } from '../component'
|
||||
|
||||
/**
|
||||
* Compiler runtime helper for rendering `<slot/>`
|
||||
|
|
|
@ -1,8 +1,25 @@
|
|||
import { toHandlers as _toHandlers } from '@vue/runtime-shared'
|
||||
import { isObject, toHandlerKey } from '@vue/shared'
|
||||
import { warn } from '../warning'
|
||||
import { NOOP } from '@vue/shared'
|
||||
|
||||
export const toHandlers: (
|
||||
/**
|
||||
* For prefixing keys in v-on="obj" with "on"
|
||||
* @private
|
||||
*/
|
||||
export function toHandlers(
|
||||
obj: Record<string, any>,
|
||||
preserveCaseIfNecessary?: boolean | undefined,
|
||||
) => Record<string, any> = _toHandlers.bind(undefined, __DEV__ ? warn : NOOP)
|
||||
preserveCaseIfNecessary?: boolean,
|
||||
): Record<string, any> {
|
||||
const ret: Record<string, any> = {}
|
||||
if (__DEV__ && !isObject(obj)) {
|
||||
warn(`v-on with no argument expects an object value.`)
|
||||
return ret
|
||||
}
|
||||
for (const key in obj) {
|
||||
ret[
|
||||
preserveCaseIfNecessary && /[A-Z]/.test(key)
|
||||
? `on:${key}`
|
||||
: toHandlerKey(key)
|
||||
] = obj[key]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
import {
|
||||
type ComponentInternalInstance,
|
||||
type ComponentOptions,
|
||||
type Data,
|
||||
type LifecycleHook,
|
||||
createComponentInstance,
|
||||
setupComponent,
|
||||
|
@ -39,7 +40,6 @@ import {
|
|||
isArray,
|
||||
isReservedProp,
|
||||
} from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import {
|
||||
type SchedulerJob,
|
||||
SchedulerJobFlags,
|
||||
|
|
|
@ -12,12 +12,12 @@ import {
|
|||
normalizeClass,
|
||||
normalizeStyle,
|
||||
} from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import {
|
||||
type ClassComponent,
|
||||
type Component,
|
||||
type ComponentInternalInstance,
|
||||
type ConcreteComponent,
|
||||
type Data,
|
||||
isClassComponent,
|
||||
} from './component'
|
||||
import type { RawSlots } from './componentSlots'
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import {
|
||||
type ComponentInternalInstance,
|
||||
type Data,
|
||||
type GenericComponentInstance,
|
||||
formatComponentName,
|
||||
} from './component'
|
||||
import { isFunction, isString } from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import { isRef, pauseTracking, resetTracking, toRaw } from '@vue/reactivity'
|
||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||
import { type VNode, isVNode } from './vnode'
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
type App,
|
||||
type Component,
|
||||
type ConcreteComponent,
|
||||
type CreateAppFunction,
|
||||
type DefineComponent,
|
||||
|
@ -144,7 +145,7 @@ export const createApp = ((...args) => {
|
|||
}
|
||||
|
||||
return app
|
||||
}) as CreateAppFunction<Element>
|
||||
}) as CreateAppFunction<Element, Component>
|
||||
|
||||
export const createSSRApp = ((...args) => {
|
||||
const app = ensureHydrationRenderer().createApp(...args)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-present, Yuxi (Evan) You
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -1 +0,0 @@
|
|||
# @vue/runtime-shared
|
|
@ -1,7 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./dist/runtime-shared.cjs.prod.js')
|
||||
} else {
|
||||
module.exports = require('./dist/runtime-shared.cjs.js')
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
{
|
||||
"name": "@vue/runtime-shared",
|
||||
"version": "3.0.0-vapor",
|
||||
"description": "@vue/runtime-shared",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-shared.esm-bundler.js",
|
||||
"types": "dist/runtime-shared.d.ts",
|
||||
"files": [
|
||||
"index.js",
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/runtime-shared.d.ts",
|
||||
"node": {
|
||||
"production": "./dist/runtime-shared.cjs.prod.js",
|
||||
"development": "./dist/runtime-shared.cjs.js",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"module": "./dist/runtime-shared.esm-bundler.js",
|
||||
"import": "./dist/runtime-shared.esm-bundler.js",
|
||||
"require": "./index.js"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"buildOptions": {
|
||||
"formats": [
|
||||
"esm-bundler",
|
||||
"cjs"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/vue-vapor.git",
|
||||
"directory": "packages/runtime-shared"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
],
|
||||
"author": "Evan You",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/vue-vapor/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vuejs/vue-vapor/tree/main/packages/runtime-shared#readme",
|
||||
"dependencies": {
|
||||
"@vue/shared": "workspace:*"
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
export { toHandlers } from './toHandlers'
|
||||
export { type Data } from './typeUtils'
|
|
@ -1,25 +0,0 @@
|
|||
import { isObject, toHandlerKey } from '@vue/shared'
|
||||
|
||||
/**
|
||||
* For prefixing keys in v-on="obj" with "on"
|
||||
* @private
|
||||
*/
|
||||
export function toHandlers(
|
||||
warn: (msg: string) => void,
|
||||
obj: Record<string, any>,
|
||||
preserveCaseIfNecessary?: boolean,
|
||||
): Record<string, any> {
|
||||
const ret: Record<string, any> = {}
|
||||
if (__DEV__ && !isObject(obj)) {
|
||||
warn(`v-on with no argument expects an object value.`)
|
||||
return ret
|
||||
}
|
||||
for (const key in obj) {
|
||||
ret[
|
||||
preserveCaseIfNecessary && /[A-Z]/.test(key)
|
||||
? `on:${key}`
|
||||
: toHandlerKey(key)
|
||||
] = obj[key]
|
||||
}
|
||||
return ret
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export type Data = Record<string, unknown>
|
|
@ -6,8 +6,8 @@ import {
|
|||
type SetupFn,
|
||||
createVaporApp,
|
||||
defineComponent,
|
||||
} from '../src'
|
||||
import type { RawProps } from '../src/componentProps'
|
||||
} from '../src/_old'
|
||||
import type { RawProps } from '../src/_old/componentProps'
|
||||
|
||||
export interface RenderContext {
|
||||
component: Component
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ref } from '@vue/reactivity'
|
||||
import { makeRender } from './_utils'
|
||||
import { createFor, createSelector, nextTick, renderEffect } from '../src'
|
||||
import { createFor, createSelector, nextTick, renderEffect } from '../src/_old'
|
||||
|
||||
const define = makeRender()
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ import {
|
|||
resolveComponent,
|
||||
resolveDirective,
|
||||
withDirectives,
|
||||
} from '../src'
|
||||
import { warn } from '../src/warning'
|
||||
} from '../src/_old'
|
||||
import { warn } from '../src/_old/warning'
|
||||
import { makeRender } from './_utils'
|
||||
|
||||
const define = makeRender()
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { ref, shallowRef } from '@vue/reactivity'
|
||||
import { createComponent } from '../src/apiCreateComponent'
|
||||
import { createComponent } from '../src/_old/apiCreateComponent'
|
||||
import { setRef } from '../src/dom/templateRef'
|
||||
import { makeRender } from './_utils'
|
||||
import {
|
||||
type ComponentInternalInstance,
|
||||
getCurrentInstance,
|
||||
} from '../src/component'
|
||||
import { defineComponent } from '../src/apiDefineComponent'
|
||||
} from '../src/_old/component'
|
||||
import { defineComponent } from '../src/_old/apiDefineComponent'
|
||||
|
||||
const define = makeRender()
|
||||
describe('api: expose', () => {
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
ref,
|
||||
renderEffect,
|
||||
setText,
|
||||
} from '../src'
|
||||
} from '../src/_old'
|
||||
import { makeRender } from './_utils'
|
||||
|
||||
const define = makeRender<any>()
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
renderEffect,
|
||||
setText,
|
||||
template,
|
||||
} from '../src'
|
||||
} from '../src/_old'
|
||||
import { makeRender } from './_utils'
|
||||
import { ITERATE_KEY } from '@vue/reactivity'
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
setInheritAttrs,
|
||||
template,
|
||||
watchEffect,
|
||||
} from '../src'
|
||||
} from '../src/_old'
|
||||
import { makeRender } from './_utils'
|
||||
|
||||
const define = makeRender()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { SetupContext } from '../src/component'
|
||||
import type { SetupContext } from '../src/_old/component'
|
||||
import {
|
||||
createComponent,
|
||||
defineComponent,
|
||||
|
@ -6,7 +6,7 @@ import {
|
|||
template,
|
||||
useAttrs,
|
||||
useSlots,
|
||||
} from '../src'
|
||||
} from '../src/_old'
|
||||
import { makeRender } from './_utils'
|
||||
|
||||
const define = makeRender<any>()
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
ref,
|
||||
watchEffect,
|
||||
watchSyncEffect,
|
||||
} from '../src'
|
||||
} from '../src/_old'
|
||||
|
||||
describe('watchEffect and onWatcherCleanup', () => {
|
||||
test('basic', async () => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ref, setText, template, watchEffect } from '../src'
|
||||
import { ref, setText, template, watchEffect } from '../src/_old'
|
||||
import { describe, expect } from 'vitest'
|
||||
import { makeRender } from './_utils'
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
setText,
|
||||
template,
|
||||
watchEffect,
|
||||
} from '../src'
|
||||
} from '../src/_old'
|
||||
import { makeRender } from './_utils'
|
||||
|
||||
const define = makeRender<any>()
|
||||
|
|
|
@ -9,8 +9,8 @@ import {
|
|||
defineComponent,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
} from '../src'
|
||||
import { isEmitListener } from '../src/componentEmits'
|
||||
} from '../src/_old'
|
||||
import { isEmitListener } from '../src/_old/componentEmits'
|
||||
import { makeRender } from './_utils'
|
||||
|
||||
const define = makeRender()
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
toRefs,
|
||||
watch,
|
||||
watchEffect,
|
||||
} from '../src'
|
||||
} from '../src/_old'
|
||||
import { makeRender } from './_utils'
|
||||
|
||||
const define = makeRender<any>()
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
setText,
|
||||
template,
|
||||
withDestructure,
|
||||
} from '../src'
|
||||
} from '../src/_old'
|
||||
import { makeRender } from './_utils'
|
||||
|
||||
const define = makeRender<any>()
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
vModelDynamic,
|
||||
vModelSelect,
|
||||
withDirectives,
|
||||
} from '../../src'
|
||||
} from '../../src/_old'
|
||||
import { makeRender } from '../_utils'
|
||||
import { nextTick } from '@vue/runtime-dom'
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
template,
|
||||
vShow,
|
||||
withDirectives,
|
||||
} from '../../src'
|
||||
} from '../../src/_old'
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { makeRender } from '../_utils'
|
||||
|
|
|
@ -13,7 +13,7 @@ import { setStyle } from '../../src/dom/style'
|
|||
import {
|
||||
ComponentInternalInstance,
|
||||
setCurrentInstance,
|
||||
} from '../../src/component'
|
||||
} from '../../src/_old/component'
|
||||
import { getMetadata, recordPropMetadata } from '../../src/componentMetadata'
|
||||
import { getCurrentScope } from '@vue/reactivity'
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
setText,
|
||||
template,
|
||||
watchEffect,
|
||||
} from '../../src'
|
||||
} from '../../src/_old'
|
||||
import { makeRender } from '../_utils'
|
||||
|
||||
const define = makeRender()
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import type { Component } from '../src/component'
|
||||
import type { Component } from '../src/_old/component'
|
||||
import { type RefEl, setRef } from '../src/dom/templateRef'
|
||||
import { onErrorCaptured, onMounted } from '../src/apiLifecycle'
|
||||
import { createComponent } from '../src/apiCreateComponent'
|
||||
import { onErrorCaptured, onMounted } from '../src/_old/apiLifecycle'
|
||||
import { createComponent } from '../src/_old/apiCreateComponent'
|
||||
import { makeRender } from './_utils'
|
||||
import { template } from '../src/dom/template'
|
||||
import { watch, watchEffect } from '../src/apiWatch'
|
||||
import { nextTick } from '../src/scheduler'
|
||||
import { watch, watchEffect } from '../src/_old/apiWatch'
|
||||
import { nextTick } from '../src/_old/scheduler'
|
||||
import { ref } from '@vue/reactivity'
|
||||
|
||||
const define = makeRender()
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
template,
|
||||
triggerRef,
|
||||
withDestructure,
|
||||
} from '../src'
|
||||
} from '../src/_old'
|
||||
import { makeRender } from './_utils'
|
||||
|
||||
const define = makeRender()
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
createVaporApp,
|
||||
resolveComponent,
|
||||
resolveDirective,
|
||||
} from '@vue/runtime-vapor'
|
||||
} from 'packages/runtime-vapor/src/_old'
|
||||
import { makeRender } from '../_utils'
|
||||
|
||||
const define = makeRender()
|
||||
|
|
|
@ -8,10 +8,10 @@ import {
|
|||
setText,
|
||||
template,
|
||||
withDirectives,
|
||||
} from '../src'
|
||||
} from '../src/_old'
|
||||
import type { Mock } from 'vitest'
|
||||
import { makeRender } from './_utils'
|
||||
import { unmountComponent } from '../src/apiRender'
|
||||
import { unmountComponent } from '../src/_old/apiRender'
|
||||
|
||||
const define = makeRender()
|
||||
|
||||
|
|
|
@ -11,11 +11,11 @@ import {
|
|||
watchEffect,
|
||||
watchPostEffect,
|
||||
watchSyncEffect,
|
||||
} from '../src'
|
||||
} from '../src/_old'
|
||||
import {
|
||||
type ComponentInternalInstance,
|
||||
currentInstance,
|
||||
} from '../src/component'
|
||||
} from '../src/_old/component'
|
||||
import { makeRender } from './_utils'
|
||||
|
||||
const define = makeRender<any>()
|
||||
|
|
|
@ -36,7 +36,9 @@
|
|||
"homepage": "https://github.com/vuejs/vue-vapor/tree/dev/packages/runtime-vapor#readme",
|
||||
"dependencies": {
|
||||
"@vue/shared": "workspace:*",
|
||||
"@vue/reactivity": "workspace:*",
|
||||
"@vue/runtime-shared": "workspace:*"
|
||||
"@vue/reactivity": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/runtime-dom": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
_old
|
|
@ -1,224 +0,0 @@
|
|||
import {
|
||||
type ComponentInternalOptions,
|
||||
type ComponentPropsOptions,
|
||||
EffectScope,
|
||||
type EmitsOptions,
|
||||
type GenericAppContext,
|
||||
type GenericComponentInstance,
|
||||
type LifecycleHook,
|
||||
type NormalizedPropsOptions,
|
||||
type ObjectEmitsOptions,
|
||||
nextUid,
|
||||
popWarningContext,
|
||||
pushWarningContext,
|
||||
} from '@vue/runtime-core'
|
||||
import type { Block } from '../block'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import { pauseTracking, resetTracking } from '@vue/reactivity'
|
||||
import { EMPTY_OBJ, isFunction } from '@vue/shared'
|
||||
import {
|
||||
type RawProps,
|
||||
getDynamicPropsHandlers,
|
||||
initStaticProps,
|
||||
} from './componentProps'
|
||||
import { setDynamicProp } from '../dom/prop'
|
||||
import { renderEffect } from './renderEffect'
|
||||
|
||||
export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent
|
||||
|
||||
export type VaporSetupFn = (
|
||||
props: any,
|
||||
ctx: SetupContext,
|
||||
) => Block | Data | undefined
|
||||
|
||||
export type FunctionalVaporComponent = VaporSetupFn &
|
||||
Omit<ObjectVaporComponent, 'setup'> & {
|
||||
displayName?: string
|
||||
} & SharedInternalOptions
|
||||
|
||||
export interface ObjectVaporComponent
|
||||
extends ComponentInternalOptions,
|
||||
SharedInternalOptions {
|
||||
setup?: VaporSetupFn
|
||||
inheritAttrs?: boolean
|
||||
props?: ComponentPropsOptions
|
||||
emits?: EmitsOptions
|
||||
render?(ctx: any): Block
|
||||
|
||||
name?: string
|
||||
vapor?: boolean
|
||||
}
|
||||
|
||||
interface SharedInternalOptions {
|
||||
/**
|
||||
* Cached normalized props options.
|
||||
* In vapor mode there are no mixins so normalized options can be cached
|
||||
* directly on the component
|
||||
*/
|
||||
__propsOptions?: NormalizedPropsOptions
|
||||
/**
|
||||
* Cached normalized props proxy handlers.
|
||||
*/
|
||||
__propsHandlers?: [ProxyHandler<any> | null, ProxyHandler<any>]
|
||||
/**
|
||||
* Cached normalized emits options.
|
||||
*/
|
||||
__emitsOptions?: ObjectEmitsOptions
|
||||
}
|
||||
|
||||
export function createComponent(
|
||||
component: VaporComponent,
|
||||
rawProps?: RawProps,
|
||||
isSingleRoot?: boolean,
|
||||
): VaporComponentInstance {
|
||||
// check if we are the single root of the parent
|
||||
// if yes, inject parent attrs as dynamic props source
|
||||
if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) {
|
||||
if (rawProps) {
|
||||
;(rawProps.$ || (rawProps.$ = [])).push(currentInstance.attrs)
|
||||
} else {
|
||||
rawProps = { $: [currentInstance.attrs] }
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new VaporComponentInstance(component, rawProps)
|
||||
|
||||
pauseTracking()
|
||||
let prevInstance = currentInstance
|
||||
currentInstance = instance
|
||||
instance.scope.on()
|
||||
|
||||
if (__DEV__) {
|
||||
pushWarningContext(instance)
|
||||
}
|
||||
|
||||
const setupFn = isFunction(component) ? component : component.setup
|
||||
const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null
|
||||
instance.block = setupFn!(
|
||||
instance.props,
|
||||
// @ts-expect-error
|
||||
setupContext,
|
||||
) as Block // TODO handle return object
|
||||
|
||||
// single root, inherit attrs
|
||||
if (
|
||||
instance.hasFallthrough &&
|
||||
component.inheritAttrs !== false &&
|
||||
instance.block instanceof Element &&
|
||||
Object.keys(instance.attrs).length
|
||||
) {
|
||||
renderEffect(() => {
|
||||
for (const key in instance.attrs) {
|
||||
setDynamicProp(instance.block as Element, key, instance.attrs[key])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
popWarningContext()
|
||||
}
|
||||
|
||||
instance.scope.off()
|
||||
currentInstance = prevInstance
|
||||
resetTracking()
|
||||
return instance
|
||||
}
|
||||
|
||||
export let currentInstance: VaporComponentInstance | null = null
|
||||
|
||||
const emptyContext: GenericAppContext = {
|
||||
app: null as any,
|
||||
config: {},
|
||||
provides: /*@__PURE__*/ Object.create(null),
|
||||
}
|
||||
|
||||
export class VaporComponentInstance implements GenericComponentInstance {
|
||||
uid: number
|
||||
type: VaporComponent
|
||||
parent: GenericComponentInstance | null
|
||||
appContext: GenericAppContext
|
||||
|
||||
block: Block
|
||||
scope: EffectScope
|
||||
rawProps: RawProps | undefined
|
||||
props: Record<string, any>
|
||||
attrs: Record<string, any>
|
||||
exposed: Record<string, any> | null
|
||||
|
||||
emitted: Record<string, boolean> | null
|
||||
propsDefaults: Record<string, any> | null
|
||||
|
||||
// for useTemplateRef()
|
||||
refs: Data
|
||||
// for provide / inject
|
||||
provides: Data
|
||||
|
||||
hasFallthrough: boolean
|
||||
|
||||
isMounted: boolean
|
||||
isUnmounted: boolean
|
||||
isDeactivated: boolean
|
||||
// LifecycleHooks.ERROR_CAPTURED
|
||||
ec: LifecycleHook
|
||||
|
||||
// dev only
|
||||
propsOptions?: NormalizedPropsOptions
|
||||
emitsOptions?: ObjectEmitsOptions | null
|
||||
|
||||
constructor(comp: VaporComponent, rawProps?: RawProps) {
|
||||
this.uid = nextUid()
|
||||
this.type = comp
|
||||
this.parent = currentInstance
|
||||
this.appContext = currentInstance
|
||||
? currentInstance.appContext
|
||||
: emptyContext
|
||||
|
||||
this.block = null! // to be set
|
||||
this.scope = new EffectScope(true)
|
||||
|
||||
this.rawProps = rawProps
|
||||
this.provides = this.refs = EMPTY_OBJ
|
||||
this.emitted = this.ec = this.exposed = null
|
||||
this.isMounted = this.isUnmounted = this.isDeactivated = false
|
||||
|
||||
// init props
|
||||
this.propsDefaults = null
|
||||
this.hasFallthrough = false
|
||||
if (rawProps && rawProps.$) {
|
||||
// has dynamic props, use proxy
|
||||
const handlers = getDynamicPropsHandlers(comp, this)
|
||||
this.props = comp.props ? new Proxy(rawProps, handlers[0]!) : EMPTY_OBJ
|
||||
this.attrs = new Proxy(rawProps, handlers[1])
|
||||
this.hasFallthrough = true
|
||||
} else {
|
||||
this.props = {}
|
||||
this.attrs = {}
|
||||
this.hasFallthrough = initStaticProps(comp, rawProps, this)
|
||||
}
|
||||
|
||||
// TODO validate props
|
||||
// TODO init slots
|
||||
}
|
||||
}
|
||||
|
||||
export function isVaporComponent(
|
||||
value: unknown,
|
||||
): value is VaporComponentInstance {
|
||||
return value instanceof VaporComponentInstance
|
||||
}
|
||||
|
||||
export class SetupContext<E = EmitsOptions> {
|
||||
attrs: Record<string, any>
|
||||
// emit: EmitFn<E>
|
||||
// slots: Readonly<StaticSlots>
|
||||
expose: (exposed?: Record<string, any>) => void
|
||||
|
||||
constructor(instance: VaporComponentInstance) {
|
||||
this.attrs = instance.attrs
|
||||
// this.emit = instance.emit as EmitFn<E>
|
||||
// this.slots = instance.slots
|
||||
this.expose = (exposed = {}) => {
|
||||
instance.exposed = exposed
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
import {
|
||||
type EmitFn,
|
||||
type ObjectEmitsOptions,
|
||||
baseEmit,
|
||||
} from '@vue/runtime-core'
|
||||
import {
|
||||
type VaporComponent,
|
||||
type VaporComponentInstance,
|
||||
currentInstance,
|
||||
} from './component'
|
||||
import { EMPTY_OBJ, NOOP, hasOwn, isArray } from '@vue/shared'
|
||||
import { resolveSource } from './componentProps'
|
||||
|
||||
/**
|
||||
* The logic from core isn't too reusable so it's better to duplicate here
|
||||
*/
|
||||
export function normalizeEmitsOptions(
|
||||
comp: VaporComponent,
|
||||
): ObjectEmitsOptions | null {
|
||||
const cached = comp.__emitsOptions
|
||||
if (cached) return cached
|
||||
|
||||
const raw = comp.emits
|
||||
if (!raw) return null
|
||||
|
||||
let normalized: ObjectEmitsOptions
|
||||
if (isArray(raw)) {
|
||||
normalized = {}
|
||||
for (const key in raw) normalized[key] = null
|
||||
} else {
|
||||
normalized = raw
|
||||
}
|
||||
|
||||
return (comp.__emitsOptions = normalized)
|
||||
}
|
||||
|
||||
export function useEmit(): EmitFn {
|
||||
if (!currentInstance) {
|
||||
// TODO warn
|
||||
return NOOP
|
||||
} else {
|
||||
return emit.bind(null, currentInstance)
|
||||
}
|
||||
}
|
||||
|
||||
export function emit(
|
||||
instance: VaporComponentInstance,
|
||||
event: string,
|
||||
...rawArgs: any[]
|
||||
): void {
|
||||
baseEmit(
|
||||
instance,
|
||||
instance.rawProps || EMPTY_OBJ,
|
||||
propGetter,
|
||||
event,
|
||||
...rawArgs,
|
||||
)
|
||||
}
|
||||
|
||||
function propGetter(rawProps: Record<string, any>, key: string) {
|
||||
const dynamicSources = rawProps.$
|
||||
if (dynamicSources) {
|
||||
let i = dynamicSources.length
|
||||
while (i--) {
|
||||
const source = resolveSource(dynamicSources[i])
|
||||
if (hasOwn(source, key)) return source[key]
|
||||
}
|
||||
}
|
||||
return rawProps[key] && rawProps[key]()
|
||||
}
|
|
@ -1,231 +0,0 @@
|
|||
import { EMPTY_ARR, NO, camelize, hasOwn, isFunction } from '@vue/shared'
|
||||
import type { VaporComponent, VaporComponentInstance } from './component'
|
||||
import {
|
||||
type NormalizedPropsOptions,
|
||||
baseNormalizePropsOptions,
|
||||
isEmitListener,
|
||||
resolvePropValue,
|
||||
} from '@vue/runtime-core'
|
||||
import { normalizeEmitsOptions } from './componentEmits'
|
||||
|
||||
export interface RawProps {
|
||||
[key: string]: PropSource
|
||||
$?: DynamicPropsSource[]
|
||||
}
|
||||
|
||||
type PropSource<T = any> = T | (() => T)
|
||||
|
||||
type DynamicPropsSource = PropSource<Record<string, any>>
|
||||
|
||||
export function initStaticProps(
|
||||
comp: VaporComponent,
|
||||
rawProps: RawProps | undefined,
|
||||
instance: VaporComponentInstance,
|
||||
): boolean {
|
||||
let hasAttrs = false
|
||||
const { props, attrs } = instance
|
||||
const [propsOptions, needCastKeys] = normalizePropsOptions(comp)
|
||||
const emitsOptions = normalizeEmitsOptions(comp)
|
||||
|
||||
// for dev emit check
|
||||
if (__DEV__) {
|
||||
instance.propsOptions = normalizePropsOptions(comp)
|
||||
instance.emitsOptions = emitsOptions
|
||||
}
|
||||
|
||||
for (const key in rawProps) {
|
||||
const normalizedKey = camelize(key)
|
||||
const needCast = needCastKeys && needCastKeys.includes(normalizedKey)
|
||||
const source = rawProps[key]
|
||||
if (propsOptions && normalizedKey in propsOptions) {
|
||||
if (isFunction(source)) {
|
||||
Object.defineProperty(props, normalizedKey, {
|
||||
enumerable: true,
|
||||
get: needCast
|
||||
? () =>
|
||||
resolvePropValue(
|
||||
propsOptions,
|
||||
normalizedKey,
|
||||
source(),
|
||||
instance,
|
||||
resolveDefault,
|
||||
)
|
||||
: source,
|
||||
})
|
||||
} else {
|
||||
props[normalizedKey] = needCast
|
||||
? resolvePropValue(
|
||||
propsOptions,
|
||||
normalizedKey,
|
||||
source,
|
||||
instance,
|
||||
resolveDefault,
|
||||
)
|
||||
: source
|
||||
}
|
||||
} else if (!isEmitListener(emitsOptions, key)) {
|
||||
if (isFunction(source)) {
|
||||
Object.defineProperty(attrs, key, {
|
||||
enumerable: true,
|
||||
get: source,
|
||||
})
|
||||
} else {
|
||||
attrs[normalizedKey] = source
|
||||
}
|
||||
hasAttrs = true
|
||||
}
|
||||
}
|
||||
for (const key in propsOptions) {
|
||||
if (!(key in props)) {
|
||||
props[key] = resolvePropValue(
|
||||
propsOptions,
|
||||
key,
|
||||
undefined,
|
||||
instance,
|
||||
resolveDefault,
|
||||
true,
|
||||
)
|
||||
}
|
||||
}
|
||||
return hasAttrs
|
||||
}
|
||||
|
||||
function resolveDefault(
|
||||
factory: (props: Record<string, any>) => unknown,
|
||||
instance: VaporComponentInstance,
|
||||
) {
|
||||
return factory.call(null, instance.props)
|
||||
}
|
||||
|
||||
// TODO optimization: maybe convert functions into computeds
|
||||
export function resolveSource(source: PropSource): Record<string, any> {
|
||||
return isFunction(source) ? source() : source
|
||||
}
|
||||
|
||||
const passThrough = (val: any) => val
|
||||
|
||||
export function getDynamicPropsHandlers(
|
||||
comp: VaporComponent,
|
||||
instance: VaporComponentInstance,
|
||||
): [ProxyHandler<RawProps> | null, ProxyHandler<RawProps>] {
|
||||
if (comp.__propsHandlers) {
|
||||
return comp.__propsHandlers
|
||||
}
|
||||
let normalizedKeys: string[] | undefined
|
||||
const propsOptions = normalizePropsOptions(comp)[0]
|
||||
const emitsOptions = normalizeEmitsOptions(comp)
|
||||
const isProp = propsOptions ? (key: string) => hasOwn(propsOptions, key) : NO
|
||||
|
||||
const getProp = (target: RawProps, key: string, asProp: boolean) => {
|
||||
if (key === '$') return
|
||||
if (asProp) {
|
||||
if (!isProp(key)) return
|
||||
} else if (isProp(key) || isEmitListener(emitsOptions, key)) {
|
||||
return
|
||||
}
|
||||
const castProp = propsOptions
|
||||
? (value: any, isAbsent = false) =>
|
||||
asProp
|
||||
? resolvePropValue(
|
||||
propsOptions,
|
||||
key as string,
|
||||
value,
|
||||
instance,
|
||||
resolveDefault,
|
||||
isAbsent,
|
||||
)
|
||||
: value
|
||||
: passThrough
|
||||
|
||||
if (key in target) {
|
||||
return castProp(resolveSource(target[key as string]))
|
||||
}
|
||||
if (target.$) {
|
||||
let i = target.$.length
|
||||
let source
|
||||
while (i--) {
|
||||
source = resolveSource(target.$[i])
|
||||
if (hasOwn(source, key)) {
|
||||
return castProp(source[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
return castProp(undefined, true)
|
||||
}
|
||||
|
||||
const propsHandlers = propsOptions
|
||||
? ({
|
||||
get: (target, key: string) => getProp(target, key, true),
|
||||
has: (_, key: string) => isProp(key),
|
||||
getOwnPropertyDescriptor(target, key: string) {
|
||||
if (isProp(key)) {
|
||||
return {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => getProp(target, key, true),
|
||||
}
|
||||
}
|
||||
},
|
||||
ownKeys: () =>
|
||||
normalizedKeys || (normalizedKeys = Object.keys(propsOptions)),
|
||||
set: NO,
|
||||
deleteProperty: NO,
|
||||
} satisfies ProxyHandler<RawProps>)
|
||||
: null
|
||||
|
||||
const hasAttr = (target: RawProps, key: string) => {
|
||||
if (key === '$' || isProp(key) || isEmitListener(emitsOptions, key))
|
||||
return false
|
||||
if (target.$) {
|
||||
let i = target.$.length
|
||||
while (i--) {
|
||||
if (hasOwn(resolveSource(target.$[i]), key)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasOwn(target, key)
|
||||
}
|
||||
|
||||
const attrsHandlers = {
|
||||
get: (target, key: string) => getProp(target, key, false),
|
||||
has: hasAttr,
|
||||
getOwnPropertyDescriptor(target, key: string) {
|
||||
if (hasAttr(target, key)) {
|
||||
return {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => getProp(target, key, false),
|
||||
}
|
||||
}
|
||||
},
|
||||
ownKeys(target) {
|
||||
const keys = Object.keys(target)
|
||||
if (target.$) {
|
||||
let i = target.$.length
|
||||
while (i--) {
|
||||
keys.push(...Object.keys(resolveSource(target.$[i])))
|
||||
}
|
||||
}
|
||||
return keys.filter(key => hasAttr(target, key))
|
||||
},
|
||||
set: NO,
|
||||
deleteProperty: NO,
|
||||
} satisfies ProxyHandler<RawProps>
|
||||
|
||||
return (comp.__propsHandlers = [propsHandlers, attrsHandlers])
|
||||
}
|
||||
|
||||
function normalizePropsOptions(comp: VaporComponent): NormalizedPropsOptions {
|
||||
const cached = comp.__propsOptions
|
||||
if (cached) return cached
|
||||
|
||||
const raw = comp.props
|
||||
if (!raw) return EMPTY_ARR as []
|
||||
|
||||
const normalized: NormalizedPropsOptions[0] = {}
|
||||
const needCastKeys: NormalizedPropsOptions[1] = []
|
||||
baseNormalizePropsOptions(raw, normalized, needCastKeys)
|
||||
|
||||
return (comp.__propsOptions = [normalized, needCastKeys])
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
export { createComponent as createComponentSimple } from './component'
|
||||
export { renderEffect as renderEffectSimple } from './renderEffect'
|
||||
export { createVaporApp as createVaporAppSimple } from './apiCreateApp'
|
||||
export { useEmit } from './componentEmits'
|
|
@ -1,19 +0,0 @@
|
|||
import { ReactiveEffect } from '@vue/reactivity'
|
||||
import { type SchedulerJob, queueJob } from '@vue/runtime-core'
|
||||
import { currentInstance } from './component'
|
||||
|
||||
export function renderEffect(fn: () => void): void {
|
||||
const updateFn = () => {
|
||||
fn()
|
||||
}
|
||||
const effect = new ReactiveEffect(updateFn)
|
||||
const job: SchedulerJob = effect.runIfDirty.bind(effect)
|
||||
job.i = currentInstance as any
|
||||
job.id = currentInstance!.uid
|
||||
effect.scheduler = () => queueJob(job)
|
||||
effect.run()
|
||||
|
||||
// TODO lifecycle
|
||||
// TODO recurse handling
|
||||
// TODO measure
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { normalizeContainer } from '../apiRender'
|
||||
import { insert } from '../dom/element'
|
||||
import { normalizeContainer } from './_old/apiRender'
|
||||
import { insert } from './dom/element'
|
||||
import { type VaporComponent, createComponent } from './component'
|
||||
import {
|
||||
type AppMountFn,
|
||||
|
@ -8,7 +8,7 @@ import {
|
|||
createAppAPI,
|
||||
} from '@vue/runtime-core'
|
||||
|
||||
let _createApp: CreateAppFunction<ParentNode>
|
||||
let _createApp: CreateAppFunction<ParentNode, VaporComponent>
|
||||
|
||||
const mountApp: AppMountFn<ParentNode> = (app, container) => {
|
||||
// clear content before mounting
|
||||
|
@ -24,7 +24,10 @@ const unmountApp: AppUnmountFn = app => {
|
|||
// TODO
|
||||
}
|
||||
|
||||
export function createVaporApp(comp: VaporComponent): any {
|
||||
export const createVaporApp: CreateAppFunction<
|
||||
ParentNode,
|
||||
VaporComponent
|
||||
> = comp => {
|
||||
if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp)
|
||||
const app = _createApp(comp)
|
||||
const mount = app.mount
|
|
@ -1,47 +0,0 @@
|
|||
import {
|
||||
type Component,
|
||||
ComponentInternalInstance,
|
||||
currentInstance,
|
||||
} from './component'
|
||||
import { setupComponent } from './apiRender'
|
||||
import type { RawProps } from './componentProps'
|
||||
import type { RawSlots } from './componentSlots'
|
||||
import { withAttrs } from './componentAttrs'
|
||||
import { isString } from '@vue/shared'
|
||||
import { fallbackComponent } from './componentFallback'
|
||||
|
||||
export function createComponent(
|
||||
comp: Component | string,
|
||||
rawProps: RawProps | null = null,
|
||||
slots: RawSlots | null = null,
|
||||
singleRoot: boolean = false,
|
||||
once: boolean = false,
|
||||
): ComponentInternalInstance | HTMLElement {
|
||||
const current = currentInstance!
|
||||
|
||||
if (isString(comp)) {
|
||||
return fallbackComponent(comp, rawProps, slots, current, singleRoot)
|
||||
}
|
||||
|
||||
const instance = new ComponentInternalInstance(
|
||||
comp,
|
||||
singleRoot ? withAttrs(rawProps) : rawProps,
|
||||
slots,
|
||||
once,
|
||||
)
|
||||
|
||||
if (singleRoot) {
|
||||
instance.scopeIds.push(...current.scopeIds)
|
||||
}
|
||||
const scopeId = current.type.__scopeId
|
||||
if (scopeId) {
|
||||
instance.scopeIds.push(scopeId)
|
||||
}
|
||||
|
||||
setupComponent(instance)
|
||||
|
||||
// register sub-component with current component for lifecycle management
|
||||
current.comps.add(instance)
|
||||
|
||||
return instance
|
||||
}
|
|
@ -1,431 +0,0 @@
|
|||
import {
|
||||
type EffectScope,
|
||||
type ShallowRef,
|
||||
effectScope,
|
||||
shallowRef,
|
||||
} from '@vue/reactivity'
|
||||
import { isArray, isObject, isString } from '@vue/shared'
|
||||
import {
|
||||
createComment,
|
||||
createTextNode,
|
||||
insert,
|
||||
remove as removeBlock,
|
||||
} from './dom/element'
|
||||
import { type Block, type Fragment, fragmentKey } from './block'
|
||||
import { warn } from './warning'
|
||||
import { currentInstance, isVaporComponent } from './component'
|
||||
import type { DynamicSlot } from './componentSlots'
|
||||
import { renderEffect } from './renderEffect'
|
||||
|
||||
interface ForBlock extends Fragment {
|
||||
scope: EffectScope
|
||||
state: [
|
||||
item: ShallowRef<any>,
|
||||
key: ShallowRef<any>,
|
||||
index: ShallowRef<number | undefined>,
|
||||
]
|
||||
key: any
|
||||
}
|
||||
|
||||
type Source = any[] | Record<any, any> | number | Set<any> | Map<any, any>
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export const createFor = (
|
||||
src: () => Source,
|
||||
renderItem: (block: ForBlock['state']) => Block,
|
||||
getKey?: (item: any, key: any, index?: number) => any,
|
||||
container?: ParentNode,
|
||||
hydrationNode?: Node,
|
||||
once?: boolean,
|
||||
): Fragment => {
|
||||
let isMounted = false
|
||||
let oldBlocks: ForBlock[] = []
|
||||
let newBlocks: ForBlock[]
|
||||
let parent: ParentNode | undefined | null
|
||||
const parentAnchor = container
|
||||
? undefined
|
||||
: __DEV__
|
||||
? createComment('for')
|
||||
: createTextNode()
|
||||
const ref: Fragment = {
|
||||
nodes: oldBlocks,
|
||||
[fragmentKey]: true,
|
||||
}
|
||||
|
||||
const instance = currentInstance!
|
||||
if (__DEV__ && !instance) {
|
||||
warn('createFor() can only be used inside setup()')
|
||||
}
|
||||
|
||||
once ? renderList() : renderEffect(renderList)
|
||||
|
||||
return ref
|
||||
|
||||
function renderList() {
|
||||
const source = src()
|
||||
const newLength = getLength(source)
|
||||
const oldLength = oldBlocks.length
|
||||
newBlocks = new Array(newLength)
|
||||
|
||||
if (!isMounted) {
|
||||
isMounted = true
|
||||
mountList(source)
|
||||
} else {
|
||||
parent = parent || container || parentAnchor!.parentNode
|
||||
if (!oldLength) {
|
||||
// fast path for all new
|
||||
mountList(source)
|
||||
} else if (!newLength) {
|
||||
// fast path for all removed
|
||||
if (container) {
|
||||
container.textContent = ''
|
||||
for (let i = 0; i < oldLength; i++) {
|
||||
oldBlocks[i].scope.stop()
|
||||
}
|
||||
} else {
|
||||
// fast path for clearing
|
||||
for (let i = 0; i < oldLength; i++) {
|
||||
unmount(oldBlocks[i])
|
||||
}
|
||||
}
|
||||
} else if (!getKey) {
|
||||
// unkeyed fast path
|
||||
const commonLength = Math.min(newLength, oldLength)
|
||||
for (let i = 0; i < commonLength; i++) {
|
||||
const [item] = getItem(source, i)
|
||||
update((newBlocks[i] = oldBlocks[i]), item)
|
||||
}
|
||||
mountList(source, oldLength)
|
||||
for (let i = newLength; i < oldLength; i++) {
|
||||
unmount(oldBlocks[i])
|
||||
}
|
||||
} else {
|
||||
let i = 0
|
||||
let e1 = oldLength - 1 // prev ending index
|
||||
let e2 = newLength - 1 // next ending index
|
||||
|
||||
// 1. sync from start
|
||||
// (a b) c
|
||||
// (a b) d e
|
||||
while (i <= e1 && i <= e2) {
|
||||
if (tryPatchIndex(source, i)) {
|
||||
i++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 2. sync from end
|
||||
// a (b c)
|
||||
// d e (b c)
|
||||
while (i <= e1 && i <= e2) {
|
||||
if (tryPatchIndex(source, i)) {
|
||||
e1--
|
||||
e2--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 3. common sequence + mount
|
||||
// (a b)
|
||||
// (a b) c
|
||||
// i = 2, e1 = 1, e2 = 2
|
||||
// (a b)
|
||||
// c (a b)
|
||||
// i = 0, e1 = -1, e2 = 0
|
||||
if (i > e1) {
|
||||
if (i <= e2) {
|
||||
const nextPos = e2 + 1
|
||||
const anchor =
|
||||
nextPos < newLength
|
||||
? normalizeAnchor(newBlocks[nextPos].nodes)
|
||||
: parentAnchor
|
||||
while (i <= e2) {
|
||||
mount(source, i, anchor)
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. common sequence + unmount
|
||||
// (a b) c
|
||||
// (a b)
|
||||
// i = 2, e1 = 2, e2 = 1
|
||||
// a (b c)
|
||||
// (b c)
|
||||
// i = 0, e1 = 0, e2 = -1
|
||||
else if (i > e2) {
|
||||
while (i <= e1) {
|
||||
unmount(oldBlocks[i])
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// 5. unknown sequence
|
||||
// [i ... e1 + 1]: a b [c d e] f g
|
||||
// [i ... e2 + 1]: a b [e d c h] f g
|
||||
// i = 2, e1 = 4, e2 = 5
|
||||
else {
|
||||
const s1 = i // prev starting index
|
||||
const s2 = i // next starting index
|
||||
|
||||
// 5.1 build key:index map for newChildren
|
||||
const keyToNewIndexMap = new Map()
|
||||
for (i = s2; i <= e2; i++) {
|
||||
keyToNewIndexMap.set(getKey(...getItem(source, i)), i)
|
||||
}
|
||||
|
||||
// 5.2 loop through old children left to be patched and try to patch
|
||||
// matching nodes & remove nodes that are no longer present
|
||||
let j
|
||||
let patched = 0
|
||||
const toBePatched = e2 - s2 + 1
|
||||
let moved = false
|
||||
// used to track whether any node has moved
|
||||
let maxNewIndexSoFar = 0
|
||||
// works as Map<newIndex, oldIndex>
|
||||
// Note that oldIndex is offset by +1
|
||||
// and oldIndex = 0 is a special value indicating the new node has
|
||||
// no corresponding old node.
|
||||
// used for determining longest stable subsequence
|
||||
const newIndexToOldIndexMap = new Array(toBePatched).fill(0)
|
||||
|
||||
for (i = s1; i <= e1; i++) {
|
||||
const prevBlock = oldBlocks[i]
|
||||
if (patched >= toBePatched) {
|
||||
// all new children have been patched so this can only be a removal
|
||||
unmount(prevBlock)
|
||||
} else {
|
||||
const newIndex = keyToNewIndexMap.get(prevBlock.key)
|
||||
if (newIndex == null) {
|
||||
unmount(prevBlock)
|
||||
} else {
|
||||
newIndexToOldIndexMap[newIndex - s2] = i + 1
|
||||
if (newIndex >= maxNewIndexSoFar) {
|
||||
maxNewIndexSoFar = newIndex
|
||||
} else {
|
||||
moved = true
|
||||
}
|
||||
update(
|
||||
(newBlocks[newIndex] = prevBlock),
|
||||
...getItem(source, newIndex),
|
||||
)
|
||||
patched++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5.3 move and mount
|
||||
// generate longest stable subsequence only when nodes have moved
|
||||
const increasingNewIndexSequence = moved
|
||||
? getSequence(newIndexToOldIndexMap)
|
||||
: []
|
||||
j = increasingNewIndexSequence.length - 1
|
||||
// looping backwards so that we can use last patched node as anchor
|
||||
for (i = toBePatched - 1; i >= 0; i--) {
|
||||
const nextIndex = s2 + i
|
||||
const anchor =
|
||||
nextIndex + 1 < newLength
|
||||
? normalizeAnchor(newBlocks[nextIndex + 1].nodes)
|
||||
: parentAnchor
|
||||
if (newIndexToOldIndexMap[i] === 0) {
|
||||
// mount new
|
||||
mount(source, nextIndex, anchor)
|
||||
} else if (moved) {
|
||||
// move if:
|
||||
// There is no stable subsequence (e.g. a reverse)
|
||||
// OR current node is not among the stable sequence
|
||||
if (j < 0 || i !== increasingNewIndexSequence[j]) {
|
||||
insert(newBlocks[nextIndex].nodes, parent!, anchor)
|
||||
} else {
|
||||
j--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ref.nodes = [(oldBlocks = newBlocks)]
|
||||
if (parentAnchor) {
|
||||
ref.nodes.push(parentAnchor)
|
||||
}
|
||||
}
|
||||
|
||||
function mount(
|
||||
source: any,
|
||||
idx: number,
|
||||
anchor: Node | undefined = parentAnchor,
|
||||
): ForBlock {
|
||||
const scope = effectScope()
|
||||
|
||||
const [item, key, index] = getItem(source, idx)
|
||||
const state = [
|
||||
shallowRef(item),
|
||||
shallowRef(key),
|
||||
shallowRef(index),
|
||||
] as ForBlock['state']
|
||||
const block: ForBlock = (newBlocks[idx] = {
|
||||
nodes: null!, // set later
|
||||
scope,
|
||||
state,
|
||||
key: getKey && getKey(item, key, index),
|
||||
[fragmentKey]: true,
|
||||
})
|
||||
block.nodes = scope.run(() => renderItem(state))!
|
||||
|
||||
if (parent) insert(block.nodes, parent, anchor)
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
function mountList(source: any, offset = 0) {
|
||||
for (let i = offset; i < getLength(source); i++) {
|
||||
mount(source, i)
|
||||
}
|
||||
}
|
||||
|
||||
function tryPatchIndex(source: any, idx: number) {
|
||||
const block = oldBlocks[idx]
|
||||
const [item, key, index] = getItem(source, idx)
|
||||
if (block.key === getKey!(item, key, index)) {
|
||||
update((newBlocks[idx] = block), item)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function update(
|
||||
block: ForBlock,
|
||||
newItem: any,
|
||||
newKey = block.state[1].value,
|
||||
newIndex = block.state[2].value,
|
||||
) {
|
||||
const [item, key, index] = block.state
|
||||
let needsUpdate =
|
||||
newItem !== item.value || newKey !== key.value || newIndex !== index.value
|
||||
if (needsUpdate) updateState(block, newItem, newKey, newIndex)
|
||||
}
|
||||
|
||||
function unmount({ nodes, scope }: ForBlock) {
|
||||
removeBlock(nodes, parent!)
|
||||
scope.stop()
|
||||
}
|
||||
}
|
||||
|
||||
function updateState(
|
||||
block: ForBlock,
|
||||
newItem: any,
|
||||
newKey: any,
|
||||
newIndex: number | undefined,
|
||||
) {
|
||||
const [item, key, index] = block.state
|
||||
item.value = newItem
|
||||
key.value = newKey
|
||||
index.value = newIndex
|
||||
}
|
||||
|
||||
export function createForSlots(
|
||||
source: any[] | Record<any, any> | number | Set<any> | Map<any, any>,
|
||||
getSlot: (item: any, key: any, index?: number) => DynamicSlot,
|
||||
): DynamicSlot[] {
|
||||
const sourceLength = getLength(source)
|
||||
const slots = new Array<DynamicSlot>(sourceLength)
|
||||
for (let i = 0; i < sourceLength; i++) {
|
||||
const [item, key, index] = getItem(source, i)
|
||||
slots[i] = getSlot(item, key, index)
|
||||
}
|
||||
return slots
|
||||
}
|
||||
|
||||
function getLength(source: any): number {
|
||||
if (isArray(source) || isString(source)) {
|
||||
return source.length
|
||||
} else if (typeof source === 'number') {
|
||||
if (__DEV__ && !Number.isInteger(source)) {
|
||||
warn(`The v-for range expect an integer value but got ${source}.`)
|
||||
}
|
||||
return source
|
||||
} else if (isObject(source)) {
|
||||
if (source[Symbol.iterator as any]) {
|
||||
return Array.from(source as Iterable<any>).length
|
||||
} else {
|
||||
return Object.keys(source).length
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function getItem(
|
||||
source: any,
|
||||
idx: number,
|
||||
): [item: any, key: any, index?: number] {
|
||||
if (isArray(source) || isString(source)) {
|
||||
return [source[idx], idx, undefined]
|
||||
} else if (typeof source === 'number') {
|
||||
return [idx + 1, idx, undefined]
|
||||
} else if (isObject(source)) {
|
||||
if (source[Symbol.iterator as any]) {
|
||||
source = Array.from(source as Iterable<any>)
|
||||
return [source[idx], idx, undefined]
|
||||
} else {
|
||||
const key = Object.keys(source)[idx]
|
||||
return [source[key], key, idx]
|
||||
}
|
||||
}
|
||||
return null!
|
||||
}
|
||||
|
||||
function normalizeAnchor(node: Block): Node {
|
||||
if (node instanceof Node) {
|
||||
return node
|
||||
} else if (isArray(node)) {
|
||||
return normalizeAnchor(node[0])
|
||||
} else if (isVaporComponent(node)) {
|
||||
return normalizeAnchor(node.block!)
|
||||
} else {
|
||||
return normalizeAnchor(node.nodes!)
|
||||
}
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Longest_increasing_subsequence
|
||||
function getSequence(arr: number[]): number[] {
|
||||
const p = arr.slice()
|
||||
const result = [0]
|
||||
let i, j, u, v, c
|
||||
const len = arr.length
|
||||
for (i = 0; i < len; i++) {
|
||||
const arrI = arr[i]
|
||||
if (arrI !== 0) {
|
||||
j = result[result.length - 1]
|
||||
if (arr[j] < arrI) {
|
||||
p[i] = j
|
||||
result.push(i)
|
||||
continue
|
||||
}
|
||||
u = 0
|
||||
v = result.length - 1
|
||||
while (u < v) {
|
||||
c = (u + v) >> 1
|
||||
if (arr[result[c]] < arrI) {
|
||||
u = c + 1
|
||||
} else {
|
||||
v = c
|
||||
}
|
||||
}
|
||||
if (arrI < arr[result[u]]) {
|
||||
if (u > 0) {
|
||||
p[i] = result[u - 1]
|
||||
}
|
||||
result[u] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
u = result.length
|
||||
v = result[u - 1]
|
||||
while (u-- > 0) {
|
||||
result[u] = v
|
||||
v = p[v]
|
||||
}
|
||||
return result
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
import { renderEffect } from './renderEffect'
|
||||
import { type Block, type Fragment, fragmentKey } from './block'
|
||||
import { type EffectScope, effectScope, shallowReactive } from '@vue/reactivity'
|
||||
import { createComment, createTextNode, insert, remove } from './dom/element'
|
||||
|
||||
type BlockFn = () => Block
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export function createBranch(
|
||||
expression: () => any,
|
||||
render: (value: any) => BlockFn | undefined,
|
||||
once?: boolean,
|
||||
commentLabel?: string,
|
||||
// hydrationNode?: Node,
|
||||
): Fragment {
|
||||
let newValue: any
|
||||
let oldValue: any
|
||||
let branch: BlockFn | undefined
|
||||
let block: Block | undefined
|
||||
let scope: EffectScope | undefined
|
||||
const anchor = __DEV__
|
||||
? createComment(commentLabel || 'dynamic')
|
||||
: createTextNode()
|
||||
const fragment: Fragment = shallowReactive({
|
||||
nodes: [],
|
||||
anchor,
|
||||
[fragmentKey]: true,
|
||||
})
|
||||
|
||||
// TODO: SSR
|
||||
// if (isHydrating) {
|
||||
// parent = hydrationNode!.parentNode
|
||||
// setCurrentHydrationNode(hydrationNode!)
|
||||
// }
|
||||
|
||||
if (once) {
|
||||
doChange()
|
||||
} else {
|
||||
renderEffect(() => doChange())
|
||||
}
|
||||
|
||||
// TODO: SSR
|
||||
// if (isHydrating) {
|
||||
// parent!.insertBefore(anchor, currentHydrationNode)
|
||||
// }
|
||||
|
||||
return fragment
|
||||
|
||||
function doChange() {
|
||||
if ((newValue = expression()) !== oldValue) {
|
||||
const parent = anchor.parentNode
|
||||
if (block) {
|
||||
scope!.stop()
|
||||
remove(block, parent!)
|
||||
}
|
||||
oldValue = newValue
|
||||
if ((branch = render(newValue))) {
|
||||
scope = effectScope()
|
||||
fragment.nodes = block = scope.run(branch)!
|
||||
parent && insert(block, parent, anchor)
|
||||
} else {
|
||||
scope = block = undefined
|
||||
fragment.nodes = []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export function createIf(
|
||||
condition: () => any,
|
||||
b1: BlockFn,
|
||||
b2?: BlockFn,
|
||||
once?: boolean,
|
||||
// hydrationNode?: Node,
|
||||
): Fragment {
|
||||
return createBranch(
|
||||
() => !!condition(),
|
||||
value => (value ? b1 : b2),
|
||||
once,
|
||||
__DEV__ ? 'if' : undefined,
|
||||
)
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
import {
|
||||
type MaybeRefOrGetter,
|
||||
type ShallowRef,
|
||||
onScopeDispose,
|
||||
shallowRef,
|
||||
toValue,
|
||||
} from '@vue/reactivity'
|
||||
import { watchEffect } from './apiWatch'
|
||||
|
||||
export function createSelector<T, U extends T>(
|
||||
source: MaybeRefOrGetter<T>,
|
||||
fn: (key: U, value: T) => boolean = (key, value) => key === value,
|
||||
): (key: U) => boolean {
|
||||
let subs = new Map()
|
||||
let val: T
|
||||
let oldVal: U
|
||||
|
||||
watchEffect(() => {
|
||||
val = toValue(source)
|
||||
const keys = [...subs.keys()]
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
const key = keys[i]
|
||||
if (fn(key, val)) {
|
||||
const o = subs.get(key)
|
||||
o.value = true
|
||||
} else if (oldVal !== undefined && fn(key, oldVal)) {
|
||||
const o = subs.get(key)
|
||||
o.value = false
|
||||
}
|
||||
}
|
||||
oldVal = val as U
|
||||
})
|
||||
|
||||
return key => {
|
||||
let l: ShallowRef<boolean | undefined> & { _count?: number }
|
||||
if (!(l = subs.get(key))) subs.set(key, (l = shallowRef()))
|
||||
l.value
|
||||
l._count ? l._count++ : (l._count = 1)
|
||||
onScopeDispose(() => (l._count! > 1 ? l._count!-- : subs.delete(key)))
|
||||
return l.value !== undefined ? l.value : fn(key, val)
|
||||
}
|
||||
}
|
|
@ -1,326 +0,0 @@
|
|||
import { NO, getGlobalThis, isFunction, isObject } from '@vue/shared'
|
||||
import {
|
||||
type Component,
|
||||
ComponentInternalInstance,
|
||||
validateComponentName,
|
||||
} from './component'
|
||||
import { warn } from './warning'
|
||||
import { version } from '.'
|
||||
import {
|
||||
normalizeContainer,
|
||||
render,
|
||||
setupComponent,
|
||||
unmountComponent,
|
||||
} from './apiRender'
|
||||
import type { InjectionKey } from './apiInject'
|
||||
import type { RawProps } from './componentProps'
|
||||
import { type Directive, validateDirectiveName } from './directives'
|
||||
import { devtoolsInitApp, setDevtoolsHook } from './devtools'
|
||||
|
||||
let uid = 0
|
||||
export function createVaporApp(
|
||||
rootComponent: Component,
|
||||
rootProps: RawProps | null = null,
|
||||
): App {
|
||||
if (rootProps != null && !isObject(rootProps) && !isFunction(rootProps)) {
|
||||
__DEV__ &&
|
||||
warn(`root props passed to app.mount() must be an object or function.`)
|
||||
rootProps = null
|
||||
}
|
||||
|
||||
const target = getGlobalThis()
|
||||
target.__VUE__ = true
|
||||
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
||||
setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__, target)
|
||||
}
|
||||
|
||||
const context = createAppContext()
|
||||
const installedPlugins = new WeakSet()
|
||||
|
||||
let instance: ComponentInternalInstance
|
||||
|
||||
const app: App = (context.app = {
|
||||
_uid: uid++,
|
||||
_component: rootComponent,
|
||||
_props: rootProps,
|
||||
_container: null,
|
||||
_context: context,
|
||||
_instance: null,
|
||||
|
||||
version,
|
||||
|
||||
get config() {
|
||||
return context.config
|
||||
},
|
||||
|
||||
set config(v) {
|
||||
if (__DEV__) {
|
||||
warn(
|
||||
`app.config cannot be replaced. Modify individual options instead.`,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
use(plugin: Plugin, ...options: any[]) {
|
||||
if (installedPlugins.has(plugin)) {
|
||||
__DEV__ && warn(`Plugin has already been applied to target app.`)
|
||||
} else if (plugin && isFunction(plugin.install)) {
|
||||
installedPlugins.add(plugin)
|
||||
plugin.install(app, ...options)
|
||||
} else if (isFunction(plugin)) {
|
||||
installedPlugins.add(plugin)
|
||||
plugin(app, ...options)
|
||||
} else if (__DEV__) {
|
||||
warn(
|
||||
`A plugin must either be a function or an object with an "install" ` +
|
||||
`function.`,
|
||||
)
|
||||
}
|
||||
return app
|
||||
},
|
||||
|
||||
component(name: string, component?: Component): any {
|
||||
if (__DEV__) {
|
||||
validateComponentName(name, context.config)
|
||||
}
|
||||
if (!component) {
|
||||
return context.components[name]
|
||||
}
|
||||
if (__DEV__ && context.components[name]) {
|
||||
warn(`Component "${name}" has already been registered in target app.`)
|
||||
}
|
||||
context.components[name] = component
|
||||
return app
|
||||
},
|
||||
|
||||
directive(name: string, directive?: Directive) {
|
||||
if (__DEV__) {
|
||||
validateDirectiveName(name)
|
||||
}
|
||||
|
||||
if (!directive) {
|
||||
return context.directives[name] as any
|
||||
}
|
||||
if (__DEV__ && context.directives[name]) {
|
||||
warn(`Directive "${name}" has already been registered in target app.`)
|
||||
}
|
||||
context.directives[name] = directive
|
||||
return app
|
||||
},
|
||||
|
||||
mount(container): any {
|
||||
if (!instance) {
|
||||
container = normalizeContainer(container)
|
||||
// #5571
|
||||
if (__DEV__ && (container as any).__vue_app__) {
|
||||
warn(
|
||||
`There is already an app instance mounted on the host container.\n` +
|
||||
` If you want to mount another app on the same host container,` +
|
||||
` you need to unmount the previous app by calling \`app.unmount()\` first.`,
|
||||
)
|
||||
}
|
||||
|
||||
// clear content before mounting
|
||||
if (container.nodeType === 1 /* Node.ELEMENT_NODE */) {
|
||||
container.textContent = ''
|
||||
}
|
||||
|
||||
instance = new ComponentInternalInstance(
|
||||
rootComponent,
|
||||
rootProps,
|
||||
null,
|
||||
false,
|
||||
context,
|
||||
)
|
||||
setupComponent(instance)
|
||||
render(instance, container)
|
||||
|
||||
app._container = container
|
||||
// for devtools and telemetry
|
||||
;(container as any).__vue_app__ = app
|
||||
|
||||
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
||||
app._instance = instance
|
||||
devtoolsInitApp(app, version)
|
||||
}
|
||||
|
||||
if (container instanceof Element) {
|
||||
container.removeAttribute('v-cloak')
|
||||
container.setAttribute('data-v-app', '')
|
||||
}
|
||||
|
||||
return instance
|
||||
} else if (__DEV__) {
|
||||
warn(
|
||||
`App has already been mounted.\n` +
|
||||
`If you want to remount the same app, move your app creation logic ` +
|
||||
`into a factory function and create fresh app instances for each ` +
|
||||
`mount - e.g. \`const createMyApp = () => createApp(App)\``,
|
||||
)
|
||||
}
|
||||
},
|
||||
unmount() {
|
||||
if (instance) {
|
||||
unmountComponent(instance)
|
||||
delete (app._container as any).__vue_app__
|
||||
} else if (__DEV__) {
|
||||
warn(`Cannot unmount an app that is not mounted.`)
|
||||
}
|
||||
},
|
||||
provide(key, value) {
|
||||
if (__DEV__ && (key as string | symbol) in context.provides) {
|
||||
warn(
|
||||
`App already provides property with key "${String(key)}". ` +
|
||||
`It will be overwritten with the new value.`,
|
||||
)
|
||||
}
|
||||
|
||||
context.provides[key as string | symbol] = value
|
||||
|
||||
return app
|
||||
},
|
||||
runWithContext(fn) {
|
||||
const lastApp = currentApp
|
||||
currentApp = app
|
||||
try {
|
||||
return fn()
|
||||
} finally {
|
||||
currentApp = lastApp
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
export function createAppContext(): AppContext {
|
||||
return {
|
||||
app: null as any,
|
||||
mixins: [],
|
||||
config: {
|
||||
isNativeTag: NO,
|
||||
performance: false,
|
||||
errorHandler: undefined,
|
||||
warnHandler: undefined,
|
||||
globalProperties: {},
|
||||
},
|
||||
provides: Object.create(null),
|
||||
components: {},
|
||||
directives: {},
|
||||
}
|
||||
}
|
||||
|
||||
type PluginInstallFunction<Options = any[]> = Options extends unknown[]
|
||||
? (app: App, ...options: Options) => any
|
||||
: (app: App, options: Options) => any
|
||||
|
||||
export type ObjectPlugin<Options = any[]> = {
|
||||
install: PluginInstallFunction<Options>
|
||||
}
|
||||
export type FunctionPlugin<Options = any[]> = PluginInstallFunction<Options> &
|
||||
Partial<ObjectPlugin<Options>>
|
||||
|
||||
export type Plugin<Options = any[]> =
|
||||
| FunctionPlugin<Options>
|
||||
| ObjectPlugin<Options>
|
||||
|
||||
export interface App {
|
||||
version: string
|
||||
config: AppConfig
|
||||
|
||||
use<Options extends unknown[]>(
|
||||
plugin: Plugin<Options>,
|
||||
...options: Options
|
||||
): this
|
||||
use<Options>(plugin: Plugin<Options>, options: Options): this
|
||||
|
||||
component(name: string): Component | undefined
|
||||
component<T extends Component>(name: string, component: T): this
|
||||
directive<T = any, V = any>(name: string): Directive<T, V> | undefined
|
||||
directive<T = any, V = any>(name: string, directive: Directive<T, V>): this
|
||||
|
||||
mount(
|
||||
rootContainer: ParentNode | string,
|
||||
isHydrate?: boolean,
|
||||
): ComponentInternalInstance
|
||||
unmount(): void
|
||||
provide<T>(key: string | InjectionKey<T>, value: T): App
|
||||
runWithContext<T>(fn: () => T): T
|
||||
|
||||
// internal, but we need to expose these for the server-renderer and devtools
|
||||
_uid: number
|
||||
_component: Component
|
||||
_props: RawProps | null
|
||||
_container: ParentNode | null
|
||||
_context: AppContext
|
||||
_instance: ComponentInternalInstance | null
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
// @private
|
||||
readonly isNativeTag: (tag: string) => boolean
|
||||
|
||||
performance: boolean
|
||||
errorHandler?: (
|
||||
err: unknown,
|
||||
instance: ComponentInternalInstance | null,
|
||||
info: string,
|
||||
) => void
|
||||
warnHandler?: (
|
||||
msg: string,
|
||||
instance: ComponentInternalInstance | null,
|
||||
trace: string,
|
||||
) => void
|
||||
globalProperties: ComponentCustomProperties & Record<string, any>
|
||||
}
|
||||
|
||||
export interface AppContext {
|
||||
app: App // for devtools
|
||||
config: AppConfig
|
||||
mixins: never[] // for devtools, but no longer supported
|
||||
provides: Record<string | symbol, any>
|
||||
|
||||
/**
|
||||
* Resolved component registry, only for components with mixins or extends
|
||||
* @internal
|
||||
*/
|
||||
components: Record<string, Component>
|
||||
/**
|
||||
* Resolved directive registry, only for components with mixins or extends
|
||||
* @internal
|
||||
*/
|
||||
directives: Record<string, Directive>
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Used to identify the current app when using `inject()` within
|
||||
* `app.runWithContext()`.
|
||||
*/
|
||||
export let currentApp: App | null = null
|
||||
|
||||
/**
|
||||
* Custom properties added to component instances in any way and can be accessed through `this`
|
||||
*
|
||||
* @example
|
||||
* Here is an example of adding a property `$router` to every component instance:
|
||||
* ```ts
|
||||
* import { createApp } from 'vue'
|
||||
* import { Router, createRouter } from 'vue-router'
|
||||
*
|
||||
* declare module '@vue/runtime-core' {
|
||||
* interface ComponentCustomProperties {
|
||||
* $router: Router
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // effectively adding the router to every component instance
|
||||
* const app = createApp({})
|
||||
* const router = createRouter()
|
||||
* app.config.globalProperties.$router = router
|
||||
*
|
||||
* const vm = app.mount('#app')
|
||||
* // we can access the router from the instance
|
||||
* vm.$router.push('/')
|
||||
* ```
|
||||
*/
|
||||
export interface ComponentCustomProperties {}
|
|
@ -1,6 +0,0 @@
|
|||
import type { Component } from './component'
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export function defineComponent(comp: Component): Component {
|
||||
return comp
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
import { isFunction } from '@vue/shared'
|
||||
import { currentInstance } from './component'
|
||||
import { currentApp } from './apiCreateVaporApp'
|
||||
import { warn } from './warning'
|
||||
|
||||
export interface InjectionKey<T> extends Symbol {}
|
||||
|
||||
export function provide<T, K = InjectionKey<T> | string | number>(
|
||||
key: K,
|
||||
value: K extends InjectionKey<infer V> ? V : T,
|
||||
): void {
|
||||
if (!currentInstance) {
|
||||
if (__DEV__) {
|
||||
warn(`provide() can only be used inside setup().`)
|
||||
}
|
||||
} else {
|
||||
let provides = currentInstance.provides
|
||||
// by default an instance inherits its parent's provides object
|
||||
// but when it needs to provide values of its own, it creates its
|
||||
// own provides object using parent provides object as prototype.
|
||||
// this way in `inject` we can simply look up injections from direct
|
||||
// parent and let the prototype chain do the work.
|
||||
const parentProvides =
|
||||
currentInstance.parent && currentInstance.parent.provides
|
||||
if (parentProvides === provides) {
|
||||
provides = currentInstance.provides = Object.create(parentProvides)
|
||||
}
|
||||
// TS doesn't allow symbol as index type
|
||||
provides[key as string] = value
|
||||
}
|
||||
}
|
||||
|
||||
export function inject<T>(key: InjectionKey<T> | string): T | undefined
|
||||
export function inject<T>(
|
||||
key: InjectionKey<T> | string,
|
||||
defaultValue: T,
|
||||
treatDefaultAsFactory?: false,
|
||||
): T
|
||||
export function inject<T>(
|
||||
key: InjectionKey<T> | string,
|
||||
defaultValue: T | (() => T),
|
||||
treatDefaultAsFactory: true,
|
||||
): T
|
||||
export function inject(
|
||||
key: InjectionKey<any> | string,
|
||||
defaultValue?: unknown,
|
||||
treatDefaultAsFactory = false,
|
||||
) {
|
||||
const instance = currentInstance
|
||||
|
||||
// also support looking up from app-level provides w/ `app.runWithContext()`
|
||||
if (instance || currentApp) {
|
||||
// #2400
|
||||
// to support `app.use` plugins,
|
||||
// fallback to appContext's `provides` if the instance is at root
|
||||
const provides = instance
|
||||
? instance.parent == null
|
||||
? instance.appContext && instance.appContext.provides
|
||||
: instance.parent.provides
|
||||
: currentApp!._context.provides
|
||||
|
||||
if (provides && (key as string | symbol) in provides) {
|
||||
// TS doesn't allow symbol as index type
|
||||
return provides[key as string]
|
||||
} else if (arguments.length > 1) {
|
||||
return treatDefaultAsFactory && isFunction(defaultValue)
|
||||
? defaultValue.call(instance && instance)
|
||||
: defaultValue
|
||||
} else if (__DEV__) {
|
||||
warn(`injection "${String(key)}" not found.`)
|
||||
}
|
||||
} else if (__DEV__) {
|
||||
warn(`inject() can only be used inside setup() or functional components.`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if `inject()` can be used without warning about being called in the wrong place (e.g. outside of
|
||||
* setup()). This is used by libraries that want to use `inject()` internally without triggering a warning to the end
|
||||
* user. One example is `useRoute()` in `vue-router`.
|
||||
*/
|
||||
export function hasInjectionContext(): boolean {
|
||||
return !!(currentInstance || currentApp)
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
import {
|
||||
type ComponentInternalInstance,
|
||||
currentInstance,
|
||||
setCurrentInstance,
|
||||
} from './component'
|
||||
import { warn } from './warning'
|
||||
import {
|
||||
type DebuggerEvent,
|
||||
pauseTracking,
|
||||
resetTracking,
|
||||
} from '@vue/reactivity'
|
||||
import { ErrorTypeStrings, callWithAsyncErrorHandling } from './errorHandling'
|
||||
import { toHandlerKey } from '@vue/shared'
|
||||
import { VaporLifecycleHooks } from './enums'
|
||||
|
||||
const injectHook = (
|
||||
type: VaporLifecycleHooks,
|
||||
hook: Function & { __weh?: Function },
|
||||
target: ComponentInternalInstance | null = currentInstance,
|
||||
prepend: boolean = false,
|
||||
) => {
|
||||
if (target) {
|
||||
const hooks = target[type] || (target[type] = [])
|
||||
const wrappedHook =
|
||||
hook.__weh ||
|
||||
(hook.__weh = (...args: unknown[]) => {
|
||||
if (target.isUnmounted) {
|
||||
return
|
||||
}
|
||||
pauseTracking()
|
||||
const reset = setCurrentInstance(target)
|
||||
const res = target.scope.run(() =>
|
||||
callWithAsyncErrorHandling(hook, target, type, args),
|
||||
)
|
||||
reset()
|
||||
resetTracking()
|
||||
return res
|
||||
})
|
||||
if (prepend) {
|
||||
hooks.unshift(wrappedHook)
|
||||
} else {
|
||||
hooks.push(wrappedHook)
|
||||
}
|
||||
return wrappedHook
|
||||
} else if (__DEV__) {
|
||||
const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, ''))
|
||||
warn(
|
||||
`${apiName} is called when there is no active component instance to be ` +
|
||||
`associated with. ` +
|
||||
`Lifecycle injection APIs can only be used during execution of setup().` +
|
||||
(__FEATURE_SUSPENSE__
|
||||
? ` If you are using async setup(), make sure to register lifecycle ` +
|
||||
`hooks before the first await statement.`
|
||||
: ``),
|
||||
)
|
||||
}
|
||||
}
|
||||
const createHook =
|
||||
<T extends Function = () => any>(lifecycle: VaporLifecycleHooks) =>
|
||||
(hook: T, target: ComponentInternalInstance | null = currentInstance) =>
|
||||
injectHook(lifecycle, (...args: unknown[]) => hook(...args), target)
|
||||
type CreateHook<T = any> = (
|
||||
hook: T,
|
||||
target?: ComponentInternalInstance | null,
|
||||
) => void
|
||||
|
||||
export const onBeforeMount: CreateHook = createHook(
|
||||
VaporLifecycleHooks.BEFORE_MOUNT,
|
||||
)
|
||||
export const onMounted: CreateHook = createHook(VaporLifecycleHooks.MOUNTED)
|
||||
export const onBeforeUpdate: CreateHook = createHook(
|
||||
VaporLifecycleHooks.BEFORE_UPDATE,
|
||||
)
|
||||
export const onUpdated: CreateHook = createHook(VaporLifecycleHooks.UPDATED)
|
||||
export const onBeforeUnmount: CreateHook = createHook(
|
||||
VaporLifecycleHooks.BEFORE_UNMOUNT,
|
||||
)
|
||||
export const onUnmounted: CreateHook = createHook(VaporLifecycleHooks.UNMOUNTED)
|
||||
|
||||
export type DebuggerHook = (e: DebuggerEvent) => void
|
||||
export const onRenderTriggered: CreateHook = createHook<DebuggerHook>(
|
||||
VaporLifecycleHooks.RENDER_TRIGGERED,
|
||||
)
|
||||
export const onRenderTracked: CreateHook = createHook<DebuggerHook>(
|
||||
VaporLifecycleHooks.RENDER_TRACKED,
|
||||
)
|
||||
|
||||
export type ErrorCapturedHook<TError = unknown> = (
|
||||
err: TError,
|
||||
instance: ComponentInternalInstance | null,
|
||||
info: string,
|
||||
) => boolean | void
|
||||
|
||||
export function onErrorCaptured<TError = Error>(
|
||||
hook: ErrorCapturedHook<TError>,
|
||||
target: ComponentInternalInstance | null = currentInstance,
|
||||
): void {
|
||||
injectHook(VaporLifecycleHooks.ERROR_CAPTURED, hook, target)
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
import {
|
||||
type ComponentInternalInstance,
|
||||
createSetupContext,
|
||||
getAttrsProxy,
|
||||
getSlotsProxy,
|
||||
isVaporComponent,
|
||||
setCurrentInstance,
|
||||
validateComponentName,
|
||||
} from './component'
|
||||
import { insert, querySelector } from './dom/element'
|
||||
import { flushPostFlushCbs, queuePostFlushCb } from './scheduler'
|
||||
import { invokeLifecycle } from './componentLifecycle'
|
||||
import { VaporLifecycleHooks } from './enums'
|
||||
import {
|
||||
pauseTracking,
|
||||
proxyRefs,
|
||||
resetTracking,
|
||||
shallowReadonly,
|
||||
} from '@vue/reactivity'
|
||||
import { isArray, isFunction, isObject } from '@vue/shared'
|
||||
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||
import { endMeasure, startMeasure } from './profiling'
|
||||
import { devtoolsComponentAdded } from './devtools'
|
||||
import { fallThroughAttrs } from './componentAttrs'
|
||||
import { type Block, findFirstRootElement, fragmentKey } from './block'
|
||||
|
||||
export function setupComponent(instance: ComponentInternalInstance): void {
|
||||
if (__DEV__) {
|
||||
startMeasure(instance, `init`)
|
||||
}
|
||||
const reset = setCurrentInstance(instance)
|
||||
instance.scope.run(function componentSetupFn() {
|
||||
const { type: component, props } = instance
|
||||
|
||||
if (__DEV__) {
|
||||
if (component.name) {
|
||||
validateComponentName(component.name, instance.appContext.config)
|
||||
}
|
||||
}
|
||||
|
||||
const setupFn = isFunction(component) ? component : component.setup
|
||||
let stateOrNode: Block | undefined
|
||||
if (setupFn) {
|
||||
const setupContext = (instance.setupContext =
|
||||
setupFn && setupFn.length > 1 ? createSetupContext(instance) : null)
|
||||
pauseTracking()
|
||||
stateOrNode = callWithErrorHandling(
|
||||
setupFn,
|
||||
instance,
|
||||
VaporErrorCodes.SETUP_FUNCTION,
|
||||
[__DEV__ ? shallowReadonly(props) : props, setupContext],
|
||||
)
|
||||
resetTracking()
|
||||
}
|
||||
|
||||
let block: Block | undefined
|
||||
|
||||
// Skip the type check for production since this is only for Dev HMR
|
||||
if (__DEV__) {
|
||||
if (
|
||||
stateOrNode &&
|
||||
(stateOrNode instanceof Node ||
|
||||
isVaporComponent(stateOrNode) ||
|
||||
isArray(stateOrNode) ||
|
||||
fragmentKey in stateOrNode)
|
||||
) {
|
||||
block = stateOrNode
|
||||
} else if (isObject(stateOrNode)) {
|
||||
instance.setupState = proxyRefs(stateOrNode)
|
||||
}
|
||||
|
||||
if (!block && component.render) {
|
||||
pauseTracking()
|
||||
block = callWithErrorHandling(
|
||||
component.render,
|
||||
instance,
|
||||
VaporErrorCodes.RENDER_FUNCTION,
|
||||
[
|
||||
instance.setupState, // _ctx
|
||||
shallowReadonly(props), // $props
|
||||
instance.emit, // $emit
|
||||
getAttrsProxy(instance), // $attrs
|
||||
getSlotsProxy(instance), // $slots
|
||||
],
|
||||
)
|
||||
resetTracking()
|
||||
}
|
||||
} else {
|
||||
block = stateOrNode
|
||||
}
|
||||
|
||||
if (!block) {
|
||||
// TODO: warn no template
|
||||
block = []
|
||||
}
|
||||
instance.block = block
|
||||
|
||||
const rootElement = findFirstRootElement(instance)
|
||||
if (rootElement) {
|
||||
fallThroughAttrs(instance, rootElement)
|
||||
|
||||
// attach scopeId
|
||||
for (const id of instance.scopeIds) {
|
||||
rootElement.setAttribute(id, '')
|
||||
}
|
||||
}
|
||||
|
||||
return block
|
||||
})
|
||||
reset()
|
||||
if (__DEV__) {
|
||||
endMeasure(instance, `init`)
|
||||
}
|
||||
}
|
||||
|
||||
export function render(
|
||||
instance: ComponentInternalInstance,
|
||||
container: string | ParentNode,
|
||||
): void {
|
||||
mountComponent(instance, (container = normalizeContainer(container)))
|
||||
flushPostFlushCbs()
|
||||
}
|
||||
|
||||
export function normalizeContainer(container: string | ParentNode): ParentNode {
|
||||
return typeof container === 'string'
|
||||
? (querySelector(container) as ParentNode)
|
||||
: container
|
||||
}
|
||||
|
||||
function mountComponent(
|
||||
instance: ComponentInternalInstance,
|
||||
container: ParentNode,
|
||||
) {
|
||||
instance.container = container
|
||||
|
||||
if (__DEV__) {
|
||||
startMeasure(instance, 'mount')
|
||||
}
|
||||
|
||||
// hook: beforeMount
|
||||
invokeLifecycle(instance, VaporLifecycleHooks.BEFORE_MOUNT)
|
||||
|
||||
insert(instance.block!, instance.container)
|
||||
|
||||
// hook: mounted
|
||||
invokeLifecycle(
|
||||
instance,
|
||||
VaporLifecycleHooks.MOUNTED,
|
||||
instance => (instance.isMounted = true),
|
||||
true,
|
||||
)
|
||||
|
||||
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
||||
devtoolsComponentAdded(instance)
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
endMeasure(instance, 'mount')
|
||||
}
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
export function unmountComponent(instance: ComponentInternalInstance): void {
|
||||
const { container, scope } = instance
|
||||
|
||||
// hook: beforeUnmount
|
||||
invokeLifecycle(instance, VaporLifecycleHooks.BEFORE_UNMOUNT)
|
||||
|
||||
scope.stop()
|
||||
container.textContent = ''
|
||||
|
||||
// hook: unmounted
|
||||
invokeLifecycle(
|
||||
instance,
|
||||
VaporLifecycleHooks.UNMOUNTED,
|
||||
instance => queuePostFlushCb(() => (instance.isUnmounted = true)),
|
||||
true,
|
||||
)
|
||||
flushPostFlushCbs()
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import {
|
||||
type SetupContext,
|
||||
createSetupContext,
|
||||
getCurrentInstance,
|
||||
} from './component'
|
||||
import { warn } from './warning'
|
||||
|
||||
export function useSlots(): SetupContext['slots'] {
|
||||
return getContext().slots
|
||||
}
|
||||
|
||||
export function useAttrs(): SetupContext['attrs'] {
|
||||
return getContext().attrs
|
||||
}
|
||||
|
||||
function getContext(): SetupContext {
|
||||
const i = getCurrentInstance()!
|
||||
if (__DEV__ && !i) {
|
||||
warn(`useContext() called without active instance.`)
|
||||
}
|
||||
return i.setupContext || (i.setupContext = createSetupContext(i))
|
||||
}
|
|
@ -1,230 +0,0 @@
|
|||
import {
|
||||
type WatchOptions as BaseWatchOptions,
|
||||
type ComputedRef,
|
||||
type DebuggerOptions,
|
||||
type Ref,
|
||||
watch as baseWatch,
|
||||
} from '@vue/reactivity'
|
||||
import { EMPTY_OBJ, extend, isFunction } from '@vue/shared'
|
||||
import { currentInstance } from './component'
|
||||
import {
|
||||
type SchedulerJob,
|
||||
VaporSchedulerJobFlags,
|
||||
queueJob,
|
||||
queuePostFlushCb,
|
||||
} from './scheduler'
|
||||
import { callWithAsyncErrorHandling } from './errorHandling'
|
||||
import { warn } from './warning'
|
||||
|
||||
export type WatchEffect = (onCleanup: OnCleanup) => void
|
||||
|
||||
export type WatchSource<T = any> = Ref<T> | ComputedRef<T> | (() => T)
|
||||
|
||||
export type WatchCallback<V = any, OV = any> = (
|
||||
value: V,
|
||||
oldValue: OV,
|
||||
onCleanup: OnCleanup,
|
||||
) => any
|
||||
|
||||
type MapSources<T, Immediate> = {
|
||||
[K in keyof T]: T[K] extends WatchSource<infer V>
|
||||
? Immediate extends true
|
||||
? V | undefined
|
||||
: V
|
||||
: T[K] extends object
|
||||
? Immediate extends true
|
||||
? T[K] | undefined
|
||||
: T[K]
|
||||
: never
|
||||
}
|
||||
|
||||
type OnCleanup = (cleanupFn: () => void) => void
|
||||
|
||||
export interface WatchOptionsBase extends DebuggerOptions {
|
||||
flush?: 'pre' | 'post' | 'sync'
|
||||
}
|
||||
|
||||
export interface WatchOptions<Immediate = boolean> extends WatchOptionsBase {
|
||||
immediate?: Immediate
|
||||
deep?: boolean
|
||||
once?: boolean
|
||||
}
|
||||
|
||||
export type WatchStopHandle = () => void
|
||||
|
||||
// Simple effect.
|
||||
export function watchEffect(
|
||||
effect: WatchEffect,
|
||||
options?: WatchOptionsBase,
|
||||
): WatchStopHandle {
|
||||
return doWatch(effect, null, options)
|
||||
}
|
||||
|
||||
export function watchPostEffect(
|
||||
effect: WatchEffect,
|
||||
options?: DebuggerOptions,
|
||||
): WatchStopHandle {
|
||||
return doWatch(
|
||||
effect,
|
||||
null,
|
||||
__DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
|
||||
)
|
||||
}
|
||||
|
||||
export function watchSyncEffect(
|
||||
effect: WatchEffect,
|
||||
options?: DebuggerOptions,
|
||||
): WatchStopHandle {
|
||||
return doWatch(
|
||||
effect,
|
||||
null,
|
||||
__DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' },
|
||||
)
|
||||
}
|
||||
|
||||
type MultiWatchSources = (WatchSource<unknown> | object)[]
|
||||
|
||||
// overload: single source + cb
|
||||
export function watch<T, Immediate extends Readonly<boolean> = false>(
|
||||
source: WatchSource<T>,
|
||||
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle
|
||||
|
||||
// overload: array of multiple sources + cb
|
||||
export function watch<
|
||||
T extends MultiWatchSources,
|
||||
Immediate extends Readonly<boolean> = false,
|
||||
>(
|
||||
sources: [...T],
|
||||
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle
|
||||
|
||||
// overload: multiple sources w/ `as const`
|
||||
// watch([foo, bar] as const, () => {})
|
||||
// somehow [...T] breaks when the type is readonly
|
||||
export function watch<
|
||||
T extends Readonly<MultiWatchSources>,
|
||||
Immediate extends Readonly<boolean> = false,
|
||||
>(
|
||||
source: T,
|
||||
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle
|
||||
|
||||
// overload: watching reactive object w/ cb
|
||||
export function watch<
|
||||
T extends object,
|
||||
Immediate extends Readonly<boolean> = false,
|
||||
>(
|
||||
source: T,
|
||||
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle
|
||||
|
||||
// implementation
|
||||
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
|
||||
source: T | WatchSource<T>,
|
||||
cb: any,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle {
|
||||
if (__DEV__ && !isFunction(cb)) {
|
||||
warn(
|
||||
`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
|
||||
`Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
|
||||
`supports \`watch(source, cb, options?) signature.`,
|
||||
)
|
||||
}
|
||||
return doWatch(source as any, cb, options)
|
||||
}
|
||||
|
||||
function doWatch(
|
||||
source: WatchSource | WatchSource[] | WatchEffect | object,
|
||||
cb: WatchCallback | null,
|
||||
options: WatchOptions = EMPTY_OBJ,
|
||||
): WatchStopHandle {
|
||||
const { immediate, deep, flush, once } = options
|
||||
|
||||
if (__DEV__ && !cb) {
|
||||
if (immediate !== undefined) {
|
||||
warn(
|
||||
`watch() "immediate" option is only respected when using the ` +
|
||||
`watch(source, callback, options?) signature.`,
|
||||
)
|
||||
}
|
||||
if (deep !== undefined) {
|
||||
warn(
|
||||
`watch() "deep" option is only respected when using the ` +
|
||||
`watch(source, callback, options?) signature.`,
|
||||
)
|
||||
}
|
||||
if (once !== undefined) {
|
||||
warn(
|
||||
`watch() "once" option is only respected when using the ` +
|
||||
`watch(source, callback, options?) signature.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const baseWatchOptions: BaseWatchOptions = extend({}, options)
|
||||
|
||||
if (__DEV__) baseWatchOptions.onWarn = warn
|
||||
|
||||
let ssrCleanup: (() => void)[] | undefined
|
||||
// TODO: SSR
|
||||
// if (__SSR__ && isInSSRComponentSetup) {
|
||||
// if (flush === 'sync') {
|
||||
// const ctx = useSSRContext()!
|
||||
// ssrCleanup = ctx.__watcherHandles || (ctx.__watcherHandles = [])
|
||||
// } else if (!cb || immediate) {
|
||||
// // immediately watch or watchEffect
|
||||
// extendOptions.once = true
|
||||
// } else {
|
||||
// // watch(source, cb)
|
||||
// return NOOP
|
||||
// }
|
||||
// }
|
||||
|
||||
const instance = currentInstance
|
||||
baseWatchOptions.call = (fn, type, args) =>
|
||||
callWithAsyncErrorHandling(fn, instance, type, args)
|
||||
|
||||
// scheduler
|
||||
let isPre = false
|
||||
if (flush === 'post') {
|
||||
baseWatchOptions.scheduler = job => {
|
||||
queuePostFlushCb(job)
|
||||
}
|
||||
} else if (flush !== 'sync') {
|
||||
// default: 'pre'
|
||||
isPre = true
|
||||
baseWatchOptions.scheduler = (job, isFirstRun) => {
|
||||
if (isFirstRun) {
|
||||
job()
|
||||
} else {
|
||||
queueJob(job)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
baseWatchOptions.augmentJob = (job: SchedulerJob) => {
|
||||
// important: mark the job as a watcher callback so that scheduler knows
|
||||
// it is allowed to self-trigger (#1727)
|
||||
if (cb) {
|
||||
job.flags! |= VaporSchedulerJobFlags.ALLOW_RECURSE
|
||||
}
|
||||
if (isPre) {
|
||||
job.flags! |= VaporSchedulerJobFlags.PRE
|
||||
if (instance) {
|
||||
job.id = instance.uid
|
||||
;(job as SchedulerJob).i = instance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const watchHandle = baseWatch(source, cb, baseWatchOptions)
|
||||
|
||||
if (__SSR__ && ssrCleanup) ssrCleanup.push(watchHandle)
|
||||
return watchHandle
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { isArray } from '@vue/shared'
|
||||
import { type VaporComponentInstance, isVaporComponent } from './_new/component'
|
||||
import { type VaporComponentInstance, isVaporComponent } from './component'
|
||||
|
||||
export const fragmentKey: unique symbol = Symbol(__DEV__ ? `fragmentKey` : ``)
|
||||
|
||||
|
|
|
@ -1,112 +1,44 @@
|
|||
import { EffectScope, isRef } from '@vue/reactivity'
|
||||
import { EMPTY_OBJ, isArray, isBuiltInTag, isFunction } from '@vue/shared'
|
||||
import type { Block } from './block'
|
||||
import {
|
||||
type ComponentInternalOptions,
|
||||
type ComponentPropsOptions,
|
||||
type NormalizedPropsOptions,
|
||||
type NormalizedRawProps,
|
||||
type RawProps,
|
||||
initProps,
|
||||
normalizePropsOptions,
|
||||
} from './componentProps'
|
||||
import {
|
||||
type EmitFn,
|
||||
EffectScope,
|
||||
type EmitsOptions,
|
||||
type GenericAppContext,
|
||||
type GenericComponentInstance,
|
||||
type LifecycleHook,
|
||||
type NormalizedPropsOptions,
|
||||
type ObjectEmitsOptions,
|
||||
emit,
|
||||
normalizeEmitsOptions,
|
||||
} from './componentEmits'
|
||||
import { type RawSlots, type StaticSlots, initSlots } from './componentSlots'
|
||||
import { VaporLifecycleHooks } from './enums'
|
||||
import { warn } from './warning'
|
||||
nextUid,
|
||||
popWarningContext,
|
||||
pushWarningContext,
|
||||
} from '@vue/runtime-core'
|
||||
import type { Block } from './block'
|
||||
import { pauseTracking, resetTracking } from '@vue/reactivity'
|
||||
import { EMPTY_OBJ, isFunction } from '@vue/shared'
|
||||
import {
|
||||
type AppConfig,
|
||||
type AppContext,
|
||||
createAppContext,
|
||||
} from './apiCreateVaporApp'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import type { ComponentInstance } from './apiCreateComponentSimple'
|
||||
type RawProps,
|
||||
getDynamicPropsHandlers,
|
||||
initStaticProps,
|
||||
} from './componentProps'
|
||||
import { setDynamicProp } from './dom/prop'
|
||||
import { renderEffect } from './renderEffect'
|
||||
|
||||
export type Component = FunctionalComponent | ObjectComponent
|
||||
export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent
|
||||
|
||||
type SharedInternalOptions = {
|
||||
__propsOptions?: NormalizedPropsOptions
|
||||
__propsHandlers?: [ProxyHandler<any>, ProxyHandler<any>]
|
||||
}
|
||||
|
||||
export type SetupFn = (
|
||||
export type VaporSetupFn = (
|
||||
props: any,
|
||||
ctx: SetupContext,
|
||||
) => Block | Data | undefined
|
||||
) => Block | Record<string, any> | undefined
|
||||
|
||||
export type FunctionalComponent = SetupFn &
|
||||
Omit<ObjectComponent, 'setup'> & {
|
||||
export type FunctionalVaporComponent = VaporSetupFn &
|
||||
Omit<ObjectVaporComponent, 'setup'> & {
|
||||
displayName?: string
|
||||
} & SharedInternalOptions
|
||||
|
||||
export class SetupContext<E = EmitsOptions> {
|
||||
attrs: Data
|
||||
emit: EmitFn<E>
|
||||
slots: Readonly<StaticSlots>
|
||||
expose: (exposed?: Record<string, any>) => void
|
||||
|
||||
constructor(instance: ComponentInstance) {
|
||||
this.attrs = instance.attrs
|
||||
this.emit = instance.emit as EmitFn<E>
|
||||
this.slots = instance.slots
|
||||
this.expose = (exposed = {}) => {
|
||||
instance.exposed = exposed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createSetupContext(
|
||||
instance: ComponentInternalInstance,
|
||||
): SetupContext {
|
||||
if (__DEV__) {
|
||||
// We use getters in dev in case libs like test-utils overwrite instance
|
||||
// properties (overwrites should not be done in prod)
|
||||
return Object.freeze({
|
||||
get attrs() {
|
||||
return getAttrsProxy(instance)
|
||||
},
|
||||
get slots() {
|
||||
return getSlotsProxy(instance)
|
||||
},
|
||||
get emit() {
|
||||
return (event: string, ...args: any[]) => instance.emit(event, ...args)
|
||||
},
|
||||
expose: (exposed?: Record<string, any>) => {
|
||||
if (instance.exposed) {
|
||||
warn(`expose() should be called only once per setup().`)
|
||||
}
|
||||
if (exposed != null) {
|
||||
let exposedType: string = typeof exposed
|
||||
if (exposedType === 'object') {
|
||||
if (isArray(exposed)) {
|
||||
exposedType = 'array'
|
||||
} else if (isRef(exposed)) {
|
||||
exposedType = 'ref'
|
||||
}
|
||||
}
|
||||
if (exposedType !== 'object') {
|
||||
warn(
|
||||
`expose() should be passed a plain object, received ${exposedType}.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
instance.exposed = exposed || {}
|
||||
},
|
||||
}) as SetupContext
|
||||
} else {
|
||||
return new SetupContext(instance)
|
||||
}
|
||||
}
|
||||
|
||||
export interface ObjectComponent
|
||||
export interface ObjectVaporComponent
|
||||
extends ComponentInternalOptions,
|
||||
SharedInternalOptions {
|
||||
setup?: SetupFn
|
||||
setup?: VaporSetupFn
|
||||
inheritAttrs?: boolean
|
||||
props?: ComponentPropsOptions
|
||||
emits?: EmitsOptions
|
||||
|
@ -116,311 +48,176 @@ export interface ObjectComponent
|
|||
vapor?: boolean
|
||||
}
|
||||
|
||||
// Note: can't mark this whole interface internal because some public interfaces
|
||||
// extend it.
|
||||
export interface ComponentInternalOptions {
|
||||
interface SharedInternalOptions {
|
||||
/**
|
||||
* @internal
|
||||
* Cached normalized props options.
|
||||
* In vapor mode there are no mixins so normalized options can be cached
|
||||
* directly on the component
|
||||
*/
|
||||
__scopeId?: string
|
||||
__propsOptions?: NormalizedPropsOptions
|
||||
/**
|
||||
* @internal
|
||||
* Cached normalized props proxy handlers.
|
||||
*/
|
||||
__cssModules?: Data
|
||||
__propsHandlers?: [ProxyHandler<any> | null, ProxyHandler<any>]
|
||||
/**
|
||||
* @internal
|
||||
* Cached normalized emits options.
|
||||
*/
|
||||
__hmrId?: string
|
||||
/**
|
||||
* Compat build only, for bailing out of certain compatibility behavior
|
||||
*/
|
||||
__isBuiltIn?: boolean
|
||||
/**
|
||||
* This one should be exposed so that devtools can make use of it
|
||||
*/
|
||||
__file?: string
|
||||
/**
|
||||
* name inferred from filename
|
||||
*/
|
||||
__name?: string
|
||||
__emitsOptions?: ObjectEmitsOptions
|
||||
}
|
||||
|
||||
type LifecycleHook<TFn = Function> = TFn[] | null
|
||||
|
||||
export let currentInstance: ComponentInternalInstance | null = null
|
||||
|
||||
export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
|
||||
currentInstance
|
||||
|
||||
export const setCurrentInstance = (instance: ComponentInternalInstance) => {
|
||||
const prev = currentInstance
|
||||
currentInstance = instance
|
||||
return (): void => {
|
||||
currentInstance = prev
|
||||
export function createComponent(
|
||||
component: VaporComponent,
|
||||
rawProps?: RawProps,
|
||||
isSingleRoot?: boolean,
|
||||
): VaporComponentInstance {
|
||||
// check if we are the single root of the parent
|
||||
// if yes, inject parent attrs as dynamic props source
|
||||
if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) {
|
||||
if (rawProps) {
|
||||
;(rawProps.$ || (rawProps.$ = [])).push(currentInstance.attrs)
|
||||
} else {
|
||||
rawProps = { $: [currentInstance.attrs] }
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new VaporComponentInstance(component, rawProps)
|
||||
|
||||
pauseTracking()
|
||||
let prevInstance = currentInstance
|
||||
currentInstance = instance
|
||||
instance.scope.on()
|
||||
|
||||
if (__DEV__) {
|
||||
pushWarningContext(instance)
|
||||
}
|
||||
|
||||
const setupFn = isFunction(component) ? component : component.setup
|
||||
const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null
|
||||
instance.block = setupFn!(
|
||||
instance.props,
|
||||
// @ts-expect-error
|
||||
setupContext,
|
||||
) as Block // TODO handle return object
|
||||
|
||||
// single root, inherit attrs
|
||||
if (
|
||||
instance.hasFallthrough &&
|
||||
component.inheritAttrs !== false &&
|
||||
instance.block instanceof Element &&
|
||||
Object.keys(instance.attrs).length
|
||||
) {
|
||||
renderEffect(() => {
|
||||
for (const key in instance.attrs) {
|
||||
setDynamicProp(instance.block as Element, key, instance.attrs[key])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
popWarningContext()
|
||||
}
|
||||
|
||||
instance.scope.off()
|
||||
currentInstance = prevInstance
|
||||
resetTracking()
|
||||
return instance
|
||||
}
|
||||
|
||||
export const unsetCurrentInstance = (): void => {
|
||||
currentInstance && currentInstance.scope.off()
|
||||
currentInstance = null
|
||||
export let currentInstance: VaporComponentInstance | null = null
|
||||
|
||||
const emptyContext: GenericAppContext = {
|
||||
app: null as any,
|
||||
config: {},
|
||||
provides: /*@__PURE__*/ Object.create(null),
|
||||
}
|
||||
|
||||
const emptyAppContext = createAppContext()
|
||||
|
||||
let uid = 0
|
||||
export class ComponentInternalInstance {
|
||||
vapor = true
|
||||
|
||||
export class VaporComponentInstance implements GenericComponentInstance {
|
||||
uid: number
|
||||
appContext: AppContext
|
||||
type: VaporComponent
|
||||
parent: GenericComponentInstance | null
|
||||
appContext: GenericAppContext
|
||||
|
||||
type: Component
|
||||
block: Block | null
|
||||
container: ParentNode
|
||||
parent: ComponentInternalInstance | null
|
||||
root: ComponentInternalInstance
|
||||
|
||||
provides: Data
|
||||
block: Block
|
||||
scope: EffectScope
|
||||
comps: Set<ComponentInternalInstance>
|
||||
scopeIds: string[]
|
||||
rawProps: RawProps | undefined
|
||||
props: Record<string, any>
|
||||
attrs: Record<string, any>
|
||||
exposed: Record<string, any> | null
|
||||
|
||||
rawProps: NormalizedRawProps
|
||||
propsOptions: NormalizedPropsOptions
|
||||
emitsOptions: ObjectEmitsOptions | null
|
||||
|
||||
// state
|
||||
setupState: Data
|
||||
setupContext: SetupContext | null
|
||||
props: Data
|
||||
emit: EmitFn
|
||||
emitted: Record<string, boolean> | null
|
||||
attrs: Data
|
||||
/**
|
||||
* - `undefined` : no props
|
||||
* - `false` : all props are static
|
||||
* - `string[]` : list of props are dynamic
|
||||
* - `true` : all props as dynamic
|
||||
*/
|
||||
dynamicAttrs?: string[] | boolean
|
||||
slots: StaticSlots
|
||||
refs: Data
|
||||
// exposed properties via expose()
|
||||
exposed?: Record<string, any>
|
||||
propsDefaults: Record<string, any> | null
|
||||
|
||||
attrsProxy?: Data
|
||||
slotsProxy?: StaticSlots
|
||||
// for useTemplateRef()
|
||||
refs: Record<string, any>
|
||||
// for provide / inject
|
||||
provides: Record<string, any>
|
||||
|
||||
hasFallthrough: boolean
|
||||
|
||||
// lifecycle
|
||||
isMounted: boolean
|
||||
isUnmounted: boolean
|
||||
isUpdating: boolean
|
||||
// TODO: registory of provides, lifecycles, ...
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
// [VaporLifecycleHooks.BEFORE_MOUNT]: LifecycleHook;
|
||||
bm: LifecycleHook
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
// [VaporLifecycleHooks.MOUNTED]: LifecycleHook;
|
||||
m: LifecycleHook
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
// [VaporLifecycleHooks.BEFORE_UPDATE]: LifecycleHook;
|
||||
bu: LifecycleHook
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
// [VaporLifecycleHooks.UPDATED]: LifecycleHook;
|
||||
u: LifecycleHook
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
// [VaporLifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook;
|
||||
bum: LifecycleHook
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
// [VaporLifecycleHooks.UNMOUNTED]: LifecycleHook;
|
||||
um: LifecycleHook
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
// [VaporLifecycleHooks.RENDER_TRACKED]: LifecycleHook;
|
||||
rtc: LifecycleHook
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
// [VaporLifecycleHooks.RENDER_TRIGGERED]: LifecycleHook;
|
||||
rtg: LifecycleHook
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
// [VaporLifecycleHooks.ACTIVATED]: LifecycleHook;
|
||||
a: LifecycleHook
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
// [VaporLifecycleHooks.DEACTIVATED]: LifecycleHook;
|
||||
da: LifecycleHook
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
// [VaporLifecycleHooks.ERROR_CAPTURED]: LifecycleHook
|
||||
isDeactivated: boolean
|
||||
// LifecycleHooks.ERROR_CAPTURED
|
||||
ec: LifecycleHook
|
||||
|
||||
constructor(
|
||||
component: Component,
|
||||
rawProps: RawProps | null,
|
||||
slots: RawSlots | null,
|
||||
once: boolean = false,
|
||||
// application root node only
|
||||
appContext?: AppContext,
|
||||
) {
|
||||
this.uid = uid++
|
||||
const parent = (this.parent = currentInstance)
|
||||
this.root = parent ? parent.root : this
|
||||
const _appContext = (this.appContext =
|
||||
(parent ? parent.appContext : appContext) || emptyAppContext)
|
||||
this.block = null
|
||||
this.container = null!
|
||||
this.root = null!
|
||||
// dev only
|
||||
propsOptions?: NormalizedPropsOptions
|
||||
emitsOptions?: ObjectEmitsOptions | null
|
||||
|
||||
constructor(comp: VaporComponent, rawProps?: RawProps) {
|
||||
this.uid = nextUid()
|
||||
this.type = comp
|
||||
this.parent = currentInstance
|
||||
this.appContext = currentInstance
|
||||
? currentInstance.appContext
|
||||
: emptyContext
|
||||
|
||||
this.block = null! // to be set
|
||||
this.scope = new EffectScope(true)
|
||||
this.provides = parent
|
||||
? parent.provides
|
||||
: Object.create(_appContext.provides)
|
||||
this.type = component
|
||||
this.comps = new Set()
|
||||
this.scopeIds = []
|
||||
this.rawProps = null!
|
||||
this.propsOptions = normalizePropsOptions(component)
|
||||
this.emitsOptions = normalizeEmitsOptions(component)
|
||||
|
||||
// state
|
||||
this.setupState = EMPTY_OBJ
|
||||
this.setupContext = null
|
||||
this.props = EMPTY_OBJ
|
||||
this.emit = emit.bind(null, this)
|
||||
this.emitted = null
|
||||
this.attrs = EMPTY_OBJ
|
||||
this.slots = EMPTY_OBJ
|
||||
this.refs = EMPTY_OBJ
|
||||
this.rawProps = rawProps
|
||||
this.provides = this.refs = EMPTY_OBJ
|
||||
this.emitted = this.ec = this.exposed = null
|
||||
this.isMounted = this.isUnmounted = this.isDeactivated = false
|
||||
|
||||
// lifecycle
|
||||
this.isMounted = false
|
||||
this.isUnmounted = false
|
||||
this.isUpdating = false
|
||||
this[VaporLifecycleHooks.BEFORE_MOUNT] = null
|
||||
this[VaporLifecycleHooks.MOUNTED] = null
|
||||
this[VaporLifecycleHooks.BEFORE_UPDATE] = null
|
||||
this[VaporLifecycleHooks.UPDATED] = null
|
||||
this[VaporLifecycleHooks.BEFORE_UNMOUNT] = null
|
||||
this[VaporLifecycleHooks.UNMOUNTED] = null
|
||||
this[VaporLifecycleHooks.RENDER_TRACKED] = null
|
||||
this[VaporLifecycleHooks.RENDER_TRIGGERED] = null
|
||||
this[VaporLifecycleHooks.ACTIVATED] = null
|
||||
this[VaporLifecycleHooks.DEACTIVATED] = null
|
||||
this[VaporLifecycleHooks.ERROR_CAPTURED] = null
|
||||
// init props
|
||||
this.propsDefaults = null
|
||||
this.hasFallthrough = false
|
||||
if (rawProps && rawProps.$) {
|
||||
// has dynamic props, use proxy
|
||||
const handlers = getDynamicPropsHandlers(comp, this)
|
||||
this.props = comp.props ? new Proxy(rawProps, handlers[0]!) : EMPTY_OBJ
|
||||
this.attrs = new Proxy(rawProps, handlers[1])
|
||||
this.hasFallthrough = true
|
||||
} else {
|
||||
this.props = {}
|
||||
this.attrs = {}
|
||||
this.hasFallthrough = initStaticProps(comp, rawProps, this)
|
||||
}
|
||||
|
||||
initProps(this, rawProps, !isFunction(component), once)
|
||||
initSlots(this, slots)
|
||||
// TODO validate props
|
||||
// TODO init slots
|
||||
}
|
||||
}
|
||||
|
||||
export function isVaporComponent(
|
||||
val: unknown,
|
||||
): val is ComponentInternalInstance {
|
||||
return val instanceof ComponentInternalInstance
|
||||
value: unknown,
|
||||
): value is VaporComponentInstance {
|
||||
return value instanceof VaporComponentInstance
|
||||
}
|
||||
|
||||
export function validateComponentName(
|
||||
name: string,
|
||||
{ isNativeTag }: AppConfig,
|
||||
): void {
|
||||
if (isBuiltInTag(name) || isNativeTag(name)) {
|
||||
warn(
|
||||
'Do not use built-in or reserved HTML elements as component id: ' + name,
|
||||
)
|
||||
}
|
||||
}
|
||||
export class SetupContext<E = EmitsOptions> {
|
||||
attrs: Record<string, any>
|
||||
// emit: EmitFn<E>
|
||||
// slots: Readonly<StaticSlots>
|
||||
expose: (exposed?: Record<string, any>) => void
|
||||
|
||||
/**
|
||||
* Dev-only
|
||||
*/
|
||||
export function getAttrsProxy(instance: ComponentInternalInstance): Data {
|
||||
return (
|
||||
instance.attrsProxy ||
|
||||
(instance.attrsProxy = new Proxy(instance.attrs, {
|
||||
get(target, key: string) {
|
||||
return target[key]
|
||||
},
|
||||
set() {
|
||||
warn(`setupContext.attrs is readonly.`)
|
||||
return false
|
||||
},
|
||||
deleteProperty() {
|
||||
warn(`setupContext.attrs is readonly.`)
|
||||
return false
|
||||
},
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dev-only
|
||||
*/
|
||||
export function getSlotsProxy(
|
||||
instance: ComponentInternalInstance,
|
||||
): StaticSlots {
|
||||
return (
|
||||
instance.slotsProxy ||
|
||||
(instance.slotsProxy = new Proxy(instance.slots, {
|
||||
get(target, key: string) {
|
||||
return target[key]
|
||||
},
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
export function getComponentName(
|
||||
Component: Component,
|
||||
): string | false | undefined {
|
||||
return isFunction(Component)
|
||||
? Component.displayName || Component.name
|
||||
: Component.name || Component.__name
|
||||
}
|
||||
|
||||
export function formatComponentName(
|
||||
instance: ComponentInternalInstance | null,
|
||||
Component: Component,
|
||||
isRoot = false,
|
||||
): string {
|
||||
let name = getComponentName(Component)
|
||||
if (!name && Component.__file) {
|
||||
const match = Component.__file.match(/([^/\\]+)\.\w+$/)
|
||||
if (match) {
|
||||
name = match[1]
|
||||
constructor(instance: VaporComponentInstance) {
|
||||
this.attrs = instance.attrs
|
||||
// this.emit = instance.emit as EmitFn<E>
|
||||
// this.slots = instance.slots
|
||||
this.expose = (exposed = {}) => {
|
||||
instance.exposed = exposed
|
||||
}
|
||||
}
|
||||
|
||||
if (!name && instance && instance.parent) {
|
||||
// try to infer the name based on reverse resolution
|
||||
const inferFromRegistry = (registry: Record<string, any> | undefined) => {
|
||||
for (const key in registry) {
|
||||
if (registry[key] === Component) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
name = inferFromRegistry(instance.appContext.components)
|
||||
}
|
||||
|
||||
return name ? classify(name) : isRoot ? `App` : `Anonymous`
|
||||
}
|
||||
|
||||
const classifyRE = /(?:^|[-_])(\w)/g
|
||||
const classify = (str: string): string =>
|
||||
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
|
||||
|
|
|
@ -1,142 +0,0 @@
|
|||
import { camelize, isArray, normalizeClass, normalizeStyle } from '@vue/shared'
|
||||
import { type ComponentInternalInstance, currentInstance } from './component'
|
||||
import { isEmitListener } from './componentEmits'
|
||||
import { type RawProps, walkRawProps } from './componentProps'
|
||||
import { renderEffect } from './renderEffect'
|
||||
import { mergeProp, setDynamicProp } from './dom/prop'
|
||||
|
||||
export function patchAttrs(
|
||||
instance: ComponentInternalInstance,
|
||||
hasDynamicProps?: boolean,
|
||||
): void {
|
||||
const {
|
||||
attrs,
|
||||
rawProps,
|
||||
propsOptions: [options],
|
||||
} = instance
|
||||
|
||||
if (!rawProps.length) return
|
||||
const keys = new Set<string>()
|
||||
const classes: any[] = []
|
||||
const styles: any[] = []
|
||||
|
||||
walkRawProps(rawProps, registerAttr)
|
||||
for (const key in attrs) {
|
||||
if (!keys.has(key)) {
|
||||
delete attrs[key]
|
||||
}
|
||||
}
|
||||
|
||||
setClassOrStyle(classes, 'class', normalizeClass)
|
||||
setClassOrStyle(styles, 'style', normalizeStyle)
|
||||
|
||||
function setClassOrStyle(
|
||||
values: any[],
|
||||
field: 'class' | 'style',
|
||||
normalize: (value: any) => any,
|
||||
) {
|
||||
if (values.length) {
|
||||
if (hasDynamicProps) {
|
||||
Object.defineProperty(attrs, field, {
|
||||
get() {
|
||||
return normalize(values.map(value => value()))
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
})
|
||||
} else {
|
||||
attrs[field] = normalizeClass(values)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerAttr(key: string, value: any, getter?: boolean) {
|
||||
if (
|
||||
(!options || !(camelize(key) in options)) &&
|
||||
!isEmitListener(instance.emitsOptions, key) &&
|
||||
(key === 'class' || key === 'style' || !keys.has(key))
|
||||
) {
|
||||
keys.add(key)
|
||||
|
||||
if (key === 'class' || key === 'style') {
|
||||
;(key === 'class' ? classes : styles).push(
|
||||
hasDynamicProps
|
||||
? getter
|
||||
? value
|
||||
: () => value
|
||||
: getter
|
||||
? value()
|
||||
: value,
|
||||
)
|
||||
} else if (getter) {
|
||||
Object.defineProperty(attrs, key, {
|
||||
get: value,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
})
|
||||
} else {
|
||||
attrs[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function withAttrs(props: RawProps): RawProps {
|
||||
const instance = currentInstance!
|
||||
if (instance.type.inheritAttrs === false) return props
|
||||
const attrsGetter = () => instance.attrs
|
||||
if (!props) return [attrsGetter]
|
||||
if (isArray(props)) {
|
||||
return [attrsGetter, ...props]
|
||||
}
|
||||
return [attrsGetter, props]
|
||||
}
|
||||
|
||||
export function fallThroughAttrs(
|
||||
instance: ComponentInternalInstance,
|
||||
element: Element,
|
||||
): void {
|
||||
const {
|
||||
type: { inheritAttrs },
|
||||
dynamicAttrs,
|
||||
} = instance
|
||||
if (
|
||||
inheritAttrs === false ||
|
||||
dynamicAttrs === true // all props as dynamic
|
||||
)
|
||||
return
|
||||
|
||||
const hasStaticAttrs = dynamicAttrs || dynamicAttrs === false
|
||||
|
||||
let initial: Record<string, string> | undefined
|
||||
if (hasStaticAttrs) {
|
||||
// attrs in static template
|
||||
initial = {}
|
||||
for (let i = 0; i < element.attributes.length; i++) {
|
||||
const attr = element.attributes[i]
|
||||
if (dynamicAttrs && dynamicAttrs.includes(attr.name)) continue
|
||||
initial[attr.name] = attr.value
|
||||
}
|
||||
}
|
||||
|
||||
renderEffect(() => {
|
||||
for (const key in instance.attrs) {
|
||||
if (dynamicAttrs && dynamicAttrs.includes(key)) continue
|
||||
|
||||
let value: unknown
|
||||
if (hasStaticAttrs && key in initial!) {
|
||||
value = mergeProp(key, instance.attrs[key], initial![key])
|
||||
} else {
|
||||
value = instance.attrs[key]
|
||||
}
|
||||
|
||||
setDynamicProp(element, key, value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function setInheritAttrs(dynamicAttrs?: string[] | boolean): void {
|
||||
const instance = currentInstance!
|
||||
if (instance.type.inheritAttrs === false) return
|
||||
instance.dynamicAttrs = dynamicAttrs
|
||||
}
|
|
@ -1,185 +1,70 @@
|
|||
import {
|
||||
type UnionToIntersection,
|
||||
camelize,
|
||||
extend,
|
||||
hasOwn,
|
||||
hyphenate,
|
||||
isArray,
|
||||
isFunction,
|
||||
isOn,
|
||||
isString,
|
||||
looseToNumber,
|
||||
toHandlerKey,
|
||||
} from '@vue/shared'
|
||||
import type { Component, ComponentInternalInstance } from './component'
|
||||
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
||||
import { type StaticProps, getDynamicPropValue } from './componentProps'
|
||||
import { warn } from './warning'
|
||||
|
||||
export type ObjectEmitsOptions = Record<
|
||||
string,
|
||||
((...args: any[]) => any) | null // TODO: call validation?
|
||||
>
|
||||
|
||||
export type EmitsOptions = ObjectEmitsOptions | string[]
|
||||
|
||||
export type EmitFn<
|
||||
Options = ObjectEmitsOptions,
|
||||
Event extends keyof Options = keyof Options,
|
||||
> =
|
||||
Options extends Array<infer V>
|
||||
? (event: V, ...args: any[]) => void
|
||||
: {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function
|
||||
? (event: string, ...args: any[]) => void
|
||||
: UnionToIntersection<
|
||||
{
|
||||
[key in Event]: Options[key] extends (...args: infer Args) => any
|
||||
? (event: key, ...args: Args) => void
|
||||
: (event: key, ...args: any[]) => void
|
||||
}[Event]
|
||||
>
|
||||
|
||||
export function emit(
|
||||
instance: ComponentInternalInstance,
|
||||
event: string,
|
||||
...rawArgs: any[]
|
||||
): void {
|
||||
if (instance.isUnmounted) return
|
||||
|
||||
if (__DEV__) {
|
||||
const {
|
||||
emitsOptions,
|
||||
propsOptions: [propsOptions],
|
||||
} = instance
|
||||
if (emitsOptions) {
|
||||
if (!(event in emitsOptions)) {
|
||||
if (!propsOptions || !(toHandlerKey(event) in propsOptions)) {
|
||||
warn(
|
||||
`Component emitted event "${event}" but it is neither declared in ` +
|
||||
`the emits option nor as an "${toHandlerKey(event)}" prop.`,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const validator = emitsOptions[event]
|
||||
if (isFunction(validator)) {
|
||||
const isValid = validator(...rawArgs)
|
||||
if (!isValid) {
|
||||
warn(
|
||||
`Invalid event arguments: event validation failed for event "${event}".`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { rawProps } = instance
|
||||
const hasDynamicProps = rawProps.some(isFunction)
|
||||
|
||||
let handlerName: string
|
||||
let handler: any
|
||||
let onceHandler: any
|
||||
|
||||
const isModelListener = event.startsWith('update:')
|
||||
const modelArg = isModelListener && event.slice(7)
|
||||
let modifiers: any
|
||||
|
||||
// has v-bind or :[eventName]
|
||||
if (hasDynamicProps) {
|
||||
tryGet(key => getDynamicPropValue(rawProps, key)[0])
|
||||
} else {
|
||||
const staticProps = rawProps[0] as StaticProps
|
||||
tryGet(key => staticProps[key] && staticProps[key]())
|
||||
}
|
||||
|
||||
function tryGet(getter: (key: string) => any) {
|
||||
handler =
|
||||
getter((handlerName = toHandlerKey(event))) ||
|
||||
// also try camelCase event handler (#2249)
|
||||
getter((handlerName = toHandlerKey(camelize(event))))
|
||||
// for v-model update:xxx events, also trigger kebab-case equivalent
|
||||
// for props passed via kebab-case
|
||||
if (!handler && isModelListener) {
|
||||
handler = getter((handlerName = toHandlerKey(hyphenate(event))))
|
||||
}
|
||||
onceHandler = getter(`${handlerName}Once`)
|
||||
modifiers =
|
||||
modelArg &&
|
||||
getter(`${modelArg === 'modelValue' ? 'model' : modelArg}Modifiers`)
|
||||
}
|
||||
|
||||
// for v-model update:xxx events, apply modifiers on args
|
||||
let args = rawArgs
|
||||
if (modifiers) {
|
||||
const { number, trim } = modifiers
|
||||
if (trim) {
|
||||
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
|
||||
}
|
||||
if (number) {
|
||||
args = rawArgs.map(looseToNumber)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: warn
|
||||
|
||||
if (handler) {
|
||||
callWithAsyncErrorHandling(
|
||||
handler,
|
||||
instance,
|
||||
VaporErrorCodes.COMPONENT_EVENT_HANDLER,
|
||||
args,
|
||||
)
|
||||
}
|
||||
|
||||
if (onceHandler) {
|
||||
if (!instance.emitted) {
|
||||
instance.emitted = {}
|
||||
} else if (instance.emitted[handlerName!]) {
|
||||
return
|
||||
}
|
||||
instance.emitted[handlerName!] = true
|
||||
callWithAsyncErrorHandling(
|
||||
onceHandler,
|
||||
instance,
|
||||
VaporErrorCodes.COMPONENT_EVENT_HANDLER,
|
||||
args,
|
||||
)
|
||||
}
|
||||
}
|
||||
type EmitFn,
|
||||
type ObjectEmitsOptions,
|
||||
baseEmit,
|
||||
} from '@vue/runtime-core'
|
||||
import {
|
||||
type VaporComponent,
|
||||
type VaporComponentInstance,
|
||||
currentInstance,
|
||||
} from './component'
|
||||
import { EMPTY_OBJ, NOOP, hasOwn, isArray } from '@vue/shared'
|
||||
import { resolveSource } from './componentProps'
|
||||
|
||||
/**
|
||||
* The logic from core isn't too reusable so it's better to duplicate here
|
||||
*/
|
||||
export function normalizeEmitsOptions(
|
||||
comp: Component,
|
||||
comp: VaporComponent,
|
||||
): ObjectEmitsOptions | null {
|
||||
// TODO: caching?
|
||||
const cached = comp.__emitsOptions
|
||||
if (cached) return cached
|
||||
|
||||
const raw = comp.emits
|
||||
if (!raw) return null
|
||||
|
||||
let normalized: ObjectEmitsOptions = {}
|
||||
let normalized: ObjectEmitsOptions
|
||||
if (isArray(raw)) {
|
||||
raw.forEach(key => (normalized[key] = null))
|
||||
normalized = {}
|
||||
for (const key in raw) normalized[key] = null
|
||||
} else {
|
||||
extend(normalized, raw)
|
||||
normalized = raw
|
||||
}
|
||||
|
||||
return normalized
|
||||
return (comp.__emitsOptions = normalized)
|
||||
}
|
||||
|
||||
// Check if an incoming prop key is a declared emit event listener.
|
||||
// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are
|
||||
// both considered matched listeners.
|
||||
export function isEmitListener(
|
||||
options: ObjectEmitsOptions | null,
|
||||
key: string,
|
||||
): boolean {
|
||||
if (!options || !isOn(key)) {
|
||||
return false
|
||||
export function useEmit(): EmitFn {
|
||||
if (!currentInstance) {
|
||||
// TODO warn
|
||||
return NOOP
|
||||
} else {
|
||||
return emit.bind(null, currentInstance)
|
||||
}
|
||||
}
|
||||
|
||||
key = key.slice(2).replace(/Once$/, '')
|
||||
return (
|
||||
hasOwn(options, key[0].toLowerCase() + key.slice(1)) ||
|
||||
hasOwn(options, hyphenate(key)) ||
|
||||
hasOwn(options, key)
|
||||
export function emit(
|
||||
instance: VaporComponentInstance,
|
||||
event: string,
|
||||
...rawArgs: any[]
|
||||
): void {
|
||||
baseEmit(
|
||||
instance,
|
||||
instance.rawProps || EMPTY_OBJ,
|
||||
propGetter,
|
||||
event,
|
||||
...rawArgs,
|
||||
)
|
||||
}
|
||||
|
||||
function propGetter(rawProps: Record<string, any>, key: string) {
|
||||
const dynamicSources = rawProps.$
|
||||
if (dynamicSources) {
|
||||
let i = dynamicSources.length
|
||||
while (i--) {
|
||||
const source = resolveSource(dynamicSources[i])
|
||||
if (hasOwn(source, key)) return source[key]
|
||||
}
|
||||
}
|
||||
return rawProps[key] && resolveSource(rawProps[key])
|
||||
}
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
import type { ComponentInternalInstance } from './component'
|
||||
import {
|
||||
type NormalizedRawProps,
|
||||
type RawProps,
|
||||
normalizeRawProps,
|
||||
walkRawProps,
|
||||
} from './componentProps'
|
||||
import { type RawSlots, isDynamicSlotFn } from './componentSlots'
|
||||
import { renderEffect } from './renderEffect'
|
||||
import { setClass, setDynamicProp } from './dom/prop'
|
||||
import { setStyle } from './dom/style'
|
||||
import { normalizeBlock } from './block'
|
||||
|
||||
export function fallbackComponent(
|
||||
comp: string,
|
||||
rawProps: RawProps | null,
|
||||
slots: RawSlots | null,
|
||||
instance: ComponentInternalInstance,
|
||||
singleRoot: boolean = false,
|
||||
): HTMLElement {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const el = document.createElement(comp)
|
||||
|
||||
if (rawProps || Object.keys(instance.attrs).length) {
|
||||
rawProps = [() => instance.attrs, ...normalizeRawProps(rawProps)]
|
||||
|
||||
renderEffect(() => {
|
||||
let classes: unknown[] | undefined
|
||||
let styles: unknown[] | undefined
|
||||
|
||||
walkRawProps(
|
||||
rawProps as NormalizedRawProps,
|
||||
(key, valueOrGetter, getter) => {
|
||||
const value = getter ? valueOrGetter() : valueOrGetter
|
||||
if (key === 'class') (classes ||= []).push(value)
|
||||
else if (key === 'style') (styles ||= []).push(value)
|
||||
else setDynamicProp(el, key, value)
|
||||
},
|
||||
)
|
||||
|
||||
if (classes) setClass(el, classes)
|
||||
if (styles) setStyle(el, styles)
|
||||
})
|
||||
}
|
||||
|
||||
if (slots) {
|
||||
if (!Array.isArray(slots)) slots = [slots]
|
||||
for (let i = 0; i < slots.length; i++) {
|
||||
const slot = slots[i]
|
||||
if (!isDynamicSlotFn(slot) && slot.default) {
|
||||
const block = slot.default && slot.default()
|
||||
if (block) el.append(...normalizeBlock(block))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (singleRoot) {
|
||||
instance.dynamicAttrs = true
|
||||
for (let i = 0; i < instance.scopeIds.length; i++) {
|
||||
const id = instance.scopeIds[i]
|
||||
el.setAttribute(id, '')
|
||||
}
|
||||
}
|
||||
|
||||
const scopeId = instance.type.__scopeId
|
||||
if (scopeId) el.setAttribute(scopeId, '')
|
||||
|
||||
return el
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import { invokeArrayFns } from '@vue/shared'
|
||||
import type { VaporLifecycleHooks } from './enums'
|
||||
import { type ComponentInternalInstance, setCurrentInstance } from './component'
|
||||
import { queuePostFlushCb } from './scheduler'
|
||||
|
||||
export function invokeLifecycle(
|
||||
instance: ComponentInternalInstance,
|
||||
lifecycle: VaporLifecycleHooks,
|
||||
cb?: (instance: ComponentInternalInstance) => void,
|
||||
post?: boolean,
|
||||
): void {
|
||||
invokeArrayFns(post ? [invokeSub, invokeCurrent] : [invokeCurrent, invokeSub])
|
||||
|
||||
function invokeCurrent() {
|
||||
cb && cb(instance)
|
||||
const hooks = instance[lifecycle]
|
||||
if (hooks) {
|
||||
const fn = () => {
|
||||
const reset = setCurrentInstance(instance)
|
||||
instance.scope.run(() => invokeArrayFns(hooks))
|
||||
reset()
|
||||
}
|
||||
post ? queuePostFlushCb(fn) : fn()
|
||||
}
|
||||
}
|
||||
|
||||
function invokeSub() {
|
||||
instance.comps.forEach(comp => invokeLifecycle(comp, lifecycle, cb, post))
|
||||
}
|
||||
}
|
|
@ -1,392 +1,231 @@
|
|||
import { EMPTY_ARR, NO, camelize, hasOwn, isFunction } from '@vue/shared'
|
||||
import type { VaporComponent, VaporComponentInstance } from './component'
|
||||
import {
|
||||
EMPTY_ARR,
|
||||
EMPTY_OBJ,
|
||||
camelize,
|
||||
extend,
|
||||
hasOwn,
|
||||
hyphenate,
|
||||
isArray,
|
||||
isFunction,
|
||||
} from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import { shallowReactive } from '@vue/reactivity'
|
||||
import { warn } from './warning'
|
||||
import type { Component, ComponentInternalInstance } from './component'
|
||||
import { patchAttrs } from './componentAttrs'
|
||||
import { firstEffect } from './renderEffect'
|
||||
type NormalizedPropsOptions,
|
||||
baseNormalizePropsOptions,
|
||||
isEmitListener,
|
||||
resolvePropValue,
|
||||
} from '@vue/runtime-core'
|
||||
import { normalizeEmitsOptions } from './componentEmits'
|
||||
|
||||
export type ComponentPropsOptions<P = Data> =
|
||||
| ComponentObjectPropsOptions<P>
|
||||
| string[]
|
||||
|
||||
export type ComponentObjectPropsOptions<P = Data> = {
|
||||
[K in keyof P]: Prop<P[K]> | null
|
||||
export interface RawProps {
|
||||
[key: string]: PropSource
|
||||
$?: DynamicPropsSource[]
|
||||
}
|
||||
|
||||
export type Prop<T, D = T> = PropOptions<T, D> | PropType<T>
|
||||
type PropSource<T = any> = T | (() => T)
|
||||
|
||||
type DefaultFactory<T> = (props: Data) => T | null | undefined
|
||||
type DynamicPropsSource = PropSource<Record<string, any>>
|
||||
|
||||
export interface PropOptions<T = any, D = T> {
|
||||
type?: PropType<T> | true | null
|
||||
required?: boolean
|
||||
default?: D | DefaultFactory<D> | null | undefined | object
|
||||
validator?(value: unknown, props: Data): boolean
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
skipFactory?: boolean
|
||||
}
|
||||
export function initStaticProps(
|
||||
comp: VaporComponent,
|
||||
rawProps: RawProps | undefined,
|
||||
instance: VaporComponentInstance,
|
||||
): boolean {
|
||||
let hasAttrs = false
|
||||
const { props, attrs } = instance
|
||||
const [propsOptions, needCastKeys] = normalizePropsOptions(comp)
|
||||
const emitsOptions = normalizeEmitsOptions(comp)
|
||||
|
||||
export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
|
||||
// for dev emit check
|
||||
if (__DEV__) {
|
||||
instance.propsOptions = normalizePropsOptions(comp)
|
||||
instance.emitsOptions = emitsOptions
|
||||
}
|
||||
|
||||
type PropConstructor<T = any> =
|
||||
| { new (...args: any[]): T & {} }
|
||||
| { (): T }
|
||||
| PropMethod<T>
|
||||
|
||||
type PropMethod<T, TConstructor = any> = [T] extends [
|
||||
((...args: any) => any) | undefined,
|
||||
] // if is function with args, allowing non-required functions
|
||||
? { new (): TConstructor; (): T; readonly prototype: TConstructor } // Create Function like constructor
|
||||
: never
|
||||
|
||||
enum BooleanFlags {
|
||||
shouldCast,
|
||||
shouldCastTrue,
|
||||
}
|
||||
|
||||
type NormalizedProp =
|
||||
| null
|
||||
| (PropOptions & {
|
||||
[BooleanFlags.shouldCast]?: boolean
|
||||
[BooleanFlags.shouldCastTrue]?: boolean
|
||||
})
|
||||
|
||||
export type NormalizedProps = Record<string, NormalizedProp>
|
||||
export type NormalizedPropsOptions =
|
||||
| [props: NormalizedProps, needCastKeys: string[]]
|
||||
| []
|
||||
|
||||
export type StaticProps = Record<string, () => unknown>
|
||||
type DynamicProps = () => Data
|
||||
export type NormalizedRawProps = Array<StaticProps | DynamicProps>
|
||||
export type RawProps = NormalizedRawProps | StaticProps | DynamicProps | null
|
||||
|
||||
export function initProps(
|
||||
instance: ComponentInternalInstance,
|
||||
rawProps: RawProps,
|
||||
isStateful: boolean,
|
||||
once: boolean,
|
||||
): void {
|
||||
instance.rawProps = rawProps = normalizeRawProps(rawProps)
|
||||
const props: Data = {}
|
||||
const attrs = (instance.attrs = shallowReactive<Data>({}))
|
||||
const [options] = instance.propsOptions
|
||||
// has v-bind or :[eventName]
|
||||
const hasDynamicProps = rawProps.some(isFunction)
|
||||
|
||||
if (options) {
|
||||
if (hasDynamicProps) {
|
||||
for (const key in options) {
|
||||
const getter = () =>
|
||||
getDynamicPropValue(rawProps as NormalizedRawProps, key)
|
||||
registerProp(instance, once, props, key, getter, true)
|
||||
for (const key in rawProps) {
|
||||
const normalizedKey = camelize(key)
|
||||
const needCast = needCastKeys && needCastKeys.includes(normalizedKey)
|
||||
const source = rawProps[key]
|
||||
if (propsOptions && normalizedKey in propsOptions) {
|
||||
if (isFunction(source)) {
|
||||
Object.defineProperty(props, normalizedKey, {
|
||||
enumerable: true,
|
||||
get: needCast
|
||||
? () =>
|
||||
resolvePropValue(
|
||||
propsOptions,
|
||||
normalizedKey,
|
||||
source(),
|
||||
instance,
|
||||
resolveDefault,
|
||||
)
|
||||
: source,
|
||||
})
|
||||
} else {
|
||||
props[normalizedKey] = needCast
|
||||
? resolvePropValue(
|
||||
propsOptions,
|
||||
normalizedKey,
|
||||
source,
|
||||
instance,
|
||||
resolveDefault,
|
||||
)
|
||||
: source
|
||||
}
|
||||
} else {
|
||||
const staticProps = rawProps[0] as StaticProps
|
||||
for (const key in options) {
|
||||
const rawKey = staticProps && getRawKey(staticProps, key)
|
||||
if (rawKey) {
|
||||
registerProp(instance, once, props, key, staticProps[rawKey])
|
||||
} else {
|
||||
registerProp(instance, once, props, key, undefined, false, true)
|
||||
} else if (!isEmitListener(emitsOptions, key)) {
|
||||
if (isFunction(source)) {
|
||||
Object.defineProperty(attrs, key, {
|
||||
enumerable: true,
|
||||
get: source,
|
||||
})
|
||||
} else {
|
||||
attrs[normalizedKey] = source
|
||||
}
|
||||
hasAttrs = true
|
||||
}
|
||||
}
|
||||
for (const key in propsOptions) {
|
||||
if (!(key in props)) {
|
||||
props[key] = resolvePropValue(
|
||||
propsOptions,
|
||||
key,
|
||||
undefined,
|
||||
instance,
|
||||
resolveDefault,
|
||||
true,
|
||||
)
|
||||
}
|
||||
}
|
||||
return hasAttrs
|
||||
}
|
||||
|
||||
function resolveDefault(
|
||||
factory: (props: Record<string, any>) => unknown,
|
||||
instance: VaporComponentInstance,
|
||||
) {
|
||||
return factory.call(null, instance.props)
|
||||
}
|
||||
|
||||
// TODO optimization: maybe convert functions into computeds
|
||||
export function resolveSource(source: PropSource): Record<string, any> {
|
||||
return isFunction(source) ? source() : source
|
||||
}
|
||||
|
||||
const passThrough = (val: any) => val
|
||||
|
||||
export function getDynamicPropsHandlers(
|
||||
comp: VaporComponent,
|
||||
instance: VaporComponentInstance,
|
||||
): [ProxyHandler<RawProps> | null, ProxyHandler<RawProps>] {
|
||||
if (comp.__propsHandlers) {
|
||||
return comp.__propsHandlers
|
||||
}
|
||||
let normalizedKeys: string[] | undefined
|
||||
const propsOptions = normalizePropsOptions(comp)[0]
|
||||
const emitsOptions = normalizeEmitsOptions(comp)
|
||||
const isProp = propsOptions ? (key: string) => hasOwn(propsOptions, key) : NO
|
||||
|
||||
const getProp = (target: RawProps, key: string, asProp: boolean) => {
|
||||
if (key === '$') return
|
||||
if (asProp) {
|
||||
if (!isProp(key)) return
|
||||
} else if (isProp(key) || isEmitListener(emitsOptions, key)) {
|
||||
return
|
||||
}
|
||||
const castProp = propsOptions
|
||||
? (value: any, isAbsent = false) =>
|
||||
asProp
|
||||
? resolvePropValue(
|
||||
propsOptions,
|
||||
key as string,
|
||||
value,
|
||||
instance,
|
||||
resolveDefault,
|
||||
isAbsent,
|
||||
)
|
||||
: value
|
||||
: passThrough
|
||||
|
||||
if (key in target) {
|
||||
return castProp(resolveSource(target[key as string]))
|
||||
}
|
||||
if (target.$) {
|
||||
let i = target.$.length
|
||||
let source
|
||||
while (i--) {
|
||||
source = resolveSource(target.$[i])
|
||||
if (hasOwn(source, key)) {
|
||||
return castProp(source[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
return castProp(undefined, true)
|
||||
}
|
||||
|
||||
// validation
|
||||
if (__DEV__) {
|
||||
validateProps(rawProps, props, options || {})
|
||||
}
|
||||
const propsHandlers = propsOptions
|
||||
? ({
|
||||
get: (target, key: string) => getProp(target, key, true),
|
||||
has: (_, key: string) => isProp(key),
|
||||
getOwnPropertyDescriptor(target, key: string) {
|
||||
if (isProp(key)) {
|
||||
return {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => getProp(target, key, true),
|
||||
}
|
||||
}
|
||||
},
|
||||
ownKeys: () =>
|
||||
normalizedKeys || (normalizedKeys = Object.keys(propsOptions)),
|
||||
set: NO,
|
||||
deleteProperty: NO,
|
||||
} satisfies ProxyHandler<RawProps>)
|
||||
: null
|
||||
|
||||
if (hasDynamicProps) {
|
||||
firstEffect(instance, () => patchAttrs(instance, true))
|
||||
} else {
|
||||
patchAttrs(instance)
|
||||
}
|
||||
|
||||
if (isStateful) {
|
||||
instance.props = /* TODO isSSR ? props : */ shallowReactive(props)
|
||||
} else {
|
||||
// functional w/ optional props, props === attrs
|
||||
instance.props = instance.propsOptions === EMPTY_ARR ? attrs : props
|
||||
}
|
||||
}
|
||||
|
||||
function registerProp(
|
||||
instance: ComponentInternalInstance,
|
||||
once: boolean,
|
||||
props: Data,
|
||||
rawKey: string,
|
||||
getter?: (() => unknown) | (() => DynamicPropResult),
|
||||
isDynamic?: boolean,
|
||||
isAbsent?: boolean,
|
||||
) {
|
||||
const key = camelize(rawKey)
|
||||
if (key in props) return
|
||||
|
||||
const [options, needCastKeys] = instance.propsOptions
|
||||
const needCast = needCastKeys && needCastKeys.includes(key)
|
||||
const withCast = (value: unknown, absent?: boolean) =>
|
||||
resolvePropValue(options!, props, key, value, absent)
|
||||
|
||||
if (isAbsent) {
|
||||
props[key] = needCast ? withCast(undefined, true) : undefined
|
||||
} else {
|
||||
const get: () => unknown = isDynamic
|
||||
? needCast
|
||||
? () => withCast(...(getter!() as DynamicPropResult))
|
||||
: () => (getter!() as DynamicPropResult)[0]
|
||||
: needCast
|
||||
? () => withCast(getter!())
|
||||
: getter!
|
||||
|
||||
const descriptor: PropertyDescriptor = once ? { value: get() } : { get }
|
||||
descriptor.enumerable = true
|
||||
Object.defineProperty(props, key, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeRawProps(rawProps: RawProps): NormalizedRawProps {
|
||||
if (!rawProps) return []
|
||||
if (!isArray(rawProps)) return [rawProps]
|
||||
return rawProps
|
||||
}
|
||||
|
||||
export function walkRawProps(
|
||||
rawProps: NormalizedRawProps,
|
||||
cb: (key: string, value: any, getter?: boolean) => void,
|
||||
): void {
|
||||
for (const props of Array.from(rawProps).reverse()) {
|
||||
if (isFunction(props)) {
|
||||
const resolved = props()
|
||||
for (const rawKey in resolved) {
|
||||
cb(rawKey, resolved[rawKey])
|
||||
}
|
||||
} else {
|
||||
for (const rawKey in props) {
|
||||
cb(rawKey, props[rawKey], true)
|
||||
const hasAttr = (target: RawProps, key: string) => {
|
||||
if (key === '$' || isProp(key) || isEmitListener(emitsOptions, key))
|
||||
return false
|
||||
if (target.$) {
|
||||
let i = target.$.length
|
||||
while (i--) {
|
||||
if (hasOwn(resolveSource(target.$[i]), key)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasOwn(target, key)
|
||||
}
|
||||
}
|
||||
|
||||
function getRawKey(obj: Data, key: string) {
|
||||
return Object.keys(obj).find(k => camelize(k) === key)
|
||||
}
|
||||
|
||||
type DynamicPropResult = [value: unknown, absent: boolean]
|
||||
export function getDynamicPropValue(
|
||||
rawProps: NormalizedRawProps,
|
||||
key: string,
|
||||
): DynamicPropResult {
|
||||
for (const props of Array.from(rawProps).reverse()) {
|
||||
if (isFunction(props)) {
|
||||
const resolved = props()
|
||||
const rawKey = getRawKey(resolved, key)
|
||||
if (rawKey) return [resolved[rawKey], false]
|
||||
} else {
|
||||
const rawKey = getRawKey(props, key)
|
||||
if (rawKey) return [props[rawKey](), false]
|
||||
}
|
||||
}
|
||||
return [undefined, true]
|
||||
}
|
||||
|
||||
export function resolvePropValue(
|
||||
options: NormalizedProps,
|
||||
props: Data,
|
||||
key: string,
|
||||
value: unknown,
|
||||
isAbsent?: boolean,
|
||||
): unknown {
|
||||
const opt = options[key]
|
||||
if (opt != null) {
|
||||
const hasDefault = hasOwn(opt, 'default')
|
||||
// default values
|
||||
if (hasDefault && value === undefined) {
|
||||
const defaultValue = opt.default
|
||||
if (
|
||||
opt.type !== Function &&
|
||||
!opt.skipFactory &&
|
||||
isFunction(defaultValue)
|
||||
) {
|
||||
value = defaultValue.call(null, props)
|
||||
} else {
|
||||
value = defaultValue
|
||||
const attrsHandlers = {
|
||||
get: (target, key: string) => getProp(target, key, false),
|
||||
has: hasAttr,
|
||||
getOwnPropertyDescriptor(target, key: string) {
|
||||
if (hasAttr(target, key)) {
|
||||
return {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => getProp(target, key, false),
|
||||
}
|
||||
}
|
||||
}
|
||||
// boolean casting
|
||||
if (opt[BooleanFlags.shouldCast]) {
|
||||
if (isAbsent && !hasDefault) {
|
||||
value = false
|
||||
} else if (
|
||||
opt[BooleanFlags.shouldCastTrue] &&
|
||||
(value === '' || value === hyphenate(key))
|
||||
) {
|
||||
value = true
|
||||
},
|
||||
ownKeys(target) {
|
||||
const keys = Object.keys(target)
|
||||
if (target.$) {
|
||||
let i = target.$.length
|
||||
while (i--) {
|
||||
keys.push(...Object.keys(resolveSource(target.$[i])))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value
|
||||
return keys.filter(key => hasAttr(target, key))
|
||||
},
|
||||
set: NO,
|
||||
deleteProperty: NO,
|
||||
} satisfies ProxyHandler<RawProps>
|
||||
|
||||
return (comp.__propsHandlers = [propsHandlers, attrsHandlers])
|
||||
}
|
||||
|
||||
export function normalizePropsOptions(comp: Component): NormalizedPropsOptions {
|
||||
function normalizePropsOptions(comp: VaporComponent): NormalizedPropsOptions {
|
||||
const cached = comp.__propsOptions
|
||||
if (cached) return cached
|
||||
|
||||
const raw = comp.props
|
||||
const normalized: NormalizedProps | undefined = {}
|
||||
if (!raw) return EMPTY_ARR as []
|
||||
|
||||
const normalized: NormalizedPropsOptions[0] = {}
|
||||
const needCastKeys: NormalizedPropsOptions[1] = []
|
||||
baseNormalizePropsOptions(raw, normalized, needCastKeys)
|
||||
|
||||
if (!raw) {
|
||||
return EMPTY_ARR as []
|
||||
}
|
||||
|
||||
if (isArray(raw)) {
|
||||
for (let i = 0; i < raw.length; i++) {
|
||||
const normalizedKey = camelize(raw[i])
|
||||
if (validatePropName(normalizedKey)) {
|
||||
normalized[normalizedKey] = EMPTY_OBJ
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const key in raw) {
|
||||
const normalizedKey = camelize(key)
|
||||
if (validatePropName(normalizedKey)) {
|
||||
const opt = raw[key]
|
||||
const prop: NormalizedProp = (normalized[normalizedKey] =
|
||||
isArray(opt) || isFunction(opt) ? { type: opt } : extend({}, opt))
|
||||
if (prop) {
|
||||
const booleanIndex = getTypeIndex(Boolean, prop.type)
|
||||
const stringIndex = getTypeIndex(String, prop.type)
|
||||
prop[BooleanFlags.shouldCast] = booleanIndex > -1
|
||||
prop[BooleanFlags.shouldCastTrue] =
|
||||
stringIndex < 0 || booleanIndex < stringIndex
|
||||
// if the prop needs boolean casting or default value
|
||||
if (booleanIndex > -1 || hasOwn(prop, 'default')) {
|
||||
needCastKeys.push(normalizedKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const res: NormalizedPropsOptions = (comp.__propsOptions = [
|
||||
normalized,
|
||||
needCastKeys,
|
||||
])
|
||||
return res
|
||||
}
|
||||
|
||||
function validatePropName(key: string) {
|
||||
if (key[0] !== '$') {
|
||||
return true
|
||||
} else if (__DEV__) {
|
||||
warn(`Invalid prop name: "${key}" is a reserved property.`)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function getType(ctor: Prop<any>): string {
|
||||
const match = ctor && ctor.toString().match(/^\s*(function|class) (\w+)/)
|
||||
return match ? match[2] : ctor === null ? 'null' : ''
|
||||
}
|
||||
|
||||
function isSameType(a: Prop<any>, b: Prop<any>): boolean {
|
||||
return getType(a) === getType(b)
|
||||
}
|
||||
|
||||
function getTypeIndex(
|
||||
type: Prop<any>,
|
||||
expectedTypes: PropType<any> | void | null | true,
|
||||
): number {
|
||||
if (isArray(expectedTypes)) {
|
||||
return expectedTypes.findIndex(t => isSameType(t, type))
|
||||
} else if (isFunction(expectedTypes)) {
|
||||
return isSameType(expectedTypes, type) ? 0 : -1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* dev only
|
||||
*/
|
||||
function validateProps(
|
||||
rawProps: NormalizedRawProps,
|
||||
props: Data,
|
||||
options: NormalizedProps,
|
||||
) {
|
||||
const presentKeys: string[] = []
|
||||
for (const props of rawProps) {
|
||||
presentKeys.push(...Object.keys(isFunction(props) ? props() : props))
|
||||
}
|
||||
|
||||
for (const key in options) {
|
||||
const opt = options[key]
|
||||
if (opt != null)
|
||||
validateProp(
|
||||
key,
|
||||
props[key],
|
||||
opt,
|
||||
props,
|
||||
!presentKeys.some(k => camelize(k) === key),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* dev only
|
||||
*/
|
||||
function validateProp(
|
||||
name: string,
|
||||
value: unknown,
|
||||
option: PropOptions,
|
||||
props: Data,
|
||||
isAbsent: boolean,
|
||||
) {
|
||||
const { required, validator } = option
|
||||
// required!
|
||||
if (required && isAbsent) {
|
||||
warn('Missing required prop: "' + name + '"')
|
||||
return
|
||||
}
|
||||
// missing but optional
|
||||
if (value == null && !required) {
|
||||
return
|
||||
}
|
||||
// NOTE: type check is not supported in vapor
|
||||
// // type check
|
||||
// if (type != null && type !== true) {
|
||||
// let isValid = false
|
||||
// const types = isArray(type) ? type : [type]
|
||||
// const expectedTypes = []
|
||||
// // value is valid as long as one of the specified types match
|
||||
// for (let i = 0; i < types.length && !isValid; i++) {
|
||||
// const { valid, expectedType } = assertType(value, types[i])
|
||||
// expectedTypes.push(expectedType || '')
|
||||
// isValid = valid
|
||||
// }
|
||||
// if (!isValid) {
|
||||
// warn(getInvalidTypeMessage(name, value, expectedTypes))
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
// custom validator
|
||||
if (validator && !validator(value, props)) {
|
||||
warn('Invalid prop: custom validator check failed for prop "' + name + '".')
|
||||
}
|
||||
return (comp.__propsOptions = [normalized, needCastKeys])
|
||||
}
|
||||
|
|
|
@ -1,267 +0,0 @@
|
|||
import { type IfAny, isArray, isFunction } from '@vue/shared'
|
||||
import {
|
||||
type EffectScope,
|
||||
effectScope,
|
||||
isReactive,
|
||||
shallowReactive,
|
||||
shallowRef,
|
||||
} from '@vue/reactivity'
|
||||
import {
|
||||
type ComponentInternalInstance,
|
||||
currentInstance,
|
||||
setCurrentInstance,
|
||||
} from './component'
|
||||
import { type Block, type Fragment, fragmentKey, isValidBlock } from './block'
|
||||
import { firstEffect, renderEffect } from './renderEffect'
|
||||
import { createComment, createTextNode, insert, remove } from './dom/element'
|
||||
import type { NormalizedRawProps } from './componentProps'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import { mergeProps } from './dom/prop'
|
||||
|
||||
// TODO: SSR
|
||||
|
||||
export type Slot<T extends any = any> = (
|
||||
...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
|
||||
) => Block
|
||||
|
||||
export type StaticSlots = Record<string, Slot>
|
||||
export type DynamicSlot = { name: string; fn: Slot }
|
||||
export type DynamicSlotFn = () => DynamicSlot | DynamicSlot[] | undefined
|
||||
export type NormalizedRawSlots = Array<StaticSlots | DynamicSlotFn>
|
||||
export type RawSlots = NormalizedRawSlots | StaticSlots | null
|
||||
|
||||
export const isDynamicSlotFn = isFunction as (
|
||||
val: StaticSlots | DynamicSlotFn,
|
||||
) => val is DynamicSlotFn
|
||||
|
||||
export function initSlots(
|
||||
instance: ComponentInternalInstance,
|
||||
rawSlots: RawSlots | null = null,
|
||||
): void {
|
||||
if (!rawSlots) return
|
||||
if (!isArray(rawSlots)) rawSlots = [rawSlots]
|
||||
|
||||
if (!rawSlots.some(slot => isDynamicSlotFn(slot))) {
|
||||
instance.slots = {}
|
||||
// with ctx
|
||||
const slots = rawSlots[0] as StaticSlots
|
||||
for (const name in slots) {
|
||||
registerSlot(name, slots[name])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
instance.slots = shallowReactive({})
|
||||
const keys: Set<string>[] = []
|
||||
rawSlots.forEach((slots, index) => {
|
||||
const isDynamicSlot = isDynamicSlotFn(slots)
|
||||
if (isDynamicSlot) {
|
||||
firstEffect(instance, () => {
|
||||
const recordNames = keys[index] || (keys[index] = new Set())
|
||||
let dynamicSlot: ReturnType<DynamicSlotFn>
|
||||
if (isDynamicSlotFn(slots)) {
|
||||
dynamicSlot = slots()
|
||||
if (isArray(dynamicSlot)) {
|
||||
for (const slot of dynamicSlot) {
|
||||
registerSlot(slot.name, slot.fn, recordNames)
|
||||
}
|
||||
} else if (dynamicSlot) {
|
||||
registerSlot(dynamicSlot.name, dynamicSlot.fn, recordNames)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
for (const name of recordNames) {
|
||||
if (
|
||||
!(isArray(dynamicSlot)
|
||||
? dynamicSlot.some(s => s.name === name)
|
||||
: dynamicSlot && dynamicSlot.name === name)
|
||||
) {
|
||||
recordNames.delete(name)
|
||||
delete instance.slots[name]
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
for (const name in slots) {
|
||||
registerSlot(name, slots[name])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function registerSlot(name: string, fn: Slot, recordNames?: Set<string>) {
|
||||
instance.slots[name] = withCtx(fn)
|
||||
recordNames && recordNames.add(name)
|
||||
}
|
||||
|
||||
function withCtx(fn: Slot): Slot {
|
||||
return (...args: any[]) => {
|
||||
const reset = setCurrentInstance(instance.parent!)
|
||||
try {
|
||||
return fn(...(args as any))
|
||||
} finally {
|
||||
reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createSlot(
|
||||
name: string | (() => string),
|
||||
binds?: NormalizedRawProps,
|
||||
fallback?: Slot,
|
||||
): Block {
|
||||
const { slots } = currentInstance!
|
||||
|
||||
const slotBlock = shallowRef<Block>()
|
||||
let slotBranch: Slot | undefined
|
||||
let slotScope: EffectScope | undefined
|
||||
|
||||
let fallbackBlock: Block | undefined
|
||||
let fallbackBranch: Slot | undefined
|
||||
let fallbackScope: EffectScope | undefined
|
||||
|
||||
const normalizeBinds = binds && normalizeSlotProps(binds)
|
||||
|
||||
const isDynamicName = isFunction(name)
|
||||
// fast path for static slots & without fallback
|
||||
if (!isDynamicName && !isReactive(slots) && !fallback) {
|
||||
if ((slotBranch = slots[name])) {
|
||||
return slotBranch(normalizeBinds)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
const anchor = __DEV__ ? createComment('slot') : createTextNode()
|
||||
const fragment: Fragment = {
|
||||
nodes: [],
|
||||
anchor,
|
||||
[fragmentKey]: true,
|
||||
}
|
||||
|
||||
// TODO lifecycle hooks
|
||||
renderEffect(() => {
|
||||
const parent = anchor.parentNode
|
||||
|
||||
if (
|
||||
!slotBlock.value || // not initied
|
||||
fallbackScope || // in fallback slot
|
||||
isValidBlock(slotBlock.value) // slot block is valid
|
||||
) {
|
||||
renderSlot(parent)
|
||||
} else {
|
||||
renderFallback(parent)
|
||||
}
|
||||
})
|
||||
|
||||
return fragment
|
||||
|
||||
function renderSlot(parent: ParentNode | null) {
|
||||
// from fallback to slot
|
||||
const fromFallback = fallbackScope
|
||||
if (fromFallback) {
|
||||
// clean fallback slot
|
||||
fallbackScope!.stop()
|
||||
remove(fallbackBlock!, parent!)
|
||||
fallbackScope = fallbackBlock = undefined
|
||||
}
|
||||
|
||||
const slotName = isFunction(name) ? name() : name
|
||||
const branch = slots[slotName]!
|
||||
|
||||
if (branch) {
|
||||
// init slot scope and block or switch branch
|
||||
if (!slotScope || slotBranch !== branch) {
|
||||
// clean previous slot
|
||||
if (slotScope && !fromFallback) {
|
||||
slotScope.stop()
|
||||
remove(slotBlock.value!, parent!)
|
||||
}
|
||||
|
||||
slotBranch = branch
|
||||
slotScope = effectScope()
|
||||
slotBlock.value = slotScope.run(() => slotBranch!(normalizeBinds))
|
||||
}
|
||||
|
||||
// if slot block is valid, render it
|
||||
if (slotBlock.value && isValidBlock(slotBlock.value)) {
|
||||
fragment.nodes = slotBlock.value
|
||||
parent && insert(slotBlock.value, parent, anchor)
|
||||
} else {
|
||||
renderFallback(parent)
|
||||
}
|
||||
} else {
|
||||
renderFallback(parent)
|
||||
}
|
||||
}
|
||||
|
||||
function renderFallback(parent: ParentNode | null) {
|
||||
// if slot branch is initied, remove it from DOM, but keep the scope
|
||||
if (slotBranch) {
|
||||
remove(slotBlock.value!, parent!)
|
||||
}
|
||||
|
||||
fallbackBranch ||= fallback
|
||||
if (fallbackBranch) {
|
||||
fallbackScope = effectScope()
|
||||
fragment.nodes = fallbackBlock = fallbackScope.run(() =>
|
||||
fallbackBranch!(normalizeBinds),
|
||||
)!
|
||||
parent && insert(fallbackBlock, parent, anchor)
|
||||
} else {
|
||||
fragment.nodes = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeSlotProps(rawPropsList: NormalizedRawProps) {
|
||||
const { length } = rawPropsList
|
||||
const mergings = length > 1 ? shallowReactive<Data[]>([]) : undefined
|
||||
const result = shallowReactive<Data>({})
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const rawProps = rawPropsList[i]
|
||||
if (isFunction(rawProps)) {
|
||||
// dynamic props
|
||||
renderEffect(() => {
|
||||
const props = rawProps()
|
||||
if (mergings) {
|
||||
mergings[i] = props
|
||||
} else {
|
||||
setDynamicProps(props)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// static props
|
||||
const props = mergings
|
||||
? (mergings[i] = shallowReactive<Data>({}))
|
||||
: result
|
||||
for (const key in rawProps) {
|
||||
const valueSource = rawProps[key]
|
||||
renderEffect(() => {
|
||||
props[key] = valueSource()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mergings) {
|
||||
renderEffect(() => {
|
||||
setDynamicProps(mergeProps(...mergings))
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
function setDynamicProps(props: Data) {
|
||||
const otherExistingKeys = new Set(Object.keys(result))
|
||||
for (const key in props) {
|
||||
result[key] = props[key]
|
||||
otherExistingKeys.delete(key)
|
||||
}
|
||||
// delete other stale props
|
||||
for (const key of otherExistingKeys) {
|
||||
delete result[key]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import {
|
||||
type ShallowUnwrapRef,
|
||||
proxyRefs,
|
||||
shallowReactive,
|
||||
} from '@vue/reactivity'
|
||||
import { renderEffect } from './renderEffect'
|
||||
|
||||
export function withDestructure<T extends any[], R>(
|
||||
assign: (data: ShallowUnwrapRef<T>) => any[],
|
||||
block: (ctx: any[]) => R,
|
||||
): (data: T) => R {
|
||||
return (data: T) => {
|
||||
const ctx = shallowReactive<any[]>([])
|
||||
renderEffect(() => {
|
||||
const res = assign(proxyRefs(data))
|
||||
const len = res.length
|
||||
for (let i = 0; i < len; i++) {
|
||||
ctx[i] = res[i]
|
||||
}
|
||||
})
|
||||
return block(ctx)
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
/* eslint-disable no-restricted-globals */
|
||||
import type { App } from './apiCreateVaporApp'
|
||||
import type { ComponentInternalInstance } from './component'
|
||||
|
||||
interface AppRecord {
|
||||
id: number
|
||||
app: App
|
||||
version: string
|
||||
types: Record<string, string | Symbol>
|
||||
}
|
||||
|
||||
enum DevtoolsHooks {
|
||||
APP_INIT = 'app:init',
|
||||
APP_UNMOUNT = 'app:unmount',
|
||||
COMPONENT_UPDATED = 'component:updated',
|
||||
COMPONENT_ADDED = 'component:added',
|
||||
COMPONENT_REMOVED = 'component:removed',
|
||||
COMPONENT_EMIT = 'component:emit',
|
||||
PERFORMANCE_START = 'perf:start',
|
||||
PERFORMANCE_END = 'perf:end',
|
||||
}
|
||||
|
||||
export interface DevtoolsHook {
|
||||
enabled?: boolean
|
||||
emit: (event: string, ...payload: any[]) => void
|
||||
on: (event: string, handler: Function) => void
|
||||
once: (event: string, handler: Function) => void
|
||||
off: (event: string, handler: Function) => void
|
||||
appRecords: AppRecord[]
|
||||
/**
|
||||
* Added at https://github.com/vuejs/devtools/commit/f2ad51eea789006ab66942e5a27c0f0986a257f9
|
||||
* Returns whether the arg was buffered or not
|
||||
*/
|
||||
cleanupBuffer?: (matchArg: unknown) => boolean
|
||||
}
|
||||
|
||||
export let devtools: DevtoolsHook
|
||||
|
||||
let buffer: { event: string; args: any[] }[] = []
|
||||
|
||||
let devtoolsNotInstalled = false
|
||||
|
||||
function emit(event: string, ...args: any[]) {
|
||||
if (devtools) {
|
||||
devtools.emit(event, ...args)
|
||||
} else if (!devtoolsNotInstalled) {
|
||||
buffer.push({ event, args })
|
||||
}
|
||||
}
|
||||
|
||||
export function setDevtoolsHook(hook: DevtoolsHook, target: any): void {
|
||||
devtools = hook
|
||||
if (devtools) {
|
||||
devtools.enabled = true
|
||||
buffer.forEach(({ event, args }) => devtools.emit(event, ...args))
|
||||
buffer = []
|
||||
} else if (
|
||||
// handle late devtools injection - only do this if we are in an actual
|
||||
// browser environment to avoid the timer handle stalling test runner exit
|
||||
// (#4815)
|
||||
typeof window !== 'undefined' &&
|
||||
// some envs mock window but not fully
|
||||
window.HTMLElement &&
|
||||
// also exclude jsdom
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
!window.navigator?.userAgent?.includes('jsdom')
|
||||
) {
|
||||
const replay = (target.__VUE_DEVTOOLS_HOOK_REPLAY__ =
|
||||
target.__VUE_DEVTOOLS_HOOK_REPLAY__ || [])
|
||||
replay.push((newHook: DevtoolsHook) => {
|
||||
setDevtoolsHook(newHook, target)
|
||||
})
|
||||
// clear buffer after 3s - the user probably doesn't have devtools installed
|
||||
// at all, and keeping the buffer will cause memory leaks (#4738)
|
||||
setTimeout(() => {
|
||||
if (!devtools) {
|
||||
target.__VUE_DEVTOOLS_HOOK_REPLAY__ = null
|
||||
devtoolsNotInstalled = true
|
||||
buffer = []
|
||||
}
|
||||
}, 3000)
|
||||
} else {
|
||||
// non-browser env, assume not installed
|
||||
devtoolsNotInstalled = true
|
||||
buffer = []
|
||||
}
|
||||
}
|
||||
|
||||
export function devtoolsInitApp(app: App, version: string): void {
|
||||
emit(DevtoolsHooks.APP_INIT, app, version, {})
|
||||
}
|
||||
|
||||
export function devtoolsUnmountApp(app: App): void {
|
||||
emit(DevtoolsHooks.APP_UNMOUNT, app)
|
||||
}
|
||||
|
||||
export const devtoolsComponentAdded: DevtoolsComponentHook =
|
||||
/*#__PURE__*/ createDevtoolsComponentHook(DevtoolsHooks.COMPONENT_ADDED)
|
||||
|
||||
export const devtoolsComponentUpdated: DevtoolsComponentHook =
|
||||
/*#__PURE__*/ createDevtoolsComponentHook(DevtoolsHooks.COMPONENT_UPDATED)
|
||||
|
||||
const _devtoolsComponentRemoved = /*#__PURE__*/ createDevtoolsComponentHook(
|
||||
DevtoolsHooks.COMPONENT_REMOVED,
|
||||
)
|
||||
|
||||
export const devtoolsComponentRemoved = (
|
||||
component: ComponentInternalInstance,
|
||||
): void => {
|
||||
if (
|
||||
devtools &&
|
||||
typeof devtools.cleanupBuffer === 'function' &&
|
||||
// remove the component if it wasn't buffered
|
||||
!devtools.cleanupBuffer(component)
|
||||
) {
|
||||
_devtoolsComponentRemoved(component)
|
||||
}
|
||||
}
|
||||
|
||||
type DevtoolsComponentHook = (component: ComponentInternalInstance) => void
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
function createDevtoolsComponentHook(
|
||||
hook: DevtoolsHooks,
|
||||
): DevtoolsComponentHook {
|
||||
return (component: ComponentInternalInstance) => {
|
||||
emit(
|
||||
hook,
|
||||
component.appContext.app,
|
||||
component.uid,
|
||||
component.parent ? component.parent.uid : undefined,
|
||||
component,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const devtoolsPerfStart: DevtoolsPerformanceHook =
|
||||
/*#__PURE__*/ createDevtoolsPerformanceHook(DevtoolsHooks.PERFORMANCE_START)
|
||||
|
||||
export const devtoolsPerfEnd: DevtoolsPerformanceHook =
|
||||
/*#__PURE__*/ createDevtoolsPerformanceHook(DevtoolsHooks.PERFORMANCE_END)
|
||||
|
||||
type DevtoolsPerformanceHook = (
|
||||
component: ComponentInternalInstance,
|
||||
type: string,
|
||||
time: number,
|
||||
) => void
|
||||
function createDevtoolsPerformanceHook(
|
||||
hook: DevtoolsHooks,
|
||||
): DevtoolsPerformanceHook {
|
||||
return (component: ComponentInternalInstance, type: string, time: number) => {
|
||||
emit(hook, component.appContext.app, component.uid, component, type, time)
|
||||
}
|
||||
}
|
||||
|
||||
export function devtoolsComponentEmit(
|
||||
component: ComponentInternalInstance,
|
||||
event: string,
|
||||
params: any[],
|
||||
): void {
|
||||
emit(
|
||||
DevtoolsHooks.COMPONENT_EMIT,
|
||||
component.appContext.app,
|
||||
component,
|
||||
event,
|
||||
params,
|
||||
)
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
import { isBuiltInDirective } from '@vue/shared'
|
||||
import {
|
||||
type ComponentInternalInstance,
|
||||
currentInstance,
|
||||
isVaporComponent,
|
||||
} from './component'
|
||||
import { warn } from './warning'
|
||||
import { normalizeBlock } from './block'
|
||||
import { getCurrentScope } from '@vue/reactivity'
|
||||
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
||||
|
||||
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
|
||||
|
||||
export interface DirectiveBinding<T = any, V = any, M extends string = string> {
|
||||
instance: ComponentInternalInstance
|
||||
source: () => V
|
||||
arg?: string
|
||||
modifiers?: DirectiveModifiers<M>
|
||||
dir: Directive<T, V, M>
|
||||
}
|
||||
|
||||
export type DirectiveBindingsMap = Map<Node, DirectiveBinding[]>
|
||||
|
||||
export type Directive<T = any, V = any, M extends string = string> = (
|
||||
node: T,
|
||||
binding: DirectiveBinding<T, V, M>,
|
||||
) => void
|
||||
|
||||
export function validateDirectiveName(name: string): void {
|
||||
if (isBuiltInDirective(name)) {
|
||||
warn('Do not use built-in directive ids as custom directive id: ' + name)
|
||||
}
|
||||
}
|
||||
|
||||
export type DirectiveArguments = Array<
|
||||
| [Directive | undefined]
|
||||
| [Directive | undefined, () => any]
|
||||
| [Directive | undefined, () => any, argument: string]
|
||||
| [
|
||||
Directive | undefined,
|
||||
value: () => any,
|
||||
argument: string,
|
||||
modifiers: DirectiveModifiers,
|
||||
]
|
||||
>
|
||||
|
||||
export function withDirectives<T extends ComponentInternalInstance | Node>(
|
||||
nodeOrComponent: T,
|
||||
directives: DirectiveArguments,
|
||||
): T {
|
||||
if (!currentInstance) {
|
||||
__DEV__ && warn(`withDirectives can only be used inside render functions.`)
|
||||
return nodeOrComponent
|
||||
}
|
||||
|
||||
let node: Node
|
||||
if (isVaporComponent(nodeOrComponent)) {
|
||||
const root = getComponentNode(nodeOrComponent)
|
||||
if (!root) return nodeOrComponent
|
||||
node = root
|
||||
} else {
|
||||
node = nodeOrComponent
|
||||
}
|
||||
|
||||
const instance = currentInstance!
|
||||
const parentScope = getCurrentScope()
|
||||
|
||||
if (__DEV__ && !parentScope) {
|
||||
warn(`Directives should be used inside of RenderEffectScope.`)
|
||||
}
|
||||
|
||||
for (const directive of directives) {
|
||||
let [dir, source = () => undefined, arg, modifiers] = directive
|
||||
if (!dir) continue
|
||||
|
||||
const binding: DirectiveBinding = {
|
||||
dir,
|
||||
source,
|
||||
instance,
|
||||
arg,
|
||||
modifiers,
|
||||
}
|
||||
|
||||
callWithAsyncErrorHandling(dir, instance, VaporErrorCodes.DIRECTIVE_HOOK, [
|
||||
node,
|
||||
binding,
|
||||
])
|
||||
}
|
||||
|
||||
return nodeOrComponent
|
||||
}
|
||||
|
||||
function getComponentNode(component: ComponentInternalInstance) {
|
||||
if (!component.block) return
|
||||
|
||||
const nodes = normalizeBlock(component.block)
|
||||
if (nodes.length !== 1) {
|
||||
warn(
|
||||
`Runtime directive used on component with non-element root node. ` +
|
||||
`The directives will not function as intended.`,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
return nodes[0]
|
||||
}
|
|
@ -1,313 +0,0 @@
|
|||
import {
|
||||
invokeArrayFns,
|
||||
isArray,
|
||||
isSet,
|
||||
looseEqual,
|
||||
looseIndexOf,
|
||||
looseToNumber,
|
||||
} from '@vue/shared'
|
||||
import type { Directive } from '../directives'
|
||||
import { addEventListener } from '../dom/event'
|
||||
import { nextTick } from '../scheduler'
|
||||
import { warn } from '../warning'
|
||||
import { MetadataKind, getMetadata } from '../componentMetadata'
|
||||
import {
|
||||
onBeforeMount,
|
||||
onBeforeUnmount,
|
||||
onBeforeUpdate,
|
||||
onMounted,
|
||||
} from '../apiLifecycle'
|
||||
import { renderEffect } from '../renderEffect'
|
||||
|
||||
type AssignerFn = (value: any) => void
|
||||
function getModelAssigner(el: Element): AssignerFn {
|
||||
const metadata = getMetadata(el)
|
||||
const fn = metadata[MetadataKind.event]['update:modelValue'] || []
|
||||
return value => invokeArrayFns(fn, value)
|
||||
}
|
||||
|
||||
function onCompositionStart(e: Event) {
|
||||
;(e.target as any).composing = true
|
||||
}
|
||||
|
||||
function onCompositionEnd(e: Event) {
|
||||
const target = e.target as any
|
||||
if (target.composing) {
|
||||
target.composing = false
|
||||
target.dispatchEvent(new Event('input'))
|
||||
}
|
||||
}
|
||||
|
||||
const assignFnMap = new WeakMap<HTMLElement, AssignerFn>()
|
||||
const assigningMap = new WeakMap<HTMLElement, boolean>()
|
||||
|
||||
// We are exporting the v-model runtime directly as vnode hooks so that it can
|
||||
// be tree-shaken in case v-model is never used.
|
||||
export const vModelText: Directive<
|
||||
HTMLInputElement | HTMLTextAreaElement,
|
||||
any,
|
||||
'lazy' | 'trim' | 'number'
|
||||
> = (el, { source, modifiers: { lazy, trim, number } = {} }) => {
|
||||
onBeforeMount(() => {
|
||||
const assigner = getModelAssigner(el)
|
||||
assignFnMap.set(el, assigner)
|
||||
|
||||
const metadata = getMetadata(el)
|
||||
const castToNumber = number || metadata[MetadataKind.prop].type === 'number'
|
||||
|
||||
addEventListener(el, lazy ? 'change' : 'input', e => {
|
||||
if ((e.target as any).composing) return
|
||||
let domValue: string | number = el.value
|
||||
if (trim) {
|
||||
domValue = domValue.trim()
|
||||
}
|
||||
if (castToNumber) {
|
||||
domValue = looseToNumber(domValue)
|
||||
}
|
||||
assigner(domValue)
|
||||
})
|
||||
if (trim) {
|
||||
addEventListener(el, 'change', () => {
|
||||
el.value = el.value.trim()
|
||||
})
|
||||
}
|
||||
if (!lazy) {
|
||||
addEventListener(el, 'compositionstart', onCompositionStart)
|
||||
addEventListener(el, 'compositionend', onCompositionEnd)
|
||||
// Safari < 10.2 & UIWebView doesn't fire compositionend when
|
||||
// switching focus before confirming composition choice
|
||||
// this also fixes the issue where some browsers e.g. iOS Chrome
|
||||
// fires "change" instead of "input" on autocomplete.
|
||||
addEventListener(el, 'change', onCompositionEnd)
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
const value = source()
|
||||
el.value = value == null ? '' : value
|
||||
})
|
||||
|
||||
renderEffect(() => {
|
||||
const value = source()
|
||||
assignFnMap.set(el, getModelAssigner(el))
|
||||
|
||||
// avoid clearing unresolved text. #2302
|
||||
if ((el as any).composing) return
|
||||
|
||||
const elValue =
|
||||
number || el.type === 'number' ? looseToNumber(el.value) : el.value
|
||||
const newValue = value == null ? '' : value
|
||||
|
||||
if (elValue === newValue) {
|
||||
return
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
if (document.activeElement === el && el.type !== 'range') {
|
||||
if (lazy) {
|
||||
return
|
||||
}
|
||||
if (trim && el.value.trim() === newValue) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
el.value = newValue
|
||||
})
|
||||
}
|
||||
|
||||
export const vModelRadio: Directive<HTMLInputElement> = (el, { source }) => {
|
||||
onBeforeMount(() => {
|
||||
el.checked = looseEqual(source(), getValue(el))
|
||||
assignFnMap.set(el, getModelAssigner(el))
|
||||
addEventListener(el, 'change', () => {
|
||||
assignFnMap.get(el)!(getValue(el))
|
||||
})
|
||||
})
|
||||
|
||||
renderEffect(() => {
|
||||
const value = source()
|
||||
assignFnMap.set(el, getModelAssigner(el))
|
||||
el.checked = looseEqual(value, getValue(el))
|
||||
})
|
||||
}
|
||||
|
||||
export const vModelSelect: Directive<HTMLSelectElement, any, 'number'> = (
|
||||
el,
|
||||
{ source, modifiers: { number = false } = {} },
|
||||
) => {
|
||||
onBeforeMount(() => {
|
||||
const value = source()
|
||||
const isSetModel = isSet(value)
|
||||
addEventListener(el, 'change', () => {
|
||||
const selectedVal = Array.prototype.filter
|
||||
.call(el.options, (o: HTMLOptionElement) => o.selected)
|
||||
.map((o: HTMLOptionElement) =>
|
||||
number ? looseToNumber(getValue(o)) : getValue(o),
|
||||
)
|
||||
assignFnMap.get(el)!(
|
||||
el.multiple
|
||||
? isSetModel
|
||||
? new Set(selectedVal)
|
||||
: selectedVal
|
||||
: selectedVal[0],
|
||||
)
|
||||
assigningMap.set(el, true)
|
||||
|
||||
nextTick(() => {
|
||||
assigningMap.set(el, false)
|
||||
})
|
||||
})
|
||||
assignFnMap.set(el, getModelAssigner(el))
|
||||
setSelected(el, value, number)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
assignFnMap.set(el, getModelAssigner(el))
|
||||
})
|
||||
|
||||
renderEffect(() => {
|
||||
if (!assigningMap.get(el)) {
|
||||
setSelected(el, source(), number)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function setSelected(el: HTMLSelectElement, value: any, number: boolean) {
|
||||
const isMultiple = el.multiple
|
||||
const isArrayValue = isArray(value)
|
||||
if (isMultiple && !isArrayValue && !isSet(value)) {
|
||||
__DEV__ &&
|
||||
warn(
|
||||
`<select multiple v-model> expects an Array or Set value for its binding, ` +
|
||||
`but got ${Object.prototype.toString.call(value).slice(8, -1)}.`,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0, l = el.options.length; i < l; i++) {
|
||||
const option = el.options[i]
|
||||
const optionValue = getValue(option)
|
||||
if (isMultiple) {
|
||||
if (isArrayValue) {
|
||||
const optionType = typeof optionValue
|
||||
// fast path for string / number values
|
||||
if (optionType === 'string' || optionType === 'number') {
|
||||
option.selected = value.includes(
|
||||
number ? looseToNumber(optionValue) : optionValue,
|
||||
)
|
||||
} else {
|
||||
option.selected = looseIndexOf(value, optionValue) > -1
|
||||
}
|
||||
} else {
|
||||
option.selected = value.has(optionValue)
|
||||
}
|
||||
} else if (looseEqual(getValue(option), value)) {
|
||||
if (el.selectedIndex !== i) el.selectedIndex = i
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!isMultiple && el.selectedIndex !== -1) {
|
||||
el.selectedIndex = -1
|
||||
}
|
||||
}
|
||||
|
||||
// retrieve raw value set via :value bindings
|
||||
function getValue(el: HTMLOptionElement | HTMLInputElement) {
|
||||
const metadata = getMetadata(el)
|
||||
return (metadata && metadata[MetadataKind.prop].value) || el.value
|
||||
}
|
||||
|
||||
// retrieve raw value for true-value and false-value set via :true-value or :false-value bindings
|
||||
function getCheckboxValue(el: HTMLInputElement, checked: boolean) {
|
||||
const metadata = getMetadata(el)
|
||||
const props = metadata && metadata[MetadataKind.prop]
|
||||
const key = checked ? 'true-value' : 'false-value'
|
||||
if (props && key in props) {
|
||||
return props[key]
|
||||
}
|
||||
if (el.hasAttribute(key)) {
|
||||
return el.getAttribute(key)
|
||||
}
|
||||
return checked
|
||||
}
|
||||
|
||||
export const vModelCheckbox: Directive<HTMLInputElement> = (el, { source }) => {
|
||||
onBeforeMount(() => {
|
||||
assignFnMap.set(el, getModelAssigner(el))
|
||||
|
||||
addEventListener(el, 'change', () => {
|
||||
const modelValue = source()
|
||||
const elementValue = getValue(el)
|
||||
const checked = el.checked
|
||||
const assigner = assignFnMap.get(el)!
|
||||
if (isArray(modelValue)) {
|
||||
const index = looseIndexOf(modelValue, elementValue)
|
||||
const found = index !== -1
|
||||
if (checked && !found) {
|
||||
assigner(modelValue.concat(elementValue))
|
||||
} else if (!checked && found) {
|
||||
const filtered = [...modelValue]
|
||||
filtered.splice(index, 1)
|
||||
assigner(filtered)
|
||||
}
|
||||
} else if (isSet(modelValue)) {
|
||||
const cloned = new Set(modelValue)
|
||||
if (checked) {
|
||||
cloned.add(elementValue)
|
||||
} else {
|
||||
cloned.delete(elementValue)
|
||||
}
|
||||
assigner(cloned)
|
||||
} else {
|
||||
assigner(getCheckboxValue(el, checked))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
setChecked()
|
||||
})
|
||||
|
||||
onBeforeUpdate(() => {
|
||||
assignFnMap.set(el, getModelAssigner(el))
|
||||
setChecked()
|
||||
})
|
||||
|
||||
function setChecked() {
|
||||
const value = source()
|
||||
if (isArray(value)) {
|
||||
el.checked = looseIndexOf(value, getValue(el)) > -1
|
||||
} else if (isSet(value)) {
|
||||
el.checked = value.has(getValue(el))
|
||||
} else {
|
||||
el.checked = looseEqual(value, getCheckboxValue(el, true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const vModelDynamic: Directive<
|
||||
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
|
||||
> = (el, binding) => {
|
||||
const type = el.getAttribute('type')
|
||||
const modelToUse = resolveDynamicModel(el.tagName, type)
|
||||
modelToUse(el, binding)
|
||||
}
|
||||
|
||||
function resolveDynamicModel(tagName: string, type: string | null): Directive {
|
||||
switch (tagName) {
|
||||
case 'SELECT':
|
||||
return vModelSelect
|
||||
case 'TEXTAREA':
|
||||
return vModelText
|
||||
default:
|
||||
switch (type) {
|
||||
case 'checkbox':
|
||||
return vModelCheckbox
|
||||
case 'radio':
|
||||
return vModelRadio
|
||||
default:
|
||||
return vModelText
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import type { Directive } from '../directives'
|
||||
import { renderEffect } from '../renderEffect'
|
||||
|
||||
export const vShowOriginalDisplay: unique symbol = Symbol('_vod')
|
||||
export const vShowHidden: unique symbol = Symbol('_vsh')
|
||||
|
||||
export interface VShowElement extends HTMLElement {
|
||||
// _vod = vue original display
|
||||
[vShowOriginalDisplay]: string
|
||||
[vShowHidden]: boolean
|
||||
}
|
||||
|
||||
export const vShow: Directive<VShowElement> = (el, { source }) => {
|
||||
el[vShowOriginalDisplay] = el.style.display === 'none' ? '' : el.style.display
|
||||
renderEffect(() => setDisplay(el, source()))
|
||||
}
|
||||
|
||||
function setDisplay(el: VShowElement, value: unknown): void {
|
||||
el.style.display = value ? el[vShowOriginalDisplay] : 'none'
|
||||
el[vShowHidden] = !value
|
||||
}
|
|
@ -2,7 +2,7 @@ import { isArray } from '@vue/shared'
|
|||
import { renderEffect } from '../renderEffect'
|
||||
import { setText } from './prop'
|
||||
import { type Block, normalizeBlock } from '../block'
|
||||
import { isVaporComponent } from '../_new/component'
|
||||
import { isVaporComponent } from '../component'
|
||||
|
||||
// export function insert(
|
||||
// block: Block,
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
recordEventMetadata,
|
||||
} from '../componentMetadata'
|
||||
import { withKeys, withModifiers } from '@vue/runtime-dom'
|
||||
import { queuePostFlushCb } from '../scheduler'
|
||||
import { queuePostFlushCb } from '@vue/runtime-dom'
|
||||
|
||||
export function addEventListener(
|
||||
el: Element,
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
shouldSetAsAttr,
|
||||
toDisplayString,
|
||||
} from '@vue/shared'
|
||||
import { warn } from '../warning'
|
||||
import { warn } from '../_old/warning'
|
||||
import { setStyle } from './style'
|
||||
import {
|
||||
MetadataKind,
|
||||
|
@ -20,8 +20,7 @@ import {
|
|||
recordPropMetadata,
|
||||
} from '../componentMetadata'
|
||||
import { on } from './event'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import { currentInstance } from '../component'
|
||||
import { currentInstance } from '../_old/component'
|
||||
|
||||
export function mergeInheritAttr(key: string, value: any): unknown {
|
||||
const instance = currentInstance!
|
||||
|
@ -199,6 +198,8 @@ export function mergeProp(
|
|||
return incoming
|
||||
}
|
||||
|
||||
type Data = Record<string, any>
|
||||
|
||||
export function mergeProps(...args: Data[]): Data {
|
||||
const ret: Data = {}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
isString,
|
||||
normalizeStyle,
|
||||
} from '@vue/shared'
|
||||
import { warn } from '../warning'
|
||||
import { warn } from '../_old/warning'
|
||||
import { recordPropMetadata } from '../componentMetadata'
|
||||
import { mergeInheritAttr } from './prop'
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ import {
|
|||
type ComponentInternalInstance,
|
||||
currentInstance,
|
||||
isVaporComponent,
|
||||
} from '../component'
|
||||
import { VaporErrorCodes, callWithErrorHandling } from '../errorHandling'
|
||||
} from '../_old/component'
|
||||
import { VaporErrorCodes, callWithErrorHandling } from '../_old/errorHandling'
|
||||
import {
|
||||
EMPTY_OBJ,
|
||||
hasOwn,
|
||||
|
@ -13,8 +13,8 @@ import {
|
|||
isString,
|
||||
remove,
|
||||
} from '@vue/shared'
|
||||
import { warn } from '../warning'
|
||||
import { type SchedulerJob, queuePostFlushCb } from '../scheduler'
|
||||
import { warn } from '../_old/warning'
|
||||
import { type SchedulerJob, queuePostFlushCb } from '../_old/scheduler'
|
||||
|
||||
export type NodeRef = string | Ref | ((ref: Element) => void)
|
||||
export type RefEl = Element | ComponentInternalInstance
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
export enum VaporLifecycleHooks {
|
||||
BEFORE_MOUNT = 'bm',
|
||||
MOUNTED = 'm',
|
||||
BEFORE_UPDATE = 'bu',
|
||||
UPDATED = 'u',
|
||||
BEFORE_UNMOUNT = 'bum',
|
||||
UNMOUNTED = 'um',
|
||||
DEACTIVATED = 'da',
|
||||
ACTIVATED = 'a',
|
||||
RENDER_TRIGGERED = 'rtg',
|
||||
RENDER_TRACKED = 'rtc',
|
||||
ERROR_CAPTURED = 'ec',
|
||||
// SERVER_PREFETCH = 'sp',
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
// These codes originate from a file of the same name in runtime-core,
|
||||
// duplicated during Vapor's early development to ensure its independence.
|
||||
// The ultimate aim is to uncouple this replicated code and
|
||||
// facilitate its shared use between two runtimes.
|
||||
|
||||
import type { ComponentInternalInstance } from './component'
|
||||
import { isFunction, isPromise } from '@vue/shared'
|
||||
import { warn } from './warning'
|
||||
import { VaporLifecycleHooks } from './enums'
|
||||
import { WatchErrorCodes, pauseTracking, resetTracking } from '@vue/reactivity'
|
||||
|
||||
// contexts where user provided function may be executed, in addition to
|
||||
// lifecycle hooks.
|
||||
export enum VaporErrorCodes {
|
||||
SETUP_FUNCTION,
|
||||
RENDER_FUNCTION,
|
||||
// The error codes for the watch have been transferred to the reactivity
|
||||
// package along with baseWatch to maintain code compatibility. Hence,
|
||||
// it is essential to keep these values unchanged.
|
||||
// WATCH_GETTER,
|
||||
// WATCH_CALLBACK,
|
||||
// WATCH_CLEANUP,
|
||||
NATIVE_EVENT_HANDLER = 5,
|
||||
COMPONENT_EVENT_HANDLER,
|
||||
VNODE_HOOK,
|
||||
DIRECTIVE_HOOK,
|
||||
TRANSITION_HOOK,
|
||||
APP_ERROR_HANDLER,
|
||||
APP_WARN_HANDLER,
|
||||
FUNCTION_REF,
|
||||
ASYNC_COMPONENT_LOADER,
|
||||
SCHEDULER,
|
||||
}
|
||||
|
||||
export type ErrorTypes = VaporLifecycleHooks | VaporErrorCodes | WatchErrorCodes
|
||||
|
||||
export const ErrorTypeStrings: Record<ErrorTypes, string> = {
|
||||
// [VaporLifecycleHooks.SERVER_PREFETCH]: 'serverPrefetch hook',
|
||||
[VaporLifecycleHooks.BEFORE_MOUNT]: 'beforeMount hook',
|
||||
[VaporLifecycleHooks.MOUNTED]: 'mounted hook',
|
||||
[VaporLifecycleHooks.BEFORE_UPDATE]: 'beforeUpdate hook',
|
||||
[VaporLifecycleHooks.UPDATED]: 'updated',
|
||||
[VaporLifecycleHooks.BEFORE_UNMOUNT]: 'beforeUnmount hook',
|
||||
[VaporLifecycleHooks.UNMOUNTED]: 'unmounted hook',
|
||||
[VaporLifecycleHooks.ACTIVATED]: 'activated hook',
|
||||
[VaporLifecycleHooks.DEACTIVATED]: 'deactivated hook',
|
||||
[VaporLifecycleHooks.ERROR_CAPTURED]: 'errorCaptured hook',
|
||||
[VaporLifecycleHooks.RENDER_TRACKED]: 'renderTracked hook',
|
||||
[VaporLifecycleHooks.RENDER_TRIGGERED]: 'renderTriggered hook',
|
||||
[VaporErrorCodes.SETUP_FUNCTION]: 'setup function',
|
||||
[VaporErrorCodes.RENDER_FUNCTION]: 'render function',
|
||||
[WatchErrorCodes.WATCH_GETTER]: 'watcher getter',
|
||||
[WatchErrorCodes.WATCH_CALLBACK]: 'watcher callback',
|
||||
[WatchErrorCodes.WATCH_CLEANUP]: 'watcher cleanup function',
|
||||
[VaporErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
|
||||
[VaporErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
|
||||
[VaporErrorCodes.VNODE_HOOK]: 'vnode hook',
|
||||
[VaporErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
|
||||
[VaporErrorCodes.TRANSITION_HOOK]: 'transition hook',
|
||||
[VaporErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
|
||||
[VaporErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
|
||||
[VaporErrorCodes.FUNCTION_REF]: 'ref function',
|
||||
[VaporErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader',
|
||||
[VaporErrorCodes.SCHEDULER]:
|
||||
'scheduler flush. This is likely a Vue internals bug. ' +
|
||||
'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/core',
|
||||
}
|
||||
|
||||
export function callWithErrorHandling(
|
||||
fn: Function,
|
||||
instance: ComponentInternalInstance | null,
|
||||
type: ErrorTypes,
|
||||
args?: unknown[],
|
||||
): any {
|
||||
let res
|
||||
try {
|
||||
res = args ? fn(...args) : fn()
|
||||
} catch (err) {
|
||||
handleError(err, instance, type)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
export function callWithAsyncErrorHandling<F extends Function | Function[]>(
|
||||
fn: F,
|
||||
instance: ComponentInternalInstance | null,
|
||||
type: ErrorTypes,
|
||||
args?: unknown[],
|
||||
): F extends Function ? any : any[] {
|
||||
if (isFunction(fn)) {
|
||||
const res = callWithErrorHandling(fn, instance, type, args)
|
||||
if (res && isPromise(res)) {
|
||||
res.catch(err => {
|
||||
handleError(err, instance, type)
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
const values = []
|
||||
for (let i = 0; i < fn.length; i++) {
|
||||
values.push(callWithAsyncErrorHandling(fn[i], instance, type, args))
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
export function handleError(
|
||||
err: unknown,
|
||||
instance: ComponentInternalInstance | null,
|
||||
type: ErrorTypes,
|
||||
throwInDev = true,
|
||||
): void {
|
||||
if (instance) {
|
||||
let cur = instance.parent
|
||||
// in production the hook receives only the error code
|
||||
const errorInfo = __DEV__
|
||||
? ErrorTypeStrings[type]
|
||||
: `https://vuejs.org/errors/#runtime-${type}`
|
||||
while (cur) {
|
||||
const errorCapturedHooks = 'ec' in cur ? cur.ec : null
|
||||
if (errorCapturedHooks) {
|
||||
for (let i = 0; i < errorCapturedHooks.length; i++) {
|
||||
if (errorCapturedHooks[i](err, instance, errorInfo) === false) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
cur = cur.parent
|
||||
}
|
||||
|
||||
// app-level handling
|
||||
const appErrorHandler = instance.appContext.config.errorHandler
|
||||
if (appErrorHandler) {
|
||||
pauseTracking()
|
||||
callWithErrorHandling(
|
||||
appErrorHandler,
|
||||
null,
|
||||
VaporErrorCodes.APP_ERROR_HANDLER,
|
||||
[err, instance, errorInfo],
|
||||
)
|
||||
resetTracking()
|
||||
return
|
||||
}
|
||||
}
|
||||
logError(err, type, throwInDev)
|
||||
}
|
||||
|
||||
function logError(err: unknown, type: ErrorTypes, throwInDev = true) {
|
||||
if (__DEV__) {
|
||||
const info = ErrorTypeStrings[type]
|
||||
warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
|
||||
// crash in dev by default so it's more noticeable
|
||||
if (throwInDev) {
|
||||
throw err
|
||||
} else if (!__TEST__) {
|
||||
console.error(err)
|
||||
}
|
||||
} else {
|
||||
// recover in prod to reduce the impact on end-user
|
||||
console.error(err)
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
import { camelize, capitalize, isString } from '@vue/shared'
|
||||
import { warn } from '../warning'
|
||||
import type { Directive } from '../directives'
|
||||
import { type Component, currentInstance } from '../component'
|
||||
import { getComponentName } from '../component'
|
||||
|
||||
const COMPONENTS = 'components'
|
||||
const DIRECTIVES = 'directives'
|
||||
|
||||
export type AssetTypes = typeof COMPONENTS | typeof DIRECTIVES
|
||||
|
||||
export function resolveComponent(name: string): string | Component {
|
||||
return resolveAsset(COMPONENTS, name, true) || name
|
||||
}
|
||||
|
||||
export function resolveDirective(name: string): Directive | undefined {
|
||||
return resolveAsset(DIRECTIVES, name)
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* overload 1: components
|
||||
*/
|
||||
function resolveAsset(
|
||||
type: typeof COMPONENTS,
|
||||
name: string,
|
||||
warnMissing?: boolean,
|
||||
): Component | undefined
|
||||
// overload 2: directives
|
||||
function resolveAsset(
|
||||
type: typeof DIRECTIVES,
|
||||
name: string,
|
||||
): Directive | undefined
|
||||
// implementation
|
||||
function resolveAsset(type: AssetTypes, name: string, warnMissing = true) {
|
||||
const instance = currentInstance
|
||||
if (instance) {
|
||||
const Component = instance.type
|
||||
|
||||
// explicit self name has highest priority
|
||||
if (type === COMPONENTS) {
|
||||
const selfName = getComponentName(Component)
|
||||
if (
|
||||
selfName &&
|
||||
(selfName === name ||
|
||||
selfName === camelize(name) ||
|
||||
selfName === capitalize(camelize(name)))
|
||||
) {
|
||||
return Component
|
||||
}
|
||||
}
|
||||
|
||||
const res =
|
||||
// global registration
|
||||
resolve(instance.appContext[type], name)
|
||||
|
||||
if (__DEV__ && warnMissing && !res) {
|
||||
const extra =
|
||||
type === COMPONENTS
|
||||
? `\nIf this is a native custom element, make sure to exclude it from ` +
|
||||
`component resolution via compilerOptions.isCustomElement.`
|
||||
: ``
|
||||
warn(`Failed to resolve ${type.slice(0, -1)}: ${name}${extra}`)
|
||||
}
|
||||
|
||||
return res
|
||||
} else if (__DEV__) {
|
||||
warn(
|
||||
`resolve${capitalize(type.slice(0, -1))} ` +
|
||||
`can only be used in render() or setup().`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function resolve(registry: Record<string, any> | undefined, name: string) {
|
||||
return (
|
||||
registry &&
|
||||
(registry[name] ||
|
||||
registry[camelize(name)] ||
|
||||
registry[capitalize(camelize(name))])
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function resolveDynamicComponent(
|
||||
component: string | Component,
|
||||
): string | Component {
|
||||
if (isString(component)) {
|
||||
return resolveAsset(COMPONENTS, component, false) || component
|
||||
} else {
|
||||
return component
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import { toHandlers as _toHandlers } from '@vue/runtime-shared'
|
||||
import { warn } from '../warning'
|
||||
import { NOOP } from '@vue/shared'
|
||||
|
||||
export const toHandlers = (
|
||||
obj: Record<string, any>,
|
||||
preserveCaseIfNecessary?: boolean | undefined,
|
||||
): Record<string, any> =>
|
||||
_toHandlers(__DEV__ ? warn : NOOP, obj, preserveCaseIfNecessary)
|
|
@ -1,110 +1,9 @@
|
|||
// Core API ------------------------------------------------------------------
|
||||
|
||||
export const version: string = __VERSION__
|
||||
export {
|
||||
// core
|
||||
TrackOpTypes,
|
||||
TriggerOpTypes,
|
||||
reactive,
|
||||
ref,
|
||||
readonly,
|
||||
computed,
|
||||
// utilities
|
||||
unref,
|
||||
proxyRefs,
|
||||
isRef,
|
||||
toRef,
|
||||
toValue,
|
||||
toRefs,
|
||||
isProxy,
|
||||
isReactive,
|
||||
isReadonly,
|
||||
isShallow,
|
||||
// advanced
|
||||
customRef,
|
||||
triggerRef,
|
||||
shallowRef,
|
||||
shallowReactive,
|
||||
shallowReadonly,
|
||||
markRaw,
|
||||
toRaw,
|
||||
// effect
|
||||
stop,
|
||||
ReactiveEffect,
|
||||
onEffectCleanup,
|
||||
// effect scope
|
||||
effectScope,
|
||||
EffectScope,
|
||||
getCurrentScope,
|
||||
onScopeDispose,
|
||||
// baseWatch
|
||||
onWatcherCleanup,
|
||||
getCurrentWatcher,
|
||||
} from '@vue/reactivity'
|
||||
export type {
|
||||
Ref,
|
||||
MaybeRef,
|
||||
MaybeRefOrGetter,
|
||||
ToRef,
|
||||
ToRefs,
|
||||
UnwrapRef,
|
||||
ShallowRef,
|
||||
ShallowUnwrapRef,
|
||||
CustomRefFactory,
|
||||
ReactiveFlags,
|
||||
DeepReadonly,
|
||||
ShallowReactive,
|
||||
UnwrapNestedRefs,
|
||||
ComputedRef,
|
||||
WritableComputedRef,
|
||||
WritableComputedOptions,
|
||||
ComputedGetter,
|
||||
ComputedSetter,
|
||||
ReactiveEffectRunner,
|
||||
ReactiveEffectOptions,
|
||||
EffectScheduler,
|
||||
DebuggerOptions,
|
||||
DebuggerEvent,
|
||||
DebuggerEventExtraInfo,
|
||||
Raw,
|
||||
Reactive,
|
||||
} from '@vue/reactivity'
|
||||
|
||||
import { NOOP } from '@vue/shared'
|
||||
import { warn as _warn } from './warning'
|
||||
export const warn = (__DEV__ ? _warn : NOOP) as typeof _warn
|
||||
|
||||
export { nextTick } from './scheduler'
|
||||
export {
|
||||
getCurrentInstance,
|
||||
ComponentInternalInstance,
|
||||
type Component as Component,
|
||||
type ObjectComponent,
|
||||
type FunctionalComponent,
|
||||
type SetupFn,
|
||||
} from './component'
|
||||
export { createSlot } from './componentSlots'
|
||||
export { createComponent } from './component'
|
||||
export { renderEffect } from './renderEffect'
|
||||
export {
|
||||
watch,
|
||||
watchEffect,
|
||||
watchPostEffect,
|
||||
watchSyncEffect,
|
||||
type WatchEffect,
|
||||
type WatchOptions,
|
||||
type WatchOptionsBase,
|
||||
type WatchCallback,
|
||||
type WatchSource,
|
||||
type WatchStopHandle,
|
||||
} from './apiWatch'
|
||||
export {
|
||||
withDirectives,
|
||||
type Directive,
|
||||
type DirectiveBinding,
|
||||
type DirectiveArguments,
|
||||
type DirectiveModifiers,
|
||||
} from './directives'
|
||||
export { createVaporApp } from './apiCreateApp'
|
||||
export { useEmit } from './componentEmits'
|
||||
|
||||
// DOM
|
||||
export { template, children, next } from './dom/template'
|
||||
export { insert, prepend, remove, createTextNode } from './dom/element'
|
||||
export { setStyle } from './dom/style'
|
||||
|
@ -120,75 +19,3 @@ export {
|
|||
} from './dom/prop'
|
||||
export { on, delegate, delegateEvents, setDynamicEvents } from './dom/event'
|
||||
export { setRef } from './dom/templateRef'
|
||||
|
||||
export { defineComponent } from './apiDefineComponent'
|
||||
export {
|
||||
type InjectionKey,
|
||||
inject,
|
||||
provide,
|
||||
hasInjectionContext,
|
||||
} from './apiInject'
|
||||
export {
|
||||
onBeforeMount,
|
||||
onMounted,
|
||||
onBeforeUpdate,
|
||||
onUpdated,
|
||||
onBeforeUnmount,
|
||||
onUnmounted,
|
||||
// onActivated,
|
||||
// onDeactivated,
|
||||
onRenderTracked,
|
||||
onRenderTriggered,
|
||||
onErrorCaptured,
|
||||
// onServerPrefetch,
|
||||
} from './apiLifecycle'
|
||||
export { useAttrs, useSlots } from './apiSetupHelpers'
|
||||
export {
|
||||
createVaporApp,
|
||||
type App,
|
||||
type AppConfig,
|
||||
type AppContext,
|
||||
type Plugin,
|
||||
type ObjectPlugin,
|
||||
type FunctionPlugin,
|
||||
} from './apiCreateVaporApp'
|
||||
export { createBranch, createIf } from './apiCreateIf'
|
||||
export { createFor, createForSlots } from './apiCreateFor'
|
||||
export { createComponent } from './apiCreateComponent'
|
||||
export { createSelector } from './apiCreateSelector'
|
||||
export { setInheritAttrs } from './componentAttrs'
|
||||
|
||||
export {
|
||||
resolveComponent,
|
||||
resolveDirective,
|
||||
resolveDynamicComponent,
|
||||
} from './helpers/resolveAssets'
|
||||
export { toHandlers } from './helpers/toHandlers'
|
||||
|
||||
export { withDestructure } from './destructure'
|
||||
|
||||
// **Internal** DOM-only runtime directive helpers
|
||||
export {
|
||||
vModelText,
|
||||
vModelCheckbox,
|
||||
vModelRadio,
|
||||
vModelSelect,
|
||||
vModelDynamic,
|
||||
} from './directives/vModel'
|
||||
export { vShow } from './directives/vShow'
|
||||
|
||||
// For devtools
|
||||
import {
|
||||
type DevtoolsHook,
|
||||
devtools as _devtools,
|
||||
setDevtoolsHook as _setDevtoolsHook,
|
||||
} from './devtools'
|
||||
|
||||
export const devtools = (
|
||||
__DEV__ || __ESM_BUNDLER__ ? _devtools : undefined
|
||||
) as DevtoolsHook
|
||||
export const setDevtoolsHook = (
|
||||
__DEV__ || __ESM_BUNDLER__ ? _setDevtoolsHook : NOOP
|
||||
) as typeof _setDevtoolsHook
|
||||
|
||||
export * from './_new'
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
/* eslint-disable no-restricted-globals */
|
||||
import {
|
||||
type ComponentInternalInstance,
|
||||
formatComponentName,
|
||||
} from './component'
|
||||
import { devtoolsPerfEnd, devtoolsPerfStart } from './devtools'
|
||||
|
||||
let supported: boolean
|
||||
let perf: Performance
|
||||
|
||||
export function startMeasure(
|
||||
instance: ComponentInternalInstance,
|
||||
type: string,
|
||||
): void {
|
||||
if (instance.appContext.config.performance && isSupported()) {
|
||||
perf.mark(`vue-${type}-${instance.uid}`)
|
||||
}
|
||||
|
||||
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
||||
devtoolsPerfStart(instance, type, isSupported() ? perf.now() : Date.now())
|
||||
}
|
||||
}
|
||||
|
||||
export function endMeasure(
|
||||
instance: ComponentInternalInstance,
|
||||
type: string,
|
||||
): void {
|
||||
if (instance.appContext.config.performance && isSupported()) {
|
||||
const startTag = `vue-${type}-${instance.uid}`
|
||||
const endTag = startTag + `:end`
|
||||
perf.mark(endTag)
|
||||
perf.measure(
|
||||
`<${formatComponentName(instance, instance.type)}> ${type}`,
|
||||
startTag,
|
||||
endTag,
|
||||
)
|
||||
perf.clearMarks(startTag)
|
||||
perf.clearMarks(endTag)
|
||||
}
|
||||
|
||||
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
||||
devtoolsPerfEnd(instance, type, isSupported() ? perf.now() : Date.now())
|
||||
}
|
||||
}
|
||||
|
||||
function isSupported() {
|
||||
if (supported !== undefined) {
|
||||
return supported
|
||||
}
|
||||
if (typeof window !== 'undefined' && window.performance) {
|
||||
supported = true
|
||||
perf = window.performance
|
||||
} else {
|
||||
supported = false
|
||||
}
|
||||
return supported
|
||||
}
|
|
@ -1,95 +1,19 @@
|
|||
import { EffectFlags, ReactiveEffect, getCurrentScope } from '@vue/reactivity'
|
||||
import { invokeArrayFns } from '@vue/shared'
|
||||
import {
|
||||
type ComponentInternalInstance,
|
||||
getCurrentInstance,
|
||||
setCurrentInstance,
|
||||
} from './component'
|
||||
import {
|
||||
type SchedulerJob,
|
||||
VaporSchedulerJobFlags,
|
||||
queueJob,
|
||||
queuePostFlushCb,
|
||||
} from './scheduler'
|
||||
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
||||
import { ReactiveEffect } from '@vue/reactivity'
|
||||
import { type SchedulerJob, queueJob } from '@vue/runtime-core'
|
||||
import { currentInstance } from './component'
|
||||
|
||||
export function renderEffect(cb: () => void): void {
|
||||
const instance = getCurrentInstance()
|
||||
const scope = getCurrentScope()
|
||||
|
||||
if (scope) {
|
||||
const baseCb = cb
|
||||
cb = () => scope.run(baseCb)
|
||||
export function renderEffect(fn: () => void): void {
|
||||
const updateFn = () => {
|
||||
fn()
|
||||
}
|
||||
|
||||
if (instance) {
|
||||
const baseCb = cb
|
||||
cb = () => {
|
||||
const reset = setCurrentInstance(instance)
|
||||
baseCb()
|
||||
reset()
|
||||
}
|
||||
job.id = instance.uid
|
||||
}
|
||||
|
||||
const effect = new ReactiveEffect(() =>
|
||||
callWithAsyncErrorHandling(cb, instance, VaporErrorCodes.RENDER_FUNCTION),
|
||||
)
|
||||
|
||||
effect.scheduler = () => queueJob(job)
|
||||
if (__DEV__ && instance) {
|
||||
effect.onTrack = instance.rtc
|
||||
? e => invokeArrayFns(instance.rtc!, e)
|
||||
: void 0
|
||||
effect.onTrigger = instance.rtg
|
||||
? e => invokeArrayFns(instance.rtg!, e)
|
||||
: void 0
|
||||
}
|
||||
effect.run()
|
||||
|
||||
function job() {
|
||||
if (!(effect.flags & EffectFlags.ACTIVE) || !effect.dirty) {
|
||||
return
|
||||
}
|
||||
|
||||
const reset = instance && setCurrentInstance(instance)
|
||||
|
||||
if (instance && instance.isMounted && !instance.isUpdating) {
|
||||
instance.isUpdating = true
|
||||
|
||||
const { bu, u } = instance
|
||||
// beforeUpdate hook
|
||||
if (bu) {
|
||||
invokeArrayFns(bu)
|
||||
}
|
||||
|
||||
effect.run()
|
||||
|
||||
queuePostFlushCb(() => {
|
||||
instance.isUpdating = false
|
||||
const reset = setCurrentInstance(instance)
|
||||
// updated hook
|
||||
if (u) {
|
||||
queuePostFlushCb(u)
|
||||
}
|
||||
reset()
|
||||
})
|
||||
} else {
|
||||
effect.run()
|
||||
}
|
||||
|
||||
reset && reset()
|
||||
}
|
||||
}
|
||||
|
||||
export function firstEffect(
|
||||
instance: ComponentInternalInstance,
|
||||
fn: () => void,
|
||||
): void {
|
||||
const effect = new ReactiveEffect(fn)
|
||||
const job: SchedulerJob = () => effect.run()
|
||||
job.flags! |= VaporSchedulerJobFlags.PRE
|
||||
job.id = instance.uid
|
||||
const effect = new ReactiveEffect(updateFn)
|
||||
const job: SchedulerJob = effect.runIfDirty.bind(effect)
|
||||
job.i = currentInstance as any
|
||||
job.id = currentInstance!.uid
|
||||
effect.scheduler = () => queueJob(job)
|
||||
effect.run()
|
||||
|
||||
// TODO lifecycle
|
||||
// TODO recurse handling
|
||||
// TODO measure
|
||||
}
|
||||
|
|
|
@ -1,227 +0,0 @@
|
|||
import type { WatchScheduler } from '@vue/reactivity'
|
||||
import type { ComponentInternalInstance } from './component'
|
||||
import { isArray } from '@vue/shared'
|
||||
|
||||
export enum VaporSchedulerJobFlags {
|
||||
QUEUED = 1 << 0,
|
||||
PRE = 1 << 1,
|
||||
/**
|
||||
* Indicates whether the effect is allowed to recursively trigger itself
|
||||
* when managed by the scheduler.
|
||||
*
|
||||
* By default, a job cannot trigger itself because some built-in method calls,
|
||||
* e.g. Array.prototype.push actually performs reads as well (#1740) which
|
||||
* can lead to confusing infinite loops.
|
||||
* The allowed cases are component update functions and watch callbacks.
|
||||
* Component update functions may update child component props, which in turn
|
||||
* trigger flush: "pre" watch callbacks that mutates state that the parent
|
||||
* relies on (#1801). Watch callbacks doesn't track its dependencies so if it
|
||||
* triggers itself again, it's likely intentional and it is the user's
|
||||
* responsibility to perform recursive state mutation that eventually
|
||||
* stabilizes (#1727).
|
||||
*/
|
||||
ALLOW_RECURSE = 1 << 2,
|
||||
DISPOSED = 1 << 3,
|
||||
}
|
||||
|
||||
export interface SchedulerJob extends Function {
|
||||
id?: number
|
||||
/**
|
||||
* flags can technically be undefined, but it can still be used in bitwise
|
||||
* operations just like 0.
|
||||
*/
|
||||
flags?: VaporSchedulerJobFlags
|
||||
/**
|
||||
* Attached by renderer.ts when setting up a component's render effect
|
||||
* Used to obtain component information when reporting max recursive updates.
|
||||
*/
|
||||
i?: ComponentInternalInstance
|
||||
}
|
||||
|
||||
export type SchedulerJobs = SchedulerJob | SchedulerJob[]
|
||||
export type QueueEffect = (
|
||||
cb: SchedulerJobs,
|
||||
suspense: ComponentInternalInstance | null,
|
||||
) => void
|
||||
|
||||
let isFlushing = false
|
||||
let isFlushPending = false
|
||||
|
||||
// TODO: The queues in Vapor need to be merged with the queues in Core.
|
||||
// this is a temporary solution, the ultimate goal is to support
|
||||
// the mixed use of vapor components and default components.
|
||||
const queue: SchedulerJob[] = []
|
||||
let flushIndex = 0
|
||||
|
||||
// TODO: The queues in Vapor need to be merged with the queues in Core.
|
||||
// this is a temporary solution, the ultimate goal is to support
|
||||
// the mixed use of vapor components and default components.
|
||||
const pendingPostFlushCbs: SchedulerJob[] = []
|
||||
let activePostFlushCbs: SchedulerJob[] | null = null
|
||||
let postFlushIndex = 0
|
||||
|
||||
const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
|
||||
let currentFlushPromise: Promise<void> | null = null
|
||||
|
||||
export function queueJob(job: SchedulerJob): void {
|
||||
let lastOne: SchedulerJob | undefined
|
||||
if (!(job.flags! & VaporSchedulerJobFlags.QUEUED)) {
|
||||
if (job.id == null) {
|
||||
queue.push(job)
|
||||
} else if (
|
||||
// fast path when the job id is larger than the tail
|
||||
!(job.flags! & VaporSchedulerJobFlags.PRE) &&
|
||||
job.id >= (((lastOne = queue[queue.length - 1]) && lastOne.id) || 0)
|
||||
) {
|
||||
queue.push(job)
|
||||
} else {
|
||||
queue.splice(findInsertionIndex(job.id), 0, job)
|
||||
}
|
||||
|
||||
if (!(job.flags! & VaporSchedulerJobFlags.ALLOW_RECURSE)) {
|
||||
job.flags! |= VaporSchedulerJobFlags.QUEUED
|
||||
}
|
||||
queueFlush()
|
||||
}
|
||||
}
|
||||
|
||||
export function queuePostFlushCb(cb: SchedulerJobs): void {
|
||||
if (!isArray(cb)) {
|
||||
if (!(cb.flags! & VaporSchedulerJobFlags.QUEUED)) {
|
||||
pendingPostFlushCbs.push(cb)
|
||||
if (!(cb.flags! & VaporSchedulerJobFlags.ALLOW_RECURSE)) {
|
||||
cb.flags! |= VaporSchedulerJobFlags.QUEUED
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if cb is an array, it is a component lifecycle hook which can only be
|
||||
// triggered by a job, which is already deduped in the main queue, so
|
||||
// we can skip duplicate check here to improve perf
|
||||
pendingPostFlushCbs.push(...cb)
|
||||
}
|
||||
queueFlush()
|
||||
}
|
||||
|
||||
function queueFlush() {
|
||||
if (!isFlushing && !isFlushPending) {
|
||||
isFlushPending = true
|
||||
currentFlushPromise = resolvedPromise.then(flushJobs)
|
||||
}
|
||||
}
|
||||
|
||||
export function flushPostFlushCbs(): void {
|
||||
if (!pendingPostFlushCbs.length) return
|
||||
|
||||
const deduped = [...new Set(pendingPostFlushCbs)]
|
||||
pendingPostFlushCbs.length = 0
|
||||
|
||||
// #1947 already has active queue, nested flushPostFlushCbs call
|
||||
if (activePostFlushCbs) {
|
||||
activePostFlushCbs.push(...deduped)
|
||||
return
|
||||
}
|
||||
|
||||
activePostFlushCbs = deduped
|
||||
|
||||
activePostFlushCbs.sort((a, b) => getId(a) - getId(b))
|
||||
|
||||
for (
|
||||
postFlushIndex = 0;
|
||||
postFlushIndex < activePostFlushCbs.length;
|
||||
postFlushIndex++
|
||||
) {
|
||||
activePostFlushCbs[postFlushIndex]()
|
||||
activePostFlushCbs[postFlushIndex].flags! &= ~VaporSchedulerJobFlags.QUEUED
|
||||
}
|
||||
activePostFlushCbs = null
|
||||
postFlushIndex = 0
|
||||
}
|
||||
|
||||
// TODO: dev mode and checkRecursiveUpdates
|
||||
function flushJobs() {
|
||||
if (__BENCHMARK__) performance.mark('flushJobs-start')
|
||||
isFlushPending = false
|
||||
isFlushing = true
|
||||
|
||||
// Sort queue before flush.
|
||||
// This ensures that:
|
||||
// 1. Components are updated from parent to child. (because parent is always
|
||||
// created before the child so its render effect will have smaller
|
||||
// priority number)
|
||||
// 2. If a component is unmounted during a parent component's update,
|
||||
// its update can be skipped.
|
||||
queue.sort(comparator)
|
||||
|
||||
try {
|
||||
for (let i = 0; i < queue!.length; i++) {
|
||||
queue[i]()
|
||||
queue[i].flags! &= ~VaporSchedulerJobFlags.QUEUED
|
||||
}
|
||||
} finally {
|
||||
flushIndex = 0
|
||||
queue.length = 0
|
||||
|
||||
flushPostFlushCbs()
|
||||
|
||||
isFlushing = false
|
||||
currentFlushPromise = null
|
||||
// some postFlushCb queued jobs!
|
||||
// keep flushing until it drains.
|
||||
if (queue.length || pendingPostFlushCbs.length) {
|
||||
flushJobs()
|
||||
}
|
||||
if (__BENCHMARK__) performance.mark('flushJobs-end')
|
||||
}
|
||||
}
|
||||
|
||||
export function nextTick<T = void, R = void>(
|
||||
this: T,
|
||||
fn?: (this: T) => R,
|
||||
): Promise<Awaited<R>> {
|
||||
const p = currentFlushPromise || resolvedPromise
|
||||
return fn ? p.then(this ? fn.bind(this) : fn) : p
|
||||
}
|
||||
|
||||
// #2768
|
||||
// Use binary-search to find a suitable position in the queue,
|
||||
// so that the queue maintains the increasing order of job's id,
|
||||
// which can prevent the job from being skipped and also can avoid repeated patching.
|
||||
function findInsertionIndex(id: number) {
|
||||
// the start index should be `flushIndex + 1`
|
||||
let start = flushIndex + 1
|
||||
let end = queue.length
|
||||
|
||||
while (start < end) {
|
||||
const middle = (start + end) >>> 1
|
||||
const middleJob = queue[middle]
|
||||
const middleJobId = getId(middleJob)
|
||||
if (
|
||||
middleJobId < id ||
|
||||
(middleJobId === id && middleJob.flags! & VaporSchedulerJobFlags.PRE)
|
||||
) {
|
||||
start = middle + 1
|
||||
} else {
|
||||
end = middle
|
||||
}
|
||||
}
|
||||
|
||||
return start
|
||||
}
|
||||
|
||||
const getId = (job: SchedulerJob): number =>
|
||||
job.id == null ? Infinity : job.id
|
||||
|
||||
const comparator = (a: SchedulerJob, b: SchedulerJob): number => {
|
||||
const diff = getId(a) - getId(b)
|
||||
if (diff === 0) {
|
||||
const isAPre = a.flags! & VaporSchedulerJobFlags.PRE
|
||||
const isBPre = b.flags! & VaporSchedulerJobFlags.PRE
|
||||
if (isAPre && !isBPre) return -1
|
||||
if (isBPre && !isAPre) return 1
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
export type SchedulerFactory = (
|
||||
instance: ComponentInternalInstance | null,
|
||||
) => WatchScheduler
|
|
@ -1,153 +0,0 @@
|
|||
import {
|
||||
type ComponentInternalInstance,
|
||||
currentInstance,
|
||||
formatComponentName,
|
||||
} from './component'
|
||||
import { isFunction, isString } from '@vue/shared'
|
||||
import { isRef, pauseTracking, resetTracking, toRaw } from '@vue/reactivity'
|
||||
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||
import type { NormalizedRawProps } from './componentProps'
|
||||
|
||||
type TraceEntry = {
|
||||
instance: ComponentInternalInstance
|
||||
recurseCount: number
|
||||
}
|
||||
|
||||
type ComponentTraceStack = TraceEntry[]
|
||||
|
||||
export function warn(msg: string, ...args: any[]): void {
|
||||
// avoid props formatting or warn handler tracking deps that might be mutated
|
||||
// during patch, leading to infinite recursion.
|
||||
pauseTracking()
|
||||
|
||||
const instance = currentInstance
|
||||
const appWarnHandler = instance && instance.appContext.config.warnHandler
|
||||
const trace = getComponentTrace()
|
||||
|
||||
if (appWarnHandler) {
|
||||
callWithErrorHandling(
|
||||
appWarnHandler,
|
||||
instance,
|
||||
VaporErrorCodes.APP_WARN_HANDLER,
|
||||
[
|
||||
msg +
|
||||
args
|
||||
.map(a => (a.toString && a.toString()) ?? JSON.stringify(a))
|
||||
.join(''),
|
||||
instance,
|
||||
trace
|
||||
.map(
|
||||
({ instance }) =>
|
||||
`at <${formatComponentName(instance, instance.type)}>`,
|
||||
)
|
||||
.join('\n'),
|
||||
trace,
|
||||
],
|
||||
)
|
||||
} else {
|
||||
const warnArgs = [`[Vue warn]: ${msg}`, ...args]
|
||||
/* istanbul ignore if */
|
||||
if (
|
||||
trace.length &&
|
||||
// avoid spamming console during tests
|
||||
!__TEST__
|
||||
) {
|
||||
warnArgs.push(`\n`, ...formatTrace(trace))
|
||||
}
|
||||
console.warn(...warnArgs)
|
||||
}
|
||||
|
||||
resetTracking()
|
||||
}
|
||||
|
||||
export function getComponentTrace(): ComponentTraceStack {
|
||||
let instance = currentInstance
|
||||
if (!instance) return []
|
||||
|
||||
// we can't just use the stack because it will be incomplete during updates
|
||||
// that did not start from the root. Re-construct the parent chain using
|
||||
// instance parent pointers.
|
||||
const stack: ComponentTraceStack = []
|
||||
|
||||
while (instance) {
|
||||
const last = stack[0]
|
||||
if (last && last.instance === instance) {
|
||||
last.recurseCount++
|
||||
} else {
|
||||
stack.push({
|
||||
instance,
|
||||
recurseCount: 0,
|
||||
})
|
||||
}
|
||||
instance = instance.parent
|
||||
}
|
||||
|
||||
return stack
|
||||
}
|
||||
|
||||
function formatTrace(trace: ComponentTraceStack): any[] {
|
||||
const logs: any[] = []
|
||||
trace.forEach((entry, i) => {
|
||||
logs.push(...(i === 0 ? [] : [`\n`]), ...formatTraceEntry(entry))
|
||||
})
|
||||
return logs
|
||||
}
|
||||
|
||||
function formatTraceEntry({ instance, recurseCount }: TraceEntry): any[] {
|
||||
const postfix =
|
||||
recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``
|
||||
const isRoot = instance ? instance.parent == null : false
|
||||
const open = ` at <${formatComponentName(instance, instance.type, isRoot)}`
|
||||
const close = `>` + postfix
|
||||
return instance.rawProps.length
|
||||
? [open, ...formatProps(instance.rawProps), close]
|
||||
: [open + close]
|
||||
}
|
||||
|
||||
function formatProps(rawProps: NormalizedRawProps): any[] {
|
||||
const fullProps: Record<string, any> = {}
|
||||
for (const props of rawProps) {
|
||||
if (isFunction(props)) {
|
||||
const propsObj = props()
|
||||
for (const key in propsObj) {
|
||||
fullProps[key] = propsObj[key]
|
||||
}
|
||||
} else {
|
||||
for (const key in props) {
|
||||
fullProps[key] = props[key]()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const res: any[] = []
|
||||
Object.keys(fullProps)
|
||||
.slice(0, 3)
|
||||
.forEach(key => res.push(...formatProp(key, fullProps[key])))
|
||||
|
||||
if (fullProps.length > 3) {
|
||||
res.push(` ...`)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
function formatProp(key: string, value: unknown, raw?: boolean): any {
|
||||
if (isString(value)) {
|
||||
value = JSON.stringify(value)
|
||||
return raw ? value : [`${key}=${value}`]
|
||||
} else if (
|
||||
typeof value === 'number' ||
|
||||
typeof value === 'boolean' ||
|
||||
value == null
|
||||
) {
|
||||
return raw ? value : [`${key}=${value}`]
|
||||
} else if (isRef(value)) {
|
||||
value = formatProp(key, toRaw(value.value), true)
|
||||
return raw ? value : [`${key}=Ref<`, value, `>`]
|
||||
} else if (isFunction(value)) {
|
||||
return [`${key}=fn${value.name ? `<${value.name}>` : ``}`]
|
||||
} else {
|
||||
value = toRaw(value)
|
||||
return raw ? value : [`${key}=`, value]
|
||||
}
|
||||
}
|
|
@ -12,4 +12,4 @@ if (__DEV__ && __BROWSER__) {
|
|||
initCustomFormatter()
|
||||
}
|
||||
|
||||
export * from '@vue/runtime-vapor'
|
||||
export * from '@vue/runtime-vapor/src'
|
||||
|
|
|
@ -399,9 +399,6 @@ importers:
|
|||
'@vue/reactivity':
|
||||
specifier: workspace:*
|
||||
version: link:../reactivity
|
||||
'@vue/runtime-shared':
|
||||
specifier: workspace:*
|
||||
version: link:../runtime-shared
|
||||
'@vue/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
|
@ -425,12 +422,6 @@ importers:
|
|||
specifier: ^2.0.7
|
||||
version: 2.0.7
|
||||
|
||||
packages/runtime-shared:
|
||||
dependencies:
|
||||
'@vue/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
|
||||
packages/runtime-test:
|
||||
dependencies:
|
||||
'@vue/runtime-core':
|
||||
|
@ -445,9 +436,9 @@ importers:
|
|||
'@vue/reactivity':
|
||||
specifier: workspace:*
|
||||
version: link:../reactivity
|
||||
'@vue/runtime-shared':
|
||||
'@vue/runtime-dom':
|
||||
specifier: workspace:*
|
||||
version: link:../runtime-shared
|
||||
version: link:../runtime-dom
|
||||
'@vue/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
|
|
|
@ -39,5 +39,8 @@
|
|||
"scripts/*",
|
||||
"rollup.*.js"
|
||||
],
|
||||
"exclude": ["packages-private/sfc-playground/src/vue-dev-proxy*"]
|
||||
"exclude": [
|
||||
"packages-private/sfc-playground/src/vue-dev-proxy*",
|
||||
"packages/runtime-vapor/src/_old"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue