refactor: rename packages
This commit is contained in:
100
packages/runtime-core/src/optional/asyncComponent.ts
Normal file
100
packages/runtime-core/src/optional/asyncComponent.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { ChildrenFlags } from '../flags'
|
||||
import { createComponentVNode, Slots } from '../vdom'
|
||||
import { Component, ComponentType, ComponentClass } from '../component'
|
||||
import { unwrap } from '@vue/observer'
|
||||
import { isFunction } from '@vue/shared'
|
||||
|
||||
interface AsyncComponentFactory {
|
||||
(): Promise<ComponentType>
|
||||
resolved?: ComponentType
|
||||
}
|
||||
|
||||
interface AsyncComponentFullOptions {
|
||||
factory: AsyncComponentFactory
|
||||
loading?: ComponentType
|
||||
error?: ComponentType
|
||||
delay?: number
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
type AsyncComponentOptions = AsyncComponentFactory | AsyncComponentFullOptions
|
||||
|
||||
export function createAsyncComponent(
|
||||
options: AsyncComponentOptions
|
||||
): ComponentClass {
|
||||
if (isFunction(options)) {
|
||||
options = { factory: options }
|
||||
}
|
||||
|
||||
const {
|
||||
factory,
|
||||
timeout,
|
||||
delay = 200,
|
||||
loading: loadingComp,
|
||||
error: errorComp
|
||||
} = options
|
||||
|
||||
return class AsyncContainer extends Component {
|
||||
comp: ComponentType | null = null
|
||||
err: Error | null = null
|
||||
delayed: boolean = false
|
||||
timedOut: boolean = false
|
||||
|
||||
// doing this in beforeMount so this is non-SSR only
|
||||
beforeMount() {
|
||||
if (factory.resolved) {
|
||||
this.comp = factory.resolved
|
||||
} else {
|
||||
factory()
|
||||
.then(resolved => {
|
||||
this.comp = factory.resolved = resolved
|
||||
})
|
||||
.catch(err => {
|
||||
this.err = err
|
||||
})
|
||||
}
|
||||
if (timeout != null) {
|
||||
setTimeout(() => {
|
||||
this.timedOut = true
|
||||
}, timeout)
|
||||
}
|
||||
if (delay != null) {
|
||||
this.delayed = true
|
||||
setTimeout(() => {
|
||||
this.delayed = false
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
render(props: any, slots: Slots) {
|
||||
if (this.err || (this.timedOut && !this.comp)) {
|
||||
const error =
|
||||
this.err || new Error(`Async component timed out after ${timeout}ms.`)
|
||||
return errorComp
|
||||
? createComponentVNode(
|
||||
errorComp,
|
||||
{ error },
|
||||
null,
|
||||
ChildrenFlags.NO_CHILDREN
|
||||
)
|
||||
: null
|
||||
} else if (this.comp) {
|
||||
return createComponentVNode(
|
||||
this.comp,
|
||||
unwrap(props),
|
||||
slots,
|
||||
ChildrenFlags.STABLE_SLOTS
|
||||
)
|
||||
} else {
|
||||
return loadingComp && !this.delayed
|
||||
? createComponentVNode(
|
||||
loadingComp,
|
||||
null,
|
||||
null,
|
||||
ChildrenFlags.NO_CHILDREN
|
||||
)
|
||||
: null
|
||||
}
|
||||
}
|
||||
} as ComponentClass
|
||||
}
|
||||
0
packages/runtime-core/src/optional/await.ts
Normal file
0
packages/runtime-core/src/optional/await.ts
Normal file
96
packages/runtime-core/src/optional/context.ts
Normal file
96
packages/runtime-core/src/optional/context.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { observable } from '@vue/observer'
|
||||
import { Component, FunctionalComponent, ComponentInstance } from '../component'
|
||||
import { warn } from '../warning'
|
||||
import { Slots, VNode } from '../vdom'
|
||||
import { VNodeFlags } from '../flags'
|
||||
|
||||
interface ProviderProps {
|
||||
id: string | symbol
|
||||
value: any
|
||||
}
|
||||
|
||||
export class Provide extends Component<ProviderProps> {
|
||||
context: Record<string | symbol, any> = observable()
|
||||
|
||||
static props = {
|
||||
id: {
|
||||
type: [String, Symbol],
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
|
||||
created() {
|
||||
const { $props, context } = this
|
||||
this.$watch(
|
||||
() => $props.value,
|
||||
value => {
|
||||
// TS doesn't allow symbol as index :/
|
||||
// https://github.com/Microsoft/TypeScript/issues/24587
|
||||
context[$props.id as string] = value
|
||||
},
|
||||
{
|
||||
sync: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
if (__DEV__) {
|
||||
this.$watch(
|
||||
() => $props.id,
|
||||
(id, oldId) => {
|
||||
warn(
|
||||
`Context provider id change detected (from "${oldId}" to "${id}"). ` +
|
||||
`This is not supported and should be avoided as it leads to ` +
|
||||
`indeterministic context resolution.`
|
||||
)
|
||||
},
|
||||
{
|
||||
sync: true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render(props: any, slots: any) {
|
||||
return slots.default && slots.default()
|
||||
}
|
||||
}
|
||||
|
||||
interface InjectProps {
|
||||
id: string | symbol
|
||||
}
|
||||
|
||||
export const Inject: FunctionalComponent<InjectProps> = (
|
||||
props: InjectProps,
|
||||
slots: Slots,
|
||||
attrs: any,
|
||||
vnode: VNode
|
||||
) => {
|
||||
let resolvedValue
|
||||
let resolved = false
|
||||
const { id } = props
|
||||
// walk the parent chain to locate context with key
|
||||
while (vnode !== null && vnode.contextVNode !== null) {
|
||||
if (
|
||||
vnode.flags & VNodeFlags.COMPONENT_STATEFUL &&
|
||||
(vnode.children as ComponentInstance).constructor === Provide &&
|
||||
(vnode.children as any).context.hasOwnProperty(id)
|
||||
) {
|
||||
resolved = true
|
||||
resolvedValue = (vnode.children as any).context[id]
|
||||
break
|
||||
}
|
||||
vnode = vnode.contextVNode
|
||||
}
|
||||
if (__DEV__ && !resolved) {
|
||||
warn(
|
||||
`Inject with id "${id.toString()}" did not match any Provider with ` +
|
||||
`corresponding property in the parent chain.`
|
||||
)
|
||||
}
|
||||
return slots.default && slots.default(resolvedValue)
|
||||
}
|
||||
|
||||
Inject.pure = true
|
||||
108
packages/runtime-core/src/optional/directives.ts
Normal file
108
packages/runtime-core/src/optional/directives.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
Runtime helper for applying directives to a vnode. Example usage:
|
||||
|
||||
const comp = resolveComponent(this, 'comp')
|
||||
const foo = resolveDirective(this, 'foo')
|
||||
const bar = resolveDirective(this, 'bar')
|
||||
|
||||
return applyDirectives(
|
||||
h(comp),
|
||||
this,
|
||||
[foo, this.x],
|
||||
[bar, this.y]
|
||||
)
|
||||
*/
|
||||
|
||||
import { VNode, cloneVNode, VNodeData } from '../vdom'
|
||||
import { ComponentInstance } from '../component'
|
||||
import { EMPTY_OBJ } from '@vue/shared'
|
||||
|
||||
interface DirectiveBinding {
|
||||
instance: ComponentInstance
|
||||
value?: any
|
||||
oldValue?: any
|
||||
arg?: string
|
||||
modifiers?: DirectiveModifiers
|
||||
}
|
||||
|
||||
type DirectiveHook = (
|
||||
el: any,
|
||||
binding: DirectiveBinding,
|
||||
vnode: VNode,
|
||||
prevVNode: VNode | void
|
||||
) => void
|
||||
|
||||
interface Directive {
|
||||
beforeMount: DirectiveHook
|
||||
mounted: DirectiveHook
|
||||
beforeUpdate: DirectiveHook
|
||||
updated: DirectiveHook
|
||||
beforeUnmount: DirectiveHook
|
||||
unmounted: DirectiveHook
|
||||
}
|
||||
|
||||
type DirectiveModifiers = Record<string, boolean>
|
||||
|
||||
const valueCache = new WeakMap<Directive, WeakMap<any, any>>()
|
||||
|
||||
export function applyDirective(
|
||||
data: VNodeData,
|
||||
instance: ComponentInstance,
|
||||
directive: Directive,
|
||||
value?: any,
|
||||
arg?: string,
|
||||
modifiers?: DirectiveModifiers
|
||||
) {
|
||||
let valueCacheForDir = valueCache.get(directive) as WeakMap<VNode, any>
|
||||
if (!valueCacheForDir) {
|
||||
valueCacheForDir = new WeakMap<VNode, any>()
|
||||
valueCache.set(directive, valueCacheForDir)
|
||||
}
|
||||
for (const key in directive) {
|
||||
const hook = directive[key as keyof Directive]
|
||||
const hookKey = `vnode` + key[0].toUpperCase() + key.slice(1)
|
||||
const vnodeHook = (vnode: VNode, prevVNode?: VNode) => {
|
||||
let oldValue
|
||||
if (prevVNode !== void 0) {
|
||||
oldValue = valueCacheForDir.get(prevVNode)
|
||||
valueCacheForDir.delete(prevVNode)
|
||||
}
|
||||
valueCacheForDir.set(vnode, value)
|
||||
hook(
|
||||
vnode.el,
|
||||
{
|
||||
instance,
|
||||
value,
|
||||
oldValue,
|
||||
arg,
|
||||
modifiers
|
||||
},
|
||||
vnode,
|
||||
prevVNode
|
||||
)
|
||||
}
|
||||
const existing = data[hookKey]
|
||||
data[hookKey] = existing
|
||||
? [].concat(existing as any, vnodeHook as any)
|
||||
: vnodeHook
|
||||
}
|
||||
}
|
||||
|
||||
type DirectiveArguments = [
|
||||
Directive,
|
||||
any,
|
||||
string | undefined,
|
||||
DirectiveModifiers | undefined
|
||||
][]
|
||||
|
||||
export function applyDirectives(
|
||||
vnode: VNode,
|
||||
instance: ComponentInstance,
|
||||
...directives: DirectiveArguments
|
||||
) {
|
||||
vnode = cloneVNode(vnode, EMPTY_OBJ)
|
||||
for (let i = 0; i < directives.length; i++) {
|
||||
applyDirective(vnode.data as VNodeData, instance, ...directives[i])
|
||||
}
|
||||
return vnode
|
||||
}
|
||||
76
packages/runtime-core/src/optional/eventEmitter.ts
Normal file
76
packages/runtime-core/src/optional/eventEmitter.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { isArray } from '@vue/shared'
|
||||
|
||||
export class EventEmitter {
|
||||
ctx: any
|
||||
events: { [event: string]: Function[] | null } = {}
|
||||
|
||||
constructor(ctx: any) {
|
||||
this.ctx = ctx
|
||||
}
|
||||
|
||||
// eventEmitter interface
|
||||
on(event: string, fn: Function) {
|
||||
if (isArray(event)) {
|
||||
for (let i = 0; i < event.length; i++) {
|
||||
this.on(event[i], fn)
|
||||
}
|
||||
} else {
|
||||
const { events } = this
|
||||
;(events[event] || (events[event] = [])).push(fn)
|
||||
}
|
||||
}
|
||||
|
||||
once(event: string, fn: Function) {
|
||||
const onceFn = (...args: any[]) => {
|
||||
this.off(event, onceFn)
|
||||
fn.apply(this, args)
|
||||
}
|
||||
;(onceFn as any).fn = fn
|
||||
this.on(event, onceFn)
|
||||
}
|
||||
|
||||
off(event?: string, fn?: Function) {
|
||||
if (!event && !fn) {
|
||||
this.events = {}
|
||||
} else if (isArray(event)) {
|
||||
for (let i = 0; i < event.length; i++) {
|
||||
this.off(event[i], fn)
|
||||
}
|
||||
} else if (!fn) {
|
||||
this.events[event as string] = null
|
||||
} else {
|
||||
const fns = this.events[event as string]
|
||||
if (fns) {
|
||||
for (let i = 0; i < fns.length; i++) {
|
||||
const f = fns[i]
|
||||
if (fn === f || fn === (f as any).fn) {
|
||||
fns.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit(name: string, ...payload: any[]) {
|
||||
const handlers = this.events[name]
|
||||
if (handlers) {
|
||||
invokeListeners(handlers, payload, this.ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function invokeListeners(
|
||||
value: Function | Function[],
|
||||
payload: any[],
|
||||
ctx: any = null
|
||||
) {
|
||||
// TODO handle error
|
||||
if (isArray(value)) {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
value[i].call(ctx, ...payload)
|
||||
}
|
||||
} else {
|
||||
value.call(ctx, ...payload)
|
||||
}
|
||||
}
|
||||
134
packages/runtime-core/src/optional/keepAlive.ts
Normal file
134
packages/runtime-core/src/optional/keepAlive.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { Component, ComponentClass, ComponentInstance } from '../component'
|
||||
import { VNode, Slots, cloneVNode } from '../vdom'
|
||||
import { VNodeFlags } from '../flags'
|
||||
import { warn } from '../warning'
|
||||
import { isString, isArray } from '@vue/shared'
|
||||
|
||||
type MatchPattern = string | RegExp | string[] | RegExp[]
|
||||
|
||||
interface KeepAliveProps {
|
||||
include?: MatchPattern
|
||||
exclude?: MatchPattern
|
||||
max?: number | string
|
||||
}
|
||||
|
||||
type CacheKey = string | number | ComponentClass
|
||||
type Cache = Map<CacheKey, VNode>
|
||||
|
||||
export const KeepAliveSymbol = Symbol()
|
||||
|
||||
export class KeepAlive extends Component<KeepAliveProps> {
|
||||
private cache: Cache
|
||||
private keys: Set<CacheKey>
|
||||
|
||||
created() {
|
||||
this.cache = new Map()
|
||||
this.keys = new Set()
|
||||
}
|
||||
|
||||
// to be set in createRenderer when instance is created
|
||||
$unmount: (instance: ComponentInstance) => void
|
||||
|
||||
beforeUnmount() {
|
||||
this.cache.forEach(vnode => {
|
||||
// change flag so it can be properly unmounted
|
||||
vnode.flags = VNodeFlags.COMPONENT_STATEFUL_NORMAL
|
||||
this.$unmount(vnode.children as ComponentInstance)
|
||||
})
|
||||
}
|
||||
|
||||
pruneCache(filter?: (name: string) => boolean) {
|
||||
this.cache.forEach((vnode, key) => {
|
||||
const name = getName(vnode.tag as ComponentClass)
|
||||
if (name && (!filter || !filter(name))) {
|
||||
this.pruneCacheEntry(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pruneCacheEntry(key: CacheKey) {
|
||||
const cached = this.cache.get(key) as VNode
|
||||
const current = this.$vnode
|
||||
if (!current || cached.tag !== current.tag) {
|
||||
this.$unmount(cached.children as ComponentInstance)
|
||||
}
|
||||
this.cache.delete(key)
|
||||
this.keys.delete(key)
|
||||
}
|
||||
|
||||
render(props: KeepAliveProps, slots: Slots) {
|
||||
if (!slots.default) {
|
||||
return
|
||||
}
|
||||
const children = slots.default()
|
||||
let vnode = children[0]
|
||||
if (children.length > 1) {
|
||||
if (__DEV__) {
|
||||
warn(`KeepAlive can only have a single child.`)
|
||||
}
|
||||
return children
|
||||
} else if ((vnode.flags & VNodeFlags.COMPONENT_STATEFUL) === 0) {
|
||||
return children
|
||||
}
|
||||
|
||||
const comp = vnode.tag as ComponentClass
|
||||
const name = getName(comp)
|
||||
const { include, exclude, max } = props
|
||||
|
||||
if (
|
||||
(include && (!name || !matches(include, name))) ||
|
||||
(exclude && name && matches(exclude, name))
|
||||
) {
|
||||
return vnode
|
||||
}
|
||||
|
||||
const { cache, keys } = this
|
||||
const key = vnode.key == null ? comp : vnode.key
|
||||
const cached = cache.get(key)
|
||||
|
||||
// clone vnode if it's reused because we are going to mutate its flags
|
||||
if (vnode.el) {
|
||||
vnode = cloneVNode(vnode)
|
||||
}
|
||||
cache.set(key, vnode)
|
||||
|
||||
if (cached) {
|
||||
vnode.children = cached.children
|
||||
// avoid vnode being mounted as fresh
|
||||
vnode.flags |= VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE
|
||||
// make this key the freshest
|
||||
keys.delete(key)
|
||||
keys.add(key)
|
||||
} else {
|
||||
keys.add(key)
|
||||
// prune oldest entry
|
||||
if (max && keys.size > parseInt(max as string, 10)) {
|
||||
this.pruneCacheEntry(Array.from(this.keys)[0])
|
||||
}
|
||||
}
|
||||
// avoid vnode being unmounted
|
||||
vnode.flags |= VNodeFlags.COMPONENT_STATEFUL_SHOULD_KEEP_ALIVE
|
||||
return vnode
|
||||
}
|
||||
}
|
||||
|
||||
// mark constructor
|
||||
// we use a symbol instead of comparing to the constructor itself
|
||||
// so that the implementation can be tree-shaken
|
||||
;(KeepAlive as any)[KeepAliveSymbol] = true
|
||||
|
||||
function getName(comp: ComponentClass): string | void {
|
||||
return comp.displayName || comp.name
|
||||
}
|
||||
|
||||
function matches(pattern: MatchPattern, name: string): boolean {
|
||||
if (isArray(pattern)) {
|
||||
return (pattern as any).some((p: string | RegExp) => matches(p, name))
|
||||
} else if (isString(pattern)) {
|
||||
return pattern.split(',').indexOf(name) > -1
|
||||
} else if (pattern.test) {
|
||||
return pattern.test(name)
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
return false
|
||||
}
|
||||
84
packages/runtime-core/src/optional/mixins.ts
Normal file
84
packages/runtime-core/src/optional/mixins.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Component } from '../component'
|
||||
import { createComponentClassFromOptions } from '../componentUtils'
|
||||
import {
|
||||
ComponentOptions,
|
||||
resolveComponentOptionsFromClass,
|
||||
mergeComponentOptions,
|
||||
mergeDataFn
|
||||
} from '../componentOptions'
|
||||
import { normalizePropsOptions } from '../componentProps'
|
||||
import { extractInitializers } from '../componentState'
|
||||
import { isFunction } from '@vue/shared'
|
||||
|
||||
interface ComponentConstructor<This = Component> {
|
||||
new (): This
|
||||
}
|
||||
|
||||
interface ComponentConstructorWithMixins<This> {
|
||||
new <P = {}, D = {}>(): This & { $data: D } & D & { $props: Readonly<P> } & P
|
||||
}
|
||||
|
||||
// mind = blown
|
||||
// https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type
|
||||
type UnionToIntersection<U> = (U extends any
|
||||
? (k: U) => void
|
||||
: never) extends ((k: infer I) => void)
|
||||
? I
|
||||
: never
|
||||
|
||||
type ExtractInstance<T> = T extends (infer U)[]
|
||||
? UnionToIntersection<U extends ComponentConstructor<infer V> ? V : never>
|
||||
: never
|
||||
|
||||
export function mixins<
|
||||
T extends ComponentConstructor[] = [],
|
||||
V = ExtractInstance<T>
|
||||
>(...args: T): ComponentConstructorWithMixins<V>
|
||||
export function mixins(...args: any[]): any {
|
||||
let options: ComponentOptions = {}
|
||||
args.forEach(mixin => {
|
||||
if (isFunction(mixin)) {
|
||||
const Class = mixin
|
||||
mixin = resolveComponentOptionsFromClass(Class)
|
||||
// in order to extract properties initialized in the mixin's constructor,
|
||||
// we create an instance of it and pass in the actual props - this
|
||||
// short-circuits the normal component initialization and allows us to
|
||||
// relatively-cheaply extract the properties added in the constructor.
|
||||
function extractData() {
|
||||
return extractInitializers(new Class(this.$props))
|
||||
}
|
||||
const { data } = mixin
|
||||
mixin.data = data ? mergeDataFn(data, extractData) : extractData
|
||||
} else {
|
||||
mixin.props = normalizePropsOptions(mixin.props)
|
||||
}
|
||||
options = mergeComponentOptions(options, mixin)
|
||||
})
|
||||
return createComponentClassFromOptions(options)
|
||||
}
|
||||
|
||||
/* Example usage
|
||||
|
||||
class Foo extends Component<{ foo: number }> {
|
||||
test() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Bar extends Component<{ bar: string }> {
|
||||
ok() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Baz extends mixins(Foo, Bar)<{ baz: number }> {
|
||||
created() {
|
||||
this.foo
|
||||
this.bar
|
||||
this.baz
|
||||
this.test()
|
||||
this.ok()
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
Reference in New Issue
Block a user