]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: add types to refactored runtime-core
authorEvan You <yyx990803@gmail.com>
Sun, 26 May 2019 07:19:44 +0000 (15:19 +0800)
committerEvan You <yyx990803@gmail.com>
Sun, 26 May 2019 07:19:44 +0000 (15:19 +0800)
packages/runtime-core/src/createRenderer.ts
packages/runtime-core/src/h.ts
packages/runtime-core/src/index.ts
packages/runtime-dom/src/index.ts
packages/runtime-dom/src/modules/props.ts
packages/runtime-dom/src/modules/style.ts
packages/runtime-dom/src/nodeOps.ts [deleted file]
packages/runtime-dom/src/patchProp.ts [moved from packages/runtime-dom/src/patchData.ts with 61% similarity]
packages/runtime-dom/src/rendererOptions.ts [new file with mode: 0644]

index e09e8ce35ae75a849f5ebd945a71a080bdc92f37..74ddf2b3a612fbefc889a8a45658c9ef938a2938 100644 (file)
@@ -1,21 +1,54 @@
 // TODO:
-// - app context
 // - component
 // - lifecycle
+// - app context
+// - svg
 // - refs
-// - reused nodes
 // - hydration
+// - warning context
+// - parent chain
+// - reused nodes (warning)
 
-import { Text, Fragment, Empty, createVNode } from './h.js'
-
+import {
+  Text,
+  Fragment,
+  Empty,
+  createVNode,
+  VNode,
+  VNodeChildren
+} from './h.js'
 import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
 
 const emptyArr: any[] = []
-const emptyObj = {}
+const emptyObj: { [key: string]: any } = {}
 
-const isSameType = (n1, n2) => n1.type === n2.type && n1.key === n2.key
+function isSameType(n1: VNode, n2: VNode): boolean {
+  return n1.type === n2.type && n1.key === n2.key
+}
+
+export type HostNode = any
+
+export interface RendererOptions {
+  patchProp(
+    el: HostNode,
+    key: string,
+    value: any,
+    oldValue: any,
+    isSVG: boolean,
+    prevChildren?: VNode[],
+    unmountChildren?: (children: VNode[]) => void
+  ): void
+  insert(el: HostNode, parent: HostNode, anchor?: HostNode): void
+  remove(el: HostNode): void
+  createElement(type: string): HostNode
+  createText(text: string): HostNode
+  createComment(text: string): HostNode
+  setText(node: HostNode, text: string): void
+  setElementText(node: HostNode, text: string): void
+  nextSibling(node: HostNode): HostNode | null
+}
 
