refactor(compiler-sfc): destructure built-in properties ($emit,$attrs,$slots) in inline mode (#13663)

Co-authored-by: daiwei <daiwei521@126.com>
This commit is contained in:
Rizumu Ayaka 2025-08-20 17:50:53 +08:00 committed by GitHub
parent e56997f0d5
commit 57354e195a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 659 additions and 26 deletions

View File

@ -823,6 +823,450 @@ return (_ctx, _cache) => {
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > should alias __emit to $emit when defineEmits is used 1`] = `
"import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
emits: ['click'],
setup(__props, { emit: __emit }) {
const $emit = __emit
const emit = __emit
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", {
onClick: _cache[0] || (_cache[0] = $event => ($emit('click')))
}))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > should alias __props to $props when $props is used 1`] = `
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props) {
const $props = __props
/* ... */
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", null, _toDisplayString($props), 1 /* TEXT */))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > should extract all built-in properties when they are used 1`] = `
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props, { emit: $emit, attrs: $attrs, slots: $slots }) {
const $props = __props
/* ... */
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", null, _toDisplayString($props) + _toDisplayString($slots) + _toDisplayString($emit) + _toDisplayString($attrs), 1 /* TEXT */))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > should extract attrs when $attrs is used 1`] = `
"import { normalizeProps as _normalizeProps, guardReactiveProps as _guardReactiveProps, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props, { attrs: $attrs }) {
/* ... */
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", _normalizeProps(_guardReactiveProps($attrs)), null, 16 /* FULL_PROPS */))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > should extract emit when $emit is used 1`] = `
"import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props, { emit: $emit }) {
/* ... */
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", {
onClick: _cache[0] || (_cache[0] = $event => ($emit('click')))
}))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > should extract slots when $slots is used 1`] = `
"import { resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export default {
setup(__props, { slots: $slots }) {
/* ... */
return (_ctx, _cache) => {
const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, {
foo: $slots.foo
}, null, 8 /* PROPS */, ["foo"]))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > should not extract built-in properties when neither is used 1`] = `
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props) {
/* ... */
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", null, _toDisplayString(_ctx.msg), 1 /* TEXT */))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > user-defined properties override > should handle mixed defineEmits and user-defined $emit 1`] = `
"import { unref as _unref, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
emits: ['click'],
setup(__props, { emit: __emit }) {
const emit = __emit
let $emit
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", {
onClick: _cache[0] || (_cache[0] = $event => (_unref($emit)('click')))
}, "click"))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > user-defined properties override > should not extract $attrs when user defines it 1`] = `
"import { unref as _unref, normalizeProps as _normalizeProps, guardReactiveProps as _guardReactiveProps, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props) {
let $attrs
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", _normalizeProps(_guardReactiveProps(_unref($attrs))), null, 16 /* FULL_PROPS */))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > user-defined properties override > should not extract $emit when user defines it 1`] = `
"import { unref as _unref, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props) {
let $emit
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", {
onClick: _cache[0] || (_cache[0] = $event => (_unref($emit)('click')))
}, "click"))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > user-defined properties override > should not extract $slots when user defines it 1`] = `
"import { unref as _unref, resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export default {
setup(__props) {
let $slots
return (_ctx, _cache) => {
const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, {
foo: _unref($slots).foo
}, null, 8 /* PROPS */, ["foo"]))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > user-defined properties override > should not generate $props alias when user defines it 1`] = `
"import { unref as _unref, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props) {
let $props
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", null, _toDisplayString(_unref($props).msg), 1 /* TEXT */))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructure setup context for built-in properties > user-defined properties override > should only extract non-user-defined properties 1`] = `
"import { unref as _unref, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props, { emit: $emit, slots: $slots }) {
const $props = __props
let $attrs
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", null, _toDisplayString(_unref($attrs)) + _toDisplayString($slots) + _toDisplayString($emit) + _toDisplayString($props), 1 /* TEXT */))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructuring setup context for built-in properties > should alias __emit to $emit when defineEmits is used 1`] = `
"import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
emits: ['click'],
setup(__props, { emit: __emit }) {
const $emit = __emit
const emit = __emit
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", {
onClick: _cache[0] || (_cache[0] = $event => ($emit('click')))
}))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructuring setup context for built-in properties > should alias __props to $props when $props is used 1`] = `
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props) {
const $props = __props
/* ... */
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", null, _toDisplayString($props), 1 /* TEXT */))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructuring setup context for built-in properties > should extract all built-in properties when they are used 1`] = `
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props, { emit: $emit, attrs: $attrs, slots: $slots }) {
const $props = __props
/* ... */
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", null, _toDisplayString($props) + _toDisplayString($slots) + _toDisplayString($emit) + _toDisplayString($attrs), 1 /* TEXT */))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructuring setup context for built-in properties > should extract attrs when $attrs is used 1`] = `
"import { normalizeProps as _normalizeProps, guardReactiveProps as _guardReactiveProps, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props, { attrs: $attrs }) {
/* ... */
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", _normalizeProps(_guardReactiveProps($attrs)), null, 16 /* FULL_PROPS */))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructuring setup context for built-in properties > should extract emit when $emit is used 1`] = `
"import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props, { emit: $emit }) {
/* ... */
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", {
onClick: _cache[0] || (_cache[0] = $event => ($emit('click')))
}))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructuring setup context for built-in properties > should extract slots when $slots is used 1`] = `
"import { resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export default {
setup(__props, { slots: $slots }) {
/* ... */
return (_ctx, _cache) => {
const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, {
foo: $slots.foo
}, null, 8 /* PROPS */, ["foo"]))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructuring setup context for built-in properties > should not extract built-in properties when neither is used 1`] = `
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props) {
/* ... */
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", null, _toDisplayString(_ctx.msg), 1 /* TEXT */))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructuring setup context for built-in properties > user-defined properties override > should handle mixed defineEmits and user-defined $emit 1`] = `
"import { unref as _unref, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
emits: ['click'],
setup(__props, { emit: __emit }) {
const emit = __emit
let $emit
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", {
onClick: _cache[0] || (_cache[0] = $event => (_unref($emit)('click')))
}, "click"))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructuring setup context for built-in properties > user-defined properties override > should not extract $attrs when user defines it 1`] = `
"import { unref as _unref, normalizeProps as _normalizeProps, guardReactiveProps as _guardReactiveProps, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props) {
let $attrs
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", _normalizeProps(_guardReactiveProps(_unref($attrs))), null, 16 /* FULL_PROPS */))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructuring setup context for built-in properties > user-defined properties override > should not extract $emit when user defines it 1`] = `
"import { unref as _unref, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props) {
let $emit
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", {
onClick: _cache[0] || (_cache[0] = $event => (_unref($emit)('click')))
}, "click"))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructuring setup context for built-in properties > user-defined properties override > should not extract $slots when user defines it 1`] = `
"import { unref as _unref, resolveComponent as _resolveComponent, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export default {
setup(__props) {
let $slots
return (_ctx, _cache) => {
const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, {
foo: _unref($slots).foo
}, null, 8 /* PROPS */, ["foo"]))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructuring setup context for built-in properties > user-defined properties override > should not generate $props alias when user defines it 1`] = `
"import { unref as _unref, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props) {
let $props
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", null, _toDisplayString(_unref($props).msg), 1 /* TEXT */))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > destructuring setup context for built-in properties > user-defined properties override > should only extract non-user-defined properties 1`] = `
"import { unref as _unref, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export default {
setup(__props, { emit: $emit, slots: $slots }) {
const $props = __props
let $attrs
return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock("div", null, _toDisplayString(_unref($attrs)) + _toDisplayString($slots) + _toDisplayString($emit) + _toDisplayString($props), 1 /* TEXT */))
}
}
}"
`;
exports[`SFC compile <script setup> > inlineTemplate mode > referencing scope components and directives 1`] = `
"import { unref as _unref, createElementVNode as _createElementVNode, withDirectives as _withDirectives, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

