]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(sfc): add `defineEmits` and deprecate `defineEmit` (#3725)
authorEduardo San Martin Morote <posva@users.noreply.github.com>
Tue, 22 Jun 2021 19:02:56 +0000 (21:02 +0200)
committerGitHub <noreply@github.com>
Tue, 22 Jun 2021 19:02:56 +0000 (15:02 -0400)
packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
packages/compiler-sfc/__tests__/compileScript.spec.ts
packages/compiler-sfc/src/compileScript.ts
packages/runtime-core/__tests__/apiSetupHelpers.spec.ts
packages/runtime-core/src/apiSetupHelpers.ts
packages/runtime-core/src/index.ts
packages/sfc-playground/src/codemirror/CodeMirror.vue
test-dts/setupHelpers.test-d.ts

index cb8e5ed28054942ea33924b6328c2831e6cf24fe..67e5856f2a8cc0e055408cbd615684f42db07b2b 100644 (file)
@@ -54,7 +54,7 @@ return { a }
 }"
 `;
 
-exports[`SFC compile <script setup> defineEmit() 1`] = `
+exports[`SFC compile <script setup> defineEmits() 1`] = `
 "export default {
   expose: [],
   emits: ['foo', 'bar'],
@@ -720,7 +720,7 @@ return { a, b, c, d, x }
 }"
 `;
 
-exports[`SFC compile <script setup> with TypeScript defineEmit w/ type (type literal w/ call signatures) 1`] = `
+exports[`SFC compile <script setup> with TypeScript defineEmits w/ type (type literal w/ call signatures) 1`] = `
 "import { defineComponent as _defineComponent } from 'vue'
 
       
@@ -741,7 +741,7 @@ return { emit }
 })"
 `;
 
-exports[`SFC compile <script setup> with TypeScript defineEmit w/ type 1`] = `
+exports[`SFC compile <script setup> with TypeScript defineEmits w/ type 1`] = `
 "import { defineComponent as _defineComponent } from 'vue'
 
       
index 54832fcb7d742135a4411b7b4220a0aa81fa1f20..cbe4427c7d3791585abea7c3063150c5252201ad 100644 (file)
@@ -49,7 +49,7 @@ const bar = 1
 },`)
   })
 
