renderFn.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. import {
  2. extend,
  3. hyphenate,
  4. isArray,
  5. isObject,
  6. isString,
  7. makeMap,
  8. normalizeClass,
  9. normalizeStyle,
  10. ShapeFlags,
  11. toHandlerKey
  12. } from '@vue/shared'
  13. import {
  14. Component,
  15. ComponentInternalInstance,
  16. ComponentOptions,
  17. Data,
  18. InternalRenderFunction
  19. } from '../component'
  20. import { currentRenderingInstance } from '../componentRenderContext'
  21. import { DirectiveArguments, withDirectives } from '../directives'
  22. import {
  23. resolveDirective,
  24. resolveDynamicComponent
  25. } from '../helpers/resolveAssets'
  26. import {
  27. Comment,
  28. createVNode,
  29. isVNode,
  30. normalizeChildren,
  31. VNode,
  32. VNodeArrayChildren,
  33. VNodeProps
  34. } from '../vnode'
  35. import {
  36. checkCompatEnabled,
  37. DeprecationTypes,
  38. isCompatEnabled
  39. } from './compatConfig'
  40. import { compatModelEventPrefix } from './componentVModel'
  41. const v3CompiledRenderFnRE = /^(?:function \w*)?\(_ctx, _cache/
  42. export function convertLegacyRenderFn(instance: ComponentInternalInstance) {
  43. const Component = instance.type as ComponentOptions
  44. const render = Component.render as InternalRenderFunction | undefined
  45. // v3 runtime compiled, or already checked / wrapped
  46. if (!render || render._rc || render._compatChecked || render._compatWrapped) {
  47. return
  48. }
  49. if (v3CompiledRenderFnRE.test(render.toString())) {
  50. // v3 pre-compiled function
  51. render._compatChecked = true
  52. return
  53. }
  54. // v2 render function, try to provide compat
  55. if (checkCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance)) {
  56. const wrapped = (Component.render = function compatRender() {
  57. // @ts-ignore
  58. return render.call(this, compatH)
  59. })
  60. // @ts-ignore
  61. wrapped._compatWrapped = true
  62. }
  63. }
  64. interface LegacyVNodeProps {
  65. key?: string | number
  66. ref?: string
  67. refInFor?: boolean
  68. staticClass?: string
  69. class?: unknown
  70. staticStyle?: Record<string, unknown>
  71. style?: Record<string, unknown>
  72. attrs?: Record<string, unknown>
  73. domProps?: Record<string, unknown>
  74. on?: Record<string, Function | Function[]>
  75. nativeOn?: Record<string, Function | Function[]>
  76. directives?: LegacyVNodeDirective[]
  77. // component only
  78. props?: Record<string, unknown>
  79. slot?: string
  80. scopedSlots?: Record<string, Function>
  81. model?: {
  82. value: any
  83. callback: (v: any) => void
  84. expression: string
  85. }
  86. }
  87. interface LegacyVNodeDirective {
  88. name: string
  89. value: unknown
  90. arg?: string
  91. modifiers?: Record<string, boolean>
  92. }
  93. type LegacyVNodeChildren =
  94. | string
  95. | number
  96. | boolean
  97. | VNode
  98. | VNodeArrayChildren
  99. export function compatH(
  100. type: string | Component,
  101. children?: LegacyVNodeChildren
  102. ): VNode
  103. export function compatH(
  104. type: string | Component,
  105. props?: Data & LegacyVNodeProps,
  106. children?: LegacyVNodeChildren
  107. ): VNode
  108. export function compatH(
  109. type: any,
  110. propsOrChildren?: any,
  111. children?: any
  112. ): VNode {
  113. if (!type) {
  114. type = Comment
  115. }
  116. // to support v2 string component name look!up
  117. if (typeof type === 'string') {
  118. const t = hyphenate(type)
  119. if (t === 'transition' || t === 'transition-group' || t === 'keep-alive') {
  120. // since transition and transition-group are runtime-dom-specific,
  121. // we cannot import them directly here. Instead they are registered using
  122. // special keys in @vue/compat entry.
  123. type = `__compat__${t}`
  124. }
  125. type = resolveDynamicComponent(type)
  126. }
  127. const l = arguments.length
  128. const is2ndArgArrayChildren = isArray(propsOrChildren)
  129. if (l === 2 || is2ndArgArrayChildren) {
  130. if (isObject(propsOrChildren) && !is2ndArgArrayChildren) {
  131. // single vnode without props
  132. if (isVNode(propsOrChildren)) {
  133. return convertLegacySlots(createVNode(type, null, [propsOrChildren]))
  134. }
  135. // props without children
  136. return convertLegacySlots(
  137. convertLegacyDirectives(
  138. createVNode(type, convertLegacyProps(propsOrChildren, type)),
  139. propsOrChildren
  140. )
  141. )
  142. } else {
  143. // omit props
  144. return convertLegacySlots(createVNode(type, null, propsOrChildren))
  145. }
  146. } else {
  147. if (isVNode(children)) {
  148. children = [children]
  149. }
  150. return convertLegacySlots(
  151. convertLegacyDirectives(
  152. createVNode(type, convertLegacyProps(propsOrChildren, type), children),
  153. propsOrChildren
  154. )
  155. )
  156. }
  157. }
  158. const skipLegacyRootLevelProps = /*#__PURE__*/ makeMap(
  159. 'staticStyle,staticClass,directives,model,hook'
  160. )
  161. function convertLegacyProps(
  162. legacyProps: LegacyVNodeProps | undefined,
  163. type: any
  164. ): Data & VNodeProps | null {
  165. if (!legacyProps) {
  166. return null
  167. }
  168. const converted: Data & VNodeProps = {}
  169. for (const key in legacyProps) {
  170. if (key === 'attrs' || key === 'domProps' || key === 'props') {
  171. extend(converted, legacyProps[key])
  172. } else if (key === 'on' || key === 'nativeOn') {
  173. const listeners = legacyProps[key]
  174. for (const event in listeners) {
  175. let handlerKey = convertLegacyEventKey(event)
  176. if (key === 'nativeOn') handlerKey += `Native`
  177. const existing = converted[handlerKey]
  178. const incoming = listeners[event]
  179. if (existing !== incoming) {
  180. if (existing) {
  181. converted[handlerKey] = [].concat(existing as any, incoming as any)
  182. } else {
  183. converted[handlerKey] = incoming
  184. }
  185. }
  186. }
  187. } else if (!skipLegacyRootLevelProps(key)) {
  188. converted[key] = legacyProps[key as keyof LegacyVNodeProps]
  189. }
  190. }
  191. if (legacyProps.staticClass) {
  192. converted.class = normalizeClass([legacyProps.staticClass, converted.class])
  193. }
  194. if (legacyProps.staticStyle) {
  195. converted.style = normalizeStyle([legacyProps.staticStyle, converted.style])
  196. }
  197. if (legacyProps.model && isObject(type)) {
  198. // v2 compiled component v-model
  199. const { prop = 'value', event = 'input' } = (type as any).model || {}
  200. converted[prop] = legacyProps.model.value
  201. converted[compatModelEventPrefix + event] = legacyProps.model.callback
  202. }
  203. return converted
  204. }
  205. function convertLegacyEventKey(event: string): string {
  206. // normalize v2 event prefixes
  207. if (event[0] === '&') {
  208. event = event.slice(1) + 'Passive'
  209. }
  210. if (event[0] === '~') {
  211. event = event.slice(1) + 'Once'
  212. }
  213. if (event[0] === '!') {
  214. event = event.slice(1) + 'Capture'
  215. }
  216. return toHandlerKey(event)
  217. }
  218. function convertLegacyDirectives(
  219. vnode: VNode,
  220. props?: LegacyVNodeProps
  221. ): VNode {
  222. if (props && props.directives) {
  223. return withDirectives(
  224. vnode,
  225. props.directives.map(({ name, value, arg, modifiers }) => {
  226. return [
  227. resolveDirective(name)!,
  228. value,
  229. arg,
  230. modifiers
  231. ] as DirectiveArguments[number]
  232. })
  233. )
  234. }
  235. return vnode
  236. }
  237. function convertLegacySlots(vnode: VNode): VNode {
  238. const { props, children } = vnode
  239. let slots: Record<string, any> | undefined
  240. if (vnode.shapeFlag & ShapeFlags.COMPONENT && isArray(children)) {
  241. slots = {}
  242. // check "slot" property on vnodes and turn them into v3 function slots
  243. for (let i = 0; i < children.length; i++) {
  244. const child = children[i]
  245. const slotName =
  246. (isVNode(child) && child.props && child.props.slot) || 'default'
  247. const slot = slots[slotName] || (slots[slotName] = [] as any[])
  248. if (isVNode(child) && child.type === 'template') {
  249. slot.push(child.children)
  250. } else {
  251. slot.push(child)
  252. }
  253. }
  254. if (slots) {
  255. for (const key in slots) {
  256. const slotChildren = slots[key]
  257. slots[key] = () => slotChildren
  258. slots[key]._nonScoped = true
  259. }
  260. }
  261. }
  262. const scopedSlots = props && props.scopedSlots
  263. if (scopedSlots) {
  264. delete props!.scopedSlots
  265. if (slots) {
  266. extend(slots, scopedSlots)
  267. } else {
  268. slots = scopedSlots
  269. }
  270. }
  271. if (slots) {
  272. normalizeChildren(vnode, slots)
  273. }
  274. return vnode
  275. }
  276. export function defineLegacyVNodeProperties(vnode: VNode) {
  277. /* istanbul ignore if */
  278. if (
  279. isCompatEnabled(
  280. DeprecationTypes.RENDER_FUNCTION,
  281. currentRenderingInstance,
  282. true /* enable for built-ins */
  283. ) &&
  284. isCompatEnabled(
  285. DeprecationTypes.PRIVATE_APIS,
  286. currentRenderingInstance,
  287. true /* enable for built-ins */
  288. )
  289. ) {
  290. const context = currentRenderingInstance
  291. const getInstance = () => vnode.component && vnode.component.proxy
  292. let componentOptions: any
  293. Object.defineProperties(vnode, {
  294. tag: { get: () => vnode.type },
  295. data: { get: () => vnode.props || {}, set: p => (vnode.props = p) },
  296. elm: { get: () => vnode.el },
  297. componentInstance: { get: getInstance },
  298. child: { get: getInstance },
  299. text: { get: () => (isString(vnode.children) ? vnode.children : null) },
  300. context: { get: () => context && context.proxy },
  301. componentOptions: {
  302. get: () => {
  303. if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
  304. if (componentOptions) {
  305. return componentOptions
  306. }
  307. return (componentOptions = {
  308. Ctor: vnode.type,
  309. propsData: vnode.props,
  310. children: vnode.children
  311. })
  312. }
  313. }
  314. }
  315. })
  316. }
  317. }