Skip to content

Commit 67d1aac

Browse files
committed
feat(sfc): allow sfcs to recursively self-reference in template via name inferred from filename
e.g. A file named `FooBar.vue` can refer to itself as `<FooBar/>`. This gets rid of the need for the `name` option.
1 parent 29d256c commit 67d1aac

File tree

8 files changed

+54
-24
lines changed

8 files changed

+54
-24
lines changed

packages/compiler-core/__tests__/transforms/transformElement.spec.ts

+8
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ describe('compiler: element transform', () => {
7070
expect(root.components).toContain(`Foo`)
7171
})
7272

73+
test('resolve implcitly self-referencing component', () => {
74+
const { root } = parseWithElementTransform(`<Example/>`, {
75+
filename: `/foo/bar/Example.vue?vue&type=template`
76+
})
77+
expect(root.helpers).toContain(RESOLVE_COMPONENT)
78+
expect(root.components).toContain(`_self`)
79+
})
80+
7381
test('static props', () => {
7482
const { node } = parseWithElementTransform(`<div id="foo" class="bar" />`)
7583
expect(node).toMatchObject({

packages/compiler-core/src/options.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ interface SharedTransformCodegenOptions {
128128
* Indicates that transforms and codegen should try to output valid TS code
129129
*/
130130
isTS?: boolean
131+
/**
132+
* Filename for source map generation.
133+
* Also used for self-recursive reference in templates
134+
* @default 'template.vue.html'
135+
*/
136+
filename?: string
131137
}
132138

133139
export interface TransformOptions extends SharedTransformCodegenOptions {
@@ -218,11 +224,6 @@ export interface CodegenOptions extends SharedTransformCodegenOptions {
218224
* @default false
219225
*/
220226
sourceMap?: boolean
221-
/**
222-
* Filename for source map generation.
223-
* @default 'template.vue.html'
224-
*/
225-
filename?: string
226227
/**
227228
* SFC scoped styles ID
228229
*/

packages/compiler-core/src/transform.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import {
2424
NOOP,
2525
PatchFlags,
2626
PatchFlagNames,
27-
EMPTY_OBJ
27+
EMPTY_OBJ,
28+
capitalize,
29+
camelize
2830
} from '@vue/shared'
2931
import { defaultOnError } from './errors'
3032
import {
@@ -79,7 +81,9 @@ export interface ImportItem {
7981
path: string
8082
}
8183

82-
export interface TransformContext extends Required<TransformOptions> {
84+
export interface TransformContext
85+
extends Required<Omit<TransformOptions, 'filename'>> {
86+
selfName: string | null
8387
root: RootNode
8488
helpers: Set<symbol>
8589
components: Set<string>
@@ -112,6 +116,7 @@ export interface TransformContext extends Required<TransformOptions> {
112116
export function createTransformContext(
113117
root: RootNode,
114118
{
119+
filename = '',
115120
prefixIdentifiers = false,
116121
hoistStatic = false,
117122
cacheHandlers = false,
@@ -130,8 +135,10 @@ export function createTransformContext(
130135
onError = defaultOnError
131136
}: TransformOptions
132137
): TransformContext {
138+
const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
133139
const context: TransformContext = {
134140
// options
141+
selfName: nameMatch && capitalize(camelize(nameMatch[1])),
135142
prefixIdentifiers,
136143
hoistStatic,
137144
cacheHandlers,

packages/compiler-core/src/transforms/transformElement.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,16 @@ export function resolveComponentType(
263263
}
264264
}
265265

266-
// 4. user component (resolve)
266+
// 4. Self referencing component (inferred from filename)
267+
if (!__BROWSER__ && context.selfName) {
268+
if (capitalize(camelize(tag)) === context.selfName) {
269+
context.helper(RESOLVE_COMPONENT)
270+
context.components.add(`_self`)
271+
return toValidAssetId(`_self`, `component`)
272+
}
273+
}
274+
275+
// 5. user component (resolve)
267276
context.helper(RESOLVE_COMPONENT)
268277
context.components.add(tag)
269278
return toValidAssetId(tag, `component`)

packages/compiler-sfc/src/parse.ts

+12-13
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export interface SFCBlock {
3333

3434
export interface SFCTemplateBlock extends SFCBlock {
3535
type: 'template'
36-
functional?: boolean
36+
ast: ElementNode
3737
}
3838

3939
export interface SFCScriptBlock extends SFCBlock {
@@ -79,7 +79,7 @@ export function parse(
7979
source: string,
8080
{
8181
sourceMap = true,
82-
filename = 'component.vue',
82+
filename = 'anonymous.vue',
8383
sourceRoot = '',
8484
pad = false,
8585
compiler = CompilerDOM
@@ -143,39 +143,40 @@ export function parse(
143143
switch (node.tag) {
144144
case 'template':
145145
if (!descriptor.template) {
146-
descriptor.template = createBlock(
146+
const templateBlock = (descriptor.template = createBlock(
147147
node,
148148
source,
149149
false
150-
) as SFCTemplateBlock
150+
) as SFCTemplateBlock)
151+
templateBlock.ast = node
151152
} else {
152153
errors.push(createDuplicateBlockError(node))
153154
}
154155
break
155156
case 'script':
156-
const block = createBlock(node, source, pad) as SFCScriptBlock
157-
const isSetup = !!block.attrs.setup
157+
const scriptBlock = createBlock(node, source, pad) as SFCScriptBlock
158+
const isSetup = !!scriptBlock.attrs.setup
158159
if (isSetup && !descriptor.scriptSetup) {
159-
descriptor.scriptSetup = block
160+
descriptor.scriptSetup = scriptBlock
160161
break
161162
}
162163
if (!isSetup && !descriptor.script) {
163-
descriptor.script = block
164+
descriptor.script = scriptBlock
164165
break
165166
}
166167
errors.push(createDuplicateBlockError(node, isSetup))
167168
break
168169
case 'style':
169-
const style = createBlock(node, source, pad) as SFCStyleBlock
170-
if (style.attrs.vars) {
170+
const styleBlock = createBlock(node, source, pad) as SFCStyleBlock
171+
if (styleBlock.attrs.vars) {
171172
errors.push(
172173
new SyntaxError(
173174
`<style vars> has been replaced by a new proposal: ` +
174175
`https://github.com/vuejs/rfcs/pull/231`
175176
)
176177
)
177178
}
178-
descriptor.styles.push(style)
179+
descriptor.styles.push(styleBlock)
179180
break
180181
default:
181182
descriptor.customBlocks.push(createBlock(node, source, pad))
@@ -290,8 +291,6 @@ function createBlock(
290291
} else if (p.name === 'module') {
291292
;(block as SFCStyleBlock).module = attrs[p.name]
292293
}
293-
} else if (type === 'template' && p.name === 'functional') {
294-
;(block as SFCTemplateBlock).functional = true
295294
} else if (type === 'script' && p.name === 'setup') {
296295
;(block as SFCScriptBlock).setup = attrs.setup
297296
}

packages/runtime-core/src/component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ export function formatComponentName(
811811
? Component.displayName || Component.name
812812
: Component.name
813813
if (!name && Component.__file) {
814-
const match = Component.__file.match(/([^/\\]+)\.vue$/)
814+
const match = Component.__file.match(/([^/\\]+)\.\w+$/)
815815
if (match) {
816816
name = match[1]
817817
}

packages/runtime-core/src/helpers/resolveAssets.ts

+6
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ function resolveAsset(
6767

6868
// self name has highest priority
6969
if (type === COMPONENTS) {
70+
// special self referencing call generated by compiler
71+
// inferred from SFC filename
72+
if (name === `_self`) {
73+
return Component
74+
}
75+
7076
const selfName =
7177
(Component as FunctionalComponent).displayName || Component.name
7278
if (

packages/template-explorer/src/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ window.init = () => {
5353
const compileFn = ssrMode.value ? ssrCompile : compile
5454
const start = performance.now()
5555
const { code, ast, map } = compileFn(source, {
56-
filename: 'template.vue',
56+
filename: 'ExampleTemplate.vue',
5757
...compilerOptions,
5858
sourceMap: true,
5959
onError: err => {
@@ -150,7 +150,7 @@ window.init = () => {
150150
clearEditorDecos()
151151
if (lastSuccessfulMap) {
152152
const pos = lastSuccessfulMap.generatedPositionFor({
153-
source: 'template.vue',
153+
source: 'ExampleTemplate.vue',
154154
line: e.position.lineNumber,
155155
column: e.position.column - 1
156156
})

0 commit comments

Comments
 (0)