Skip to content

Commit 1693924

Browse files
authored
fix(compiler-sfc): automatically infer component name from filename when using script setup (#4997)
close #4993
1 parent 7dfe146 commit 1693924

File tree

5 files changed

+137
-5
lines changed

5 files changed

+137
-5
lines changed

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

+43
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,48 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`SFC analyze <script> bindings auto name inference basic 1`] = `
4+
"export default {
5+
name: 'FooBar',
6+
setup(__props, { expose }) {
7+
expose();
8+
const a = 1
9+
return { a }
10+
}
11+
12+
}"
13+
`;
14+
15+
exports[`SFC analyze <script> bindings auto name inference do not overwrite manual name (call) 1`] = `
16+
"import { defineComponent } from 'vue'
17+
const __default__ = defineComponent({
18+
name: 'Baz'
19+
})
20+
21+
export default /*#__PURE__*/Object.assign(__default__, {
22+
setup(__props, { expose }) {
23+
expose();
24+
const a = 1
25+
return { a, defineComponent }
26+
}
27+
28+
})"
29+
`;
30+
31+
exports[`SFC analyze <script> bindings auto name inference do not overwrite manual name (object) 1`] = `
32+
"const __default__ = {
33+
name: 'Baz'
34+
}
35+
36+
export default /*#__PURE__*/Object.assign(__default__, {
37+
setup(__props, { expose }) {
38+
expose();
39+
const a = 1
40+
return { a }
41+
}
42+
43+
})"
44+
`;
45+
346
exports[`SFC compile <script setup> <script> and <script setup> co-usage script first 1`] = `
447
"import { x } from './x'
548

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

+55
Original file line numberDiff line numberDiff line change
@@ -1550,4 +1550,59 @@ describe('SFC analyze <script> bindings', () => {
15501550
foo: BindingTypes.PROPS
15511551
})
15521552
})
1553+
1554+
describe('auto name inference', () => {
1555+
test('basic', () => {
1556+
const { content } = compile(
1557+
`<script setup>const a = 1</script>
1558+
<template>{{ a }}</template>`,
1559+
undefined,
1560+
{
1561+
filename: 'FooBar.vue'
1562+
}
1563+
)
1564+
expect(content).toMatch(`export default {
1565+
name: 'FooBar'`)
1566+
assertCode(content)
1567+
})
1568+
1569+
test('do not overwrite manual name (object)', () => {
1570+
const { content } = compile(
1571+
`<script>
1572+
export default {
1573+
name: 'Baz'
1574+
}
1575+
</script>
1576+
<script setup>const a = 1</script>
1577+
<template>{{ a }}</template>`,
1578+
undefined,
1579+
{
1580+
filename: 'FooBar.vue'
1581+
}
1582+
)
1583+
expect(content).not.toMatch(`name: 'FooBar'`)
1584+
expect(content).toMatch(`name: 'Baz'`)
1585+
assertCode(content)
1586+
})
1587+
1588+
test('do not overwrite manual name (call)', () => {
1589+
const { content } = compile(
1590+
`<script>
1591+
import { defineComponent } from 'vue'
1592+
export default defineComponent({
1593+
name: 'Baz'
1594+
})
1595+
</script>
1596+
<script setup>const a = 1</script>
1597+
<template>{{ a }}</template>`,
1598+
undefined,
1599+
{
1600+
filename: 'FooBar.vue'
1601+
}
1602+
)
1603+
expect(content).not.toMatch(`name: 'FooBar'`)
1604+
expect(content).toMatch(`name: 'Baz'`)
1605+
assertCode(content)
1606+
})
1607+
})
15531608
})

packages/compiler-sfc/__tests__/utils.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1-
import { parse, SFCScriptCompileOptions, compileScript } from '../src'
1+
import {
2+
parse,
3+
SFCScriptCompileOptions,
4+
compileScript,
5+
SFCParseOptions
6+
} from '../src'
27
import { parse as babelParse } from '@babel/parser'
38

49
export const mockId = 'xxxxxxxx'
510

611
export function compileSFCScript(
712
src: string,
8-
options?: Partial<SFCScriptCompileOptions>
13+
options?: Partial<SFCScriptCompileOptions>,
14+
parseOptions?: SFCParseOptions
915
) {
10-
const { descriptor } = parse(src)
16+
const { descriptor } = parse(src, parseOptions)
1117
return compileScript(descriptor, {
1218
...options,
1319
id: mockId

packages/compiler-sfc/src/compileScript.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
isFunctionType,
1212
walkIdentifiers
1313
} from '@vue/compiler-dom'
14-
import { SFCDescriptor, SFCScriptBlock } from './parse'
14+
import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse'
1515
import { parse as _parse, ParserOptions, ParserPlugin } from '@babel/parser'
1616
import { camelize, capitalize, generateCodeFrame, makeMap } from '@vue/shared'
1717
import {
@@ -263,6 +263,7 @@ export function compileScript(
263263
let hasDefinePropsCall = false
264264
let hasDefineEmitCall = false
265265
let hasDefineExposeCall = false
266+
let hasDefaultExportName = false
266267
let propsRuntimeDecl: Node | undefined
267268
let propsRuntimeDefaults: ObjectExpression | undefined
268269
let propsDestructureDecl: Node | undefined
@@ -811,6 +812,25 @@ export function compileScript(
811812
} else if (node.type === 'ExportDefaultDeclaration') {
812813
// export default
813814
defaultExport = node
815+
816+
// check if user has manually specified `name` option in export default
817+
// if yes, skip infer later
818+
let optionProperties
819+
if (defaultExport.declaration.type === 'ObjectExpression') {
820+
optionProperties = defaultExport.declaration.properties
821+
} else if (
822+
defaultExport.declaration.type === 'CallExpression' &&
823+
defaultExport.declaration.arguments[0].type === 'ObjectExpression'
824+
) {
825+
optionProperties = defaultExport.declaration.arguments[0].properties
826+
}
827+
hasDefaultExportName = !!optionProperties?.some(
828+
s =>
829+
s.type === 'ObjectProperty' &&
830+
s.key.type === 'Identifier' &&
831+
s.key.name === 'name'
832+
)
833+
814834
// export default { ... } --> const __default__ = { ... }
815835
const start = node.start! + scriptStartOffset!
816836
const end = node.declaration.start! + scriptStartOffset!
@@ -1364,6 +1384,12 @@ export function compileScript(
13641384

13651385
// 11. finalize default export
13661386
let runtimeOptions = ``
1387+
if (!hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
1388+
const match = filename.match(/([^/\\]+)\.\w+$/)
1389+
if (match) {
1390+
runtimeOptions += `\n name: '${match[1]}',`
1391+
}
1392+
}
13671393
if (hasInlinedSsrRenderFn) {
13681394
runtimeOptions += `\n __ssrInlineRender: true,`
13691395
}

packages/compiler-sfc/src/parse.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { parseCssVars } from './cssVars'
1313
import { createCache } from './cache'
1414
import { hmrShouldReload, ImportBinding } from './compileScript'
1515

16+
export const DEFAULT_FILENAME = 'anonymous.vue'
17+
1618
export interface SFCParseOptions {
1719
filename?: string
1820
sourceMap?: boolean
@@ -95,7 +97,7 @@ export function parse(
9597
source: string,
9698
{
9799
sourceMap = true,
98-
filename = 'anonymous.vue',
100+
filename = DEFAULT_FILENAME,
99101
sourceRoot = '',
100102
pad = false,
101103
ignoreEmpty = true,

0 commit comments

Comments
 (0)