diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
index 2acac64b0fb..0c6f78a19a1 100644
--- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
@@ -823,6 +823,450 @@ return (_ctx, _cache) => {
}"
`;
+exports[`SFC compile \n${template}`,
+ { inlineTemplate: true },
+ )
+
+ test('should extract attrs when $attrs is used', () => {
+ let { content } = theCompile('
')
+ expect(content).toMatch('setup(__props, { attrs: $attrs })')
+ expect(content).not.toMatch('slots: $slots')
+ expect(content).not.toMatch('emit: $emit')
+ expect(content).not.toMatch('const $props = __props')
+ assertCode(content)
+ })
+
+ test('should extract slots when $slots is used', () => {
+ let { content } = theCompile('')
+ expect(content).toMatch('setup(__props, { slots: $slots })')
+ assertCode(content)
+ })
+
+ test('should alias __props to $props when $props is used', () => {
+ let { content } = theCompile('{{ $props }}
')
+ expect(content).toMatch('setup(__props)')
+ expect(content).toMatch('const $props = __props')
+ assertCode(content)
+ })
+
+ test('should extract emit when $emit is used', () => {
+ let { content } = theCompile(``)
+ expect(content).toMatch('setup(__props, { emit: $emit })')
+ expect(content).not.toMatch('const $emit = __emit')
+ assertCode(content)
+ })
+
+ test('should alias __emit to $emit when defineEmits is used', () => {
+ let { content } = compile(
+ `
+
+
+
+
+ `,
+ { inlineTemplate: true },
+ )
+ expect(content).toMatch('setup(__props, { emit: __emit })')
+ expect(content).toMatch('const $emit = __emit')
+ expect(content).toMatch('const emit = __emit')
+ assertCode(content)
+ })
+
+ test('should extract all built-in properties when they are used', () => {
+ let { content } = theCompile(
+ '{{ $props }}{{ $slots }}{{ $emit }}{{ $attrs }}
',
+ )
+ expect(content).toMatch(
+ 'setup(__props, { emit: $emit, attrs: $attrs, slots: $slots })',
+ )
+ expect(content).toMatch('const $props = __props')
+ assertCode(content)
+ })
+
+ test('should not extract built-in properties when neither is used', () => {
+ let { content } = theCompile('{{ msg }}
')
+ expect(content).toMatch('setup(__props)')
+ expect(content).not.toMatch('attrs: $attrs')
+ expect(content).not.toMatch('slots: $slots')
+ expect(content).not.toMatch('emit: $emit')
+ expect(content).not.toMatch('props: $props')
+ assertCode(content)
+ })
+
+ describe('user-defined properties override', () => {
+ test('should not extract $attrs when user defines it', () => {
+ let { content } = theCompile(
+ '',
+ 'let $attrs',
+ )
+ expect(content).toMatch('setup(__props)')
+ expect(content).not.toMatch('attrs: $attrs')
+ assertCode(content)
+ })
+
+ test('should not extract $slots when user defines it', () => {
+ let { content } = theCompile(
+ '',
+ 'let $slots',
+ )
+ expect(content).toMatch('setup(__props)')
+ expect(content).not.toMatch('slots: $slots')
+ assertCode(content)
+ })
+
+ test('should not extract $emit when user defines it', () => {
+ let { content } = theCompile(
+ `click
`,
+ 'let $emit',
+ )
+ expect(content).toMatch('setup(__props)')
+ expect(content).not.toMatch('emit: $emit')
+ assertCode(content)
+ })
+
+ test('should not generate $props alias when user defines it', () => {
+ let { content } = theCompile(
+ '{{ $props.msg }}
',
+ 'let $props',
+ )
+ expect(content).toMatch('setup(__props)')
+ expect(content).not.toMatch('const $props = __props')
+ assertCode(content)
+ })
+
+ test('should only extract non-user-defined properties', () => {
+ let { content } = theCompile(
+ '{{ $attrs }}{{ $slots }}{{ $emit }}{{ $props }}
',
+ 'let $attrs',
+ )
+ expect(content).toMatch(
+ 'setup(__props, { emit: $emit, slots: $slots })',
+ )
+ expect(content).not.toMatch('attrs: $attrs')
+ expect(content).toMatch('const $props = __props')
+ assertCode(content)
+ })
+
+ test('should handle mixed defineEmits and user-defined $emit', () => {
+ let { content } = theCompile(
+ `click
`,
+ `
+ const emit = defineEmits(['click'])
+ let $emit
+ `,
+ )
+ expect(content).toMatch('setup(__props, { emit: __emit })')
+ expect(content).toMatch('const emit = __emit')
+ expect(content).not.toMatch('const $emit = __emit')
+ assertCode(content)
+ })
+ })
+ })
})
describe('with TypeScript', () => {
diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts
index 54ca260bdd6..c2fcc46c073 100644
--- a/packages/compiler-sfc/src/compileScript.ts
+++ b/packages/compiler-sfc/src/compileScript.ts
@@ -59,7 +59,7 @@ import { DEFINE_SLOTS, processDefineSlots } from './script/defineSlots'
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
import { getImportedName, isCallOf, isLiteralNode } from './script/utils'
import { analyzeScriptBindings } from './script/analyzeScriptBindings'
-import { isImportUsed } from './script/importUsageCheck'
+import { isUsedInTemplate } from './script/importUsageCheck'
import { processAwait } from './script/topLevelAwait'
export interface SFCScriptCompileOptions {
@@ -181,6 +181,7 @@ export function compileScript(
const scriptSetupLang = scriptSetup && scriptSetup.lang
const vapor = sfc.vapor || options.vapor
const ssr = options.templateOptions?.ssr
+ const setupPreambleLines = [] as string[]
if (!scriptSetup) {
if (!script) {
@@ -246,7 +247,7 @@ export function compileScript(
) {
// template usage check is only needed in non-inline mode, so we can skip
// the work if inlineTemplate is true.
- let isUsedInTemplate = needTemplateUsageCheck
+ let isImportUsed = needTemplateUsageCheck
if (
needTemplateUsageCheck &&
ctx.isTS &&
@@ -254,7 +255,7 @@ export function compileScript(
!sfc.template.src &&
!sfc.template.lang
) {
- isUsedInTemplate = isImportUsed(local, sfc)
+ isImportUsed = isUsedInTemplate(local, sfc)
}
ctx.userImports[local] = {
@@ -263,7 +264,7 @@ export function compileScript(
local,
source,
isFromSetup,
- isUsedInTemplate,
+ isUsedInTemplate: isImportUsed,
}
}
@@ -284,8 +285,42 @@ export function compileScript(
})
}
+ function buildDestructureElements() {
+ if (!sfc.template || !sfc.template.ast) return
+
+ const builtins = {
+ $props: {
+ bindingType: BindingTypes.SETUP_REACTIVE_CONST,
+ setup: () => setupPreambleLines.push(`const $props = __props`),
+ },
+ $emit: {
+ bindingType: BindingTypes.SETUP_CONST,
+ setup: () =>
+ ctx.emitDecl
+ ? setupPreambleLines.push(`const $emit = __emit`)
+ : destructureElements.push('emit: $emit'),
+ },
+ $attrs: {
+ bindingType: BindingTypes.SETUP_REACTIVE_CONST,
+ setup: () => destructureElements.push('attrs: $attrs'),
+ },
+ $slots: {
+ bindingType: BindingTypes.SETUP_REACTIVE_CONST,
+ setup: () => destructureElements.push('slots: $slots'),
+ },
+ }
+
+ for (const [name, config] of Object.entries(builtins)) {
+ if (isUsedInTemplate(name, sfc) && !ctx.bindingMetadata[name]) {
+ config.setup()
+ ctx.bindingMetadata[name] = config.bindingType
+ }
+ }
+ }
+
const scriptAst = ctx.scriptAst
const scriptSetupAst = ctx.scriptSetupAst!
+ const inlineMode = options.inlineTemplate
// 1.1 walk import declarations of