diff --git a/packages/runtime-core/__tests__/vdomComponent.spec.ts b/packages/runtime-core/__tests__/vdomComponent.spec.ts index e69de29b..70b786d1 100644 --- a/packages/runtime-core/__tests__/vdomComponent.spec.ts +++ b/packages/runtime-core/__tests__/vdomComponent.spec.ts @@ -0,0 +1 @@ +// TODO diff --git a/packages/runtime-core/__tests__/vdomOptimizedMode.spec.ts b/packages/runtime-core/__tests__/vdomOptimizedMode.spec.ts index e69de29b..70b786d1 100644 --- a/packages/runtime-core/__tests__/vdomOptimizedMode.spec.ts +++ b/packages/runtime-core/__tests__/vdomOptimizedMode.spec.ts @@ -0,0 +1 @@ +// TODO diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index b4008d7e..9353681c 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -3,9 +3,9 @@ import { ReactiveEffect, UnwrapRef, reactive, immutable } from '@vue/reactivity' import { EMPTY_OBJ, isFunction, capitalize, invokeHandlers } from '@vue/shared' import { RenderProxyHandlers } from './componentProxy' import { ComponentPropsOptions, ExtractPropTypes } from './componentProps' -import { PROPS, DYNAMIC_SLOTS, FULL_PROPS } from './patchFlags' import { Slots } from './componentSlots' -import { STATEFUL_COMPONENT } from './typeFlags' +import { PatchFlags } from './patchFlags' +import { ShapeFlags } from './shapeFlags' export type Data = { [key: string]: unknown } @@ -302,7 +302,7 @@ export function renderComponentRoot(instance: ComponentInstance): VNode { parent, root } = instance - if (vnode.shapeFlag & STATEFUL_COMPONENT) { + if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { return normalizeVNode( (instance.render as RenderFunction).call(renderProxy, props, setupContext) ) @@ -332,15 +332,15 @@ export function shouldUpdateComponent( const { props: prevProps, children: prevChildren } = prevVNode const { props: nextProps, children: nextChildren, patchFlag } = nextVNode if (patchFlag) { - if (patchFlag & DYNAMIC_SLOTS) { + if (patchFlag & PatchFlags.DYNAMIC_SLOTS) { // slot content that references values that might have changed, // e.g. in a v-for return true } - if (patchFlag & FULL_PROPS) { + if (patchFlag & PatchFlags.FULL_PROPS) { // presence of this flag indicates props are always non-null return hasPropsChanged(prevProps as Data, nextProps as Data) - } else if (patchFlag & PROPS) { + } else if (patchFlag & PatchFlags.PROPS) { const dynamicProps = nextVNode.dynamicProps as string[] for (let i = 0; i < dynamicProps.length; i++) { const key = dynamicProps[i] diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index cb8ebb2c..1ef1f3d9 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -12,7 +12,7 @@ import { } from '@vue/shared' import { warn } from './warning' import { Data, ComponentInstance } from './component' -import { FULL_PROPS } from './patchFlags' +import { PatchFlags } from './patchFlags' export type ComponentPropsOptions