-export function createRenderer(hostConfig) {
+export function createRenderer(options: RendererOptions) {
   const {
     insert,
     remove,
@@ -26,9 +59,15 @@ export function createRenderer(hostConfig) {
     setText: hostSetText,
     setElementText: hostSetElementText,
     nextSibling: hostNextSibling
-  } = hostConfig
+  } = options
 
-  function patch(n1, n2, container, anchor, optimized) {
+  function patch(
+    n1: VNode | null, // null means this is a mount
+    n2: VNode,
+    container: HostNode,
+    anchor?: HostNode,
+    optimized?: boolean
+  ) {
     // patching & not same type, unmount old tree
     if (n1 != null && !isSameType(n1, n2)) {
       anchor = hostNextSibling(n1.el)
@@ -50,18 +89,28 @@ export function createRenderer(hostConfig) {
     }
   }
 
-  function processText(n1, n2, container, anchor) {
+  function processText(
+    n1: VNode | null,
+    n2: VNode,
+    container: HostNode,
+    anchor?: HostNode
+  ) {
     if (n1 == null) {
-      insert((n2.el = hostCreateText(n2.children)), container, anchor)
+      insert((n2.el = hostCreateText(n2.children as string)), container, anchor)
     } else {
       const el = (n2.el = n1.el)
       if (n2.children !== n1.children) {
-        hostSetText(el, n2, children)
+        hostSetText(el, n2.children as string)
       }
     }
   }
 
-  function processEmptyNode(n1, n2, container, anchor) {
+  function processEmptyNode(
+    n1: VNode | null,
+    n2: VNode,
+    container: HostNode,
+    anchor?: HostNode
+  ) {
     if (n1 == null) {
       insert((n2.el = hostCreateComment('')), container, anchor)
     } else {
@@ -69,51 +118,65 @@ export function createRenderer(hostConfig) {
     }
   }
 
-  function processElement(n1, n2, container, anchor, optimized) {
+  function processElement(
+    n1: VNode | null,
+    n2: VNode,
+    container: HostNode,
+    anchor?: HostNode,
+    optimized?: boolean
+  ) {
     // mount
     if (n1 == null) {
       mountElement(n2, container, anchor)
     } else {
-      patchElement(n1, n2, container, optimized)
+      patchElement(n1, n2, optimized)
     }
   }
 
-  function mountElement(vnode, container, anchor) {
-    const el = (vnode.el = hostCreateElement(vnode.type))
+  function mountElement(vnode: VNode, container: HostNode, anchor?: HostNode) {
+    const el = (vnode.el = hostCreateElement(vnode.type as string))
     if (vnode.props != null) {
       for (const key in vnode.props) {
-        hostPatchProp(el, key, vnode.props[key], null)
+        hostPatchProp(el, key, vnode.props[key], null, false)
       }
     }
     if (typeof vnode.children === 'string') {
       hostSetElementText(el, vnode.children)
-    } else {
+    } else if (vnode.children != null) {
       mountChildren(vnode.children, el)
     }
     insert(el, container, anchor)
   }
 
-  function mountChildren(children, container, anchor, start = 0) {
+  function mountChildren(
+    children: VNodeChildren,
+    container: HostNode,
+    anchor?: HostNode,
+    start: number = 0
+  ) {
     for (let i = start; i < children.length; i++) {
       const child = (children[i] = normalizeChild(children[i]))
       patch(null, child, container, anchor)
     }
   }
 
-  function normalizeChild(child) {
-    // empty placeholder
+  function normalizeChild(child: any): VNode {
     if (child == null) {
+      // empty placeholder
       return createVNode(Empty)
-    } else if (typeof child === 'string' || typeof child === 'number') {
-      return createVNode(Text, null, child + '')
     } else if (Array.isArray(child)) {
+      // fragment
       return createVNode(Fragment, null, child)
+    } else if (typeof child === 'object') {
+      // already vnode
+      return child as VNode
     } else {
-      return child
+      // primitive types
+      return createVNode(Text, null, child + '')
     }
   }
 
-  function patchElement(n1, n2, container, optimized) {
+  function patchElement(n1: VNode, n2: VNode, optimized?: boolean) {
     const el = (n2.el = n1.el)
     const { patchFlag, dynamicChildren } = n2
     const oldProps = (n1 && n1.props) || emptyObj
@@ -138,7 +201,7 @@ export function createRenderer(hostConfig) {
       // this flag is matched when the element has dynamic style bindings
       // TODO separate static and dynamic styles?
       if (patchFlag & STYLE) {
-        setStyles(el.style, oldProps.style, newProps.style)
+        hostPatchProp(el, 'style', oldProps.style, newProps.style, false)
       }
 
       // props
@@ -148,13 +211,22 @@ export function createRenderer(hostConfig) {
       // 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) {
-        const propsToUpdate = n2.dynamicProps
+        // 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++) {
           const key = propsToUpdate[i]
           const prev = oldProps[key]
           const next = newProps[key]
           if (prev !== next) {
-            hostPatchProp(el, key, next, prev)
+            hostPatchProp(
+              el,
+              key,
+              next,
+              prev,
+              false,
+              n1.children as VNode[],
+              unmountChildren
+            )
           }
         }
       }
@@ -164,18 +236,18 @@ export function createRenderer(hostConfig) {
       // this flag is terminal (i.e. skips children diffing).
       if (patchFlag & TEXT) {
         if (n1.children !== n2.children) {
-          hostSetElementText(el, n2.children)
+          hostSetElementText(el, n2.children as string)
         }
         return // terminal
       }
     } else if (!optimized) {
       // unoptimized, full diff
-      patchProps(el, oldProps, newProps)
+      patchProps(el, n2, oldProps, newProps)
     }
 
     if (dynamicChildren != null) {
       // children fast path
-      const olddynamicChildren = n1.dynamicChildren
+      const olddynamicChildren = n1.dynamicChildren as VNode[]
       for (let i = 0; i < dynamicChildren.length; i++) {
         patch(olddynamicChildren[i], dynamicChildren[i], el, null, true)
       }
@@ -185,36 +257,70 @@ export function createRenderer(hostConfig) {
     }
   }
 
-  function patchProps(el, oldProps, newProps) {
+  function patchProps(
+    el: HostNode,
+    vnode: VNode,
+    oldProps: any,
+    newProps: any
+  ) {
     if (oldProps !== newProps) {
       for (const key in newProps) {
         const next = newProps[key]
         const prev = oldProps[key]
         if (next !== prev) {
-          hostPatchProp(el, key, next, prev)
+          hostPatchProp(
+            el,
+            key,
+            next,
+            prev,
+            false,
+            vnode.children as VNode[],
+            unmountChildren
+          )
         }
       }
       if (oldProps !== emptyObj) {
         for (const key in oldProps) {
           if (!(key in newProps)) {
-            hostPatchProp(el, key, null, null)
+            hostPatchProp(
+              el,
+              key,
+              null,
+              null,
+              false,
+              vnode.children as VNode[],
+              unmountChildren
+            )
           }
         }
       }
     }
   }
 
-  function processFragment(n1, n2, container, anchor, optimized) {
+  function processFragment(
+    n1: VNode | null,
+    n2: VNode,
+    container: HostNode,
+    anchor?: HostNode,
+    optimized?: boolean
+  ) {
     const fragmentAnchor = (n2.el = n1 ? n1.el : document.createComment(''))
     if (n1 == null) {
       insert(fragmentAnchor, container, anchor)
-      mountChildren(n2.children, container, fragmentAnchor)
+      // a fragment can only have array children
+      mountChildren(n2.children as VNodeChildren, container, fragmentAnchor)
     } else {
       patchChildren(n1, n2, container, fragmentAnchor, optimized)
     }
   }
 
-  function patchChildren(n1, n2, container, anchor, optimized) {
+  function patchChildren(
+    n1: VNode | null,
+    n2: VNode,
+    container: HostNode,
+    anchor?: HostNode,
+    optimized?: boolean
+  ) {
     const c1 = n1 && n1.children
     const c2 = n2.children
 
@@ -223,11 +329,24 @@ export function createRenderer(hostConfig) {
     if (patchFlag != null) {
       if (patchFlag & KEYED) {
         // this could be either fully-keyed or mixed (some keyed some not)
-        patchKeyedChildren(c1, c2, container, anchor, optimized)
+        // presence of patchFlag means children are guaranteed to be arrays
+        patchKeyedChildren(
+          c1 as VNode[],
+          c2 as VNodeChildren,
+          container,
+          anchor,
+          optimized
+        )
         return
       } else if (patchFlag & UNKEYED) {
         // unkeyed
-        patchUnkeyedChildren(c1, c2, container, anchor, optimized)
+        patchUnkeyedChildren(
+          c1 as VNode[],
+          c2 as VNodeChildren,
+          container,
+          anchor,
+          optimized
+        )
         return
       }
     }
@@ -235,21 +354,34 @@ export function createRenderer(hostConfig) {
     if (typeof c2 === 'string') {
       // text children fast path
       if (Array.isArray(c1)) {
-        unmountChildren(c1, false)
+        unmountChildren(c1 as VNode[])
       }
       hostSetElementText(container, c2)
     } else {
       if (typeof c1 === 'string') {
-        hostSetElementText('')
-        mountChildren(c2, container, anchor)
-      } else {
-        // two arrays, cannot assume anything, do full diff
-        patchKeyedChildren(c1, c2, container, anchor, optimized)
+        hostSetElementText(container, '')
+        if (c2 != null) {
+          mountChildren(c2, container, anchor)
+        }
+      } else if (Array.isArray(c1)) {
+        if (Array.isArray(c2)) {
+          // two arrays, cannot assume anything, do full diff
+          patchKeyedChildren(c1 as VNode[], c2, container, anchor, optimized)
+        } else {
+          // c2 is null in this case
+          unmountChildren(c1 as VNode[], 0, true)
+        }
       }
     }
   }
 
-  function patchUnkeyedChildren(c1, c2, container, anchor, optimized) {
+  function patchUnkeyedChildren(
+    c1: VNode[],
+    c2: VNodeChildren,
+    container: HostNode,
+    anchor?: HostNode,
+    optimized?: boolean
+  ) {
     c1 = c1 || emptyArr
     c2 = c2 || emptyArr
     const oldLength = c1.length
@@ -270,30 +402,43 @@ export function createRenderer(hostConfig) {
   }
 
   // can be all-keyed or mixed
-  function patchKeyedChildren(c1, c2, container, anchor, optimized) {
+  function patchKeyedChildren(
+    c1: VNode[],
+    c2: VNodeChildren,
+    container: HostNode,
+    anchor?: HostNode,
+    optimized?: boolean
+  ) {
     // TODO
     patchUnkeyedChildren(c1, c2, container, anchor, optimized)
   }
 
-  function unmount(vnode, doRemove) {
+  function unmount(vnode: VNode, doRemove?: boolean) {
+    if (vnode.dynamicChildren != null) {
+      unmountChildren(vnode.dynamicChildren)
+    } else if (Array.isArray(vnode.children)) {
+      unmountChildren(vnode.children as VNode[])
+    }
     if (doRemove) {
       if (vnode.type === Fragment) {
-        unmountChildren(vnode.children, 0, doRemove)
+        // raw VNodeChildren is normalized to VNode[] when the VNode is patched
+        unmountChildren(vnode.children as VNode[], 0, doRemove)
       }
       remove(vnode.el)
     }
-    if (Array.isArray(vnode.children)) {
-      unmountChildren(vnode.children)
-    }
   }
 
-  function unmountChildren(children, start = 0, doRemove) {
+  function unmountChildren(
+    children: VNode[],
+    start: number = 0,
+    doRemove?: boolean
+  ) {
     for (let i = start; i < children.length; i++) {
       unmount(children[i], doRemove)
     }
   }
 
-  return function render(vnode, dom) {
+  return function render(vnode: VNode, dom: HostNode): VNode {
     patch(dom._vnode, vnode, dom)
     return (dom._vnode = vnode)
   }
index 095191c9a3f89b9f8f70ecbf46fa28a8fb2dbfac..3f3077a4b8f8d4bc867a8f7640552aec3b820b5a 100644 (file)
@@ -13,6 +13,7 @@ export type VNodeChild = VNode | string | number | null
 export interface VNodeChildren extends Array<VNodeChildren | VNodeChild> {}
 
 export interface VNode {
+  el: any
   type: VNodeTypes
   props: { [key: string]: any } | null
   key: string | number | null
@@ -58,6 +59,7 @@ export function createVNode(
   dynamicProps: string[] | null = null
 ): VNode {
   const vnode: VNode = {
+    el: null,
     type,
     props,
     key: props && props.key,
@@ -81,4 +83,5 @@ function trackDynamicNode(vnode: VNode) {
 
 export function cloneVNode(vnode: VNode): VNode {
   // TODO
+  return vnode
 }
index 1ea0a177767e1f1e1e3ed2c550d1b019d6b6eb01..67477283887d59730a0c167a459d0d9ec5ba3af8 100644 (file)
@@ -1,5 +1,12 @@
+export {
+  VNode,
+  openBlock,
+  createBlock,
+  createVNode,
+  Fragment,
+  Text,
+  Empty
+} from './h'
+export { createRenderer, RendererOptions } from './createRenderer'
+export * from '@vue/observer'
 export { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
-
-export { openBlock, createBlock, createVNode, Fragment, Text, Empty } from './h'
-
-export { createRenderer } from './createRenderer'
index 8b8f591da4b5272f347044ded98f77f78644611b..dd9d7c31b40d7d69b49e66686c6877c6e8cda05d 100644 (file)
@@ -1,17 +1,10 @@
-import { createRenderer, Component } from '@vue/runtime-core'
-import { nodeOps } from './nodeOps'
-import { patchData } from './patchData'
+import { createRenderer, VNode } from '@vue/runtime-core'
+import { DOMRendererOptions } from './rendererOptions'
 
-const { render: _render } = createRenderer({
-  nodeOps,
-  patchData
-})
-
-type publicRender = (
-  node: {} | null,
+export const render = createRenderer(DOMRendererOptions) as (
+  vnode: VNode | null,
   container: HTMLElement
-) => Promise<Component | null>
-export const render = _render as publicRender
+) => VNode
 
 // re-export everything from core
 // h, Component, observer API, nextTick, flags & types
index e988a438a306381b5275423952513ee4f28638ff..153c9b2049378d85147c96561b1a85bb1f5eb20b 100644 (file)
@@ -1,18 +1,12 @@
-import { VNode, ChildrenFlags } from '@vue/runtime-core'
-
 export function patchDOMProp(
   el: any,
   key: string,
   value: any,
-  prevVNode: VNode,
+  prevChildren: any,
   unmountChildren: any
 ) {
-  if (key === 'innerHTML' || key === 'textContent') {
-    if (prevVNode && prevVNode.children) {
-      unmountChildren(prevVNode.children, prevVNode.childFlags)
-      prevVNode.children = null
-      prevVNode.childFlags = ChildrenFlags.NO_CHILDREN
-    }
+  if ((key === 'innerHTML' || key === 'textContent') && prevChildren != null) {
+    unmountChildren(prevChildren)
   }
   el[key] = value
 }
index c55afd3dabcfb65f653319b6cc44f96645abacc9..24241c59432b861d581ae92545bcaca6812325de 100644 (file)
@@ -1,6 +1,6 @@
 import { isString } from '@vue/shared'
 
-export function patchStyle(el: any, prev: any, next: any, data: any) {
+export function patchStyle(el: any, prev: any, next: any) {
   const { style } = el
   if (!next) {
     el.removeAttribute('style')
diff --git a/packages/runtime-dom/src/nodeOps.ts b/packages/runtime-dom/src/nodeOps.ts
deleted file mode 100644 (file)
index b53bb1e..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-import { NodeOps } from '@vue/runtime-core'
-
-const svgNS = 'http://www.w3.org/2000/svg'
-
-export const nodeOps: NodeOps = {
-  createElement: (tag: string, isSVG?: boolean): Element =>
-    isSVG ? document.createElementNS(svgNS, tag) : document.createElement(tag),
-
-  createText: (text: string): Text => document.createTextNode(text),
-
-  setText: (node: Text, text: string) => {
-    node.nodeValue = text
-  },
-
-  appendChild: (parent: Node, child: Node) => {
-    parent.appendChild(child)
-  },
-
-  insertBefore: (parent: Node, child: Node, ref: Node) => {
-    parent.insertBefore(child, ref)
-  },
-
-  removeChild: (parent: Node, child: Node) => {
-    parent.removeChild(child)
-  },
-
-  clearContent: (node: Node) => {
-    node.textContent = ''
-  },
-
-  parentNode: (node: Node): Node | null => node.parentNode,
-
-  nextSibling: (node: Node): Node | null => node.nextSibling,
-
-  querySelector: (selector: string): Node | null =>
-    document.querySelector(selector)
-}
similarity index 61%
rename from packages/runtime-dom/src/patchData.ts
rename to packages/runtime-dom/src/patchProp.ts
index 9308cba16fd409b7990879b631d167c40c1a30a4..98c5b5a574ed9d0385bc12a594af0bc961ef4728 100644 (file)
@@ -1,24 +1,19 @@
-import { VNode } from '@vue/runtime-core'
 import { patchClass } from './modules/class'
 import { patchStyle } from './modules/style'
 import { patchAttr } from './modules/attrs'
 import { patchDOMProp } from './modules/props'
 import { patchEvent } from './modules/events'
 import { isOn } from '@vue/shared'
+import { VNode } from '@vue/runtime-core'
 
-// value, checked, selected & muted
-// plus anything with upperCase letter in it are always patched as properties
-const domPropsReplaceRE = /^domProps/
-
-export function patchData(
+export function patchProp(
   el: Element,
   key: string,
   prevValue: any,
   nextValue: any,
-  prevVNode: VNode,
-  nextVNode: VNode,
   isSVG: boolean,
-  unmountChildren: any
+  prevChildren?: VNode[],
+  unmountChildren?: any
 ) {
   switch (key) {
     // special
@@ -26,19 +21,13 @@ export function patchData(
       patchClass(el, nextValue, isSVG)
       break
     case 'style':
-      patchStyle(el, prevValue, nextValue, nextVNode.data)
+      patchStyle(el, prevValue, nextValue)
       break
     default:
       if (isOn(key)) {
         patchEvent(el, key.slice(2).toLowerCase(), prevValue, nextValue)
       } else if (key in el) {
-        patchDOMProp(
-          el,
-          key.replace(domPropsReplaceRE, '').toLowerCase(),
-          nextValue,
-          prevVNode,
-          unmountChildren
-        )
+        patchDOMProp(el, key, nextValue, prevChildren, unmountChildren)
       } else {
         patchAttr(el, key, nextValue, isSVG)
       }
diff --git a/packages/runtime-dom/src/rendererOptions.ts b/packages/runtime-dom/src/rendererOptions.ts
new file mode 100644 (file)
index 0000000..e71a72c
--- /dev/null
@@ -0,0 +1,40 @@
+import { RendererOptions } from '@vue/runtime-core'
+import { patchProp } from './patchProp'
+
+const svgNS = 'http://www.w3.org/2000/svg'
+
+export const DOMRendererOptions: RendererOptions = {
+  patchProp,
+
+  insert: (parent: Node, child: Node, anchor?: Node) => {
+    if (anchor != null) {
+      parent.insertBefore(child, anchor)
+    } else {
+      parent.appendChild(child)
+    }
+  },
+
+  remove: (child: Node) => {
+    const parent = child.parentNode
+    if (parent != null) {
+      parent.removeChild(child)
+    }
+  },
+
+  createElement: (tag: string, isSVG?: boolean): Element =>
+    isSVG ? document.createElementNS(svgNS, tag) : document.createElement(tag),
+
+  createText: (text: string): Text => document.createTextNode(text),
+
+  createComment: (text: string): Comment => document.createComment(text),
+
+  setText: (node: Text, text: string) => {
+    node.nodeValue = text
+  },
+
+  setElementText: (el: HTMLElement, text: string) => {
+    el.textContent = text
+  },
+
+  nextSibling: (node: Node): Node | null => node.nextSibling
+}