From 7292d70ff6fc03406b6da98f0fc9d000c5d01c38 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Sun, 20 Jul 2025 19:29:36 +0800 Subject: [PATCH 1/9] fix(compiler-vapor): $props $emit $attrs $slots in prod --- packages/compiler-sfc/src/compileScript.ts | 15 +++++++++++---- packages/compiler-sfc/src/script/context.ts | 3 +++ packages/compiler-vapor/src/generate.ts | 4 ++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 54ca260bdd6..eedc9287d8e 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -817,7 +817,14 @@ export function compileScript( if (ctx.emitDecl) { destructureElements.push(`emit: __emit`) } - if (destructureElements.length) { + + let destructureElementsDeclaration = '' + if (ctx.needToGetCtx) { + args += `, __ctx` + if (destructureElements.length) { + destructureElementsDeclaration = ` const { ${destructureElements.join(', ')} } = __ctx;\n` + } + } else if (destructureElements.length) { args += `, { ${destructureElements.join(', ')} }` } @@ -994,7 +1001,7 @@ export function compileScript( vapor && !ssr ? `defineVaporComponent` : `defineComponent`, )}({${def}${runtimeOptions}\n ${ hasAwait ? `async ` : `` - }setup(${args}) {\n${exposeCall}`, + }setup(${args}) {\n${destructureElementsDeclaration}${exposeCall}`, ) ctx.s.appendRight(endOffset, `})`) } else { @@ -1010,14 +1017,14 @@ export function compileScript( `\n${genDefaultAs} /*@__PURE__*/Object.assign(${ defaultExport ? `${normalScriptDefaultVar}, ` : '' }${definedOptions ? `${definedOptions}, ` : ''}{${runtimeOptions}\n ` + - `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`, + `${hasAwait ? `async ` : ``}setup(${args}) {\n${destructureElementsDeclaration}${exposeCall}`, ) ctx.s.appendRight(endOffset, `})`) } else { ctx.s.prependLeft( startOffset, `\n${genDefaultAs} {${runtimeOptions}\n ` + - `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`, + `${hasAwait ? `async ` : ``}setup(${args}) {\n${destructureElementsDeclaration}${exposeCall}`, ) ctx.s.appendRight(endOffset, `}`) } diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts index 47b6b442a49..199e198e03a 100644 --- a/packages/compiler-sfc/src/script/context.ts +++ b/packages/compiler-sfc/src/script/context.ts @@ -30,6 +30,9 @@ export class ScriptCompileContext { globalScopes?: TypeScope[] userImports: Record = Object.create(null) + // get the full ctx, currently used for Vapor's prod setup function + needToGetCtx = true + // macros presence check hasDefinePropsCall = false hasDefineEmitCall = false diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index 193a0f5da77..d2f4d69795b 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -123,6 +123,10 @@ export function generate( } push(INDENT_START) + if (bindingMetadata && inline) { + push(NEWLINE, `const $props = __props`) + push(NEWLINE, `const { emit: $emit, attrs: $attrs, slots: $slots } = __ctx`) + } if (ir.hasTemplateRef) { push( NEWLINE, From 7c3730c3c749b429e8d833ad458237d3f4eaac3c Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Mon, 21 Jul 2025 15:08:19 +0800 Subject: [PATCH 2/9] refactor: directly destructure in setup --- packages/compiler-sfc/src/compileScript.ts | 58 ++++++++-- packages/compiler-sfc/src/script/context.ts | 3 - .../src/script/importUsageCheck.ts | 104 ++++++++++++++++++ packages/compiler-vapor/src/generate.ts | 4 - 4 files changed, 150 insertions(+), 19 deletions(-) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index eedc9287d8e..f71b1caf948 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -59,7 +59,10 @@ 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 { + checkTemplateGlobalsUsage, + isImportUsed, +} from './script/importUsageCheck' import { processAwait } from './script/topLevelAwait' export interface SFCScriptCompileOptions { @@ -181,6 +184,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) { @@ -818,13 +822,39 @@ export function compileScript( destructureElements.push(`emit: __emit`) } - let destructureElementsDeclaration = '' - if (ctx.needToGetCtx) { - args += `, __ctx` - if (destructureElements.length) { - destructureElementsDeclaration = ` const { ${destructureElements.join(', ')} } = __ctx;\n` + // generate $props, $emit, $attrs, $slots + if (options.inlineTemplate) { + if (sfc.template && sfc.template.ast) { + const { usesProps, usesEmit, usesAttrs, usesSlots } = + checkTemplateGlobalsUsage(sfc) + // $props + if (usesProps && !('$props' in ctx.bindingMetadata)) { + setupPreambleLines.push(`const $props = __props`) + ctx.bindingMetadata['$props'] = BindingTypes.SETUP_REACTIVE_CONST + } + // $emit + if (usesEmit && !('$emit' in ctx.bindingMetadata)) { + if (ctx.emitDecl) { + setupPreambleLines.push(`const $emit = __emit`) + } else { + destructureElements.push('emit: $emit') + } + ctx.bindingMetadata['$emit'] = BindingTypes.SETUP_CONST + } + // $attrs + if (usesAttrs && !('$attrs' in ctx.bindingMetadata)) { + destructureElements.push('attrs: $attrs') + ctx.bindingMetadata['$attrs'] = BindingTypes.SETUP_REACTIVE_CONST + } + // $slots + if (usesSlots && !('$slots' in ctx.bindingMetadata)) { + destructureElements.push('slots: $slots') + ctx.bindingMetadata['$slots'] = BindingTypes.SETUP_REACTIVE_CONST + } } - } else if (destructureElements.length) { + } + + if (destructureElements.length) { args += `, { ${destructureElements.join(', ')} }` } @@ -983,8 +1013,12 @@ export function compileScript( // \n`, + { inlineTemplate: true }, + ) + + test('should include 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 include attrs when $slots is used', () => { + let { content } = theCompile('') + expect(content).toMatch('setup(__props, { slots: $slots })') + assertCode(content) + }) + + test('should include props when $props is used', () => { + let { content } = theCompile('
{{ $props }}
') + expect(content).toMatch('setup(__props)') + expect(content).toMatch('const $props = __props') + assertCode(content) + }) + + test('should include emit when $emit is used', () => { + let { content } = theCompile(`
`) + expect(content).toMatch('setup(__props, { emit: $emit })') + expect(content).not.toMatch('const $emit = __emit') + assertCode(content) + }) + + // 当用户使用 defineEmits 的时候, $emit 需要改成 const $emit = __emit + test('should include 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 include all globals 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 include globals 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 globals override', () => { + test('should not destructure $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 destructure $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 destructure $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 assignment 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 destructure non-user-defined globals', () => { + 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') + assertCode(content) + }) + }) + }) }) describe('with TypeScript', () => { diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index f71b1caf948..601d4f12876 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -1017,7 +1017,7 @@ export function compileScript( setupPreambleLines.push(`__expose();`) const setupPreamble = setupPreambleLines.length - ? ' ' + setupPreambleLines.join('\n ') + ? ` ${setupPreambleLines.join('\n ')}\n` : '' // wrap setup code with function. if (ctx.isTS) { From 3ce03e09a5a17c59bc2b772c1325f89436714ab7 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Mon, 21 Jul 2025 17:05:15 +0800 Subject: [PATCH 4/9] refactor: remove checkTemplateGlobalsUsage, use isImportUsed directly --- packages/compiler-sfc/src/compileScript.ts | 15 +-- .../src/script/importUsageCheck.ts | 104 ------------------ 2 files changed, 5 insertions(+), 114 deletions(-) diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 601d4f12876..a0e2ce24d0c 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -59,10 +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 { - checkTemplateGlobalsUsage, - isImportUsed, -} from './script/importUsageCheck' +import { isImportUsed } from './script/importUsageCheck' import { processAwait } from './script/topLevelAwait' export interface SFCScriptCompileOptions { @@ -825,15 +822,13 @@ export function compileScript( // generate $props, $emit, $attrs, $slots if (options.inlineTemplate) { if (sfc.template && sfc.template.ast) { - const { usesProps, usesEmit, usesAttrs, usesSlots } = - checkTemplateGlobalsUsage(sfc) // $props - if (usesProps && !('$props' in ctx.bindingMetadata)) { + if (isImportUsed('$props', sfc) && !('$props' in ctx.bindingMetadata)) { setupPreambleLines.push(`const $props = __props`) ctx.bindingMetadata['$props'] = BindingTypes.SETUP_REACTIVE_CONST } // $emit - if (usesEmit && !('$emit' in ctx.bindingMetadata)) { + if (isImportUsed('$emit', sfc) && !('$emit' in ctx.bindingMetadata)) { if (ctx.emitDecl) { setupPreambleLines.push(`const $emit = __emit`) } else { @@ -842,12 +837,12 @@ export function compileScript( ctx.bindingMetadata['$emit'] = BindingTypes.SETUP_CONST } // $attrs - if (usesAttrs && !('$attrs' in ctx.bindingMetadata)) { + if (isImportUsed('$attrs', sfc) && !('$attrs' in ctx.bindingMetadata)) { destructureElements.push('attrs: $attrs') ctx.bindingMetadata['$attrs'] = BindingTypes.SETUP_REACTIVE_CONST } // $slots - if (usesSlots && !('$slots' in ctx.bindingMetadata)) { + if (isImportUsed('$slots', sfc) && !('$slots' in ctx.bindingMetadata)) { destructureElements.push('slots: $slots') ctx.bindingMetadata['$slots'] = BindingTypes.SETUP_REACTIVE_CONST } diff --git a/packages/compiler-sfc/src/script/importUsageCheck.ts b/packages/compiler-sfc/src/script/importUsageCheck.ts index 50539f99b39..22ef37cf37f 100644 --- a/packages/compiler-sfc/src/script/importUsageCheck.ts +++ b/packages/compiler-sfc/src/script/importUsageCheck.ts @@ -92,107 +92,3 @@ function extractIdentifiers(ids: Set, node: ExpressionNode) { ids.add((node as SimpleExpressionNode).content) } } - -interface TemplateGlobalsUsage { - usesAttrs: boolean - usesSlots: boolean - usesEmit: boolean - usesProps: boolean -} - -/** - * Check which template globals ($attrs, $slots) are used in the SFC's template. - * Returns an object with boolean flags indicating usage. - */ -export function checkTemplateGlobalsUsage( - sfc: SFCDescriptor, -): TemplateGlobalsUsage { - if (!sfc.template || !sfc.template.ast) { - return { - usesAttrs: false, - usesSlots: false, - usesEmit: false, - usesProps: false, - } - } - - const { content, ast } = sfc.template - const cacheKey = `globals:${content}` - const cached = templateUsageCheckCache.get(cacheKey) as - | TemplateGlobalsUsage - | undefined - if (cached) { - return cached - } - - const globals: TemplateGlobalsUsage = { - usesAttrs: false, - usesSlots: false, - usesEmit: false, - usesProps: false, - } - const targetIdentifiers = new Set(['$attrs', '$slots', '$emit', '$props']) - - ast.children.forEach(walk) - - function walk(node: TemplateChildNode) { - switch (node.type) { - case NodeTypes.ELEMENT: - for (let i = 0; i < node.props.length; i++) { - const prop = node.props[i] - if (prop.type === NodeTypes.DIRECTIVE) { - // process dynamic directive arguments - if (prop.arg && !(prop.arg as SimpleExpressionNode).isStatic) { - extractTargetIdentifiers(prop.arg) - } - - if (prop.name === 'for') { - extractTargetIdentifiers(prop.forParseResult!.source) - } else if (prop.exp) { - extractTargetIdentifiers(prop.exp) - } - } - } - node.children.forEach(walk) - break - case NodeTypes.INTERPOLATION: - extractTargetIdentifiers(node.content) - break - } - } - - function extractTargetIdentifiers(node: ExpressionNode) { - const setGlobalFlag = (name: string) => { - switch (name) { - case '$attrs': - globals.usesAttrs = true - break - case '$slots': - globals.usesSlots = true - break - case '$emit': - globals.usesEmit = true - break - case '$props': - globals.usesProps = true - break - } - } - - if (node.ast) { - walkIdentifiers(node.ast, n => { - if (targetIdentifiers.has(n.name)) { - setGlobalFlag(n.name) - } - }) - } else if (node.ast === null) { - const content = (node as SimpleExpressionNode).content - if (targetIdentifiers.has(content)) { - setGlobalFlag(content) - } - } - } - - templateUsageCheckCache.set(cacheKey, globals as any) - return globals -} From 7deed39753936fb609f65caa110fc776308848d0 Mon Sep 17 00:00:00 2001 From: Rizumu Ayaka Date: Mon, 21 Jul 2025 17:07:17 +0800 Subject: [PATCH 5/9] chore: clean up code --- packages/compiler-sfc/__tests__/compileScript.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/compiler-sfc/__tests__/compileScript.spec.ts b/packages/compiler-sfc/__tests__/compileScript.spec.ts index 00fa477ccc6..7e2a9ec7976 100644 --- a/packages/compiler-sfc/__tests__/compileScript.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript.spec.ts @@ -754,7 +754,6 @@ describe('SFC compile \n`, { inlineTemplate: true }, ) - test('should include attrs when $attrs is used', () => { + test('should destructure attrs when $attrs is used', () => { let { content } = theCompile('
') expect(content).toMatch('setup(__props, { attrs: $attrs })') expect(content).not.toMatch('slots: $slots') @@ -734,27 +734,27 @@ describe('SFC compile \n`, { inlineTemplate: true }, ) - test('should include attrs when $attrs is used', () => { + test('should destructure attrs when $attrs is used', () => { let { content } = theCompile('
') expect(content).toMatch('setup(__props, { attrs: $attrs })') expect(content).not.toMatch('slots: $slots') @@ -734,27 +734,27 @@ describe('SFC compile