refactor: rename packages

This commit is contained in:
Evan You
2018-10-26 15:44:50 -04:00
parent f142c322e0
commit e05673f4d3
81 changed files with 91 additions and 91 deletions

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

View 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

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

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

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

View 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()
}
}
*/