fix(compiler-sfc): don't hoist props and emit (#8535)

fix #7805
close #7812
This commit is contained in:
三咲智子 Kevin Deng 2023-07-12 11:03:14 +08:00 committed by GitHub
parent 2a2810c716
commit 24db9516d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 260 additions and 255 deletions

View File

@ -62,6 +62,24 @@ return { fn }
})"
`;
exports[`SFC compile <script setup> > <script> and <script setup> co-usage > keep original semi style 1`] = `
"export default {
props: ['item'],
emits: ['change'],
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
console.log('test')
const props = __props;
const emit = __emit;
(function () {})()
return { props, emit }
}
}"
`;
exports[`SFC compile <script setup> > <script> and <script setup> co-usage > script first 1`] = `
"import { x } from './x'
@ -612,75 +630,6 @@ return { foo, bar, baz, y, z }
}"
`;
exports[`SFC compile <script setup> > defineProps/defineEmits in multi-variable declaration (full removal) 1`] = `
"export default {
props: ['item'],
emits: ['a'],
setup(__props, { expose: __expose, emit }) {
__expose();
const props = __props;
return { props, emit }
}
}"
`;
exports[`SFC compile <script setup> > defineProps/defineEmits in multi-variable declaration 1`] = `
"export default {
props: ['item'],
emits: ['a'],
setup(__props, { expose: __expose, emit }) {
__expose();
const props = __props;
const a = 1;
return { props, a, emit }
}
}"
`;
exports[`SFC compile <script setup> > defineProps/defineEmits in multi-variable declaration fix #6757 1`] = `
"export default {
props: ['item'],
emits: ['a'],
setup(__props, { expose: __expose, emit }) {
__expose();
const props = __props;
const a = 1;
return { a, props, emit }
}
}"
`;
exports[`SFC compile <script setup> > defineProps/defineEmits in multi-variable declaration fix #7422 1`] = `
"export default {
props: ['item'],
emits: ['foo'],
setup(__props, { expose: __expose, emit: emits }) {
__expose();
const props = __props;
const a = 0,
b = 0;
return { props, emits, a, b }
}
}"
`;
exports[`SFC compile <script setup> > dev mode import usage check > TS annotations 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
import { Foo, Bar, Baz, Qux, Fred } from './x'

View File

