createRenderer.ts 50 KB

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