createRenderer.ts 52 KB

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