diff --git a/packages/core/__tests__/parentChain.spec.ts b/packages/core/__tests__/parentChain.spec.ts
index 93dd7528..c34eaf7b 100644
--- a/packages/core/__tests__/parentChain.spec.ts
+++ b/packages/core/__tests__/parentChain.spec.ts
@@ -3,7 +3,6 @@ import {
Component,
render,
nodeOps,
- ComponentInstance,
observable,
nextTick
} from '@vue/renderer-test'
@@ -41,7 +40,7 @@ describe('Parent chain management', () => {
}
const root = nodeOps.createElement('div')
- const parent = render(h(Parent), root) as ComponentInstance
+ const parent = render(h(Parent), root) as Component
expect(child.$parent).toBe(parent)
expect(child.$root).toBe(parent)
@@ -100,7 +99,7 @@ describe('Parent chain management', () => {
}
const root = nodeOps.createElement('div')
- const parent = render(h(Parent), root) as ComponentInstance
+ const parent = render(h(Parent), root) as Component
expect(child.$parent).toBe(parent)
expect(child.$root).toBe(parent)
diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts
index ee62fd22..34631eec 100644
--- a/packages/core/src/component.ts
+++ b/packages/core/src/component.ts
@@ -1,4 +1,4 @@
-import { EMPTY_OBJ, NOOP } from './utils'
+import { EMPTY_OBJ, NOOP, isArray } from '@vue/shared'
import { VNode, Slots, RenderNode, MountedVNode } from './vdom'
import {
Data,
@@ -44,30 +44,30 @@ interface PublicInstanceMethods {
$emit(name: string, ...payload: any[]): this
}
-interface APIMethods
{
- data?(): Partial
+export interface APIMethods {
+ data(): Partial
render(props: Readonly, slots: Slots, attrs: Data, parentVNode: VNode): any
}
-interface LifecycleMethods {
- beforeCreate?(): void
- created?(): void
- beforeMount?(): void
- mounted?(): void
- beforeUpdate?(vnode: VNode): void
- updated?(vnode: VNode): void
- beforeUnmount?(): void
- unmounted?(): void
- errorCaptured?(): (
+export interface LifecycleMethods {
+ beforeCreate(): void
+ created(): void
+ beforeMount(): void
+ mounted(): void
+ beforeUpdate(vnode: VNode): void
+ updated(vnode: VNode): void
+ beforeUnmount(): void
+ unmounted(): void
+ errorCaptured(): (
err: Error,
type: ErrorTypes,
instance: ComponentInstance | null,
vnode: VNode
) => boolean | void
- activated?(): void
- deactivated?(): void
- renderTracked?(e: DebuggerEvent): void
- renderTriggered?(e: DebuggerEvent): void
+ activated(): void
+ deactivated(): void
+ renderTracked(e: DebuggerEvent): void
+ renderTriggered(e: DebuggerEvent): void
}
export interface ComponentClass extends ComponentClassOptions {
@@ -88,9 +88,10 @@ export type ComponentType = ComponentClass | FunctionalComponent
// It extends InternalComponent with mounted instance properties.
export interface ComponentInstance
extends InternalComponent,
- APIMethods
,
- LifecycleMethods {
+ Partial>,
+ Partial {
constructor: ComponentClass
+ render: APIMethods['render']
$vnode: MountedVNode
$data: D
@@ -157,7 +158,7 @@ class InternalComponent implements PublicInstanceMethods {
// eventEmitter interface
$on(event: string, fn: Function): this {
- if (Array.isArray(event)) {
+ if (isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.$on(event[i], fn)
}
@@ -181,7 +182,7 @@ class InternalComponent implements PublicInstanceMethods {
if (this._events) {
if (!event && !fn) {
this._events = null
- } else if (Array.isArray(event)) {
+ } else if (isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.$off(event[i], fn)
}
@@ -223,7 +224,7 @@ class InternalComponent implements PublicInstanceMethods {
function invokeListeners(value: Function | Function[], payload: any[]) {
// TODO handle error
- if (Array.isArray(value)) {
+ if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
value[i](...payload)
}
diff --git a/packages/core/src/componentComputed.ts b/packages/core/src/componentComputed.ts
index 4f3e99c2..7e914a1c 100644
--- a/packages/core/src/componentComputed.ts
+++ b/packages/core/src/componentComputed.ts
@@ -1,4 +1,4 @@
-import { NOOP } from './utils'
+import { NOOP, isFunction } from '@vue/shared'
import { computed, stop, ComputedGetter } from '@vue/observer'
import { ComponentInstance } from './component'
import { ComponentComputedOptions } from './componentOptions'
@@ -17,7 +17,7 @@ export function initializeComputed(
const proxy = instance.$proxy
for (const key in computedOptions) {
const option = computedOptions[key]
- const getter = typeof option === 'function' ? option : option.get || NOOP
+ const getter = isFunction(option) ? option : option.get || NOOP
handles[key] = computed(getter, proxy)
}
}
diff --git a/packages/core/src/componentOptions.ts b/packages/core/src/componentOptions.ts
index bab81745..2b033940 100644
--- a/packages/core/src/componentOptions.ts
+++ b/packages/core/src/componentOptions.ts
@@ -1,5 +1,12 @@
-import { ComponentInstance } from './component'
+import {
+ ComponentInstance,
+ ComponentClass,
+ APIMethods,
+ LifecycleMethods
+} from './component'
import { Slots } from './vdom'
+import { isArray, isObject, isFunction } from '@vue/shared'
+import { normalizePropsOptions } from './componentProps'
export type Data = Record
@@ -73,3 +80,100 @@ export interface WatchOptions {
deep?: boolean
immediate?: boolean
}
+
+type ReservedKeys = { [K in keyof (APIMethods & LifecycleMethods)]: 1 }
+
+export const reservedMethods: ReservedKeys = {
+ data: 1,
+ render: 1,
+ beforeCreate: 1,
+ created: 1,
+ beforeMount: 1,
+ mounted: 1,
+ beforeUpdate: 1,
+ updated: 1,
+ beforeUnmount: 1,
+ unmounted: 1,
+ errorCaptured: 1,
+ activated: 1,
+ deactivated: 1,
+ renderTracked: 1,
+ renderTriggered: 1
+}
+
+// This is called in the base component constructor and the return value is
+// set on the instance as $options.
+export function resolveComponentOptionsFromClass(
+ Component: ComponentClass
+): ComponentOptions {
+ if (Component.options) {
+ return Component.options
+ }
+ const staticDescriptors = Object.getOwnPropertyDescriptors(Component)
+ const options = {} as any
+ for (const key in staticDescriptors) {
+ const { enumerable, get, value } = staticDescriptors[key]
+ if (enumerable || get) {
+ options[key] = get ? get() : value
+ }
+ }
+ const instanceDescriptors = Object.getOwnPropertyDescriptors(
+ Component.prototype
+ )
+ for (const key in instanceDescriptors) {
+ const { get, value } = instanceDescriptors[key]
+ if (get) {
+ // computed properties
+ ;(options.computed || (options.computed = {}))[key] = get
+ // there's no need to do anything for the setter
+ // as it's already defined on the prototype
+ } else if (isFunction(value)) {
+ if (key in reservedMethods) {
+ options[key] = value
+ } else {
+ ;(options.methods || (options.methods = {}))[key] = value
+ }
+ }
+ }
+ options.props = normalizePropsOptions(options.props)
+ Component.options = options
+ return options
+}
+
+export function mergeComponentOptions(to: any, from: any): ComponentOptions {
+ const res: any = Object.assign({}, to)
+ if (from.mixins) {
+ from.mixins.forEach((mixin: any) => {
+ from = mergeComponentOptions(from, mixin)
+ })
+ }
+ for (const key in from) {
+ const value = from[key]
+ const existing = res[key]
+ if (isFunction(value) && isFunction(existing)) {
+ if (key === 'data') {
+ // for data we need to merge the returned value
+ res[key] = function() {
+ return Object.assign(existing(), value())
+ }
+ } else if (/^render|^errorCaptured/.test(key)) {
+ // render, renderTracked, renderTriggered & errorCaptured
+ // are never merged
+ res[key] = value
+ } else {
+ // merge lifecycle hooks
+ res[key] = function(...args: any[]) {
+ existing.call(this, ...args)
+ value.call(this, ...args)
+ }
+ }
+ } else if (isArray(value) && isArray(existing)) {
+ res[key] = existing.concat(value)
+ } else if (isObject(value) && isObject(existing)) {
+ res[key] = Object.assign({}, existing, value)
+ } else {
+ res[key] = value
+ }
+ }
+ return res
+}
diff --git a/packages/core/src/componentProps.ts b/packages/core/src/componentProps.ts
index 9dae489c..da1fa825 100644
--- a/packages/core/src/componentProps.ts
+++ b/packages/core/src/componentProps.ts
@@ -2,19 +2,40 @@ import { immutable, unwrap, lock, unlock } from '@vue/observer'
import { ComponentInstance } from './component'
import {
Data,
- ComponentPropsOptions,
PropOptions,
Prop,
- PropType
+ PropType,
+ ComponentPropsOptions
} from './componentOptions'
-import { EMPTY_OBJ, camelize, hyphenate, capitalize } from './utils'
+import {
+ EMPTY_OBJ,
+ camelize,
+ hyphenate,
+ capitalize,
+ isString,
+ isFunction,
+ isArray,
+ isObject
+} from '@vue/shared'
import { warn } from './warning'
const EMPTY_PROPS = { props: EMPTY_OBJ }
+const enum BooleanFlags {
+ shouldCast = '1',
+ shouldCastTrue = '2'
+}
+
+type NormalizedProp = PropOptions & {
+ [BooleanFlags.shouldCast]?: boolean
+ [BooleanFlags.shouldCastTrue]?: boolean
+}
+
+type NormalizedPropsOptions = Record
+
export function initializeProps(
instance: ComponentInstance,
- options: ComponentPropsOptions | undefined,
+ options: NormalizedPropsOptions | undefined,
data: Data | null
) {
const { props, attrs } = resolveProps(data, options)
@@ -31,13 +52,13 @@ export function initializeProps(
// - else: everything goes in `props`.
export function resolveProps(
rawData: any,
- rawOptions: ComponentPropsOptions | void
+ _options: NormalizedPropsOptions | void
): { props: Data; attrs?: Data } {
- const hasDeclaredProps = rawOptions !== void 0
+ const hasDeclaredProps = _options !== void 0
+ const options = _options as NormalizedPropsOptions
if (!rawData && !hasDeclaredProps) {
return EMPTY_PROPS
}
- const options = normalizePropsOptions(rawOptions) as NormalizedPropsOptions
const props: any = {}
let attrs: any = void 0
if (rawData != null) {
@@ -66,8 +87,7 @@ export function resolveProps(
// default values
if (hasDefault && currentValue === void 0) {
const defaultValue = opt.default
- props[key] =
- typeof defaultValue === 'function' ? defaultValue() : defaultValue
+ props[key] = isFunction(defaultValue) ? defaultValue() : defaultValue
}
// boolean casting
if (opt[BooleanFlags.shouldCast]) {
@@ -129,58 +149,34 @@ export function updateProps(instance: ComponentInstance, nextData: Data) {
}
}
-const enum BooleanFlags {
- shouldCast = '1',
- shouldCastTrue = '2'
-}
-
-type NormalizedProp = PropOptions & {
- [BooleanFlags.shouldCast]?: boolean
- [BooleanFlags.shouldCastTrue]?: boolean
-}
-
-type NormalizedPropsOptions = Record
-
-const normalizationCache = new WeakMap<
- ComponentPropsOptions,
- NormalizedPropsOptions
->()
-
-function normalizePropsOptions(
+export function normalizePropsOptions(
raw: ComponentPropsOptions | void
-): NormalizedPropsOptions {
+): NormalizedPropsOptions | void {
if (!raw) {
- return EMPTY_OBJ
- }
- const hit = normalizationCache.get(raw)
- if (hit) {
- return hit
+ return
}
const normalized: NormalizedPropsOptions = {}
- if (Array.isArray(raw)) {
+ if (isArray(raw)) {
for (let i = 0; i < raw.length; i++) {
- if (__DEV__ && typeof raw !== 'string') {
- warn(`props must be strings when using array syntax.`)
+ if (__DEV__ && !isString(raw[i])) {
+ warn(`props must be strings when using array syntax.`, raw[i])
}
normalized[camelize(raw[i])] = EMPTY_OBJ
}
} else {
- if (__DEV__ && typeof raw !== 'object') {
+ if (__DEV__ && !isObject(raw)) {
warn(`invalid props options`, raw)
}
for (const key in raw) {
const opt = raw[key]
const prop = (normalized[camelize(key)] =
- Array.isArray(opt) || typeof opt === 'function'
- ? { type: opt }
- : opt) as NormalizedProp
+ isArray(opt) || isFunction(opt) ? { type: opt } : opt) as NormalizedProp
const booleanIndex = getTypeIndex(Boolean, prop.type)
const stringIndex = getTypeIndex(String, prop.type)
prop[BooleanFlags.shouldCast] = booleanIndex > -1
prop[BooleanFlags.shouldCastTrue] = booleanIndex < stringIndex
}
}
- normalizationCache.set(raw, normalized)
return normalized
}
@@ -199,13 +195,13 @@ function getTypeIndex(
type: Prop,
expectedTypes: PropType | void | null | true
): number {
- if (Array.isArray(expectedTypes)) {
+ if (isArray(expectedTypes)) {
for (let i = 0, len = expectedTypes.length; i < len; i++) {
if (isSameType(expectedTypes[i], type)) {
return i
}
}
- } else if (expectedTypes != null && typeof expectedTypes === 'object') {
+ } else if (isObject(expectedTypes)) {
return isSameType(expectedTypes, type) ? 0 : -1
}
return -1
@@ -235,7 +231,7 @@ function validateProp(
// type check
if (type != null && type !== true) {
let isValid = false
- const types = Array.isArray(type) ? type : [type]
+ 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++) {
@@ -269,7 +265,7 @@ function assertType(value: any, type: Prop): AssertionResult {
} else if (expectedType === 'Object') {
valid = toRawType(value) === 'Object'
} else if (expectedType === 'Array') {
- valid = Array.isArray(value)
+ valid = isArray(value)
} else {
valid = value instanceof type
}
diff --git a/packages/core/src/componentProxy.ts b/packages/core/src/componentProxy.ts
index 33dc6ff5..ba9945a1 100644
--- a/packages/core/src/componentProxy.ts
+++ b/packages/core/src/componentProxy.ts
@@ -1,4 +1,5 @@
import { ComponentInstance } from './component'
+import { isString, isFunction } from '@vue/shared'
const bindCache = new WeakMap()
@@ -41,7 +42,7 @@ const renderProxyHandlers = {
// TODO warn non-present property
}
const value = Reflect.get(target, key, receiver)
- if (typeof value === 'function') {
+ if (isFunction(value)) {
// auto bind
return getBoundMethod(value, target, receiver)
} else {
@@ -56,7 +57,7 @@ const renderProxyHandlers = {
receiver: any
): boolean {
if (__DEV__) {
- if (typeof key === 'string' && key[0] === '$') {
+ if (isString(key) && key[0] === '$') {
// TODO warn setting immutable properties
return false
}
diff --git a/packages/core/src/componentState.ts b/packages/core/src/componentState.ts
index 931794f4..5c1477ce 100644
--- a/packages/core/src/componentState.ts
+++ b/packages/core/src/componentState.ts
@@ -1,4 +1,3 @@
-// import { EMPTY_OBJ } from './utils'
import { ComponentInstance } from './component'
import { observable } from '@vue/observer'
diff --git a/packages/core/src/componentUtils.ts b/packages/core/src/componentUtils.ts
index b277c822..3eb69f5a 100644
--- a/packages/core/src/componentUtils.ts
+++ b/packages/core/src/componentUtils.ts
@@ -1,5 +1,5 @@
import { VNodeFlags } from './flags'
-import { EMPTY_OBJ } from './utils'
+import { EMPTY_OBJ, isArray, isFunction, isObject } from '@vue/shared'
import { h } from './h'
import { VNode, MountedVNode, createFragment } from './vdom'
import {
@@ -13,7 +13,10 @@ import { initializeState } from './componentState'
import { initializeProps, resolveProps } from './componentProps'
import { initializeComputed, teardownComputed } from './componentComputed'
import { initializeWatch, teardownWatch } from './componentWatch'
-import { ComponentOptions } from './componentOptions'
+import {
+ ComponentOptions,
+ resolveComponentOptionsFromClass
+} from './componentOptions'
import { createRenderProxy } from './componentProxy'
import { handleError, ErrorTypes } from './errorHandling'
import { warn } from './warning'
@@ -58,7 +61,7 @@ export function initializeComponentInstance(instance: ComponentInstance) {
)
}
- instance.$options = resolveComponentOptions(instance.constructor)
+ instance.$options = resolveComponentOptionsFromClass(instance.constructor)
instance.$parentVNode = currentVNode as MountedVNode
// renderProxy
@@ -143,9 +146,9 @@ function normalizeComponentRoot(
): VNode {
if (vnode == null) {
vnode = createTextVNode('')
- } else if (typeof vnode !== 'object') {
+ } else if (!isObject(vnode)) {
vnode = createTextVNode(vnode + '')
- } else if (Array.isArray(vnode)) {
+ } else if (isArray(vnode)) {
if (vnode.length === 1) {
vnode = normalizeComponentRoot(vnode[0], componentVNode)
} else {
@@ -158,13 +161,13 @@ function normalizeComponentRoot(
(flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
) {
if (el) {
- vnode = cloneVNode(vnode)
+ vnode = cloneVNode(vnode as VNode)
}
if (flags & VNodeFlags.COMPONENT) {
vnode.parentVNode = componentVNode
}
} else if (el) {
- vnode = cloneVNode(vnode)
+ vnode = cloneVNode(vnode as VNode)
}
}
return vnode
@@ -209,7 +212,7 @@ export function createComponentClassFromOptions(
// name -> displayName
if (key === 'name') {
options.displayName = options.name
- } else if (typeof value === 'function') {
+ } else if (isFunction(value)) {
// lifecycle hook / data / render
if (__COMPAT__) {
if (key === 'render') {
@@ -229,7 +232,7 @@ export function createComponentClassFromOptions(
} else if (key === 'computed') {
for (const computedKey in value) {
const computed = value[computedKey]
- const isGet = typeof computed === 'function'
+ const isGet = isFunction(computed)
Object.defineProperty(proto, computedKey, {
configurable: true,
get: isGet ? computed : computed.get,
@@ -250,37 +253,3 @@ export function createComponentClassFromOptions(
}
return AnonymousComponent as ComponentClass
}
-
-// This is called in the base component constructor and the return value is
-// set on the instance as $options.
-export function resolveComponentOptions(
- Component: ComponentClass
-): ComponentOptions {
- if (Component.options) {
- return Component.options
- }
- const staticDescriptors = Object.getOwnPropertyDescriptors(Component)
- const options = {} as any
- for (const key in staticDescriptors) {
- const { enumerable, get, value } = staticDescriptors[key]
- if (enumerable || get) {
- options[key] = get ? get() : value
- }
- }
- const instanceDescriptors = Object.getOwnPropertyDescriptors(
- Component.prototype
- )
- for (const key in instanceDescriptors) {
- const { get, value } = instanceDescriptors[key]
- if (get) {
- // computed properties
- ;(options.computed || (options.computed = {}))[key] = get
- // there's no need to do anything for the setter
- // as it's already defined on the prototype
- } else if (typeof value === 'function') {
- ;(options.methods || (options.methods = {}))[key] = value
- }
- }
- Component.options = options
- return options
-}
diff --git a/packages/core/src/componentWatch.ts b/packages/core/src/componentWatch.ts
index 79aa47e7..33ceb025 100644
--- a/packages/core/src/componentWatch.ts
+++ b/packages/core/src/componentWatch.ts
@@ -1,4 +1,11 @@
-import { EMPTY_OBJ, NOOP } from './utils'
+import {
+ EMPTY_OBJ,
+ NOOP,
+ isFunction,
+ isArray,
+ isString,
+ isObject
+} from '@vue/shared'
import { ComponentInstance } from './component'
import { ComponentWatchOptions, WatchOptions } from './componentOptions'
import { autorun, stop } from '@vue/observer'
@@ -13,11 +20,11 @@ export function initializeWatch(
if (options !== void 0) {
for (const key in options) {
const opt = options[key]
- if (Array.isArray(opt)) {
+ if (isArray(opt)) {
opt.forEach(o => setupWatcher(instance, key, o))
- } else if (typeof opt === 'function') {
+ } else if (isFunction(opt)) {
setupWatcher(instance, key, opt)
- } else if (typeof opt === 'string') {
+ } else if (isString(opt)) {
setupWatcher(instance, key, (instance as any)[opt])
} else if (opt.handler) {
setupWatcher(instance, key, opt.handler, opt)
@@ -35,10 +42,9 @@ export function setupWatcher(
const handles = instance._watchHandles || (instance._watchHandles = new Set())
const proxy = instance.$proxy
- const rawGetter =
- typeof keyOrFn === 'string'
- ? parseDotPath(keyOrFn, proxy)
- : () => keyOrFn.call(proxy)
+ const rawGetter = isString(keyOrFn)
+ ? parseDotPath(keyOrFn, proxy)
+ : () => keyOrFn.call(proxy)
if (__DEV__ && rawGetter === NOOP) {
warn(
@@ -116,11 +122,11 @@ function parseDotPath(path: string, ctx: any): Function {
}
function traverse(value: any, seen: Set = new Set()) {
- if (value === null || typeof value !== 'object' || seen.has(value)) {
+ if (!isObject(value) || seen.has(value)) {
return
}
seen.add(value)
- if (Array.isArray(value)) {
+ if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
traverse(value[i], seen)
}
diff --git a/packages/core/src/createRenderer.ts b/packages/core/src/createRenderer.ts
index 0817cf68..8ffffca3 100644
--- a/packages/core/src/createRenderer.ts
+++ b/packages/core/src/createRenderer.ts
@@ -1,7 +1,7 @@
import { autorun, stop } from '@vue/observer'
import { queueJob } from '@vue/scheduler'
import { VNodeFlags, ChildrenFlags } from './flags'
-import { EMPTY_OBJ, reservedPropRE, lis } from './utils'
+import { EMPTY_OBJ, reservedPropRE, isString } from '@vue/shared'
import {
VNode,
MountedVNode,
@@ -297,7 +297,7 @@ export function createRenderer(options: RendererOptions) {
contextVNode: MountedVNode | null
) {
const { tag, children, childFlags, ref } = vnode
- const target = typeof tag === 'string' ? platformQuerySelector(tag) : tag
+ const target = isString(tag) ? platformQuerySelector(tag) : tag
if (__DEV__ && !target) {
// TODO warn poartal target not found
@@ -1410,3 +1410,49 @@ export function createRenderer(options: RendererOptions) {
return { render }
}
+
+// https://en.wikipedia.org/wiki/Longest_increasing_subsequence
+export function lis(arr: number[]): number[] {
+ const p = arr.slice()
+ const result = [0]
+ let i
+ let j
+ let u
+ let v
+ let 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) / 2) | 0
+ 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
+}
diff --git a/packages/core/src/h.ts b/packages/core/src/h.ts
index 9d4bcb97..f7323f31 100644
--- a/packages/core/src/h.ts
+++ b/packages/core/src/h.ts
@@ -14,6 +14,7 @@ import {
} from './vdom'
import { isObservable } from '@vue/observer'
import { warn } from './warning'
+import { isString, isArray, isFunction, isObject } from '@vue/shared'
export const Fragment = Symbol()
export const Portal = Symbol()
@@ -98,10 +99,7 @@ interface createElement extends VNodeFactories {
}
export const h = ((tag: ElementType, data?: any, children?: any): VNode => {
- if (
- Array.isArray(data) ||
- (data != null && (typeof data !== 'object' || data._isVNode))
- ) {
+ if (isArray(data) || !isObject(data) || data._isVNode) {
children = data
data = null
}
@@ -133,7 +131,7 @@ export const h = ((tag: ElementType, data?: any, children?: any): VNode => {
}
}
- if (typeof tag === 'string') {
+ if (isString(tag)) {
// element
return createElementVNode(
tag,
@@ -160,10 +158,7 @@ export const h = ((tag: ElementType, data?: any, children?: any): VNode => {
ref
)
} else {
- if (
- __DEV__ &&
- (!tag || (typeof tag !== 'function' && typeof tag !== 'object'))
- ) {
+ if (__DEV__ && !isFunction(tag) && !isObject(tag)) {
warn('Invalid component passed to h(): ', tag)
}
// component
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 6c2d323c..14b544de 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -10,21 +10,26 @@ export * from '@vue/observer'
// Scheduler API
export { nextTick } from '@vue/scheduler'
-// Internal API
-export {
- createComponentInstance,
- createComponentClassFromOptions
-} from './componentUtils'
-
// Optional APIs
// these are imported on-demand and can be tree-shaken
export { applyDirectives } from './optional/directive'
export { Provide, Inject } from './optional/context'
export { createAsyncComponent } from './optional/asyncComponent'
export { KeepAlive } from './optional/keepAlive'
+export { mixins } from './optional/mixin'
// flags & types
export { ComponentType, ComponentClass, FunctionalComponent } from './component'
-export * from './componentOptions'
export { VNodeFlags, ChildrenFlags } from './flags'
export { VNode, Slots } from './vdom'
+
+// Internal API, for libraries or renderers that need to perform low level work
+export {
+ reservedMethods,
+ resolveComponentOptionsFromClass,
+ mergeComponentOptions
+} from './componentOptions'
+export {
+ createComponentInstance,
+ createComponentClassFromOptions
+} from './componentUtils'
diff --git a/packages/core/src/optional/asyncComponent.ts b/packages/core/src/optional/asyncComponent.ts
index e0a6d0d4..61920571 100644
--- a/packages/core/src/optional/asyncComponent.ts
+++ b/packages/core/src/optional/asyncComponent.ts
@@ -2,6 +2,7 @@ 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
@@ -21,7 +22,7 @@ type AsyncComponentOptions = AsyncComponentFactory | AsyncComponentFullOptions
export function createAsyncComponent(
options: AsyncComponentOptions
): ComponentClass {
- if (typeof options === 'function') {
+ if (isFunction(options)) {
options = { factory: options }
}
diff --git a/packages/core/src/optional/directive.ts b/packages/core/src/optional/directive.ts
index 7ee84bb7..ba09cee6 100644
--- a/packages/core/src/optional/directive.ts
+++ b/packages/core/src/optional/directive.ts
@@ -15,7 +15,7 @@ return applyDirectives(
import { VNode, cloneVNode, VNodeData } from '../vdom'
import { ComponentInstance } from '../component'
-import { EMPTY_OBJ } from '../utils'
+import { EMPTY_OBJ } from '@vue/shared'
interface DirectiveBinding {
instance: ComponentInstance
diff --git a/packages/core/src/optional/keepAlive.ts b/packages/core/src/optional/keepAlive.ts
index 664b1777..fa973928 100644
--- a/packages/core/src/optional/keepAlive.ts
+++ b/packages/core/src/optional/keepAlive.ts
@@ -2,6 +2,7 @@ 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[]
@@ -119,9 +120,9 @@ function getName(comp: ComponentClass): string | void {
}
function matches(pattern: MatchPattern, name: string): boolean {
- if (Array.isArray(pattern)) {
+ if (isArray(pattern)) {
return (pattern as any).some((p: string | RegExp) => matches(p, name))
- } else if (typeof pattern === 'string') {
+ } else if (isString(pattern)) {
return pattern.split(',').indexOf(name) > -1
} else if (pattern.test) {
return pattern.test(name)
diff --git a/packages/core/src/optional/mixin.ts b/packages/core/src/optional/mixin.ts
index 7b56416a..f6de2861 100644
--- a/packages/core/src/optional/mixin.ts
+++ b/packages/core/src/optional/mixin.ts
@@ -1,4 +1,12 @@
import { Component } from '../component'
+import { createComponentClassFromOptions } from '../componentUtils'
+import {
+ ComponentOptions,
+ resolveComponentOptionsFromClass,
+ mergeComponentOptions
+} from '../componentOptions'
+import { normalizePropsOptions } from '../componentProps'
+import { isFunction } from '@vue/shared'
interface ComponentConstructor {
new (): This
@@ -25,7 +33,19 @@ export function mixins<
V = ExtractInstance
>(...args: T): ComponentConstructorWithMixins
export function mixins(...args: any[]): any {
- // TODO
+ let options: ComponentOptions = {}
+ args.forEach(mixin => {
+ if (isFunction(mixin)) {
+ options = mergeComponentOptions(
+ options,
+ resolveComponentOptionsFromClass(mixin)
+ )
+ } else {
+ mixin.props = normalizePropsOptions(mixin.props)
+ options = mergeComponentOptions(options, mixin)
+ }
+ })
+ return createComponentClassFromOptions(options)
}
/* Example usage
diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts
deleted file mode 100644
index 701aa69c..00000000
--- a/packages/core/src/utils.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-export const EMPTY_OBJ: { readonly [key: string]: any } = Object.freeze({})
-
-export const NOOP = () => {}
-
-export const onRE = /^on/
-export const vnodeHookRE = /^vnode/
-export const handlersRE = /^on|^vnode/
-export const reservedPropRE = /^(?:key|ref|slots)$|^vnode/
-
-export function normalizeStyle(
- value: any
-): Record | void {
- if (Array.isArray(value)) {
- const res: Record = {}
- for (let i = 0; i < value.length; i++) {
- const normalized = normalizeStyle(value[i])
- if (normalized) {
- for (const key in normalized) {
- res[key] = normalized[key]
- }
- }
- }
- return res
- } else if (value && typeof value === 'object') {
- return value
- }
-}
-
-export function normalizeClass(value: any): string {
- let res = ''
- if (typeof value === 'string') {
- res = value
- } else if (Array.isArray(value)) {
- for (let i = 0; i < value.length; i++) {
- res += normalizeClass(value[i]) + ' '
- }
- } else if (typeof value === 'object') {
- for (const name in value) {
- if (value[name]) {
- res += name + ' '
- }
- }
- }
- return res.trim()
-}
-
-const camelizeRE = /-(\w)/g
-export const camelize = (str: string): string => {
- return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))
-}
-
-const hyphenateRE = /\B([A-Z])/g
-export const hyphenate = (str: string): string => {
- return str.replace(hyphenateRE, '-$1').toLowerCase()
-}
-
-export const capitalize = (str: string): string => {
- return str.charAt(0).toUpperCase() + str.slice(1)
-}
-
-// https://en.wikipedia.org/wiki/Longest_increasing_subsequence
-export function lis(arr: number[]): number[] {
- const p = arr.slice()
- const result = [0]
- let i
- let j
- let u
- let v
- let 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) / 2) | 0
- 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
-}
diff --git a/packages/core/src/vdom.ts b/packages/core/src/vdom.ts
index 3db116df..dededddd 100644
--- a/packages/core/src/vdom.ts
+++ b/packages/core/src/vdom.ts
@@ -5,7 +5,14 @@ import {
} from './component'
import { VNodeFlags, ChildrenFlags } from './flags'
import { createComponentClassFromOptions } from './componentUtils'
-import { normalizeClass, normalizeStyle, handlersRE, EMPTY_OBJ } from './utils'
+import {
+ handlersRE,
+ EMPTY_OBJ,
+ isObject,
+ isArray,
+ isFunction,
+ isString
+} from '@vue/shared'
import { RawChildrenType, RawSlots } from './h'
// Vue core is platform agnostic, so we are not using Element for "DOM" nodes.
@@ -98,15 +105,6 @@ export function createVNode(
return vnode
}
-function normalizeClassAndStyle(data: VNodeData) {
- if (data.class != null) {
- data.class = normalizeClass(data.class)
- }
- if (data.style != null) {
- data.style = normalizeStyle(data.style)
- }
-}
-
export function createElementVNode(
tag: string,
data: VNodeData | null,
@@ -122,6 +120,50 @@ export function createElementVNode(
return createVNode(flags, tag, data, children, childFlags, key, ref, null)
}
+function normalizeClassAndStyle(data: VNodeData) {
+ if (data.class != null) {
+ data.class = normalizeClass(data.class)
+ }
+ if (data.style != null) {
+ data.style = normalizeStyle(data.style)
+ }
+}
+
+function normalizeStyle(value: any): Record | void {
+ if (isArray(value)) {
+ const res: Record = {}
+ for (let i = 0; i < value.length; i++) {
+ const normalized = normalizeStyle(value[i])
+ if (normalized) {
+ for (const key in normalized) {
+ res[key] = normalized[key]
+ }
+ }
+ }
+ return res
+ } else if (isObject(value)) {
+ return value
+ }
+}
+
+function normalizeClass(value: any): string {
+ let res = ''
+ if (isString(value)) {
+ res = value
+ } else if (isArray(value)) {
+ for (let i = 0; i < value.length; i++) {
+ res += normalizeClass(value[i]) + ' '
+ }
+ } else if (isObject(value)) {
+ for (const name in value) {
+ if (value[name]) {
+ res += name + ' '
+ }
+ }
+ }
+ return res.trim()
+}
+
export function createComponentVNode(
comp: any,
data: VNodeData | null,
@@ -134,8 +176,7 @@ export function createComponentVNode(
let flags: VNodeFlags
// flags
- const compType = typeof comp
- if (compType === 'object') {
+ if (isObject(comp)) {
if (comp.functional) {
// object literal functional
flags = VNodeFlags.COMPONENT_FUNCTIONAL
@@ -155,7 +196,7 @@ export function createComponentVNode(
}
} else {
// assumes comp is function here now
- if (__DEV__ && compType !== 'function') {
+ if (__DEV__ && !isFunction(comp)) {
// TODO warn invalid comp value in dev
}
if (comp.prototype && comp.prototype.render) {
@@ -178,14 +219,13 @@ export function createComponentVNode(
? ChildrenFlags.DYNAMIC_SLOTS
: ChildrenFlags.NO_CHILDREN
if (children != null) {
- const childrenType = typeof children
- if (childrenType === 'function') {
+ if (isFunction(children)) {
// function as children
slots = { default: children }
- } else if (Array.isArray(children) || (children as any)._isVNode) {
+ } else if (isArray(children) || (children as any)._isVNode) {
// direct vnode children
slots = { default: () => children }
- } else if (typeof children === 'object') {
+ } else if (isObject(children)) {
// slot object as children
slots = children
}
@@ -313,7 +353,7 @@ export function cloneVNode(vnode: VNode, extraData?: VNodeData): VNode {
function normalizeChildren(vnode: VNode, children: any) {
let childFlags
- if (Array.isArray(children)) {
+ if (isArray(children)) {
const { length } = children
if (length === 0) {
childFlags = ChildrenFlags.NO_CHILDREN
@@ -356,7 +396,7 @@ export function normalizeVNodes(
newChild = createTextVNode('')
} else if (child._isVNode) {
newChild = child.el ? cloneVNode(child) : child
- } else if (Array.isArray(child)) {
+ } else if (isArray(child)) {
normalizeVNodes(child, newChildren, currentPrefix + i + '|')
} else {
newChild = createTextVNode(child + '')
@@ -386,7 +426,7 @@ function normalizeSlots(slots: { [name: string]: any }): Slots {
function normalizeSlot(value: any): VNode[] {
if (value == null) {
return [createTextVNode('')]
- } else if (Array.isArray(value)) {
+ } else if (isArray(value)) {
return normalizeVNodes(value)
} else if (value._isVNode) {
return [value]
diff --git a/packages/core/src/warning.ts b/packages/core/src/warning.ts
index e7d1a814..7c5e73a3 100644
--- a/packages/core/src/warning.ts
+++ b/packages/core/src/warning.ts
@@ -1,5 +1,5 @@
import { ComponentType, ComponentClass, FunctionalComponent } from './component'
-import { EMPTY_OBJ } from './utils'
+import { EMPTY_OBJ, isString } from '@vue/shared'
import { VNode } from './vdom'
import { Data } from './componentOptions'
@@ -119,7 +119,7 @@ function formatProps(props: Data) {
const res = []
for (const key in props) {
const value = props[key]
- if (typeof value === 'string') {
+ if (isString(value)) {
res.push(`${key}=${JSON.stringify(value)}`)
} else {
res.push(`${key}=`, value)
diff --git a/packages/observer/src/baseHandlers.ts b/packages/observer/src/baseHandlers.ts
index beb47e8e..0be06e3a 100644
--- a/packages/observer/src/baseHandlers.ts
+++ b/packages/observer/src/baseHandlers.ts
@@ -2,6 +2,7 @@ import { observable, immutable, unwrap } from './index'
import { OperationTypes } from './operations'
import { track, trigger } from './autorun'
import { LOCKED } from './lock'
+import { isObject } from '@vue/shared'
const hasOwnProperty = Object.prototype.hasOwnProperty
@@ -18,7 +19,7 @@ function createGetter(isImmutable: boolean) {
return res
}
track(target, OperationTypes.GET, key)
- return res !== null && typeof res === 'object'
+ return isObject(res)
? isImmutable
? // need to lazy access immutable and observable here to avoid
// circular dependency
diff --git a/packages/observer/src/collectionHandlers.ts b/packages/observer/src/collectionHandlers.ts
index 4f0531d7..8fd40a94 100644
--- a/packages/observer/src/collectionHandlers.ts
+++ b/packages/observer/src/collectionHandlers.ts
@@ -2,8 +2,8 @@ import { unwrap, observable, immutable } from './index'
import { track, trigger } from './autorun'
import { OperationTypes } from './operations'
import { LOCKED } from './lock'
+import { isObject } from '@vue/shared'
-const isObject = (value: any) => value !== null && typeof value === 'object'
const toObservable = (value: any) =>
isObject(value) ? observable(value) : value
const toImmutable = (value: any) => (isObject(value) ? immutable(value) : value)
diff --git a/packages/observer/src/index.ts b/packages/observer/src/index.ts
index 7f8a9f5a..836731ed 100644
--- a/packages/observer/src/index.ts
+++ b/packages/observer/src/index.ts
@@ -1,3 +1,4 @@
+import { isObject, EMPTY_OBJ } from '@vue/shared'
import { mutableHandlers, immutableHandlers } from './baseHandlers'
import {
@@ -28,7 +29,6 @@ export { OperationTypes } from './operations'
export { computed, ComputedGetter } from './computed'
export { lock, unlock } from './lock'
-const EMPTY_OBJ = {}
const collectionTypes: Set = new Set([Set, Map, WeakMap, WeakSet])
const observableValueRE = /^\[object (?:Object|Array|Map|Set|WeakMap|WeakSet)\]$/
@@ -83,7 +83,7 @@ function createObservable(
baseHandlers: ProxyHandler,
collectionHandlers: ProxyHandler
) {
- if (target === null || typeof target !== 'object') {
+ if (!isObject(target)) {
if (__DEV__) {
console.warn(`value is not observable: ${String(target)}`)
}
diff --git a/packages/renderer-dom/src/index.ts b/packages/renderer-dom/src/index.ts
index b9162429..4ddcd3c3 100644
--- a/packages/renderer-dom/src/index.ts
+++ b/packages/renderer-dom/src/index.ts
@@ -1,4 +1,4 @@
-import { createRenderer, VNode, ComponentInstance } from '@vue/core'
+import { createRenderer, VNode, Component } from '@vue/core'
import { nodeOps } from './nodeOps'
import { patchData } from './patchData'
import { teardownVNode } from './teardownVNode'
@@ -12,7 +12,7 @@ const { render: _render } = createRenderer({
type publicRender = (
node: VNode | null,
container: HTMLElement
-) => ComponentInstance | null
+) => Component | null
export const render = _render as publicRender
// re-export everything from core
diff --git a/packages/renderer-dom/src/modules/style.ts b/packages/renderer-dom/src/modules/style.ts
index f347064b..e5c3e0ee 100644
--- a/packages/renderer-dom/src/modules/style.ts
+++ b/packages/renderer-dom/src/modules/style.ts
@@ -1,3 +1,5 @@
+import { isString } from '@vue/shared'
+
// style properties that should NOT have "px" added when numeric
const nonNumericRE = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i
@@ -5,7 +7,7 @@ export function patchStyle(el: any, prev: any, next: any, data: any) {
const { style } = el
if (!next) {
el.removeAttribute('style')
- } else if (typeof next === 'string') {
+ } else if (isString(next)) {
style.cssText = next
} else {
for (const key in next) {
@@ -15,7 +17,7 @@ export function patchStyle(el: any, prev: any, next: any, data: any) {
}
style[key] = value
}
- if (prev && typeof prev !== 'string') {
+ if (prev && !isString(prev)) {
for (const key in prev) {
if (!next[key]) {
style[key] = ''
diff --git a/packages/renderer-test/src/index.ts b/packages/renderer-test/src/index.ts
index ba406389..fda7490c 100644
--- a/packages/renderer-test/src/index.ts
+++ b/packages/renderer-test/src/index.ts
@@ -1,4 +1,4 @@
-import { createRenderer, VNode, ComponentInstance } from '@vue/core'
+import { createRenderer, VNode, Component } from '@vue/core'
import { nodeOps, TestElement } from './nodeOps'
import { patchData } from './patchData'
@@ -10,7 +10,7 @@ const { render: _render } = createRenderer({
type publicRender = (
node: VNode | null,
container: TestElement
-) => ComponentInstance | null
+) => Component | null
export const render = _render as publicRender
export { serialize } from './serialize'
diff --git a/packages/shared/README.md b/packages/shared/README.md
new file mode 100644
index 00000000..6f4b137f
--- /dev/null
+++ b/packages/shared/README.md
@@ -0,0 +1 @@
+# @vue/shared
\ No newline at end of file
diff --git a/packages/shared/package.json b/packages/shared/package.json
new file mode 100644
index 00000000..53668013
--- /dev/null
+++ b/packages/shared/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "@vue/shared",
+ "private": true
+}
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
new file mode 100644
index 00000000..798f0961
--- /dev/null
+++ b/packages/shared/src/index.ts
@@ -0,0 +1,29 @@
+export const EMPTY_OBJ: { readonly [key: string]: any } = Object.freeze({})
+
+export const NOOP = () => {}
+
+export const onRE = /^on/
+export const vnodeHookRE = /^vnode/
+export const handlersRE = /^on|^vnode/
+export const reservedPropRE = /^(?:key|ref|slots)$|^vnode/
+
+export const isArray = Array.isArray
+export const isFunction = (val: any): val is Function =>
+ typeof val === 'function'
+export const isString = (val: any): val is string => typeof val === 'string'
+export const isObject = (val: any): val is Record =>
+ val !== null && typeof val === 'object'
+
+const camelizeRE = /-(\w)/g
+export const camelize = (str: string): string => {
+ return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))
+}
+
+const hyphenateRE = /\B([A-Z])/g
+export const hyphenate = (str: string): string => {
+ return str.replace(hyphenateRE, '-$1').toLowerCase()
+}
+
+export const capitalize = (str: string): string => {
+ return str.charAt(0).toUpperCase() + str.slice(1)
+}
diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts
index 7bcae758..6ae92e1e 100644
--- a/packages/vue/src/index.ts
+++ b/packages/vue/src/index.ts
@@ -16,7 +16,7 @@ class Vue {
// convert it to a class
const Component = createComponentClassFromOptions(options || {})
const vnode = h(Component)
- const instance = createComponentInstance(vnode, Component, null)
+ const instance = createComponentInstance(vnode, Component)
function mount(el: any) {
const dom = typeof el === 'string' ? document.querySelector(el) : el
diff --git a/rollup.config.js b/rollup.config.js
index a7dd1924..4a31cbb8 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -95,12 +95,13 @@ function createConfig(output, plugins = []) {
// during a single build.
hasTSChecked = true
+ const externals = Object.keys(aliasOptions).filter(p => p !== '@vue/shared')
+
return {
input: resolve(`src/index.ts`),
// Global and Browser ESM builds inlines everything so that they can be
// used alone.
- external:
- isGlobalBuild || isBrowserESMBuild ? [] : Object.keys(aliasOptions),
+ external: isGlobalBuild || isBrowserESMBuild ? [] : externals,
plugins: [
tsPlugin,
aliasPlugin,
diff --git a/scripts/utils.js b/scripts/utils.js
index aa1ca3db..627ed740 100644
--- a/scripts/utils.js
+++ b/scripts/utils.js
@@ -1,8 +1,8 @@
const fs = require('fs')
-const targets = exports.targets = fs.readdirSync('packages').filter(f => {
- return fs.statSync(`packages/${f}`).isDirectory()
-})
+const targets = (exports.targets = fs.readdirSync('packages').filter(f => {
+ return f !== 'shared' && fs.statSync(`packages/${f}`).isDirectory()
+}))
exports.fuzzyMatchTarget = partialTarget => {
const matched = []
diff --git a/tsconfig.json b/tsconfig.json
index 42ef9176..1815a49f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -19,6 +19,7 @@
],
"rootDir": ".",
"paths": {
+ "@vue/shared": ["packages/shared/src"],
"@vue/core": ["packages/core/src"],
"@vue/observer": ["packages/observer/src"],
"@vue/scheduler": ["packages/scheduler/src"],