-  test('defineEmit()', () => {
+  test('defineEmit() (deprecated)', () => {
     const { content, bindings } = compile(`
 <script setup>
 import { defineEmit } from 'vue'
@@ -61,7 +61,28 @@ const myEmit = defineEmit(['foo', 'bar'])
       myEmit: BindingTypes.SETUP_CONST
     })
     // should remove defineOptions import and call
-    expect(content).not.toMatch('defineEmit')
+    expect(content).not.toMatch(/defineEmits?/)
+    // should generate correct setup signature
+    expect(content).toMatch(`setup(__props, { emit: myEmit }) {`)
+    // should include context options in default export
+    expect(content).toMatch(`export default {
+  expose: [],
+  emits: ['foo', 'bar'],`)
+  })
+
+  test('defineEmits()', () => {
+    const { content, bindings } = compile(`
+<script setup>
+import { defineEmits } from 'vue'
+const myEmit = defineEmits(['foo', 'bar'])
+</script>
+  `)
+    assertCode(content)
+    expect(bindings).toStrictEqual({
+      myEmit: BindingTypes.SETUP_CONST
+    })
+    // should remove defineOptions import and call
+    expect(content).not.toMatch('defineEmits')
     // should generate correct setup signature
     expect(content).toMatch(`setup(__props, { emit: myEmit }) {`)
     // should include context options in default export
@@ -145,9 +166,9 @@ const myEmit = defineEmit(['foo', 'bar'])
     test('should allow defineProps/Emit at the start of imports', () => {
       assertCode(
         compile(`<script setup>
-      import { defineProps, defineEmit, ref } from 'vue'
+      import { defineProps, defineEmits, ref } from 'vue'
       defineProps(['foo'])
-      defineEmit(['bar'])
+      defineEmits(['bar'])
       const r = ref(0)
       </script>`).content
       )
@@ -450,9 +471,9 @@ const myEmit = defineEmit(['foo', 'bar'])
     test('defineProps/Emit w/ runtime options', () => {
       const { content } = compile(`
 <script setup lang="ts">
-import { defineProps, defineEmit } from 'vue'
+import { defineProps, defineEmits } from 'vue'
 const props = defineProps({ foo: String })
-const emit = defineEmit(['a', 'b'])
+const emit = defineEmits(['a', 'b'])
 </script>
       `)
       assertCode(content)
@@ -549,11 +570,11 @@ const emit = defineEmit(['a', 'b'])
       })
     })
 
-    test('defineEmit w/ type', () => {
+    test('defineEmits w/ type', () => {
       const { content } = compile(`
       <script setup lang="ts">
-      import { defineEmit } from 'vue'
-      const emit = defineEmit<(e: 'foo' | 'bar') => void>()
+      import { defineEmits } from 'vue'
+      const emit = defineEmits<(e: 'foo' | 'bar') => void>()
       </script>
       `)
       assertCode(content)
@@ -561,24 +582,24 @@ const emit = defineEmit(['a', 'b'])
       expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
     })
 
-    test('defineEmit w/ type (union)', () => {
+    test('defineEmits w/ type (union)', () => {
       const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`
       expect(() =>
         compile(`
       <script setup lang="ts">
-      import { defineEmit } from 'vue'
-      const emit = defineEmit<${type}>()
+      import { defineEmits } from 'vue'
+      const emit = defineEmits<${type}>()
       </script>
       `)
       ).toThrow()
     })
 
-    test('defineEmit w/ type (type literal w/ call signatures)', () => {
+    test('defineEmits w/ type (type literal w/ call signatures)', () => {
       const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}`
       const { content } = compile(`
       <script setup lang="ts">
-      import { defineEmit } from 'vue'
-      const emit = defineEmit<${type}>()
+      import { defineEmits } from 'vue'
+      const emit = defineEmits<${type}>()
       </script>
       `)
       assertCode(content)
@@ -906,8 +927,8 @@ const emit = defineEmit(['a', 'b'])
 
       expect(() => {
         compile(`<script setup lang="ts">
-        import { defineEmit } from 'vue'
-        defineEmit<{}>({})
+        import { defineEmits } from 'vue'
+        defineEmits<{}>({})
         </script>`)
       }).toThrow(`cannot accept both type and non-type arguments`)
     })
@@ -927,9 +948,9 @@ const emit = defineEmit(['a', 'b'])
 
       expect(() =>
         compile(`<script setup>
-        import { defineEmit } from 'vue'
+        import { defineEmits } from 'vue'
         const bar = 'hello'
-        defineEmit([bar])
+        defineEmits([bar])
         </script>`)
       ).toThrow(`cannot reference locally declared variables`)
     })
@@ -947,9 +968,9 @@ const emit = defineEmit(['a', 'b'])
 
       expect(() =>
         compile(`<script setup>
-        import { defineEmit } from 'vue'
+        import { defineEmits } from 'vue'
         ref: bar = 1
-        defineEmit({
+        defineEmits({
           bar
         })
       </script>`)
@@ -959,14 +980,14 @@ const emit = defineEmit(['a', 'b'])
     test('should allow defineProps/Emit() referencing scope var', () => {
       assertCode(
         compile(`<script setup>
-          import { defineProps, defineEmit } from 'vue'
+          import { defineProps, defineEmits } from 'vue'
           const bar = 1
           defineProps({
             foo: {
               default: bar => bar + 1
             }
           })
-          defineEmit({
+          defineEmits({
             foo: bar => bar > 1
           })
         </script>`).content
@@ -976,14 +997,14 @@ const emit = defineEmit(['a', 'b'])
     test('should allow defineProps/Emit() referencing imported binding', () => {
       assertCode(
         compile(`<script setup>
-        import { defineProps, defineEmit } from 'vue'
+        import { defineProps, defineEmits } from 'vue'
         import { bar } from './bar'
         defineProps({
           foo: {
             default: () => bar
           }
         })
-        defineEmit({
+        defineEmits({
           foo: () => bar > 1
         })
         </script>`).content
index c29125e0e1ca30c5a5e949d75e2e349c932f130f..5789d67392d223d72efdc408370240b7685cd533 100644 (file)
@@ -36,6 +36,7 @@ import { rewriteDefault } from './rewriteDefault'
 
 const DEFINE_PROPS = 'defineProps'
 const DEFINE_EMIT = 'defineEmit'
+const DEFINE_EMITS = 'defineEmits'
 
 export interface SFCScriptCompileOptions {
   /**
@@ -286,10 +287,10 @@ export function compileScript(
     return false
   }
 
-  function processDefineEmit(node: Node): boolean {
-    if (isCallOf(node, DEFINE_EMIT)) {
+  function processDefineEmits(node: Node): boolean {
+    if (isCallOf(node, DEFINE_EMIT) || isCallOf(node, DEFINE_EMITS)) {
       if (hasDefineEmitCall) {
-        error(`duplicate ${DEFINE_EMIT}() call`, node)
+        error(`duplicate ${DEFINE_EMITS}() call`, node)
       }
       hasDefineEmitCall = true
       emitRuntimeDecl = node.arguments[0]
@@ -309,7 +310,7 @@ export function compileScript(
           emitTypeDecl = typeArg
         } else {
           error(
-            `type argument passed to ${DEFINE_EMIT}() must be a function type ` +
+            `type argument passed to ${DEFINE_EMITS}() must be a function type ` +
               `or a literal type with call signatures.`,
             typeArg
           )
@@ -627,7 +628,9 @@ export function compileScript(
         const existing = userImports[local]
         if (
           source === 'vue' &&
-          (imported === DEFINE_PROPS || imported === DEFINE_EMIT)
+          (imported === DEFINE_PROPS ||
+            imported === DEFINE_EMIT ||
+            imported === DEFINE_EMITS)
         ) {
           removeSpecifier(i)
         } else if (existing) {
@@ -651,11 +654,11 @@ export function compileScript(
       }
     }
 
-    // process `defineProps` and `defineEmit` calls
+    // process `defineProps` and `defineEmit(s)` calls
     if (
       node.type === 'ExpressionStatement' &&
       (processDefineProps(node.expression) ||
-        processDefineEmit(node.expression))
+        processDefineEmits(node.expression))
     ) {
       s.remove(node.start! + startOffset, node.end! + startOffset)
     }
@@ -669,14 +672,14 @@ export function compileScript(
               decl.id.end!
             )
           }
-          const isDefineEmit = processDefineEmit(decl.init)
-          if (isDefineEmit) {
+          const isDefineEmits = processDefineEmits(decl.init)
+          if (isDefineEmits) {
             emitIdentifier = scriptSetup.content.slice(
               decl.id.start!,
               decl.id.end!
             )
           }
-          if (isDefineProps || isDefineEmit)
+          if (isDefineProps || isDefineEmits)
             if (node.declarations.length === 1) {
               s.remove(node.start! + startOffset, node.end! + startOffset)
             } else {
@@ -1040,7 +1043,9 @@ function walkDeclaration(
     for (const { id, init } of node.declarations) {
       const isDefineCall = !!(
         isConst &&
-        (isCallOf(init, DEFINE_PROPS) || isCallOf(init, DEFINE_EMIT))
+        (isCallOf(init, DEFINE_PROPS) ||
+          isCallOf(init, DEFINE_EMIT) ||
+          isCallOf(init, DEFINE_EMITS))
       )
       if (id.type === 'Identifier') {
         let bindingType
index 3c0e228bec4ee0b6b001c561bd9f4fa2764b9799..12a8c67bb574a5d867e0511dae6c50ff345cb691 100644 (file)
@@ -5,15 +5,15 @@ import {
   render,
   SetupContext
 } from '@vue/runtime-test'
-import { defineEmit, defineProps, useContext } from '../src/apiSetupHelpers'
+import { defineEmits, defineProps, useContext } from '../src/apiSetupHelpers'
 
 describe('SFC <script setup> helpers', () => {
   test('should warn runtime usage', () => {
     defineProps()
     expect(`defineProps() is a compiler-hint`).toHaveBeenWarned()
 
-    defineEmit()
-    expect(`defineEmit() is a compiler-hint`).toHaveBeenWarned()
+    defineEmits()
+    expect(`defineEmits() is a compiler-hint`).toHaveBeenWarned()
   })
 
   test('useContext (no args)', () => {
index 7858bb39d9b6a2838ae15dbb7d9ee59993f34415..03955277a0da04f862891f0bc565b8a715a16ffc 100644 (file)
@@ -38,17 +38,17 @@ export function defineProps() {
   return null as any
 }
 
-export function defineEmit<
+export function defineEmits<
   TypeEmit = undefined,
   E extends EmitsOptions = EmitsOptions,
   EE extends string = string,
   InferredEmit = EmitFn<E>
 >(emitOptions?: E | EE[]): TypeEmit extends undefined ? InferredEmit : TypeEmit
 // implementation
-export function defineEmit() {
+export function defineEmits() {
   if (__DEV__) {
     warn(
-      `defineEmit() is a compiler-hint helper that is only usable inside ` +
+      `defineEmits() is a compiler-hint helper that is only usable inside ` +
         `<script setup> of a single file component. Its arguments should be ` +
         `compiled away and passing it at runtime has no effect.`
     )
@@ -56,6 +56,11 @@ export function defineEmit() {
   return null as any
 }
 
+/**
+ * @deprecated use `defineEmits` instead.
+ */
+export const defineEmit = defineEmits
+
 export function useContext(): SetupContext {
   const i = getCurrentInstance()!
   if (__DEV__ && !i) {
index bb90574d07169ac83e99a435bfcda7356cbb62f8..e7140f3efbc6e02114ae4dc4e571a4d5100cf2b3 100644 (file)
@@ -44,7 +44,12 @@ export { provide, inject } from './apiInject'
 export { nextTick } from './scheduler'
 export { defineComponent } from './apiDefineComponent'
 export { defineAsyncComponent } from './apiAsyncComponent'
-export { defineProps, defineEmit, useContext } from './apiSetupHelpers'
+export {
+  defineProps,
+  defineEmits,
+  defineEmit,
+  useContext
+} from './apiSetupHelpers'
 
 // Advanced API ----------------------------------------------------------------
 
index 9631182d1282b9792eca3df51907b0953fe66b38..d9823c305ee34834080d19969b0fe1496d00d9b0 100644 (file)
@@ -3,7 +3,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, defineProps, defineEmit, watchEffect } from 'vue'
+import { ref, onMounted, defineProps, defineEmits, watchEffect } from 'vue'
 import { debounce } from '../utils'
 import CodeMirror from './codemirror'
 
@@ -24,7 +24,7 @@ const props = defineProps({
   }
 })
 
-const emit = defineEmit<(e: 'change', value: string) => void>()
+const emit = defineEmits<(e: 'change', value: string) => void>()
 
 onMounted(() => {
   const addonOptions = {
index 53d073fa616da09e96670591d15cc1075b19f89c..fc4d801a510e8c305115019d050b52af7cce0d6e 100644 (file)
@@ -2,6 +2,7 @@ import {
   expectType,
   defineProps,
   defineEmit,
+  defineEmits,
   useContext,
   Slots,
   describe
@@ -49,8 +50,8 @@ describe('defineProps w/ runtime declaration', () => {
   props2.baz
 })
 
-describe('defineEmit w/ type declaration', () => {
-  const emit = defineEmit<(e: 'change') => void>()
+describe('defineEmits w/ type declaration', () => {
+  const emit = defineEmits<(e: 'change') => void>()
   emit('change')
   // @ts-expect-error
   emit()
@@ -58,7 +59,7 @@ describe('defineEmit w/ type declaration', () => {
   emit('bar')
 
   type Emits = { (e: 'foo' | 'bar'): void; (e: 'baz', id: number): void }
-  const emit2 = defineEmit<Emits>()
+  const emit2 = defineEmits<Emits>()
 
   emit2('foo')
   emit2('bar')
@@ -67,8 +68,8 @@ describe('defineEmit w/ type declaration', () => {
   emit2('baz')
 })
 
-describe('defineEmit w/ runtime declaration', () => {
-  const emit = defineEmit({
+describe('defineEmits w/ runtime declaration', () => {
+  const emit = defineEmits({
     foo: () => {},
     bar: null
   })
@@ -77,13 +78,24 @@ describe('defineEmit w/ runtime declaration', () => {
   // @ts-expect-error
   emit('baz')
 
-  const emit2 = defineEmit(['foo', 'bar'])
+  const emit2 = defineEmits(['foo', 'bar'])
   emit2('foo')
   emit2('bar', 123)
   // @ts-expect-error
   emit2('baz')
 })
 
+describe('deprecated defineEmit', () => {
+  const emit = defineEmit({
+    foo: () => {},
+    bar: null
+  })
+  emit('foo')
+  emit('bar', 123)
+  // @ts-expect-error
+  emit('baz')
+})
+
 describe('useContext', () => {
   const { attrs, emit, slots } = useContext()
   expectType<Record<string, unknown>>(attrs)