renderer.ts 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802
  1. import {
  2. Text,
  3. Fragment,
  4. Comment,
  5. Portal,
  6. cloneIfMounted,
  7. normalizeVNode,
  8. VNode,
  9. VNodeArrayChildren,
  10. createVNode,
  11. isSameVNodeType
  12. } from './vnode'
  13. import {
  14. ComponentInternalInstance,
  15. createComponentInstance,
  16. Component,
  17. Data,
  18. setupComponent
  19. } from './component'
  20. import {
  21. renderComponentRoot,
  22. shouldUpdateComponent,
  23. updateHOCHostEl
  24. } from './componentRenderUtils'
  25. import {
  26. isString,
  27. EMPTY_OBJ,
  28. EMPTY_ARR,
  29. isReservedProp,
  30. isFunction,
  31. PatchFlags
  32. } from '@vue/shared'
  33. import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
  34. import {
  35. effect,
  36. stop,
  37. ReactiveEffectOptions,
  38. isRef,
  39. Ref,
  40. toRaw,
  41. DebuggerEvent
  42. } from '@vue/reactivity'
  43. import { resolveProps } from './componentProps'
  44. import { resolveSlots } from './componentSlots'
  45. import { ShapeFlags } from './shapeFlags'
  46. import { pushWarningContext, popWarningContext, warn } from './warning'
  47. import { invokeDirectiveHook } from './directives'
  48. import { ComponentPublicInstance } from './componentProxy'
  49. import { createAppAPI, CreateAppFunction } from './apiCreateApp'
  50. import {
  51. SuspenseBoundary,
  52. queueEffectWithSuspense,
  53. SuspenseImpl
  54. } from './components/Suspense'
  55. import { ErrorCodes, callWithErrorHandling } from './errorHandling'
  56. import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
  57. import { registerHMR, unregisterHMR } from './hmr'
  58. const __HMR__ = __BUNDLER__ && __DEV__
  59. export interface RendererOptions<HostNode = any, HostElement = any> {
  60. patchProp(
  61. el: HostElement,
  62. key: string,
  63. value: any,
  64. oldValue: any,
  65. isSVG?: boolean,
  66. prevChildren?: VNode<HostNode, HostElement>[],
  67. parentComponent?: ComponentInternalInstance | null,
  68. parentSuspense?: SuspenseBoundary<HostNode, HostElement> | null,
  69. unmountChildren?: (
  70. children: VNode<HostNode, HostElement>[],
  71. parentComponent: ComponentInternalInstance | null,
  72. parentSuspense: SuspenseBoundary<HostNode, HostElement> | null
  73. ) => void
  74. ): void
  75. insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
  76. remove(el: HostNode): void
  77. createElement(type: string, isSVG?: boolean): HostElement
  78. createText(text: string): HostNode
  79. createComment(text: string): HostNode
  80. setText(node: HostNode, text: string): void
  81. setElementText(node: HostElement, text: string): void
  82. parentNode(node: HostNode): HostElement | null
  83. nextSibling(node: HostNode): HostNode | null
  84. querySelector(selector: string): HostElement | null
  85. setScopeId(el: HostNode, id: string): void
  86. }
  87. export type RootRenderFunction<HostNode, HostElement> = (
  88. vnode: VNode<HostNode, HostElement> | null,
  89. dom: HostElement
  90. ) => void
  91. // An object exposing the internals of a renderer, passed to tree-shakeable
  92. // features so that they can be decoupled from this file.
  93. export interface RendererInternals<HostNode = any, HostElement = any> {
  94. patch: (
  95. n1: VNode<HostNode, HostElement> | null, // null means this is a mount
  96. n2: VNode<HostNode, HostElement>,
  97. container: HostElement,
  98. anchor?: HostNode | null,
  99. parentComponent?: ComponentInternalInstance | null,
  100. parentSuspense?: SuspenseBoundary<HostNode, HostElement> | null,
  101. isSVG?: boolean,
  102. optimized?: boolean
  103. ) => void
  104. unmount: (
  105. vnode: VNode<HostNode, HostElement>,
  106. parentComponent: ComponentInternalInstance | null,
  107. parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
  108. doRemove?: boolean
  109. ) => void
  110. move: (
  111. vnode: VNode<HostNode, HostElement>,
  112. container: HostElement,
  113. anchor: HostNode | null,
  114. type: MoveType,
  115. parentSuspense?: SuspenseBoundary<HostNode, HostElement> | null
  116. ) => void
  117. next: (vnode: VNode<HostNode, HostElement>) => HostNode | null
  118. options: RendererOptions<HostNode, HostElement>
  119. }
  120. export const enum MoveType {
  121. ENTER,
  122. LEAVE,
  123. REORDER
  124. }
  125. const prodEffectOptions = {
  126. scheduler: queueJob
  127. }
  128. function createDevEffectOptions(
  129. instance: ComponentInternalInstance
  130. ): ReactiveEffectOptions {
  131. return {
  132. scheduler: queueJob,
  133. onTrack: instance.rtc ? e => invokeHooks(instance.rtc!, e) : void 0,
  134. onTrigger: instance.rtg ? e => invokeHooks(instance.rtg!, e) : void 0
  135. }
  136. }
  137. export function invokeHooks(hooks: Function[], arg?: DebuggerEvent) {
  138. for (let i = 0; i < hooks.length; i++) {
  139. hooks[i](arg)
  140. }
  141. }
  142. export const queuePostRenderEffect = __FEATURE_SUSPENSE__
  143. ? queueEffectWithSuspense
  144. : queuePostFlushCb
  145. /**
  146. * The createRenderer function accepts two generic arguments:
  147. * HostNode and HostElement, corresponding to Node and Element types in the
  148. * host environment. For example, for runtime-dom, HostNode would be the DOM
  149. * `Node` interface and HostElement would be the DOM `Element` interface.
  150. *
  151. * Custom renderers can pass in the platform specific types like this:
  152. *
  153. * ``` js
  154. * const { render, createApp } = createRenderer<Node, Element>({
  155. * patchProp,
  156. * ...nodeOps
  157. * })
  158. * ```
  159. */
  160. export function createRenderer<
  161. HostNode extends object = any,
  162. HostElement extends HostNode = any
  163. >(
  164. options: RendererOptions<HostNode, HostElement>
  165. ): {
  166. render: RootRenderFunction<HostNode, HostElement>
  167. createApp: CreateAppFunction<HostElement>
  168. } {
  169. type HostVNode = VNode<HostNode, HostElement>
  170. type HostVNodeChildren = VNodeArrayChildren<HostNode, HostElement>
  171. type HostSuspenseBoundary = SuspenseBoundary<HostNode, HostElement>
  172. const {
  173. insert: hostInsert,
  174. remove: hostRemove,
  175. patchProp: hostPatchProp,
  176. createElement: hostCreateElement,
  177. createText: hostCreateText,
  178. createComment: hostCreateComment,
  179. setText: hostSetText,
  180. setElementText: hostSetElementText,
  181. parentNode: hostParentNode,
  182. nextSibling: hostNextSibling,
  183. querySelector: hostQuerySelector,
  184. setScopeId: hostSetScopeId
  185. } = options
  186. const internals: RendererInternals<HostNode, HostElement> = {
  187. patch,
  188. unmount,
  189. move,
  190. next: getNextHostNode,
  191. options
  192. }
  193. function patch(
  194. n1: HostVNode | null, // null means this is a mount
  195. n2: HostVNode,
  196. container: HostElement,
  197. anchor: HostNode | null = null,
  198. parentComponent: ComponentInternalInstance | null = null,
  199. parentSuspense: HostSuspenseBoundary | null = null,
  200. isSVG: boolean = false,
  201. optimized: boolean = false
  202. ) {
  203. // patching & not same type, unmount old tree
  204. if (n1 != null && !isSameVNodeType(n1, n2)) {
  205. anchor = getNextHostNode(n1)
  206. unmount(n1, parentComponent, parentSuspense, true)
  207. n1 = null
  208. }
  209. const { type, shapeFlag } = n2
  210. switch (type) {
  211. case Text:
  212. processText(n1, n2, container, anchor)
  213. break
  214. case Comment:
  215. processCommentNode(n1, n2, container, anchor)
  216. break
  217. case Fragment:
  218. processFragment(
  219. n1,
  220. n2,
  221. container,
  222. anchor,
  223. parentComponent,
  224. parentSuspense,
  225. isSVG,
  226. optimized
  227. )
  228. break
  229. case Portal:
  230. processPortal(
  231. n1,
  232. n2,
  233. container,
  234. anchor,
  235. parentComponent,
  236. parentSuspense,
  237. isSVG,
  238. optimized
  239. )
  240. break
  241. default:
  242. if (shapeFlag & ShapeFlags.ELEMENT) {
  243. processElement(
  244. n1,
  245. n2,
  246. container,
  247. anchor,
  248. parentComponent,
  249. parentSuspense,
  250. isSVG,
  251. optimized
  252. )
  253. } else if (shapeFlag & ShapeFlags.COMPONENT) {
  254. processComponent(
  255. n1,
  256. n2,
  257. container,
  258. anchor,
  259. parentComponent,
  260. parentSuspense,
  261. isSVG,
  262. optimized
  263. )
  264. } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
  265. ;(type as typeof SuspenseImpl).process(
  266. n1,
  267. n2,
  268. container,
  269. anchor,
  270. parentComponent,
  271. parentSuspense,
  272. isSVG,
  273. optimized,
  274. internals
  275. )
  276. } else if (__DEV__) {
  277. warn('Invalid HostVNode type:', type, `(${typeof type})`)
  278. }
  279. }
  280. }
  281. function processText(
  282. n1: HostVNode | null,
  283. n2: HostVNode,
  284. container: HostElement,
  285. anchor: HostNode | null
  286. ) {
  287. if (n1 == null) {
  288. hostInsert(
  289. (n2.el = hostCreateText(n2.children as string)),
  290. container,
  291. anchor
  292. )
  293. } else {
  294. const el = (n2.el = n1.el) as HostNode
  295. if (n2.children !== n1.children) {
  296. hostSetText(el, n2.children as string)
  297. }
  298. }
  299. }
  300. function processCommentNode(
  301. n1: HostVNode | null,
  302. n2: HostVNode,
  303. container: HostElement,
  304. anchor: HostNode | null
  305. ) {
  306. if (n1 == null) {
  307. hostInsert(
  308. (n2.el = hostCreateComment((n2.children as string) || '')),
  309. container,
  310. anchor
  311. )
  312. } else {
  313. // there's no support for dynamic comments
  314. n2.el = n1.el
  315. }
  316. }
  317. function processElement(
  318. n1: HostVNode | null,
  319. n2: HostVNode,
  320. container: HostElement,
  321. anchor: HostNode | null,
  322. parentComponent: ComponentInternalInstance | null,
  323. parentSuspense: HostSuspenseBoundary | null,
  324. isSVG: boolean,
  325. optimized: boolean
  326. ) {
  327. isSVG = isSVG || (n2.type as string) === 'svg'
  328. if (n1 == null) {
  329. mountElement(
  330. n2,
  331. container,
  332. anchor,
  333. parentComponent,
  334. parentSuspense,
  335. isSVG,
  336. optimized
  337. )
  338. } else {
  339. patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
  340. }
  341. if (n2.ref !== null && parentComponent !== null) {
  342. setRef(n2.ref, n1 && n1.ref, parentComponent, n2.el)
  343. }
  344. }
  345. function mountElement(
  346. vnode: HostVNode,
  347. container: HostElement,
  348. anchor: HostNode | null,
  349. parentComponent: ComponentInternalInstance | null,
  350. parentSuspense: HostSuspenseBoundary | null,
  351. isSVG: boolean,
  352. optimized: boolean
  353. ) {
  354. const el = (vnode.el = hostCreateElement(vnode.type as string, isSVG))
  355. const { type, props, shapeFlag, transition, scopeId } = vnode
  356. // props
  357. if (props != null) {
  358. for (const key in props) {
  359. if (isReservedProp(key)) continue
  360. hostPatchProp(el, key, props[key], null, isSVG)
  361. }
  362. if (props.onVnodeBeforeMount != null) {
  363. invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode)
  364. }
  365. }
  366. // scopeId
  367. if (__BUNDLER__) {
  368. if (scopeId !== null) {
  369. hostSetScopeId(el, scopeId)
  370. }
  371. const treeOwnerId = parentComponent && parentComponent.type.__scopeId
  372. // vnode's own scopeId and the current patched component's scopeId is
  373. // different - this is a slot content node.
  374. if (treeOwnerId != null && treeOwnerId !== scopeId) {
  375. hostSetScopeId(el, treeOwnerId + '-s')
  376. }
  377. }
  378. // children
  379. if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
  380. hostSetElementText(el, vnode.children as string)
  381. } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  382. mountChildren(
  383. vnode.children as HostVNodeChildren,
  384. el,
  385. null,
  386. parentComponent,
  387. parentSuspense,
  388. isSVG && type !== 'foreignObject',
  389. optimized || vnode.dynamicChildren !== null
  390. )
  391. }
  392. if (transition != null && !transition.persisted) {
  393. transition.beforeEnter(el)
  394. }
  395. hostInsert(el, container, anchor)
  396. const vnodeMountedHook = props && props.onVnodeMounted
  397. if (
  398. vnodeMountedHook != null ||
  399. (transition != null && !transition.persisted)
  400. ) {
  401. queuePostRenderEffect(() => {
  402. vnodeMountedHook &&
  403. invokeDirectiveHook(vnodeMountedHook, parentComponent, vnode)
  404. transition && !transition.persisted && transition.enter(el)
  405. }, parentSuspense)
  406. }
  407. }
  408. function mountChildren(
  409. children: HostVNodeChildren,
  410. container: HostElement,
  411. anchor: HostNode | null,
  412. parentComponent: ComponentInternalInstance | null,
  413. parentSuspense: HostSuspenseBoundary | null,
  414. isSVG: boolean,
  415. optimized: boolean,
  416. start: number = 0
  417. ) {
  418. for (let i = start; i < children.length; i++) {
  419. const child = (children[i] = optimized
  420. ? cloneIfMounted(children[i] as HostVNode)
  421. : normalizeVNode(children[i]))
  422. patch(
  423. null,
  424. child,
  425. container,
  426. anchor,
  427. parentComponent,
  428. parentSuspense,
  429. isSVG,
  430. optimized
  431. )
  432. }
  433. }
  434. function patchElement(
  435. n1: HostVNode,
  436. n2: HostVNode,
  437. parentComponent: ComponentInternalInstance | null,
  438. parentSuspense: HostSuspenseBoundary | null,
  439. isSVG: boolean,
  440. optimized: boolean
  441. ) {
  442. const el = (n2.el = n1.el) as HostElement
  443. let { patchFlag, dynamicChildren } = n2
  444. const oldProps = (n1 && n1.props) || EMPTY_OBJ
  445. const newProps = n2.props || EMPTY_OBJ
  446. if (newProps.onVnodeBeforeUpdate != null) {
  447. invokeDirectiveHook(newProps.onVnodeBeforeUpdate, parentComponent, n2, n1)
  448. }
  449. if (__HMR__ && parentComponent && parentComponent.renderUpdated) {
  450. // HMR updated, force full diff
  451. patchFlag = 0
  452. optimized = false
  453. dynamicChildren = null
  454. }
  455. if (patchFlag > 0) {
  456. // the presence of a patchFlag means this element's render code was
  457. // generated by the compiler and can take the fast path.
  458. // in this path old node and new node are guaranteed to have the same shape
  459. // (i.e. at the exact same position in the source template)
  460. if (patchFlag & PatchFlags.FULL_PROPS) {
  461. // element props contain dynamic keys, full diff needed
  462. patchProps(
  463. el,
  464. n2,
  465. oldProps,
  466. newProps,
  467. parentComponent,
  468. parentSuspense,
  469. isSVG
  470. )
  471. } else {
  472. // class
  473. // this flag is matched when the element has dynamic class bindings.
  474. if (patchFlag & PatchFlags.CLASS) {
  475. if (oldProps.class !== newProps.class) {
  476. hostPatchProp(el, 'class', newProps.class, null, isSVG)
  477. }
  478. }
  479. // style
  480. // this flag is matched when the element has dynamic style bindings
  481. if (patchFlag & PatchFlags.STYLE) {
  482. hostPatchProp(el, 'style', newProps.style, oldProps.style, isSVG)
  483. }
  484. // props
  485. // This flag is matched when the element has dynamic prop/attr bindings
  486. // other than class and style. The keys of dynamic prop/attrs are saved for
  487. // faster iteration.
  488. // Note dynamic keys like :[foo]="bar" will cause this optimization to
  489. // bail out and go through a full diff because we need to unset the old key
  490. if (patchFlag & PatchFlags.PROPS) {
  491. // if the flag is present then dynamicProps must be non-null
  492. const propsToUpdate = n2.dynamicProps!
  493. for (let i = 0; i < propsToUpdate.length; i++) {
  494. const key = propsToUpdate[i]
  495. const prev = oldProps[key]
  496. const next = newProps[key]
  497. if (prev !== next) {
  498. hostPatchProp(
  499. el,
  500. key,
  501. next,
  502. prev,
  503. isSVG,
  504. n1.children as HostVNode[],
  505. parentComponent,
  506. parentSuspense,
  507. unmountChildren
  508. )
  509. }
  510. }
  511. }
  512. }
  513. // text
  514. // This flag is matched when the element has only dynamic text children.
  515. // this flag is terminal (i.e. skips children diffing).
  516. if (patchFlag & PatchFlags.TEXT) {
  517. if (n1.children !== n2.children) {
  518. hostSetElementText(el, n2.children as string)
  519. }
  520. return // terminal
  521. }
  522. } else if (!optimized && dynamicChildren == null) {
  523. // unoptimized, full diff
  524. patchProps(
  525. el,
  526. n2,
  527. oldProps,
  528. newProps,
  529. parentComponent,
  530. parentSuspense,
  531. isSVG
  532. )
  533. }
  534. const areChildrenSVG = isSVG && n2.type !== 'foreignObject'
  535. if (dynamicChildren != null) {
  536. patchBlockChildren(
  537. n1.dynamicChildren!,
  538. dynamicChildren,
  539. el,
  540. parentComponent,
  541. parentSuspense,
  542. areChildrenSVG
  543. )
  544. } else if (!optimized) {
  545. // full diff
  546. patchChildren(
  547. n1,
  548. n2,
  549. el,
  550. null,
  551. parentComponent,
  552. parentSuspense,
  553. areChildrenSVG
  554. )
  555. }
  556. if (newProps.onVnodeUpdated != null) {
  557. queuePostRenderEffect(() => {
  558. invokeDirectiveHook(newProps.onVnodeUpdated, parentComponent, n2, n1)
  559. }, parentSuspense)
  560. }
  561. }
  562. // The fast path for blocks.
  563. function patchBlockChildren(
  564. oldChildren: HostVNode[],
  565. newChildren: HostVNode[],
  566. fallbackContainer: HostElement,
  567. parentComponent: ComponentInternalInstance | null,
  568. parentSuspense: HostSuspenseBoundary | null,
  569. isSVG: boolean
  570. ) {
  571. for (let i = 0; i < newChildren.length; i++) {
  572. const oldVNode = oldChildren[i]
  573. const newVNode = newChildren[i]
  574. // Determine the container (parent element) for the patch.
  575. const container =
  576. // - In the case of a Fragment, we need to provide the actual parent
  577. // of the Fragment itself so it can move its children.
  578. oldVNode.type === Fragment ||
  579. // - In the case of different nodes, there is going to be a replacement
  580. // which also requires the correct parent container
  581. !isSameVNodeType(oldVNode, newVNode) ||
  582. // - In the case of a component, it could contain anything.
  583. oldVNode.shapeFlag & ShapeFlags.COMPONENT
  584. ? hostParentNode(oldVNode.el!)!
  585. : // In other cases, the parent container is not actually used so we
  586. // just pass the block element here to avoid a DOM parentNode call.
  587. fallbackContainer
  588. patch(
  589. oldVNode,
  590. newVNode,
  591. container,
  592. null,
  593. parentComponent,
  594. parentSuspense,
  595. isSVG,
  596. true
  597. )
  598. }
  599. }
  600. function patchProps(
  601. el: HostElement,
  602. vnode: HostVNode,
  603. oldProps: Data,
  604. newProps: Data,
  605. parentComponent: ComponentInternalInstance | null,
  606. parentSuspense: HostSuspenseBoundary | null,
  607. isSVG: boolean
  608. ) {
  609. if (oldProps !== newProps) {
  610. for (const key in newProps) {
  611. if (isReservedProp(key)) continue
  612. const next = newProps[key]
  613. const prev = oldProps[key]
  614. if (next !== prev) {
  615. hostPatchProp(
  616. el,
  617. key,
  618. next,
  619. prev,
  620. isSVG,
  621. vnode.children as HostVNode[],
  622. parentComponent,
  623. parentSuspense,
  624. unmountChildren
  625. )
  626. }
  627. }
  628. if (oldProps !== EMPTY_OBJ) {
  629. for (const key in oldProps) {
  630. if (!isReservedProp(key) && !(key in newProps)) {
  631. hostPatchProp(
  632. el,
  633. key,
  634. null,
  635. null,
  636. isSVG,
  637. vnode.children as HostVNode[],
  638. parentComponent,
  639. parentSuspense,
  640. unmountChildren
  641. )
  642. }
  643. }
  644. }
  645. }
  646. }
  647. let devFragmentID = 0
  648. function processFragment(
  649. n1: HostVNode | null,
  650. n2: HostVNode,
  651. container: HostElement,
  652. anchor: HostNode | null,
  653. parentComponent: ComponentInternalInstance | null,
  654. parentSuspense: HostSuspenseBoundary | null,
  655. isSVG: boolean,
  656. optimized: boolean
  657. ) {
  658. const showID = __DEV__ && !__TEST__
  659. const fragmentStartAnchor = (n2.el = n1
  660. ? n1.el
  661. : hostCreateComment(showID ? `fragment-${devFragmentID}-start` : ''))!
  662. const fragmentEndAnchor = (n2.anchor = n1
  663. ? n1.anchor
  664. : hostCreateComment(showID ? `fragment-${devFragmentID}-end` : ''))!
  665. let { patchFlag, dynamicChildren } = n2
  666. if (patchFlag > 0) {
  667. optimized = true
  668. }
  669. if (__HMR__ && parentComponent && parentComponent.renderUpdated) {
  670. // HMR updated, force full diff
  671. patchFlag = 0
  672. optimized = false
  673. dynamicChildren = null
  674. }
  675. if (n1 == null) {
  676. if (showID) {
  677. devFragmentID++
  678. }
  679. hostInsert(fragmentStartAnchor, container, anchor)
  680. hostInsert(fragmentEndAnchor, container, anchor)
  681. // a fragment can only have array children
  682. // since they are either generated by the compiler, or implicitly created
  683. // from arrays.
  684. mountChildren(
  685. n2.children as HostVNodeChildren,
  686. container,
  687. fragmentEndAnchor,
  688. parentComponent,
  689. parentSuspense,
  690. isSVG,
  691. optimized
  692. )
  693. } else {
  694. if (patchFlag & PatchFlags.STABLE_FRAGMENT && dynamicChildren != null) {
  695. // a stable fragment (template root or <template v-for>) doesn't need to
  696. // patch children order, but it may contain dynamicChildren.
  697. patchBlockChildren(
  698. n1.dynamicChildren!,
  699. dynamicChildren,
  700. container,
  701. parentComponent,
  702. parentSuspense,
  703. isSVG
  704. )
  705. } else {
  706. // keyed / unkeyed, or manual fragments.
  707. // for keyed & unkeyed, since they are compiler generated from v-for,
  708. // each child is guaranteed to be a block so the fragment will never
  709. // have dynamicChildren.
  710. patchChildren(
  711. n1,
  712. n2,
  713. container,
  714. fragmentEndAnchor,
  715. parentComponent,
  716. parentSuspense,
  717. isSVG,
  718. optimized
  719. )
  720. }
  721. }
  722. }
  723. function processPortal(
  724. n1: HostVNode | null,
  725. n2: HostVNode,
  726. container: HostElement,
  727. anchor: HostNode | null,
  728. parentComponent: ComponentInternalInstance | null,
  729. parentSuspense: HostSuspenseBoundary | null,
  730. isSVG: boolean,
  731. optimized: boolean
  732. ) {
  733. const targetSelector = n2.props && n2.props.target
  734. const { patchFlag, shapeFlag, children } = n2
  735. if (n1 == null) {
  736. const target = (n2.target = isString(targetSelector)
  737. ? hostQuerySelector(targetSelector)
  738. : targetSelector)
  739. if (target != null) {
  740. if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
  741. hostSetElementText(target, children as string)
  742. } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  743. mountChildren(
  744. children as HostVNodeChildren,
  745. target,
  746. null,
  747. parentComponent,
  748. parentSuspense,
  749. isSVG,
  750. optimized
  751. )
  752. }
  753. } else if (__DEV__) {
  754. warn('Invalid Portal target on mount:', target, `(${typeof target})`)
  755. }
  756. } else {
  757. // update content
  758. const target = (n2.target = n1.target)!
  759. if (patchFlag === PatchFlags.TEXT) {
  760. hostSetElementText(target, children as string)
  761. } else if (n2.dynamicChildren) {
  762. // fast path when the portal happens to be a block root
  763. patchBlockChildren(
  764. n1.dynamicChildren!,
  765. n2.dynamicChildren,
  766. container,
  767. parentComponent,
  768. parentSuspense,
  769. isSVG
  770. )
  771. } else if (!optimized) {
  772. patchChildren(
  773. n1,
  774. n2,
  775. target,
  776. null,
  777. parentComponent,
  778. parentSuspense,
  779. isSVG
  780. )
  781. }
  782. // target changed
  783. if (targetSelector !== (n1.props && n1.props.target)) {
  784. const nextTarget = (n2.target = isString(targetSelector)
  785. ? hostQuerySelector(targetSelector)
  786. : targetSelector)
  787. if (nextTarget != null) {
  788. // move content
  789. if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
  790. hostSetElementText(target, '')
  791. hostSetElementText(nextTarget, children as string)
  792. } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  793. for (let i = 0; i < (children as HostVNode[]).length; i++) {
  794. move(
  795. (children as HostVNode[])[i],
  796. nextTarget,
  797. null,
  798. MoveType.REORDER
  799. )
  800. }
  801. }
  802. } else if (__DEV__) {
  803. warn('Invalid Portal target on update:', target, `(${typeof target})`)
  804. }
  805. }
  806. }
  807. // insert an empty node as the placeholder for the portal
  808. processCommentNode(n1, n2, container, anchor)
  809. }
  810. function processComponent(
  811. n1: HostVNode | null,
  812. n2: HostVNode,
  813. container: HostElement,
  814. anchor: HostNode | null,
  815. parentComponent: ComponentInternalInstance | null,
  816. parentSuspense: HostSuspenseBoundary | null,
  817. isSVG: boolean,
  818. optimized: boolean
  819. ) {
  820. if (n1 == null) {
  821. if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
  822. ;(parentComponent!.sink as KeepAliveSink).activate(
  823. n2,
  824. container,
  825. anchor
  826. )
  827. } else {
  828. mountComponent(
  829. n2,
  830. container,
  831. anchor,
  832. parentComponent,
  833. parentSuspense,
  834. isSVG
  835. )
  836. }
  837. } else {
  838. const instance = (n2.component = n1.component)!
  839. if (shouldUpdateComponent(n1, n2, parentComponent, optimized)) {
  840. if (
  841. __FEATURE_SUSPENSE__ &&
  842. instance.asyncDep &&
  843. !instance.asyncResolved
  844. ) {
  845. // async & still pending - just update props and slots
  846. // since the component's reactive effect for render isn't set-up yet
  847. if (__DEV__) {
  848. pushWarningContext(n2)
  849. }
  850. updateComponentPreRender(instance, n2)
  851. if (__DEV__) {
  852. popWarningContext()
  853. }
  854. return
  855. } else {
  856. // normal update
  857. instance.next = n2
  858. // instance.update is the reactive effect runner.
  859. instance.update()
  860. }
  861. } else {
  862. // no update needed. just copy over properties
  863. n2.component = n1.component
  864. n2.el = n1.el
  865. }
  866. }
  867. if (n2.ref !== null && parentComponent !== null) {
  868. if (__DEV__ && !(n2.shapeFlag & ShapeFlags.STATEFUL_COMPONENT)) {
  869. pushWarningContext(n2)
  870. warn(
  871. `Functional components do not support "ref" because they do not ` +
  872. `have instances.`
  873. )
  874. popWarningContext()
  875. }
  876. setRef(n2.ref, n1 && n1.ref, parentComponent, n2.component!.proxy)
  877. }
  878. }
  879. function mountComponent(
  880. initialVNode: HostVNode,
  881. container: HostElement,
  882. anchor: HostNode | null,
  883. parentComponent: ComponentInternalInstance | null,
  884. parentSuspense: HostSuspenseBoundary | null,
  885. isSVG: boolean
  886. ) {
  887. const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
  888. initialVNode,
  889. parentComponent
  890. ))
  891. if (__HMR__ && instance.type.__hmrId != null) {
  892. registerHMR(instance)
  893. }
  894. if (__DEV__) {
  895. pushWarningContext(initialVNode)
  896. }
  897. // inject renderer internals for keepAlive
  898. if (isKeepAlive(initialVNode)) {
  899. const sink = instance.sink as KeepAliveSink
  900. sink.renderer = internals
  901. sink.parentSuspense = parentSuspense
  902. }
  903. // resolve props and slots for setup context
  904. setupComponent(instance, parentSuspense)
  905. // setup() is async. This component relies on async logic to be resolved
  906. // before proceeding
  907. if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
  908. if (!parentSuspense) {
  909. if (__DEV__) warn('async setup() is used without a suspense boundary!')
  910. return
  911. }
  912. parentSuspense.registerDep(instance, setupRenderEffect)
  913. // give it a placeholder
  914. const placeholder = (instance.subTree = createVNode(Comment))
  915. processCommentNode(null, placeholder, container, anchor)
  916. initialVNode.el = placeholder.el
  917. return
  918. }
  919. setupRenderEffect(
  920. instance,
  921. parentSuspense,
  922. initialVNode,
  923. container,
  924. anchor,
  925. isSVG
  926. )
  927. if (__DEV__) {
  928. popWarningContext()
  929. }
  930. }
  931. function setupRenderEffect(
  932. instance: ComponentInternalInstance,
  933. parentSuspense: HostSuspenseBoundary | null,
  934. initialVNode: HostVNode,
  935. container: HostElement,
  936. anchor: HostNode | null,
  937. isSVG: boolean
  938. ) {
  939. // create reactive effect for rendering
  940. instance.update = effect(function componentEffect() {
  941. if (!instance.isMounted) {
  942. const subTree = (instance.subTree = renderComponentRoot(instance))
  943. // beforeMount hook
  944. if (instance.bm !== null) {
  945. invokeHooks(instance.bm)
  946. }
  947. patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
  948. initialVNode.el = subTree.el
  949. // mounted hook
  950. if (instance.m !== null) {
  951. queuePostRenderEffect(instance.m, parentSuspense)
  952. }
  953. // activated hook for keep-alive roots.
  954. if (
  955. instance.a !== null &&
  956. instance.vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
  957. ) {
  958. queuePostRenderEffect(instance.a, parentSuspense)
  959. }
  960. instance.isMounted = true
  961. } else {
  962. // updateComponent
  963. // This is triggered by mutation of component's own state (next: null)
  964. // OR parent calling processComponent (next: HostVNode)
  965. const { next } = instance
  966. if (__DEV__) {
  967. pushWarningContext(next || instance.vnode)
  968. }
  969. if (next !== null) {
  970. updateComponentPreRender(instance, next)
  971. }
  972. const nextTree = renderComponentRoot(instance)
  973. const prevTree = instance.subTree
  974. instance.subTree = nextTree
  975. // beforeUpdate hook
  976. if (instance.bu !== null) {
  977. invokeHooks(instance.bu)
  978. }
  979. // reset refs
  980. // only needed if previous patch had refs
  981. if (instance.refs !== EMPTY_OBJ) {
  982. instance.refs = {}
  983. }
  984. patch(
  985. prevTree,
  986. nextTree,
  987. // parent may have changed if it's in a portal
  988. hostParentNode(prevTree.el as HostNode) as HostElement,
  989. // anchor may have changed if it's in a fragment
  990. getNextHostNode(prevTree),
  991. instance,
  992. parentSuspense,
  993. isSVG
  994. )
  995. instance.vnode.el = nextTree.el
  996. if (next === null) {
  997. // self-triggered update. In case of HOC, update parent component
  998. // vnode el. HOC is indicated by parent instance's subTree pointing
  999. // to child component's vnode
  1000. updateHOCHostEl(instance, nextTree.el)
  1001. }
  1002. // updated hook
  1003. if (instance.u !== null) {
  1004. queuePostRenderEffect(instance.u, parentSuspense)
  1005. }
  1006. if (__DEV__) {
  1007. popWarningContext()
  1008. }
  1009. }
  1010. }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
  1011. }
  1012. function updateComponentPreRender(
  1013. instance: ComponentInternalInstance,
  1014. nextVNode: HostVNode
  1015. ) {
  1016. nextVNode.component = instance
  1017. instance.vnode = nextVNode
  1018. instance.next = null
  1019. resolveProps(instance, nextVNode.props, (nextVNode.type as Component).props)
  1020. resolveSlots(instance, nextVNode.children)
  1021. }
  1022. function patchChildren(
  1023. n1: HostVNode | null,
  1024. n2: HostVNode,
  1025. container: HostElement,
  1026. anchor: HostNode | null,
  1027. parentComponent: ComponentInternalInstance | null,
  1028. parentSuspense: HostSuspenseBoundary | null,
  1029. isSVG: boolean,
  1030. optimized: boolean = false
  1031. ) {
  1032. const c1 = n1 && n1.children
  1033. const prevShapeFlag = n1 ? n1.shapeFlag : 0
  1034. const c2 = n2.children
  1035. const { patchFlag, shapeFlag } = n2
  1036. if (patchFlag === PatchFlags.BAIL) {
  1037. optimized = false
  1038. }
  1039. // fast path
  1040. if (patchFlag > 0) {
  1041. if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
  1042. // this could be either fully-keyed or mixed (some keyed some not)
  1043. // presence of patchFlag means children are guaranteed to be arrays
  1044. patchKeyedChildren(
  1045. c1 as HostVNode[],
  1046. c2 as HostVNodeChildren,
  1047. container,
  1048. anchor,
  1049. parentComponent,
  1050. parentSuspense,
  1051. isSVG,
  1052. optimized
  1053. )
  1054. return
  1055. } else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
  1056. // unkeyed
  1057. patchUnkeyedChildren(
  1058. c1 as HostVNode[],
  1059. c2 as HostVNodeChildren,
  1060. container,
  1061. anchor,
  1062. parentComponent,
  1063. parentSuspense,
  1064. isSVG,
  1065. optimized
  1066. )
  1067. return
  1068. }
  1069. }
  1070. // children has 3 possibilities: text, array or no children.
  1071. if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
  1072. // text children fast path
  1073. if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  1074. unmountChildren(c1 as HostVNode[], parentComponent, parentSuspense)
  1075. }
  1076. if (c2 !== c1) {
  1077. hostSetElementText(container, c2 as string)
  1078. }
  1079. } else {
  1080. if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  1081. // prev children was array
  1082. if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  1083. // two arrays, cannot assume anything, do full diff
  1084. patchKeyedChildren(
  1085. c1 as HostVNode[],
  1086. c2 as HostVNodeChildren,
  1087. container,
  1088. anchor,
  1089. parentComponent,
  1090. parentSuspense,
  1091. isSVG,
  1092. optimized
  1093. )
  1094. } else {
  1095. // no new children, just unmount old
  1096. unmountChildren(
  1097. c1 as HostVNode[],
  1098. parentComponent,
  1099. parentSuspense,
  1100. true
  1101. )
  1102. }
  1103. } else {
  1104. // prev children was text OR null
  1105. // new children is array OR null
  1106. if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
  1107. hostSetElementText(container, '')
  1108. }
  1109. // mount new if array
  1110. if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  1111. mountChildren(
  1112. c2 as HostVNodeChildren,
  1113. container,
  1114. anchor,
  1115. parentComponent,
  1116. parentSuspense,
  1117. isSVG,
  1118. optimized
  1119. )
  1120. }
  1121. }
  1122. }
  1123. }
  1124. function patchUnkeyedChildren(
  1125. c1: HostVNode[],
  1126. c2: HostVNodeChildren,
  1127. container: HostElement,
  1128. anchor: HostNode | null,
  1129. parentComponent: ComponentInternalInstance | null,
  1130. parentSuspense: HostSuspenseBoundary | null,
  1131. isSVG: boolean,
  1132. optimized: boolean
  1133. ) {
  1134. c1 = c1 || EMPTY_ARR
  1135. c2 = c2 || EMPTY_ARR
  1136. const oldLength = c1.length
  1137. const newLength = c2.length
  1138. const commonLength = Math.min(oldLength, newLength)
  1139. let i
  1140. for (i = 0; i < commonLength; i++) {
  1141. const nextChild = (c2[i] = optimized
  1142. ? cloneIfMounted(c2[i] as HostVNode)
  1143. : normalizeVNode(c2[i]))
  1144. patch(
  1145. c1[i],
  1146. nextChild,
  1147. container,
  1148. null,
  1149. parentComponent,
  1150. parentSuspense,
  1151. isSVG,
  1152. optimized
  1153. )
  1154. }
  1155. if (oldLength > newLength) {
  1156. // remove old
  1157. unmountChildren(c1, parentComponent, parentSuspense, true, commonLength)
  1158. } else {
  1159. // mount new
  1160. mountChildren(
  1161. c2,
  1162. container,
  1163. anchor,
  1164. parentComponent,
  1165. parentSuspense,
  1166. isSVG,
  1167. optimized,
  1168. commonLength
  1169. )
  1170. }
  1171. }
  1172. // can be all-keyed or mixed
  1173. function patchKeyedChildren(
  1174. c1: HostVNode[],
  1175. c2: HostVNodeChildren,
  1176. container: HostElement,
  1177. parentAnchor: HostNode | null,
  1178. parentComponent: ComponentInternalInstance | null,
  1179. parentSuspense: HostSuspenseBoundary | null,
  1180. isSVG: boolean,
  1181. optimized: boolean
  1182. ) {
  1183. let i = 0
  1184. const l2 = c2.length
  1185. let e1 = c1.length - 1 // prev ending index
  1186. let e2 = l2 - 1 // next ending index
  1187. // 1. sync from start
  1188. // (a b) c
  1189. // (a b) d e
  1190. while (i <= e1 && i <= e2) {
  1191. const n1 = c1[i]
  1192. const n2 = (c2[i] = optimized
  1193. ? cloneIfMounted(c2[i] as HostVNode)
  1194. : normalizeVNode(c2[i]))
  1195. if (isSameVNodeType(n1, n2)) {
  1196. patch(
  1197. n1,
  1198. n2,
  1199. container,
  1200. parentAnchor,
  1201. parentComponent,
  1202. parentSuspense,
  1203. isSVG,
  1204. optimized
  1205. )
  1206. } else {
  1207. break
  1208. }
  1209. i++
  1210. }
  1211. // 2. sync from end
  1212. // a (b c)
  1213. // d e (b c)
  1214. while (i <= e1 && i <= e2) {
  1215. const n1 = c1[e1]
  1216. const n2 = (c2[e2] = optimized
  1217. ? cloneIfMounted(c2[e2] as HostVNode)
  1218. : normalizeVNode(c2[e2]))
  1219. if (isSameVNodeType(n1, n2)) {
  1220. patch(
  1221. n1,
  1222. n2,
  1223. container,
  1224. parentAnchor,
  1225. parentComponent,
  1226. parentSuspense,
  1227. isSVG,
  1228. optimized
  1229. )
  1230. } else {
  1231. break
  1232. }
  1233. e1--
  1234. e2--
  1235. }
  1236. // 3. common sequence + mount
  1237. // (a b)
  1238. // (a b) c
  1239. // i = 2, e1 = 1, e2 = 2
  1240. // (a b)
  1241. // c (a b)
  1242. // i = 0, e1 = -1, e2 = 0
  1243. if (i > e1) {
  1244. if (i <= e2) {
  1245. const nextPos = e2 + 1
  1246. const anchor =
  1247. nextPos < l2 ? (c2[nextPos] as HostVNode).el : parentAnchor
  1248. while (i <= e2) {
  1249. patch(
  1250. null,
  1251. (c2[i] = optimized
  1252. ? cloneIfMounted(c2[i] as HostVNode)
  1253. : normalizeVNode(c2[i])),
  1254. container,
  1255. anchor,
  1256. parentComponent,
  1257. parentSuspense,
  1258. isSVG
  1259. )
  1260. i++
  1261. }
  1262. }
  1263. }
  1264. // 4. common sequence + unmount
  1265. // (a b) c
  1266. // (a b)
  1267. // i = 2, e1 = 2, e2 = 1
  1268. // a (b c)
  1269. // (b c)
  1270. // i = 0, e1 = 0, e2 = -1
  1271. else if (i > e2) {
  1272. while (i <= e1) {
  1273. unmount(c1[i], parentComponent, parentSuspense, true)
  1274. i++
  1275. }
  1276. }
  1277. // 5. unknown sequence
  1278. // [i ... e1 + 1]: a b [c d e] f g
  1279. // [i ... e2 + 1]: a b [e d c h] f g
  1280. // i = 2, e1 = 4, e2 = 5
  1281. else {
  1282. const s1 = i // prev starting index
  1283. const s2 = i // next starting index
  1284. // 5.1 build key:index map for newChildren
  1285. const keyToNewIndexMap: Map<string | number, number> = new Map()
  1286. for (i = s2; i <= e2; i++) {
  1287. const nextChild = (c2[i] = optimized
  1288. ? cloneIfMounted(c2[i] as HostVNode)
  1289. : normalizeVNode(c2[i]))
  1290. if (nextChild.key != null) {
  1291. if (__DEV__ && keyToNewIndexMap.has(nextChild.key)) {
  1292. warn(
  1293. `Duplicate keys found during update:`,
  1294. JSON.stringify(nextChild.key),
  1295. `Make sure keys are unique.`
  1296. )
  1297. }
  1298. keyToNewIndexMap.set(nextChild.key, i)
  1299. }
  1300. }
  1301. // 5.2 loop through old children left to be patched and try to patch
  1302. // matching nodes & remove nodes that are no longer present
  1303. let j
  1304. let patched = 0
  1305. const toBePatched = e2 - s2 + 1
  1306. let moved = false
  1307. // used to track whether any node has moved
  1308. let maxNewIndexSoFar = 0
  1309. // works as Map<newIndex, oldIndex>
  1310. // Note that oldIndex is offset by +1
  1311. // and oldIndex = 0 is a special value indicating the new node has
  1312. // no corresponding old node.
  1313. // used for determining longest stable subsequence
  1314. const newIndexToOldIndexMap = new Array(toBePatched)
  1315. for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0
  1316. for (i = s1; i <= e1; i++) {
  1317. const prevChild = c1[i]
  1318. if (patched >= toBePatched) {
  1319. // all new children have been patched so this can only be a removal
  1320. unmount(prevChild, parentComponent, parentSuspense, true)
  1321. continue
  1322. }
  1323. let newIndex
  1324. if (prevChild.key != null) {
  1325. newIndex = keyToNewIndexMap.get(prevChild.key)
  1326. } else {
  1327. // key-less node, try to locate a key-less node of the same type
  1328. for (j = s2; j <= e2; j++) {
  1329. if (
  1330. newIndexToOldIndexMap[j - s2] === 0 &&
  1331. isSameVNodeType(prevChild, c2[j] as HostVNode)
  1332. ) {
  1333. newIndex = j
  1334. break
  1335. }
  1336. }
  1337. }
  1338. if (newIndex === undefined) {
  1339. unmount(prevChild, parentComponent, parentSuspense, true)
  1340. } else {
  1341. newIndexToOldIndexMap[newIndex - s2] = i + 1
  1342. if (newIndex >= maxNewIndexSoFar) {
  1343. maxNewIndexSoFar = newIndex
  1344. } else {
  1345. moved = true
  1346. }
  1347. patch(
  1348. prevChild,
  1349. c2[newIndex] as HostVNode,
  1350. container,
  1351. null,
  1352. parentComponent,
  1353. parentSuspense,
  1354. isSVG,
  1355. optimized
  1356. )
  1357. patched++
  1358. }
  1359. }
  1360. // 5.3 move and mount
  1361. // generate longest stable subsequence only when nodes have moved
  1362. const increasingNewIndexSequence = moved
  1363. ? getSequence(newIndexToOldIndexMap)
  1364. : EMPTY_ARR
  1365. j = increasingNewIndexSequence.length - 1
  1366. // looping backwards so that we can use last patched node as anchor
  1367. for (i = toBePatched - 1; i >= 0; i--) {
  1368. const nextIndex = s2 + i
  1369. const nextChild = c2[nextIndex] as HostVNode
  1370. const anchor =
  1371. nextIndex + 1 < l2
  1372. ? (c2[nextIndex + 1] as HostVNode).el
  1373. : parentAnchor
  1374. if (newIndexToOldIndexMap[i] === 0) {
  1375. // mount new
  1376. patch(
  1377. null,
  1378. nextChild,
  1379. container,
  1380. anchor,
  1381. parentComponent,
  1382. parentSuspense,
  1383. isSVG
  1384. )
  1385. } else if (moved) {
  1386. // move if:
  1387. // There is no stable subsequence (e.g. a reverse)
  1388. // OR current node is not among the stable sequence
  1389. if (j < 0 || i !== increasingNewIndexSequence[j]) {
  1390. move(nextChild, container, anchor, MoveType.REORDER)
  1391. } else {
  1392. j--
  1393. }
  1394. }
  1395. }
  1396. }
  1397. }
  1398. function move(
  1399. vnode: HostVNode,
  1400. container: HostElement,
  1401. anchor: HostNode | null,
  1402. type: MoveType,
  1403. parentSuspense: HostSuspenseBoundary | null = null
  1404. ) {
  1405. if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
  1406. move(vnode.component!.subTree, container, anchor, type)
  1407. return
  1408. }
  1409. if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
  1410. vnode.suspense!.move(container, anchor, type)
  1411. return
  1412. }
  1413. if (vnode.type === Fragment) {
  1414. hostInsert(vnode.el!, container, anchor)
  1415. const children = vnode.children as HostVNode[]
  1416. for (let i = 0; i < children.length; i++) {
  1417. move(children[i], container, anchor, type)
  1418. }
  1419. hostInsert(vnode.anchor!, container, anchor)
  1420. } else {
  1421. // Plain element
  1422. const { el, transition, shapeFlag } = vnode
  1423. const needTransition =
  1424. type !== MoveType.REORDER &&
  1425. shapeFlag & ShapeFlags.ELEMENT &&
  1426. transition != null
  1427. if (needTransition) {
  1428. if (type === MoveType.ENTER) {
  1429. transition!.beforeEnter(el!)
  1430. hostInsert(el!, container, anchor)
  1431. queuePostRenderEffect(() => transition!.enter(el!), parentSuspense)
  1432. } else {
  1433. const { leave, delayLeave, afterLeave } = transition!
  1434. const remove = () => hostInsert(el!, container, anchor)
  1435. const performLeave = () => {
  1436. leave(el!, () => {
  1437. remove()
  1438. afterLeave && afterLeave()
  1439. })
  1440. }
  1441. if (delayLeave) {
  1442. delayLeave(el!, remove, performLeave)
  1443. } else {
  1444. performLeave()
  1445. }
  1446. }
  1447. } else {
  1448. hostInsert(el!, container, anchor)
  1449. }
  1450. }
  1451. }
  1452. function unmount(
  1453. vnode: HostVNode,
  1454. parentComponent: ComponentInternalInstance | null,
  1455. parentSuspense: HostSuspenseBoundary | null,
  1456. doRemove?: boolean
  1457. ) {
  1458. const { props, ref, children, dynamicChildren, shapeFlag } = vnode
  1459. // unset ref
  1460. if (ref !== null && parentComponent !== null) {
  1461. setRef(ref, null, parentComponent, null)
  1462. }
  1463. if (shapeFlag & ShapeFlags.COMPONENT) {
  1464. if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
  1465. ;(parentComponent!.sink as KeepAliveSink).deactivate(vnode)
  1466. } else {
  1467. unmountComponent(vnode.component!, parentSuspense, doRemove)
  1468. }
  1469. return
  1470. }
  1471. if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
  1472. vnode.suspense!.unmount(parentSuspense, doRemove)
  1473. return
  1474. }
  1475. if (props != null && props.onVnodeBeforeUnmount != null) {
  1476. invokeDirectiveHook(props.onVnodeBeforeUnmount, parentComponent, vnode)
  1477. }
  1478. if (dynamicChildren != null) {
  1479. // fast path for block nodes: only need to unmount dynamic children.
  1480. unmountChildren(dynamicChildren, parentComponent, parentSuspense)
  1481. } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  1482. unmountChildren(children as HostVNode[], parentComponent, parentSuspense)
  1483. }
  1484. if (doRemove) {
  1485. remove(vnode)
  1486. }
  1487. if (props != null && props.onVnodeUnmounted != null) {
  1488. queuePostRenderEffect(() => {
  1489. invokeDirectiveHook(props.onVnodeUnmounted!, parentComponent, vnode)
  1490. }, parentSuspense)
  1491. }
  1492. }
  1493. function remove(vnode: HostVNode) {
  1494. const { type, el, anchor, transition } = vnode
  1495. if (type === Fragment) {
  1496. removeFragment(el!, anchor!)
  1497. return
  1498. }
  1499. const performRemove = () => {
  1500. hostRemove(el!)
  1501. if (
  1502. transition != null &&
  1503. !transition.persisted &&
  1504. transition.afterLeave
  1505. ) {
  1506. transition.afterLeave()
  1507. }
  1508. }
  1509. if (
  1510. vnode.shapeFlag & ShapeFlags.ELEMENT &&
  1511. transition != null &&
  1512. !transition.persisted
  1513. ) {
  1514. const { leave, delayLeave } = transition
  1515. const performLeave = () => leave(el!, performRemove)
  1516. if (delayLeave) {
  1517. delayLeave(vnode.el!, performRemove, performLeave)
  1518. } else {
  1519. performLeave()
  1520. }
  1521. } else {
  1522. performRemove()
  1523. }
  1524. }
  1525. function removeFragment(cur: HostNode, end: HostNode) {
  1526. // For fragments, directly remove all contained DOM nodes.
  1527. // (fragment child nodes cannot have transition)
  1528. let next
  1529. while (cur !== end) {
  1530. next = hostNextSibling(cur)!
  1531. hostRemove(cur)
  1532. cur = next
  1533. }
  1534. hostRemove(end)
  1535. }
  1536. function unmountComponent(
  1537. instance: ComponentInternalInstance,
  1538. parentSuspense: HostSuspenseBoundary | null,
  1539. doRemove?: boolean
  1540. ) {
  1541. if (__HMR__ && instance.type.__hmrId != null) {
  1542. unregisterHMR(instance)
  1543. }
  1544. const { bum, effects, update, subTree, um, da, isDeactivated } = instance
  1545. // beforeUnmount hook
  1546. if (bum !== null) {
  1547. invokeHooks(bum)
  1548. }
  1549. if (effects !== null) {
  1550. for (let i = 0; i < effects.length; i++) {
  1551. stop(effects[i])
  1552. }
  1553. }
  1554. // update may be null if a component is unmounted before its async
  1555. // setup has resolved.
  1556. if (update !== null) {
  1557. stop(update)
  1558. unmount(subTree, instance, parentSuspense, doRemove)
  1559. }
  1560. // unmounted hook
  1561. if (um !== null) {
  1562. queuePostRenderEffect(um, parentSuspense)
  1563. }
  1564. // deactivated hook
  1565. if (
  1566. da !== null &&
  1567. !isDeactivated &&
  1568. instance.vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
  1569. ) {
  1570. queuePostRenderEffect(da, parentSuspense)
  1571. }
  1572. queuePostFlushCb(() => {
  1573. instance.isUnmounted = true
  1574. })
  1575. // A component with async dep inside a pending suspense is unmounted before
  1576. // its async dep resolves. This should remove the dep from the suspense, and
  1577. // cause the suspense to resolve immediately if that was the last dep.
  1578. if (
  1579. __FEATURE_SUSPENSE__ &&
  1580. parentSuspense !== null &&
  1581. !parentSuspense.isResolved &&
  1582. !parentSuspense.isUnmounted &&
  1583. instance.asyncDep !== null &&
  1584. !instance.asyncResolved
  1585. ) {
  1586. parentSuspense.deps--
  1587. if (parentSuspense.deps === 0) {
  1588. parentSuspense.resolve()
  1589. }
  1590. }
  1591. }
  1592. function unmountChildren(
  1593. children: HostVNode[],
  1594. parentComponent: ComponentInternalInstance | null,
  1595. parentSuspense: HostSuspenseBoundary | null,
  1596. doRemove?: boolean,
  1597. start: number = 0
  1598. ) {
  1599. for (let i = start; i < children.length; i++) {
  1600. unmount(children[i], parentComponent, parentSuspense, doRemove)
  1601. }
  1602. }
  1603. function getNextHostNode(vnode: HostVNode): HostNode | null {
  1604. if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
  1605. return getNextHostNode(vnode.component!.subTree)
  1606. }
  1607. if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
  1608. return vnode.suspense!.next()
  1609. }
  1610. return hostNextSibling((vnode.anchor || vnode.el)!)
  1611. }
  1612. function setRef(
  1613. ref: string | Function | Ref,
  1614. oldRef: string | Function | Ref | null,
  1615. parent: ComponentInternalInstance,
  1616. value: HostNode | ComponentPublicInstance | null
  1617. ) {
  1618. const refs = parent.refs === EMPTY_OBJ ? (parent.refs = {}) : parent.refs
  1619. const renderContext = toRaw(parent.renderContext)
  1620. // unset old ref
  1621. if (oldRef !== null && oldRef !== ref) {
  1622. if (isString(oldRef)) {
  1623. refs[oldRef] = null
  1624. const oldSetupRef = renderContext[oldRef]
  1625. if (isRef(oldSetupRef)) {
  1626. oldSetupRef.value = null
  1627. }
  1628. } else if (isRef(oldRef)) {
  1629. oldRef.value = null
  1630. }
  1631. }
  1632. if (isString(ref)) {
  1633. const setupRef = renderContext[ref]
  1634. if (isRef(setupRef)) {
  1635. setupRef.value = value
  1636. }
  1637. refs[ref] = value
  1638. } else if (isRef(ref)) {
  1639. ref.value = value
  1640. } else if (isFunction(ref)) {
  1641. callWithErrorHandling(ref, parent, ErrorCodes.FUNCTION_REF, [value, refs])
  1642. } else if (__DEV__) {
  1643. warn('Invalid template ref type:', value, `(${typeof value})`)
  1644. }
  1645. }
  1646. type HostRootElement = HostElement & { _vnode: HostVNode | null }
  1647. const render: RootRenderFunction<HostNode, HostElement> = (
  1648. vnode,
  1649. container: HostRootElement
  1650. ) => {
  1651. if (vnode == null) {
  1652. if (container._vnode) {
  1653. unmount(container._vnode, null, null, true)
  1654. }
  1655. } else {
  1656. patch(container._vnode || null, vnode, container)
  1657. }
  1658. flushPostFlushCbs()
  1659. container._vnode = vnode
  1660. }
  1661. return {
  1662. render,
  1663. createApp: createAppAPI(render)
  1664. }
  1665. }
  1666. // https://en.wikipedia.org/wiki/Longest_increasing_subsequence
  1667. function getSequence(arr: number[]): number[] {
  1668. const p = arr.slice()
  1669. const result = [0]
  1670. let i, j, u, v, c
  1671. const len = arr.length
  1672. for (i = 0; i < len; i++) {
  1673. const arrI = arr[i]
  1674. if (arrI !== 0) {
  1675. j = result[result.length - 1]
  1676. if (arr[j] < arrI) {
  1677. p[i] = j
  1678. result.push(i)
  1679. continue
  1680. }
  1681. u = 0
  1682. v = result.length - 1
  1683. while (u < v) {
  1684. c = ((u + v) / 2) | 0
  1685. if (arr[result[c]] < arrI) {
  1686. u = c + 1
  1687. } else {
  1688. v = c
  1689. }
  1690. }
  1691. if (arrI < arr[result[u]]) {
  1692. if (u > 0) {
  1693. p[i] = result[u - 1]
  1694. }
  1695. result[u] = i
  1696. }
  1697. }
  1698. }
  1699. u = result.length
  1700. v = result[u - 1]
  1701. while (u-- > 0) {
  1702. result[u] = v
  1703. v = p[v]
  1704. }
  1705. return result
  1706. }