@ -68,64 +68,6 @@ describe('SFC compile <script setup>', () => {
assertCode(content)
})
test('defineProps/defineEmits in multi-variable declaration', () => {
const { content } = compile(`
<script setup>
const props = defineProps(['item']),
a = 1,
emit = defineEmits(['a']);
</script>
`)
assertCode(content)
expect(content).toMatch(`const a = 1;`) // test correct removal
expect(content).toMatch(`props: ['item'],`)
expect(content).toMatch(`emits: ['a'],`)
})
// #6757
test('defineProps/defineEmits in multi-variable declaration fix #6757 ', () => {
const { content } = compile(`
<script setup>
const a = 1,
props = defineProps(['item']),
emit = defineEmits(['a']);
</script>
`)
assertCode(content)
expect(content).toMatch(`const a = 1;`) // test correct removal
expect(content).toMatch(`props: ['item'],`)
expect(content).toMatch(`emits: ['a'],`)
})
// #7422
test('defineProps/defineEmits in multi-variable declaration fix #7422', () => {
const { content } = compile(`
<script setup>
const props = defineProps(['item']),
emits = defineEmits(['foo']),
a = 0,
b = 0;
</script>
`)
assertCode(content)
expect(content).toMatch(`props: ['item'],`)
expect(content).toMatch(`emits: ['foo'],`)
expect(content).toMatch(`const a = 0,`)
expect(content).toMatch(`b = 0;`)
})
test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
const { content } = compile(`
<script setup>
const props = defineProps(['item']),
emit = defineEmits(['a']);
</script>
`)
assertCode(content)
expect(content).toMatch(`props: ['item'],`)
expect(content).toMatch(`emits: ['a'],`)
})
describe('<script> and <script setup> co-usage', () => {
test('script first', () => {
const { content } = compile(`
@ -156,6 +98,24 @@ describe('SFC compile <script setup>', () => {
assertCode(content)
})
// #7805
test('keep original semi style', () => {
const { content } = compile(`
<script setup>
console.log('test')
const props = defineProps(['item']);
const emit = defineEmits(['change']);
(function () {})()
</script>
`)
assertCode(content)
expect(content).toMatch(`console.log('test')`)
expect(content).toMatch(`const props = __props;`)
expect(content).toMatch(`const emit = __emit;`)
expect(content).toMatch(`(function () {})()`)
})
test('script setup first, named default export', () => {
const { content } = compile(`
<script setup>

View File

@ -3,10 +3,10 @@
exports[`defineEmits > basic usage 1`] = `
"export default {
emits: ['foo', 'bar'],
setup(__props, { expose: __expose, emit: myEmit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const myEmit = __emit
return { myEmit }
}
@ -19,10 +19,10 @@ exports[`defineEmits > w/ runtime options 1`] = `
export default /*#__PURE__*/_defineComponent({
emits: ['a', 'b'],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit = __emit
return { emit }
}
@ -36,10 +36,10 @@ export interface Emits { (e: 'foo' | 'bar'): void }
export default /*#__PURE__*/_defineComponent({
emits: [\\"foo\\", \\"bar\\"],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit = __emit
return { emit }
}
@ -53,10 +53,10 @@ export type Emits = { (e: 'foo' | 'bar'): void }
export default /*#__PURE__*/_defineComponent({
emits: [\\"foo\\", \\"bar\\"],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit = __emit
return { emit }
}
@ -70,10 +70,10 @@ interface Emits { (e: 'foo'): void }
export default /*#__PURE__*/_defineComponent({
emits: ['foo'],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit: Emits = __emit
return { emit }
}
@ -87,10 +87,10 @@ interface Emits { (e: 'foo' | 'bar'): void }
export default /*#__PURE__*/_defineComponent({
emits: [\\"foo\\", \\"bar\\"],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit = __emit
return { emit }
}
@ -103,10 +103,10 @@ exports[`defineEmits > w/ type (property syntax string literal) 1`] = `
export default /*#__PURE__*/_defineComponent({
emits: [\\"foo:bar\\"],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit = __emit
return { emit }
}
@ -119,10 +119,10 @@ exports[`defineEmits > w/ type (property syntax) 1`] = `
export default /*#__PURE__*/_defineComponent({
emits: [\\"foo\\", \\"bar\\"],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit = __emit
return { emit }
}
@ -136,10 +136,10 @@ export type Emits = (e: 'foo' | 'bar') => void
export default /*#__PURE__*/_defineComponent({
emits: [\\"foo\\", \\"bar\\"],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit = __emit
return { emit }
}
@ -153,10 +153,10 @@ type Emits = (e: 'foo' | 'bar') => void
export default /*#__PURE__*/_defineComponent({
emits: [\\"foo\\", \\"bar\\"],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit = __emit
return { emit }
}
@ -170,10 +170,10 @@ type Emits = { (e: 'foo' | 'bar'): void }
export default /*#__PURE__*/_defineComponent({
emits: [\\"foo\\", \\"bar\\"],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit = __emit
return { emit }
}
@ -186,10 +186,10 @@ exports[`defineEmits > w/ type (type literal w/ call signatures) 1`] = `
export default /*#__PURE__*/_defineComponent({
emits: [\\"foo\\", \\"bar\\", \\"baz\\"],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit = __emit
return { emit }
}
@ -204,10 +204,10 @@ type BaseEmit = \\"change\\"
export default /*#__PURE__*/_defineComponent({
emits: [\\"some\\", \\"emit\\", \\"change\\", \\"another\\"],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit = __emit;
return { emit }
}
@ -220,10 +220,10 @@ exports[`defineEmits > w/ type (union) 1`] = `
export default /*#__PURE__*/_defineComponent({
emits: [\\"foo\\", \\"bar\\", \\"baz\\"],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit = __emit
return { emit }
}
@ -236,10 +236,10 @@ exports[`defineEmits > w/ type 1`] = `
export default /*#__PURE__*/_defineComponent({
emits: [\\"foo\\", \\"bar\\"],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit = __emit
return { emit }
}
@ -254,10 +254,10 @@ exports[`defineEmits > w/ type from normal script 1`] = `
export default /*#__PURE__*/_defineComponent({
emits: [\\"foo\\", \\"bar\\"],
setup(__props, { expose: __expose, emit }) {
setup(__props, { expose: __expose, emit: __emit }) {
__expose();
const emit = __emit
return { emit }
}

View File

@ -10,9 +10,7 @@ export default {
setup(__props, { expose: __expose }) {
__expose();
const props = __props;
const props = __props
return { props, bar }
}
@ -28,9 +26,7 @@ export default /*#__PURE__*/_defineComponent({
setup(__props, { expose: __expose }) {
__expose();
const props = __props;
const props = __props
return { props }
}
@ -48,9 +44,7 @@ export default /*#__PURE__*/_defineComponent({
setup(__props: any, { expose: __expose }) {
__expose();
const { foo } = __props;
const { foo } = __props
return { }
}
@ -167,9 +161,7 @@ export default {
setup(__props, { expose: __expose }) {
__expose();
const props = __props;
const props = __props
return { props, get propsModel() { return propsModel } }
}
@ -203,9 +195,7 @@ export default {
props: {},
setup(__props, { expose: __expose }) {
__expose();
const props = __props;
const props = __props
return { props, get x() { return x } }
}
@ -304,9 +294,7 @@ export default /*#__PURE__*/_defineComponent({
setup(__props: any, { expose: __expose }) {
__expose();
const props = __props;
const props = __props
return { props, get defaults() { return defaults } }
}
@ -328,9 +316,7 @@ export default /*#__PURE__*/_defineComponent({
setup(__props: any, { expose: __expose }) {
__expose();
const props = __props;
const props = __props
return { props, get defaults() { return defaults } }
}
@ -351,9 +337,7 @@ export default /*#__PURE__*/_defineComponent({
setup(__props: any, { expose: __expose }) {
__expose();
const props = __props;
const props = __props
return { props, get defaults() { return defaults } }
}
@ -375,9 +359,7 @@ export default /*#__PURE__*/_defineComponent({
setup(__props: any, { expose: __expose }) {
__expose();
const props = __props;
const props = __props;
return { props }
}
@ -401,9 +383,7 @@ export default /*#__PURE__*/_defineComponent({
setup(__props: any, { expose: __expose }) {
__expose();
const props = __props;
const props = __props
return { props }
}
@ -424,9 +404,7 @@ export default /*#__PURE__*/_defineComponent({
setup(__props: any, { expose: __expose }) {
__expose();
const props = __props;
const props = __props
return { props }
}
@ -446,9 +424,7 @@ export default /*#__PURE__*/_defineComponent({
setup(__props: any, { expose: __expose }) {
__expose();
const props = __props;
const props = __props
return { props }
}

View File

@ -176,6 +176,61 @@ return () => {}
})"
`;
exports[`sfc reactive props destructure > defineProps/defineEmits in multi-variable declaration (full removal) 1`] = `
"export default {
props: ['item'],
emits: ['a'],
setup(__props, { emit: __emit }) {
const props = __props,
emit = __emit;
return () => {}
}
}"
`;
exports[`sfc reactive props destructure > multi-variable declaration 1`] = `
"export default {
props: ['item'],
setup(__props) {
const a = 1;
return () => {}
}
}"
`;
exports[`sfc reactive props destructure > multi-variable declaration fix #6757 1`] = `
"export default {
props: ['item'],
setup(__props) {
const a = 1;
return () => {}
}
}"
`;
exports[`sfc reactive props destructure > multi-variable declaration fix #7422 1`] = `
"export default {
props: ['item'],
setup(__props) {
const a = 0,
b = 0;
return () => {}
}
}"
`;
exports[`sfc reactive props destructure > multiple variable declarations 1`] = `
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
@ -237,9 +292,7 @@ export default {
props: ['foo', 'bar', 'baz'],
setup(__props) {
const rest = _createPropsRestProxy(__props, [\\"foo\\",\\"bar\\"]);
const rest = _createPropsRestProxy(__props, [\\"foo\\",\\"bar\\"])
return () => {}
}

View File

@ -16,8 +16,9 @@ const myEmit = defineEmits(['foo', 'bar'])
expect(content).not.toMatch('defineEmits')
// should generate correct setup signature
expect(content).toMatch(
`setup(__props, { expose: __expose, emit: myEmit }) {`
`setup(__props, { expose: __expose, emit: __emit }) {`
)
expect(content).toMatch('const myEmit = __emit')
// should include context options in default export
expect(content).toMatch(`export default {
emits: ['foo', 'bar'],`)
@ -32,7 +33,8 @@ const emit = defineEmits(['a', 'b'])
assertCode(content)
expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({
emits: ['a', 'b'],
setup(__props, { expose: __expose, emit }) {`)
setup(__props, { expose: __expose, emit: __emit }) {`)
expect(content).toMatch('const emit = __emit')
})
test('w/ type', () => {

View File

@ -282,6 +282,58 @@ describe('sfc reactive props destructure', () => {
})
})
test('multi-variable declaration', () => {
const { content } = compile(`
<script setup>
const { item } = defineProps(['item']),
a = 1;
</script>
`)
assertCode(content)
expect(content).toMatch(`const a = 1;`)
expect(content).toMatch(`props: ['item'],`)
})
// #6757
test('multi-variable declaration fix #6757 ', () => {
const { content } = compile(`
<script setup>
const a = 1,
{ item } = defineProps(['item']);
</script>
`)
assertCode(content)
expect(content).toMatch(`const a = 1;`)
expect(content).toMatch(`props: ['item'],`)
})
// #7422
test('multi-variable declaration fix #7422', () => {
const { content } = compile(`
<script setup>
const { item } = defineProps(['item']),
a = 0,
b = 0;
</script>
`)
assertCode(content)
expect(content).toMatch(`const a = 0,`)
expect(content).toMatch(`b = 0;`)
expect(content).toMatch(`props: ['item'],`)
})
test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
const { content } = compile(`
<script setup>
const props = defineProps(['item']),
emit = defineEmits(['a']);
</script>
`)
assertCode(content)
expect(content).toMatch(`props: ['item'],`)
expect(content).toMatch(`emits: ['a'],`)
})
describe('errors', () => {
test('should error on deep destructure', () => {
expect(() =>

View File

@ -552,7 +552,11 @@ export function compileScript(
(processDefineSlots(ctx, init, decl.id) ||
processDefineModel(ctx, init, decl.id))
if (isDefineProps || isDefineEmits) {
if (
isDefineProps &&
!ctx.propsDestructureRestId &&
ctx.propsDestructureDecl
) {
if (left === 1) {
ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
} else {
@ -570,6 +574,12 @@ export function compileScript(
ctx.s.remove(start, end)
left--
}
} else if (isDefineEmits) {
ctx.s.overwrite(
startOffset + init.start!,
startOffset + init.end!,
'__emit'
)
} else {
lastNonRemoved = i
}
@ -781,22 +791,29 @@ export function compileScript(
// inject user assignment of props
// we use a default __props so that template expressions referencing props
// can use it directly
if (ctx.propsIdentifier) {
ctx.s.prependLeft(
startOffset,
`\nconst ${ctx.propsIdentifier} = __props;\n`
)
}
if (ctx.propsDestructureRestId) {
ctx.s.prependLeft(
startOffset,
`\nconst ${ctx.propsDestructureRestId} = ${ctx.helper(
`createPropsRestProxy`
)}(__props, ${JSON.stringify(
Object.keys(ctx.propsDestructuredBindings)
)});\n`
)
if (ctx.propsDecl) {
if (ctx.propsDestructureRestId) {
ctx.s.overwrite(
startOffset + ctx.propsCall!.start!,
startOffset + ctx.propsCall!.end!,
`${ctx.helper(`createPropsRestProxy`)}(__props, ${JSON.stringify(
Object.keys(ctx.propsDestructuredBindings)
)})`
)
ctx.s.overwrite(
startOffset + ctx.propsDestructureDecl!.start!,
startOffset + ctx.propsDestructureDecl!.end!,
ctx.propsDestructureRestId
)
} else if (!ctx.propsDestructureDecl) {
ctx.s.overwrite(
startOffset + ctx.propsCall!.start!,
startOffset + ctx.propsCall!.end!,
'__props'
)
}
}
// inject temp variables for async context preservation
if (hasAwait) {
const any = ctx.isTS ? `: any` : ``
@ -807,10 +824,8 @@ export function compileScript(
ctx.hasDefineExposeCall || !options.inlineTemplate
? [`expose: __expose`]
: []
if (ctx.emitIdentifier) {
destructureElements.push(
ctx.emitIdentifier === `emit` ? `emit` : `emit: ${ctx.emitIdentifier}`
)
if (ctx.emitDecl) {
destructureElements.push(`emit: __emit`)
}
if (destructureElements.length) {
args += `, { ${destructureElements.join(', ')} }`

View File

@ -1,4 +1,4 @@
import { Node, ObjectPattern, Program } from '@babel/types'
import { CallExpression, Node, ObjectPattern, Program } from '@babel/types'
import { SFCDescriptor } from '../parse'
import { generateCodeFrame } from '@vue/shared'
import { parse as babelParse, ParserPlugin } from '@babel/parser'
@ -38,7 +38,8 @@ export class ScriptCompileContext {
hasDefineModelCall = false
// defineProps
propsIdentifier: string | undefined
propsCall: CallExpression | undefined
propsDecl: Node | undefined
propsRuntimeDecl: Node | undefined
propsTypeDecl: Node | undefined
propsDestructureDecl: ObjectPattern | undefined
@ -49,7 +50,7 @@ export class ScriptCompileContext {
// defineEmits
emitsRuntimeDecl: Node | undefined
emitsTypeDecl: Node | undefined
emitIdentifier: string | undefined
emitDecl: Node | undefined
// defineModel
modelDecls: Record<string, ModelDecl> = {}

View File

@ -29,10 +29,7 @@ export function processDefineEmits(
ctx.emitsTypeDecl = node.typeParameters.params[0]
}
if (declId) {
ctx.emitIdentifier =
declId.type === 'Identifier' ? declId.name : ctx.getString(declId)
}
ctx.emitDecl = declId
return true
}

View File

@ -77,15 +77,14 @@ export function processDefineProps(
ctx.propsTypeDecl = node.typeParameters.params[0]
}
if (declId) {
// handle props destructure
if (declId.type === 'ObjectPattern') {
processPropsDestructure(ctx, declId)
} else {
ctx.propsIdentifier = ctx.getString(declId)
}
// handle props destructure
if (declId && declId.type === 'ObjectPattern') {
processPropsDestructure(ctx, declId)
}
ctx.propsCall = node
ctx.propsDecl = declId
return true
}
@ -97,31 +96,33 @@ function processWithDefaults(
if (!isCallOf(node, WITH_DEFAULTS)) {
return false
}
if (processDefineProps(ctx, node.arguments[0], declId)) {
if (ctx.propsRuntimeDecl) {
ctx.error(
`${WITH_DEFAULTS} can only be used with type-based ` +
`${DEFINE_PROPS} declaration.`,
node
)
}
if (ctx.propsDestructureDecl) {
ctx.error(
`${WITH_DEFAULTS}() is unnecessary when using destructure with ${DEFINE_PROPS}().\n` +
`Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...).`,
node.callee
)
}
ctx.propsRuntimeDefaults = node.arguments[1]
if (!ctx.propsRuntimeDefaults) {
ctx.error(`The 2nd argument of ${WITH_DEFAULTS} is required.`, node)
}
} else {
if (!processDefineProps(ctx, node.arguments[0], declId)) {
ctx.error(
`${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`,
node.arguments[0] || node
)
}
if (ctx.propsRuntimeDecl) {
ctx.error(
`${WITH_DEFAULTS} can only be used with type-based ` +
`${DEFINE_PROPS} declaration.`,
node
)
}
if (ctx.propsDestructureDecl) {
ctx.error(
`${WITH_DEFAULTS}() is unnecessary when using destructure with ${DEFINE_PROPS}().\n` +
`Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...).`,
node.callee
)
}
ctx.propsRuntimeDefaults = node.arguments[1]
if (!ctx.propsRuntimeDefaults) {
ctx.error(`The 2nd argument of ${WITH_DEFAULTS} is required.`, node)
}
ctx.propsCall = node
return true
}

View File

@ -28,7 +28,6 @@ export function processPropsDestructure(
declId: ObjectPattern
) {
if (!ctx.options.propsDestructure && !ctx.options.reactivityTransform) {
ctx.propsIdentifier = ctx.getString(declId)
return
}