= { [K in keyof P]: Prop | null @@ -167,7 +167,10 @@ export function resolveProps( // in case of dynamic props, check if we need to delete keys from // the props proxy const { patchFlag } = instance.vnode - if (propsProxy !== null && (patchFlag === 0 || patchFlag & FULL_PROPS)) { + if ( + propsProxy !== null && + (patchFlag === 0 || patchFlag & PatchFlags.FULL_PROPS) + ) { const rawInitialProps = toRaw(propsProxy) for (const key in rawInitialProps) { if (!props.hasOwnProperty(key)) { diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts index 32f71e7b..377832b4 100644 --- a/packages/runtime-core/src/componentSlots.ts +++ b/packages/runtime-core/src/componentSlots.ts @@ -1,7 +1,7 @@ import { ComponentInstance } from './component' import { VNode, NormalizedChildren, normalizeVNode, VNodeChild } from './vnode' import { isArray, isFunction } from '@vue/shared' -import { SLOTS_CHILDREN } from './typeFlags' +import { ShapeFlags } from './shapeFlags' export type Slot = (...args: any[]) => VNode[] export type Slots = Readonly<{ @@ -24,7 +24,7 @@ export function resolveSlots( children: NormalizedChildren ) { let slots: Slots | void - if (instance.vnode.shapeFlag & SLOTS_CHILDREN) { + if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { if ((children as any)._normalized) { // pre-normalized slots object generated by compiler slots = children as Slots diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index 565e5728..86414829 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -21,15 +21,6 @@ import { isReservedProp, isFunction } from '@vue/shared' -import { - TEXT, - CLASS, - STYLE, - PROPS, - KEYED, - UNKEYED, - FULL_PROPS -} from './patchFlags' import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler' import { effect, @@ -41,13 +32,8 @@ import { } from '@vue/reactivity' import { resolveProps } from './componentProps' import { resolveSlots } from './componentSlots' -import { - ELEMENT, - STATEFUL_COMPONENT, - FUNCTIONAL_COMPONENT, - TEXT_CHILDREN, - ARRAY_CHILDREN -} from './typeFlags' +import { PatchFlags } from './patchFlags' +import { ShapeFlags } from './shapeFlags' const prodEffectOptions = { scheduler: queueJob @@ -167,7 +153,7 @@ export function createRenderer(options: RendererOptions) { ) break default: - if (shapeFlag & ELEMENT) { + if (shapeFlag & ShapeFlags.ELEMENT) { processElement( n1, n2, @@ -180,8 +166,8 @@ export function createRenderer(options: RendererOptions) { } else { if ( __DEV__ && - !(shapeFlag & STATEFUL_COMPONENT) && - !(shapeFlag & FUNCTIONAL_COMPONENT) + !(shapeFlag & ShapeFlags.STATEFUL_COMPONENT) && + !(shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT) ) { // TODO warn invalid node type debugger @@ -269,9 +255,9 @@ export function createRenderer(options: RendererOptions) { hostPatchProp(el, key, props[key], null, isSVG) } } - if (shapeFlag & TEXT_CHILDREN) { + if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { hostSetElementText(el, vnode.children as string) - } else if (shapeFlag & ARRAY_CHILDREN) { + } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { mountChildren( vnode.children as VNodeChildren, el, @@ -315,13 +301,13 @@ export function createRenderer(options: RendererOptions) { // in this path old node and new node are guaranteed to have the same shape // (i.e. at the exact same position in the source template) - if (patchFlag & FULL_PROPS) { + if (patchFlag & PatchFlags.FULL_PROPS) { // element props contain dynamic keys, full diff needed patchProps(el, n2, oldProps, newProps, parentComponent, isSVG) } else { // class // this flag is matched when the element has dynamic class bindings. - if (patchFlag & CLASS) { + if (patchFlag & PatchFlags.CLASS) { if (oldProps.class !== newProps.class) { hostPatchProp(el, 'class', newProps.class, null, isSVG) } @@ -329,7 +315,7 @@ export function createRenderer(options: RendererOptions) { // style // this flag is matched when the element has dynamic style bindings - if (patchFlag & STYLE) { + if (patchFlag & PatchFlags.STYLE) { hostPatchProp(el, 'style', newProps.style, oldProps.style, isSVG) } @@ -339,7 +325,7 @@ export function createRenderer(options: RendererOptions) { // faster iteration. // Note dynamic keys like :[foo]="bar" will cause this optimization to // bail out and go through a full diff because we need to unset the old key - if (patchFlag & PROPS) { + if (patchFlag & PatchFlags.PROPS) { // if the flag is present then dynamicProps must be non-null const propsToUpdate = n2.dynamicProps as string[] for (let i = 0; i < propsToUpdate.length; i++) { @@ -365,7 +351,7 @@ export function createRenderer(options: RendererOptions) { // text // This flag is matched when the element has only dynamic text children. // this flag is terminal (i.e. skips children diffing). - if (patchFlag & TEXT) { + if (patchFlag & PatchFlags.TEXT) { if (n1.children !== n2.children) { hostSetElementText(el, n2.children as string) } @@ -495,9 +481,9 @@ export function createRenderer(options: RendererOptions) { ? hostQuerySelector(targetSelector) : null) if (target != null) { - if (shapeFlag & TEXT_CHILDREN) { + if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { hostSetElementText(target, children as string) - } else if (shapeFlag & ARRAY_CHILDREN) { + } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { mountChildren( children as VNodeChildren, target, @@ -512,7 +498,7 @@ export function createRenderer(options: RendererOptions) { } else { // update content const target = (n2.target = n1.target) - if (patchFlag === TEXT) { + if (patchFlag === PatchFlags.TEXT) { hostSetElementText(target, children as string) } else if (!optimized) { patchChildren(n1, n2, target, null, parentComponent, isSVG) @@ -524,10 +510,10 @@ export function createRenderer(options: RendererOptions) { : null) if (nextTarget != null) { // move content - if (shapeFlag & TEXT_CHILDREN) { + if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { hostSetElementText(target, '') hostSetElementText(nextTarget, children as string) - } else if (shapeFlag & ARRAY_CHILDREN) { + } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { for (let i = 0; i < (children as VNode[]).length; i++) { move((children as VNode[])[i], nextTarget, null) } @@ -591,7 +577,7 @@ export function createRenderer(options: RendererOptions) { resolveProps(instance, initialVNode.props, Component.props) resolveSlots(instance, initialVNode.children) // setup stateful - if (initialVNode.shapeFlag & STATEFUL_COMPONENT) { + if (initialVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { setupStatefulComponent(instance) } const subTree = (instance.subTree = renderComponentRoot(instance)) @@ -675,7 +661,7 @@ export function createRenderer(options: RendererOptions) { // fast path const { patchFlag, shapeFlag } = n2 if (patchFlag) { - if (patchFlag & KEYED) { + if (patchFlag & PatchFlags.KEYED) { // this could be either fully-keyed or mixed (some keyed some not) // presence of patchFlag means children are guaranteed to be arrays patchKeyedChildren( @@ -688,7 +674,7 @@ export function createRenderer(options: RendererOptions) { optimized ) return - } else if (patchFlag & UNKEYED) { + } else if (patchFlag & PatchFlags.UNKEYED) { // unkeyed patchUnkeyedChildren( c1 as VNode[], @@ -703,16 +689,16 @@ export function createRenderer(options: RendererOptions) { } } - if (shapeFlag & TEXT_CHILDREN) { + if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { // text children fast path - if (prevShapeFlag & ARRAY_CHILDREN) { + if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { unmountChildren(c1 as VNode[], parentComponent) } hostSetElementText(container, c2 as string) } else { - if (prevShapeFlag & TEXT_CHILDREN) { + if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) { hostSetElementText(container, '') - if (shapeFlag & ARRAY_CHILDREN) { + if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { mountChildren( c2 as VNodeChildren, container, @@ -721,8 +707,8 @@ export function createRenderer(options: RendererOptions) { isSVG ) } - } else if (prevShapeFlag & ARRAY_CHILDREN) { - if (shapeFlag & ARRAY_CHILDREN) { + } else if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) { + if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { // two arrays, cannot assume anything, do full diff patchKeyedChildren( c1 as VNode[], @@ -1020,7 +1006,7 @@ export function createRenderer(options: RendererOptions) { parentComponent, shouldRemoveChildren ) - } else if (vnode.shapeFlag & ARRAY_CHILDREN) { + } else if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) { unmountChildren( vnode.children as VNode[], parentComponent, diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index ee92001a..4a30c80a 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -1,5 +1,12 @@ +// Types +export { VNode } from './vnode' +export { FunctionalComponent } from './component' +export { RendererOptions } from './createRenderer' +export { Slot, Slots } from './componentSlots' +export { PropType, ComponentPropsOptions } from './componentProps' + +// API export { - VNode, openBlock, createBlock, createVNode, @@ -8,20 +15,14 @@ export { Fragment, Portal } from './vnode' - +export { createComponent, getCurrentInstance } from './component' +export { createRenderer } from './createRenderer' export { nextTick } from './scheduler' -export { - createComponent, - getCurrentInstance, - FunctionalComponent -} from './component' -export { createRenderer, RendererOptions } from './createRenderer' -export { Slot, Slots } from './componentSlots' -export { PropType, ComponentPropsOptions } from './componentProps' - export * from './apiReactivity' export * from './apiWatch' export * from './apiLifecycle' export * from './apiInject' -export * from './patchFlags' -export * from './typeFlags' + +// Flags +export { PublicPatchFlags as PatchFlags } from './patchFlags' +export { PublicShapeFlags as ShapeFlags } from './shapeFlags' diff --git a/packages/runtime-core/src/patchFlags.ts b/packages/runtime-core/src/patchFlags.ts index 119111f1..279a0019 100644 --- a/packages/runtime-core/src/patchFlags.ts +++ b/packages/runtime-core/src/patchFlags.ts @@ -13,48 +13,63 @@ // Check the `patchElement` function in './createRednerer.ts' to see how the // flags are handled during diff. -// Indicates an element with dynamic textContent (children fast path) -export const TEXT = 1 +export const enum PatchFlags { + // Indicates an element with dynamic textContent (children fast path) + TEXT = 1, -// Indicates an element with dynamic class. -// The compiler also pre-normalizes the :class binding: -// - b -> normalize(b) -// - ['foo', b] -> 'foo' + normalize(b) -// - { a, b: c } -> (a ? a : '') + (b ? c : '') -// - ['a', b, { c }] -> 'a' + normalize(b) + (c ? c : '') -export const CLASS = 1 << 1 + // Indicates an element with dynamic class. + // The compiler also pre-normalizes the :class binding: + // - b -> normalize(b) + // - ['foo', b] -> 'foo' + normalize(b) + // - { a, b: c } -> (a ? a : '') + (b ? c : '') + // - ['a', b, { c }] -> 'a' + normalize(b) + (c ? c : '') + CLASS = 1 << 1, -// Indicates an element with dynamic style -// The compiler pre-compiles static string styles into static objects -// + detects and hoists inline static objects -// e.g. style="color: red" and :style="{ color: 'red' }" both get hoisted as -// const style = { color: 'red' } -// render() { return e('div', { style }) } -export const STYLE = 1 << 2 + // Indicates an element with dynamic style + // The compiler pre-compiles static string styles into static objects + // + detects and hoists inline static objects + // e.g. style="color: red" and :style="{ color: 'red' }" both get hoisted as + // const style = { color: 'red' } + // render() { return e('div', { style }) } + STYLE = 1 << 2, -// Indicates an element that has non-class/style dynamic props. -// Can also be on a component that has any dynamic props (includes class/style). -// when this flag is present, the vnode also has a dynamicProps array that -// contains the keys of the props that may change so the runtime can diff -// them faster (without having to worry about removed props) -export const PROPS = 1 << 3 + // Indicates an element that has non-class/style dynamic props. + // Can also be on a component that has any dynamic props (includes + // class/style). when this flag is present, the vnode also has a dynamicProps + // array that contains the keys of the props that may change so the runtime + // can diff them faster (without having to worry about removed props) + PROPS = 1 << 3, -// Indicates an element with props with dynamic keys. When keys change, a full -// diff is always needed to remove the old key. This flag is mutually exclusive -// with CLASS, STYLE and PROPS. -export const FULL_PROPS = 1 << 4 + // Indicates an element with props with dynamic keys. When keys change, a full + // diff is always needed to remove the old key. This flag is mutually + // exclusive with CLASS, STYLE and PROPS. + FULL_PROPS = 1 << 4, -// Indicates a fragment or element with keyed or partially-keyed v-for children -export const KEYED = 1 << 5 + // Indicates a fragment or element with keyed or partially-keyed v-for + // children + KEYED = 1 << 5, -// Indicates a fragment or element that contains unkeyed v-for children -export const UNKEYED = 1 << 6 + // Indicates a fragment or element that contains unkeyed v-for children + UNKEYED = 1 << 6, -// Indicates a component with dynamic slots (e.g. slot that references a v-for -// iterated value, or dynamic slot names). -// Components with this flag are always force updated. -export const DYNAMIC_SLOTS = 1 << 7 + // Indicates a component with dynamic slots (e.g. slot that references a v-for + // iterated value, or dynamic slot names). + // Components with this flag are always force updated. + DYNAMIC_SLOTS = 1 << 7, -// Indicates an element with ref. This includes static string refs because the -// refs object is refreshed on each update and all refs need to set again. -export const REF = 1 << 8 + // Indicates an element with ref. This includes static string refs because the + // refs object is refreshed on each update and all refs need to set again. + REF = 1 << 8 +} + +// runtime object for public consumption +export const PublicPatchFlags = { + TEXT: PatchFlags.TEXT, + CLASS: PatchFlags.CLASS, + STYLE: PatchFlags.STYLE, + PROPS: PatchFlags.PROPS, + FULL_PROPS: PatchFlags.FULL_PROPS, + KEYED: PatchFlags.KEYED, + UNKEYED: PatchFlags.UNKEYED, + REF: PatchFlags.REF +} diff --git a/packages/runtime-core/src/shapeFlags.ts b/packages/runtime-core/src/shapeFlags.ts new file mode 100644 index 00000000..173aa905 --- /dev/null +++ b/packages/runtime-core/src/shapeFlags.ts @@ -0,0 +1,20 @@ +// internally the const enum flags are used to avoid overhead of property +// access +export const enum ShapeFlags { + ELEMENT = 1, + FUNCTIONAL_COMPONENT = 1 << 1, + STATEFUL_COMPONENT = 1 << 2, + TEXT_CHILDREN = 1 << 3, + ARRAY_CHILDREN = 1 << 4, + SLOTS_CHILDREN = 1 << 5 +} + +// but the flags are also exported as an actual object for external use +export const PublicShapeFlags = { + ELEMENT: ShapeFlags.ELEMENT, + FUNCTIONAL_COMPONENT: ShapeFlags.FUNCTIONAL_COMPONENT, + STATEFUL_COMPONENT: ShapeFlags.STATEFUL_COMPONENT, + TEXT_CHILDREN: ShapeFlags.TEXT_CHILDREN, + ARRAY_CHILDREN: ShapeFlags.ARRAY_CHILDREN, + SLOTS_CHILDREN: ShapeFlags.SLOTS_CHILDREN +} diff --git a/packages/runtime-core/src/typeFlags.ts b/packages/runtime-core/src/typeFlags.ts deleted file mode 100644 index 14a19311..00000000 --- a/packages/runtime-core/src/typeFlags.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const ELEMENT = 1 -export const FUNCTIONAL_COMPONENT = 1 << 1 -export const STATEFUL_COMPONENT = 1 << 2 -export const TEXT_CHILDREN = 1 << 3 -export const ARRAY_CHILDREN = 1 << 4 -export const SLOTS_CHILDREN = 1 << 5 diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 862bded0..0a07918c 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -1,16 +1,9 @@ import { isArray, isFunction, isString, isObject, EMPTY_ARR } from '@vue/shared' -import { ComponentInstance } from './component' +import { ComponentInstance, Data } from './component' import { HostNode } from './createRenderer' import { RawSlots } from './componentSlots' -import { CLASS } from './patchFlags' -import { - ELEMENT, - FUNCTIONAL_COMPONENT, - STATEFUL_COMPONENT, - TEXT_CHILDREN, - ARRAY_CHILDREN, - SLOTS_CHILDREN -} from './typeFlags' +import { PatchFlags } from './patchFlags' +import { ShapeFlags } from './shapeFlags' export const Fragment = Symbol('Fragment') export const Text = Symbol('Text') @@ -108,12 +101,12 @@ export function createVNode( props = props || null // encode the vnode type information into a bitmap - const typeFlag = isString(type) - ? ELEMENT + const shapeFlag = isString(type) + ? ShapeFlags.ELEMENT : isObject(type) - ? STATEFUL_COMPONENT + ? ShapeFlags.STATEFUL_COMPONENT : isFunction(type) - ? FUNCTIONAL_COMPONENT + ? ShapeFlags.FUNCTIONAL_COMPONENT : 0 const vnode: VNode = { @@ -126,7 +119,7 @@ export function createVNode( el: null, anchor: null, target: null, - shapeFlag: typeFlag, + shapeFlag, patchFlag, dynamicProps, dynamicChildren: null @@ -138,7 +131,7 @@ export function createVNode( if (props !== null) { // class normalization only needed if the vnode isn't generated by // compiler-optimized code - if (props.class != null && !(patchFlag & CLASS)) { + if (props.class != null && !(patchFlag & PatchFlags.CLASS)) { props.class = normalizeClass(props.class) } if (props.style != null) { @@ -153,8 +146,8 @@ export function createVNode( if ( shouldTrack && (patchFlag || - typeFlag & STATEFUL_COMPONENT || - typeFlag & FUNCTIONAL_COMPONENT) + shapeFlag & ShapeFlags.STATEFUL_COMPONENT || + shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT) ) { trackDynamicNode(vnode) } @@ -169,7 +162,7 @@ function trackDynamicNode(vnode: VNode) { } } -export function cloneVNode(vnode: VNode): VNode { +export function cloneVNode(vnode: VNode, extraProps?: Data): VNode { // TODO return vnode } @@ -196,15 +189,15 @@ export function normalizeChildren(vnode: VNode, children: unknown) { if (children == null) { children = null } else if (isArray(children)) { - type = ARRAY_CHILDREN + type = ShapeFlags.ARRAY_CHILDREN } else if (typeof children === 'object') { - type = SLOTS_CHILDREN + type = ShapeFlags.SLOTS_CHILDREN } else if (isFunction(children)) { children = { default: children } - type = SLOTS_CHILDREN + type = ShapeFlags.SLOTS_CHILDREN } else { children = isString(children) ? children : children + '' - type = TEXT_CHILDREN + type = ShapeFlags.TEXT_CHILDREN } vnode.children = children as NormalizedChildren vnode.shapeFlag |= type