Skip to content

Commit a137da8

Browse files
authored
feat(sfc): add defineEmits and deprecate defineEmit (#3725)
1 parent 6b6d566 commit a137da8

File tree

8 files changed

+102
-54
lines changed

8 files changed

+102
-54
lines changed

packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap

+3-3
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ return { a }
5454
}"
5555
`;
5656
57-
exports[`SFC compile <script setup> defineEmit() 1`] = `
57+
exports[`SFC compile <script setup> defineEmits() 1`] = `
5858
"export default {
5959
expose: [],
6060
emits: ['foo', 'bar'],
@@ -720,7 +720,7 @@ return { a, b, c, d, x }
720720
}"
721721
`;
722722
723-
exports[`SFC compile <script setup> with TypeScript defineEmit w/ type (type literal w/ call signatures) 1`] = `
723+
exports[`SFC compile <script setup> with TypeScript defineEmits w/ type (type literal w/ call signatures) 1`] = `
724724
"import { defineComponent as _defineComponent } from 'vue'
725725
726726
@@ -741,7 +741,7 @@ return { emit }
741741
})"
742742
`;
743743
744-
exports[`SFC compile <script setup> with TypeScript defineEmit w/ type 1`] = `
744+
exports[`SFC compile <script setup> with TypeScript defineEmits w/ type 1`] = `
745745
"import { defineComponent as _defineComponent } from 'vue'
746746
747747

packages/compiler-sfc/__tests__/compileScript.spec.ts

+46-25
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const bar = 1
4949
},`)
5050
})
5151

