apiSetupHelpers.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. import {
  2. type IfAny,
  3. type LooseRequired,
  4. type Prettify,
  5. type UnionToIntersection,
  6. extend,
  7. isArray,
  8. isFunction,
  9. isPromise,
  10. } from '@vue/shared'
  11. import {
  12. type ComponentInternalInstance,
  13. type SetupContext,
  14. createSetupContext,
  15. getCurrentGenericInstance,
  16. isInSSRComponentSetup,
  17. setCurrentInstance,
  18. setInSSRSetupState,
  19. } from './component'
  20. import type { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits'
  21. import type {
  22. ComponentOptionsBase,
  23. ComponentOptionsMixin,
  24. ComputedOptions,
  25. MethodOptions,
  26. } from './componentOptions'
  27. import type {
  28. ComponentObjectPropsOptions,
  29. ComponentPropsOptions,
  30. ExtractPropTypes,
  31. PropOptions,
  32. } from './componentProps'
  33. import { warn } from './warning'
  34. import type { SlotsType, StrictUnwrapSlotsType } from './componentSlots'
  35. import type { Ref } from '@vue/reactivity'
  36. // dev only
  37. const warnRuntimeUsage = (method: string) =>
  38. warn(
  39. `${method}() is a compiler-hint helper that is only usable inside ` +
  40. `<script setup> of a single file component. Its arguments should be ` +
  41. `compiled away and passing it at runtime has no effect.`,
  42. )
  43. /**
  44. * Vue `<script setup>` compiler macro for declaring component props. The
  45. * expected argument is the same as the component `props` option.
  46. *
  47. * Example runtime declaration:
  48. * ```js
  49. * // using Array syntax
  50. * const props = defineProps(['foo', 'bar'])
  51. * // using Object syntax
  52. * const props = defineProps({
  53. * foo: String,
  54. * bar: {
  55. * type: Number,
  56. * required: true
  57. * }
  58. * })
  59. * ```
  60. *
  61. * Equivalent type-based declaration:
  62. * ```ts
  63. * // will be compiled into equivalent runtime declarations
  64. * const props = defineProps<{
  65. * foo?: string
  66. * bar: number
  67. * }>()
  68. * ```
  69. *
  70. * @see {@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}
  71. *
  72. * This is only usable inside `<script setup>`, is compiled away in the
  73. * output and should **not** be actually called at runtime.
  74. */
  75. // overload 1: runtime props w/ array
  76. export function defineProps<PropNames extends string = string>(
  77. props: PropNames[],
  78. ): Prettify<Readonly<{ [key in PropNames]?: any }>>
  79. // overload 2: runtime props w/ object
  80. export function defineProps<
  81. PP extends ComponentObjectPropsOptions = ComponentObjectPropsOptions,
  82. >(props: PP): Prettify<Readonly<ExtractPropTypes<PP>>>
  83. // overload 3: typed-based declaration
  84. export function defineProps<TypeProps>(): DefineProps<
  85. LooseRequired<TypeProps>,
  86. BooleanKey<TypeProps>
  87. >
  88. // implementation
  89. export function defineProps() {
  90. if (__DEV__) {
  91. warnRuntimeUsage(`defineProps`)
  92. }
  93. return null as any
  94. }
  95. export type DefineProps<T, BKeys extends keyof T> = Readonly<T> & {
  96. readonly [K in BKeys]-?: boolean
  97. }
  98. type BooleanKey<T, K extends keyof T = keyof T> = K extends any
  99. ? T[K] extends boolean | undefined
  100. ? T[K] extends never | undefined
  101. ? never
  102. : K
  103. : never
  104. : never
  105. /**
  106. * Vue `<script setup>` compiler macro for declaring a component's emitted
  107. * events. The expected argument is the same as the component `emits` option.
  108. *
  109. * Example runtime declaration:
  110. * ```js
  111. * const emit = defineEmits(['change', 'update'])
  112. * ```
  113. *
  114. * Example type-based declaration:
  115. * ```ts
  116. * const emit = defineEmits<{
  117. * // <eventName>: <expected arguments>
  118. * change: []
  119. * update: [value: number] // named tuple syntax
  120. * }>()
  121. *
  122. * emit('change')
  123. * emit('update', 1)
  124. * ```
  125. *
  126. * This is only usable inside `<script setup>`, is compiled away in the
  127. * output and should **not** be actually called at runtime.
  128. *
  129. * @see {@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}
  130. */
  131. // overload 1: runtime emits w/ array
  132. export function defineEmits<EE extends string = string>(
  133. emitOptions: EE[],
  134. ): EmitFn<EE[]>
  135. export function defineEmits<E extends EmitsOptions = EmitsOptions>(
  136. emitOptions: E,
  137. ): EmitFn<E>
  138. export function defineEmits<T extends ComponentTypeEmits>(): T extends (
  139. ...args: any[]
  140. ) => any
  141. ? T
  142. : ShortEmits<T>
  143. // implementation
  144. export function defineEmits() {
  145. if (__DEV__) {
  146. warnRuntimeUsage(`defineEmits`)
  147. }
  148. return null as any
  149. }
  150. export type ComponentTypeEmits = ((...args: any[]) => any) | Record<string, any>
  151. type RecordToUnion<T extends Record<string, any>> = T[keyof T]
  152. type ShortEmits<T extends Record<string, any>> = UnionToIntersection<
  153. RecordToUnion<{
  154. [K in keyof T]: (evt: K, ...args: T[K]) => void
  155. }>
  156. >
  157. /**
  158. * Vue `<script setup>` compiler macro for declaring a component's exposed
  159. * instance properties when it is accessed by a parent component via template
  160. * refs.
  161. *
  162. * `<script setup>` components are closed by default - i.e. variables inside
  163. * the `<script setup>` scope is not exposed to parent unless explicitly exposed
  164. * via `defineExpose`.
  165. *
  166. * This is only usable inside `<script setup>`, is compiled away in the
  167. * output and should **not** be actually called at runtime.
  168. *
  169. * @see {@link https://vuejs.org/api/sfc-script-setup.html#defineexpose}
  170. */
  171. export function defineExpose<
  172. Exposed extends Record<string, any> = Record<string, any>,
  173. >(exposed?: Exposed): void {
  174. if (__DEV__) {
  175. warnRuntimeUsage(`defineExpose`)
  176. }
  177. }
  178. /**
  179. * Vue `<script setup>` compiler macro for declaring a component's additional
  180. * options. This should be used only for options that cannot be expressed via
  181. * Composition API - e.g. `inheritAttrs`.
  182. *
  183. * @see {@link https://vuejs.org/api/sfc-script-setup.html#defineoptions}
  184. */
  185. export function defineOptions<
  186. RawBindings = {},
  187. D = {},
  188. C extends ComputedOptions = {},
  189. M extends MethodOptions = {},
  190. Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
  191. Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
  192. >(
  193. options?: ComponentOptionsBase<
  194. {},
  195. RawBindings,
  196. D,
  197. C,
  198. M,
  199. Mixin,
  200. Extends,
  201. {}
  202. > & {
  203. /**
  204. * props should be defined via defineProps().
  205. */
  206. props?: never
  207. /**
  208. * emits should be defined via defineEmits().
  209. */
  210. emits?: never
  211. /**
  212. * expose should be defined via defineExpose().
  213. */
  214. expose?: never
  215. /**
  216. * slots should be defined via defineSlots().
  217. */
  218. slots?: never
  219. },
  220. ): void {
  221. if (__DEV__) {
  222. warnRuntimeUsage(`defineOptions`)
  223. }
  224. }
  225. /**
  226. * Vue `<script setup>` compiler macro for providing type hints to IDEs for
  227. * slot name and slot props type checking.
  228. *
  229. * Example usage:
  230. * ```ts
  231. * const slots = defineSlots<{
  232. * default(props: { msg: string }): any
  233. * }>()
  234. * ```
  235. *
  236. * This is only usable inside `<script setup>`, is compiled away in the
  237. * output and should **not** be actually called at runtime.
  238. *
  239. * @see {@link https://vuejs.org/api/sfc-script-setup.html#defineslots}
  240. */
  241. export function defineSlots<
  242. S extends Record<string, any> = Record<string, any>,
  243. >(): StrictUnwrapSlotsType<SlotsType<S>> {
  244. if (__DEV__) {
  245. warnRuntimeUsage(`defineSlots`)
  246. }
  247. return null as any
  248. }
  249. export type ModelRef<T, M extends PropertyKey = string, G = T, S = T> = Ref<
  250. G,
  251. S
  252. > &
  253. [ModelRef<T, M, G, S>, Record<M, true | undefined>]
  254. export type DefineModelOptions<T = any, G = T, S = T> = {
  255. get?: (v: T) => G
  256. set?: (v: S) => any
  257. }
  258. /**
  259. * Vue `<script setup>` compiler macro for declaring a
  260. * two-way binding prop that can be consumed via `v-model` from the parent
  261. * component. This will declare a prop with the same name and a corresponding
  262. * `update:propName` event.
  263. *
  264. * If the first argument is a string, it will be used as the prop name;
  265. * Otherwise the prop name will default to "modelValue". In both cases, you
  266. * can also pass an additional object which will be used as the prop's options.
  267. *
  268. * The returned ref behaves differently depending on whether the parent
  269. * provided the corresponding v-model props or not:
  270. * - If yes, the returned ref's value will always be in sync with the parent
  271. * prop.
  272. * - If not, the returned ref will behave like a normal local ref.
  273. *
  274. * @example
  275. * ```ts
  276. * // default model (consumed via `v-model`)
  277. * const modelValue = defineModel<string>()
  278. * modelValue.value = "hello"
  279. *
  280. * // default model with options
  281. * const modelValue = defineModel<string>({ required: true })
  282. *
  283. * // with specified name (consumed via `v-model:count`)
  284. * const count = defineModel<number>('count')
  285. * count.value++
  286. *
  287. * // with specified name and default value
  288. * const count = defineModel<number>('count', { default: 0 })
  289. * ```
  290. */
  291. export function defineModel<T, M extends PropertyKey = string, G = T, S = T>(
  292. options: ({ default: any } | { required: true }) &
  293. PropOptions<T> &
  294. DefineModelOptions<T, G, S>,
  295. ): ModelRef<T, M, G, S>
  296. export function defineModel<T, M extends PropertyKey = string, G = T, S = T>(
  297. options?: PropOptions<T> & DefineModelOptions<T, G, S>,
  298. ): ModelRef<T | undefined, M, G | undefined, S | undefined>
  299. export function defineModel<T, M extends PropertyKey = string, G = T, S = T>(
  300. name: string,
  301. options: ({ default: any } | { required: true }) &
  302. PropOptions<T> &
  303. DefineModelOptions<T, G, S>,
  304. ): ModelRef<T, M, G, S>
  305. export function defineModel<T, M extends PropertyKey = string, G = T, S = T>(
  306. name: string,
  307. options?: PropOptions<T> & DefineModelOptions<T, G, S>,
  308. ): ModelRef<T | undefined, M, G | undefined, S | undefined>
  309. export function defineModel(): any {
  310. if (__DEV__) {
  311. warnRuntimeUsage('defineModel')
  312. }
  313. }
  314. type NotUndefined<T> = T extends undefined ? never : T
  315. type MappedOmit<T, K extends keyof any> = {
  316. [P in keyof T as P extends K ? never : P]: T[P]
  317. }
  318. type InferDefaults<T> = {
  319. [K in keyof T]?: InferDefault<T, T[K]>
  320. }
  321. type NativeType =
  322. | null
  323. | undefined
  324. | number
  325. | string
  326. | boolean
  327. | symbol
  328. | Function
  329. type InferDefault<P, T> =
  330. | ((props: P) => T & {})
  331. | (T extends NativeType ? T : never)
  332. type PropsWithDefaults<
  333. T,
  334. Defaults extends InferDefaults<T>,
  335. BKeys extends keyof T,
  336. > = T extends unknown
  337. ? Readonly<MappedOmit<T, keyof Defaults>> & {
  338. readonly [K in keyof Defaults as K extends keyof T
  339. ? K
  340. : never]-?: K extends keyof T
  341. ? Defaults[K] extends undefined
  342. ? IfAny<Defaults[K], NotUndefined<T[K]>, T[K]>
  343. : NotUndefined<T[K]>
  344. : never
  345. } & {
  346. readonly [K in BKeys]-?: K extends keyof Defaults
  347. ? Defaults[K] extends undefined
  348. ? boolean | undefined
  349. : boolean
  350. : boolean
  351. }
  352. : never
  353. /**
  354. * Vue `<script setup>` compiler macro for providing props default values when
  355. * using type-based `defineProps` declaration.
  356. *
  357. * Example usage:
  358. * ```ts
  359. * withDefaults(defineProps<{
  360. * size?: number
  361. * labels?: string[]
  362. * }>(), {
  363. * size: 3,
  364. * labels: () => ['default label']
  365. * })
  366. * ```
  367. *
  368. * This is only usable inside `<script setup>`, is compiled away in the output
  369. * and should **not** be actually called at runtime.
  370. *
  371. * @see {@link https://vuejs.org/guide/typescript/composition-api.html#typing-component-props}
  372. */
  373. export function withDefaults<
  374. T,
  375. BKeys extends keyof T,
  376. Defaults extends InferDefaults<T>,
  377. >(
  378. props: DefineProps<T, BKeys>,
  379. defaults: Defaults,
  380. ): PropsWithDefaults<T, Defaults, BKeys> {
  381. if (__DEV__) {
  382. warnRuntimeUsage(`withDefaults`)
  383. }
  384. return null as any
  385. }
  386. // TODO return type for Vapor components
  387. export function useSlots(): SetupContext['slots'] {
  388. return getContext('useSlots').slots
  389. }
  390. export function useAttrs(): SetupContext['attrs'] {
  391. return getContext('useAttrs').attrs
  392. }
  393. function getContext(calledFunctionName: string): SetupContext {
  394. const i = getCurrentGenericInstance()!
  395. if (__DEV__ && !i) {
  396. warn(`${calledFunctionName}() called without active instance.`)
  397. }
  398. if (i.vapor) {
  399. return i as any // vapor instance act as its own setup context
  400. } else {
  401. const ii = i as ComponentInternalInstance
  402. return ii.setupContext || (ii.setupContext = createSetupContext(ii))
  403. }
  404. }
  405. /**
  406. * @internal
  407. */
  408. export function normalizePropsOrEmits(
  409. props: ComponentPropsOptions | EmitsOptions,
  410. ): ComponentObjectPropsOptions | ObjectEmitsOptions {
  411. return isArray(props)
  412. ? props.reduce(
  413. (normalized, p) => ((normalized[p] = null), normalized),
  414. {} as ComponentObjectPropsOptions | ObjectEmitsOptions,
  415. )
  416. : props
  417. }
  418. /**
  419. * Runtime helper for merging default declarations. Imported by compiled code
  420. * only.
  421. * @internal
  422. */
  423. export function mergeDefaults(
  424. raw: ComponentPropsOptions,
  425. defaults: Record<string, any>,
  426. ): ComponentObjectPropsOptions {
  427. const props = normalizePropsOrEmits(raw)
  428. for (const key in defaults) {
  429. if (key.startsWith('__skip')) continue
  430. let opt = props[key]
  431. if (opt) {
  432. if (isArray(opt) || isFunction(opt)) {
  433. opt = props[key] = { type: opt, default: defaults[key] }
  434. } else {
  435. opt.default = defaults[key]
  436. }
  437. } else if (opt === null) {
  438. opt = props[key] = { default: defaults[key] }
  439. } else if (__DEV__) {
  440. warn(`props default key "${key}" has no corresponding declaration.`)
  441. }
  442. if (opt && defaults[`__skip_${key}`]) {
  443. opt.skipFactory = true
  444. }
  445. }
  446. return props
  447. }
  448. /**
  449. * Runtime helper for merging model declarations.
  450. * Imported by compiled code only.
  451. * @internal
  452. */
  453. export function mergeModels(
  454. a: ComponentPropsOptions | EmitsOptions,
  455. b: ComponentPropsOptions | EmitsOptions,
  456. ): ComponentPropsOptions | EmitsOptions {
  457. if (!a || !b) return a || b
  458. if (isArray(a) && isArray(b)) return a.concat(b)
  459. return extend({}, normalizePropsOrEmits(a), normalizePropsOrEmits(b))
  460. }
  461. /**
  462. * Used to create a proxy for the rest element when destructuring props with
  463. * defineProps().
  464. * @internal
  465. */
  466. export function createPropsRestProxy(
  467. props: any,
  468. excludedKeys: string[],
  469. ): Record<string, any> {
  470. const ret: Record<string, any> = {}
  471. for (const key in props) {
  472. if (!excludedKeys.includes(key)) {
  473. Object.defineProperty(ret, key, {
  474. enumerable: true,
  475. get: () => props[key],
  476. })
  477. }
  478. }
  479. return ret
  480. }
  481. /**
  482. * `<script setup>` helper for persisting the current instance context over
  483. * async/await flows.
  484. *
  485. * `@vue/compiler-sfc` converts the following:
  486. *
  487. * ```ts
  488. * const x = await foo()
  489. * ```
  490. *
  491. * into:
  492. *
  493. * ```ts
  494. * let __temp, __restore
  495. * const x = (([__temp, __restore] = withAsyncContext(() => foo())),__temp=await __temp,__restore(),__temp)
  496. * ```
  497. * @internal
  498. */
  499. export function withAsyncContext(getAwaitable: () => any): [any, () => void] {
  500. const ctx = getCurrentGenericInstance()!
  501. const inSSRSetup = isInSSRComponentSetup
  502. if (__DEV__ && !ctx) {
  503. warn(
  504. `withAsyncContext called without active current instance. ` +
  505. `This is likely a bug.`,
  506. )
  507. }
  508. let awaitable = getAwaitable()
  509. setCurrentInstance(null, undefined)
  510. if (inSSRSetup) {
  511. setInSSRSetupState(false)
  512. }
  513. const restore = () => {
  514. setCurrentInstance(ctx)
  515. if (inSSRSetup) {
  516. setInSSRSetupState(true)
  517. }
  518. }
  519. // Never restore a captured "prev" instance here: in concurrent async setup
  520. // continuations it may belong to a sibling component and cause leaks.
  521. // clear global currentInstance for user microtasks.
  522. const cleanup = () => {
  523. setCurrentInstance(null, undefined)
  524. if (inSSRSetup) {
  525. setInSSRSetupState(false)
  526. }
  527. }
  528. if (isPromise(awaitable)) {
  529. awaitable = awaitable.catch(e => {
  530. restore()
  531. // Defer cleanup so the async function's catch continuation
  532. // still runs with the restored instance.
  533. Promise.resolve().then(() => Promise.resolve().then(cleanup))
  534. throw e
  535. })
  536. }
  537. return [
  538. awaitable,
  539. () => {
  540. restore()
  541. // Keep instance for the current continuation, then cleanup.
  542. Promise.resolve().then(cleanup)
  543. },
  544. ]
  545. }