View File

@ -717,6 +717,151 @@ describe('SFC compile <script setup>', () => {
consumer.originalPositionFor(getPositionInCode(content, 'Error')),
).toMatchObject(getPositionInCode(source, `Error`))
})
describe('destructure setup context for built-in properties', () => {
const theCompile = (template: string, setup = '/* ... */') =>
compile(
`<script setup>${setup}</script>\n<template>${template}</template>`,
{ inlineTemplate: true },
)
test('should extract attrs when $attrs is used', () => {
let { content } = theCompile('<div v-bind="$attrs"></div>')
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('<Comp :foo="$slots.foo"></Comp>')
expect(content).toMatch('setup(__props, { slots: $slots })')
assertCode(content)
})
test('should alias __props to $props when $props is used', () => {
let { content } = theCompile('<div>{{ $props }}</div>')
expect(content).toMatch('setup(__props)')
expect(content).toMatch('const $props = __props')
assertCode(content)
})
test('should extract emit when $emit is used', () => {
let { content } = theCompile(`<div @click="$emit('click')"></div>`)
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(
`
<script setup>
const emit = defineEmits(['click'])
</script>
<template>
<div @click="$emit('click')"></div>
</template>
`,
{ 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(
'<div>{{ $props }}{{ $slots }}{{ $emit }}{{ $attrs }}</div>',
)
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('<div>{{ msg }}</div>')
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(
'<div v-bind="$attrs"></div>',
'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(
'<Comp :foo="$slots.foo"></Comp>',
'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(
`<div @click="$emit('click')">click</div>`,
'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(
'<div>{{ $props.msg }}</div>',
'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(
'<div>{{ $attrs }}{{ $slots }}{{ $emit }}{{ $props }}</div>',
'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(
`<div @click="$emit('click')">click</div>`,
`
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', () => {

View File

@ -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 <script>
if (scriptAst) {
@ -302,7 +337,7 @@ export function compileScript(
(specifier.type === 'ImportSpecifier' &&
specifier.importKind === 'type'),
false,
!options.inlineTemplate,
!inlineMode,
)
}
}
@ -370,7 +405,7 @@ export function compileScript(
(specifier.type === 'ImportSpecifier' &&
specifier.importKind === 'type'),
true,
!options.inlineTemplate,
!inlineMode,
)
}
}
@ -811,12 +846,16 @@ export function compileScript(
}
const destructureElements =
ctx.hasDefineExposeCall || !options.inlineTemplate
? [`expose: __expose`]
: []
ctx.hasDefineExposeCall || !inlineMode ? [`expose: __expose`] : []
if (ctx.emitDecl) {
destructureElements.push(`emit: __emit`)
}
// destructure built-in properties (e.g. $emit, $attrs, $slots)
if (inlineMode) {
buildDestructureElements()
}
if (destructureElements.length) {
args += `, { ${destructureElements.join(', ')} }`
}
@ -824,10 +863,7 @@ export function compileScript(
let templateMap
// 9. generate return statement
let returned
if (
!options.inlineTemplate ||
(!sfc.template && ctx.hasDefaultExportRender)
) {
if (!inlineMode || (!sfc.template && ctx.hasDefaultExportRender)) {
// non-inline mode, or has manual render in normal <script>
// return bindings from script and script setup
const allBindings: Record<string, any> = {
@ -927,7 +963,7 @@ export function compileScript(
}
}
if (!options.inlineTemplate && !__TEST__) {
if (!inlineMode && !__TEST__) {
// in non-inline mode, the `__isScriptSetup: true` flag is used by
// componentPublicInstance proxy to allow properties that start with $ or _
ctx.s.appendRight(
@ -976,8 +1012,12 @@ export function compileScript(
// <script setup> components are closed by default. If the user did not
// explicitly call `defineExpose`, call expose() with no args.
const exposeCall =
ctx.hasDefineExposeCall || options.inlineTemplate ? `` : ` __expose();\n`
if (!ctx.hasDefineExposeCall && !inlineMode)
setupPreambleLines.push(`__expose();`)
const setupPreamble = setupPreambleLines.length
? ` ${setupPreambleLines.join('\n ')}\n`
: ''
// wrap setup code with function.
if (ctx.isTS) {
// for TS, make sure the exported type is still valid type with
@ -994,7 +1034,7 @@ export function compileScript(
vapor && !ssr ? `defineVaporComponent` : `defineComponent`,
)}({${def}${runtimeOptions}\n ${
hasAwait ? `async ` : ``
}setup(${args}) {\n${exposeCall}`,
}setup(${args}) {\n${setupPreamble}`,
)
ctx.s.appendRight(endOffset, `})`)
} else {
@ -1010,14 +1050,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${setupPreamble}`,
)
ctx.s.appendRight(endOffset, `})`)
} else {
ctx.s.prependLeft(
startOffset,
`\n${genDefaultAs} {${runtimeOptions}\n ` +
`${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`,
`${hasAwait ? `async ` : ``}setup(${args}) {\n${setupPreamble}`,
)
ctx.s.appendRight(endOffset, `}`)
}

View File

@ -16,7 +16,7 @@ import type { TemplateCompiler } from './compileTemplate'
import { parseCssVars } from './style/cssVars'
import { createCache } from './cache'
import type { ImportBinding } from './compileScript'
import { isImportUsed } from './script/importUsageCheck'
import { isUsedInTemplate } from './script/importUsageCheck'
import type { LRUCache } from 'lru-cache'
import { genCacheKey } from '@vue/shared'
@ -449,7 +449,7 @@ export function hmrShouldReload(
for (const key in prevImports) {
// if an import was previous unused, but now is used, we need to force
// reload so that the script now includes that import.
if (!prevImports[key].isUsedInTemplate && isImportUsed(key, next)) {
if (!prevImports[key].isUsedInTemplate && isUsedInTemplate(key, next)) {
return true
}
}

View File

@ -11,12 +11,16 @@ import { createCache } from '../cache'
import { camelize, capitalize, isBuiltInDirective } from '@vue/shared'
/**
* Check if an import is used in the SFC's template. This is used to determine
* the properties that should be included in the object returned from setup()
* Check if an identifier is used in the SFC's template.
* - 1.used to determine the properties that should be included in the object returned from setup()
* when not using inline mode.
* - 2.check whether the built-in properties such as $attrs, $slots, $emit are used in the template
*/
export function isImportUsed(local: string, sfc: SFCDescriptor): boolean {
return resolveTemplateUsedIdentifiers(sfc).has(local)
export function isUsedInTemplate(
identifier: string,
sfc: SFCDescriptor,
): boolean {
return resolveTemplateUsedIdentifiers(sfc).has(identifier)
}
const templateUsageCheckCache = createCache<Set<string>>()