52-
test('defineEmit()', () => {
52+
test('defineEmit() (deprecated)', () => {
5353
const { content, bindings } = compile(`
5454
<script setup>
5555
import { defineEmit } from 'vue'
@@ -61,7 +61,28 @@ const myEmit = defineEmit(['foo', 'bar'])
6161
myEmit: BindingTypes.SETUP_CONST
6262
})
6363
// should remove defineOptions import and call
64-
expect(content).not.toMatch('defineEmit')
64+
expect(content).not.toMatch(/defineEmits?/)
65+
// should generate correct setup signature
66+
expect(content).toMatch(`setup(__props, { emit: myEmit }) {`)
67+
// should include context options in default export
68+
expect(content).toMatch(`export default {
69+
expose: [],
70+
emits: ['foo', 'bar'],`)
71+
})
72+
73+
test('defineEmits()', () => {
74+
const { content, bindings } = compile(`
75+
<script setup>
76+
import { defineEmits } from 'vue'
77+
const myEmit = defineEmits(['foo', 'bar'])
78+
</script>
79+
`)
80+
assertCode(content)
81+
expect(bindings).toStrictEqual({
82+
myEmit: BindingTypes.SETUP_CONST
83+
})
84+
// should remove defineOptions import and call
85+
expect(content).not.toMatch('defineEmits')
6586
// should generate correct setup signature
6687
expect(content).toMatch(`setup(__props, { emit: myEmit }) {`)
6788
// should include context options in default export
@@ -145,9 +166,9 @@ const myEmit = defineEmit(['foo', 'bar'])
145166
test('should allow defineProps/Emit at the start of imports', () => {
146167
assertCode(
147168
compile(`<script setup>
148-
import { defineProps, defineEmit, ref } from 'vue'
169+
import { defineProps, defineEmits, ref } from 'vue'
149170
defineProps(['foo'])
150-
defineEmit(['bar'])
171+
defineEmits(['bar'])
151172
const r = ref(0)
152173
</script>`).content
153174
)
@@ -450,9 +471,9 @@ const myEmit = defineEmit(['foo', 'bar'])
450471
test('defineProps/Emit w/ runtime options', () => {
451472
const { content } = compile(`
452473
<script setup lang="ts">
453-
import { defineProps, defineEmit } from 'vue'
474+
import { defineProps, defineEmits } from 'vue'
454475
const props = defineProps({ foo: String })
455-
const emit = defineEmit(['a', 'b'])
476+
const emit = defineEmits(['a', 'b'])
456477
</script>
457478
`)
458479
assertCode(content)
@@ -549,36 +570,36 @@ const emit = defineEmit(['a', 'b'])
549570
})
550571
})
551572

552-
test('defineEmit w/ type', () => {
573+
test('defineEmits w/ type', () => {
553574
const { content } = compile(`
554575
<script setup lang="ts">
555-
import { defineEmit } from 'vue'
556-
const emit = defineEmit<(e: 'foo' | 'bar') => void>()
576+
import { defineEmits } from 'vue'
577+
const emit = defineEmits<(e: 'foo' | 'bar') => void>()
557578
</script>
558579
`)
559580
assertCode(content)
560581
expect(content).toMatch(`emit: ((e: 'foo' | 'bar') => void),`)
561582
expect(content).toMatch(`emits: ["foo", "bar"] as unknown as undefined`)
562583
})
563584

564-
test('defineEmit w/ type (union)', () => {
585+
test('defineEmits w/ type (union)', () => {
565586
const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`
566587
expect(() =>
567588
compile(`
568589
<script setup lang="ts">
569-
import { defineEmit } from 'vue'
570-
const emit = defineEmit<${type}>()
590+
import { defineEmits } from 'vue'
591+
const emit = defineEmits<${type}>()
571592
</script>
572593
`)
573594
).toThrow()
574595
})
575596

576-
test('defineEmit w/ type (type literal w/ call signatures)', () => {
597+
test('defineEmits w/ type (type literal w/ call signatures)', () => {
577598
const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}`
578599
const { content } = compile(`
579600
<script setup lang="ts">
580-
import { defineEmit } from 'vue'
581-
const emit = defineEmit<${type}>()
601+
import { defineEmits } from 'vue'
602+
const emit = defineEmits<${type}>()
582603
</script>
583604
`)
584605
assertCode(content)
@@ -906,8 +927,8 @@ const emit = defineEmit(['a', 'b'])
906927

907928
expect(() => {
908929
compile(`<script setup lang="ts">
909-
import { defineEmit } from 'vue'
910-
defineEmit<{}>({})
930+
import { defineEmits } from 'vue'
931+
defineEmits<{}>({})
911932
</script>`)
912933
}).toThrow(`cannot accept both type and non-type arguments`)
913934
})
@@ -927,9 +948,9 @@ const emit = defineEmit(['a', 'b'])
927948

928949
expect(() =>
929950
compile(`<script setup>
930-
import { defineEmit } from 'vue'
951+
import { defineEmits } from 'vue'
931952
const bar = 'hello'
932-
defineEmit([bar])
953+
defineEmits([bar])
933954
</script>`)
934955
).toThrow(`cannot reference locally declared variables`)
935956
})
@@ -947,9 +968,9 @@ const emit = defineEmit(['a', 'b'])
947968

948969
expect(() =>
949970
compile(`<script setup>
950-
import { defineEmit } from 'vue'
971+
import { defineEmits } from 'vue'
951972
ref: bar = 1
952-
defineEmit({
973+
defineEmits({
953974
bar
954975
})
955976
</script>`)
@@ -959,14 +980,14 @@ const emit = defineEmit(['a', 'b'])
959980
test('should allow defineProps/Emit() referencing scope var', () => {
960981
assertCode(
961982
compile(`<script setup>
962-
import { defineProps, defineEmit } from 'vue'
983+
import { defineProps, defineEmits } from 'vue'
963984
const bar = 1
964985
defineProps({
965986
foo: {
966987
default: bar => bar + 1
967988
}
968989
})
969-
defineEmit({
990+
defineEmits({
970991
foo: bar => bar > 1
971992
})
972993
</script>`).content
@@ -976,14 +997,14 @@ const emit = defineEmit(['a', 'b'])
976997
test('should allow defineProps/Emit() referencing imported binding', () => {
977998
assertCode(
978999
compile(`<script setup>
979-
import { defineProps, defineEmit } from 'vue'
1000+
import { defineProps, defineEmits } from 'vue'
9801001
import { bar } from './bar'
9811002
defineProps({
9821003
foo: {
9831004
default: () => bar
9841005
}
9851006
})
986-
defineEmit({
1007+
defineEmits({
9871008
foo: () => bar > 1
9881009
})
9891010
</script>`).content

packages/compiler-sfc/src/compileScript.ts

+16-11
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { rewriteDefault } from './rewriteDefault'
3636

3737
const DEFINE_PROPS = 'defineProps'
3838
const DEFINE_EMIT = 'defineEmit'
39+
const DEFINE_EMITS = 'defineEmits'
3940

4041
export interface SFCScriptCompileOptions {
4142
/**
@@ -286,10 +287,10 @@ export function compileScript(
286287
return false
287288
}
288289

289-
function processDefineEmit(node: Node): boolean {
290-
if (isCallOf(node, DEFINE_EMIT)) {
290+
function processDefineEmits(node: Node): boolean {
291+
if (isCallOf(node, DEFINE_EMIT) || isCallOf(node, DEFINE_EMITS)) {
291292
if (hasDefineEmitCall) {
292-
error(`duplicate ${DEFINE_EMIT}() call`, node)
293+
error(`duplicate ${DEFINE_EMITS}() call`, node)
293294
}
294295
hasDefineEmitCall = true
295296
emitRuntimeDecl = node.arguments[0]
@@ -309,7 +310,7 @@ export function compileScript(
309310
emitTypeDecl = typeArg
310311
} else {
311312
error(
312-
`type argument passed to ${DEFINE_EMIT}() must be a function type ` +
313+
`type argument passed to ${DEFINE_EMITS}() must be a function type ` +
313314
`or a literal type with call signatures.`,
314315
typeArg
315316
)
@@ -627,7 +628,9 @@ export function compileScript(
627628
const existing = userImports[local]
628629
if (
629630
source === 'vue' &&
630-
(imported === DEFINE_PROPS || imported === DEFINE_EMIT)
631+
(imported === DEFINE_PROPS ||
632+
imported === DEFINE_EMIT ||
633+
imported === DEFINE_EMITS)
631634
) {
632635
removeSpecifier(i)
633636
} else if (existing) {
@@ -651,11 +654,11 @@ export function compileScript(
651654
}
652655
}
653656

654-
// process `defineProps` and `defineEmit` calls
657+
// process `defineProps` and `defineEmit(s)` calls
655658
if (
656659
node.type === 'ExpressionStatement' &&
657660
(processDefineProps(node.expression) ||
658-
processDefineEmit(node.expression))
661+
processDefineEmits(node.expression))
659662
) {
660663
s.remove(node.start! + startOffset, node.end! + startOffset)
661664
}
@@ -669,14 +672,14 @@ export function compileScript(
669672
decl.id.end!
670673
)
671674
}
672-
const isDefineEmit = processDefineEmit(decl.init)
673-
if (isDefineEmit) {
675+
const isDefineEmits = processDefineEmits(decl.init)
676+
if (isDefineEmits) {
674677
emitIdentifier = scriptSetup.content.slice(
675678
decl.id.start!,
676679
decl.id.end!
677680
)
678681
}
679-
if (isDefineProps || isDefineEmit)
682+
if (isDefineProps || isDefineEmits)
680683
if (node.declarations.length === 1) {
681684
s.remove(node.start! + startOffset, node.end! + startOffset)
682685
} else {
@@ -1040,7 +1043,9 @@ function walkDeclaration(
10401043
for (const { id, init } of node.declarations) {
10411044
const isDefineCall = !!(
10421045
isConst &&
1043-
(isCallOf(init, DEFINE_PROPS) || isCallOf(init, DEFINE_EMIT))
1046+
(isCallOf(init, DEFINE_PROPS) ||
1047+
isCallOf(init, DEFINE_EMIT) ||
1048+
isCallOf(init, DEFINE_EMITS))
10441049
)
10451050
if (id.type === 'Identifier') {
10461051
let bindingType

packages/runtime-core/__tests__/apiSetupHelpers.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import {
55
render,
66
SetupContext
77
} from '@vue/runtime-test'
8-
import { defineEmit, defineProps, useContext } from '../src/apiSetupHelpers'
8+
import { defineEmits, defineProps, useContext } from '../src/apiSetupHelpers'
99

1010
describe('SFC <script setup> helpers', () => {
1111
test('should warn runtime usage', () => {
1212
defineProps()
1313
expect(`defineProps() is a compiler-hint`).toHaveBeenWarned()
1414

15-
defineEmit()
16-
expect(`defineEmit() is a compiler-hint`).toHaveBeenWarned()
15+
defineEmits()
16+
expect(`defineEmits() is a compiler-hint`).toHaveBeenWarned()
1717
})
1818

1919
test('useContext (no args)', () => {

packages/runtime-core/src/apiSetupHelpers.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -38,24 +38,29 @@ export function defineProps() {
3838
return null as any
3939
}
4040

41-
export function defineEmit<
41+
export function defineEmits<
4242
TypeEmit = undefined,
4343
E extends EmitsOptions = EmitsOptions,
4444
EE extends string = string,
4545
InferredEmit = EmitFn<E>
4646
>(emitOptions?: E | EE[]): TypeEmit extends undefined ? InferredEmit : TypeEmit
4747
// implementation
48-
export function defineEmit() {
48+
export function defineEmits() {
4949
if (__DEV__) {
5050
warn(
51-
`defineEmit() is a compiler-hint helper that is only usable inside ` +
51+
`defineEmits() is a compiler-hint helper that is only usable inside ` +
5252
`<script setup> of a single file component. Its arguments should be ` +
5353
`compiled away and passing it at runtime has no effect.`
5454
)
5555
}
5656
return null as any
5757
}
5858

59+
/**
60+
* @deprecated use `defineEmits` instead.
61+
*/
62+
export const defineEmit = defineEmits
63+
5964
export function useContext(): SetupContext {
6065
const i = getCurrentInstance()!
6166
if (__DEV__ && !i) {

packages/runtime-core/src/index.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ export { provide, inject } from './apiInject'
4444
export { nextTick } from './scheduler'
4545
export { defineComponent } from './apiDefineComponent'
4646
export { defineAsyncComponent } from './apiAsyncComponent'
47-
export { defineProps, defineEmit, useContext } from './apiSetupHelpers'
47+
export {
48+
defineProps,
49+
defineEmits,
50+
defineEmit,
51+
useContext
52+
} from './apiSetupHelpers'
4853

4954
// Advanced API ----------------------------------------------------------------
5055

packages/sfc-playground/src/codemirror/CodeMirror.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
</template>
44

55
<script setup lang="ts">
6-
import { ref, onMounted, defineProps, defineEmit, watchEffect } from 'vue'
6+
import { ref, onMounted, defineProps, defineEmits, watchEffect } from 'vue'
77
import { debounce } from '../utils'
88
import CodeMirror from './codemirror'
99
@@ -24,7 +24,7 @@ const props = defineProps({
2424
}
2525
})
2626
27-
const emit = defineEmit<(e: 'change', value: string) => void>()
27+
const emit = defineEmits<(e: 'change', value: string) => void>()
2828
2929
onMounted(() => {
3030
const addonOptions = {

0 commit comments

Comments
 (0)