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.
This commit is contained in:
parent
29d256c39d
commit
67d1aac6ae
@ -70,6 +70,14 @@ describe('compiler: element transform', () => {
|
||||
expect(root.components).toContain(`Foo`)
|
||||
})
|
||||
|
||||
test('resolve implcitly self-referencing component', () => {
|
||||
const { root } = parseWithElementTransform(`<Example/>`, {
|
||||
filename: `/foo/bar/Example.vue?vue&type=template`
|
||||
})
|
||||
expect(root.helpers).toContain(RESOLVE_COMPONENT)
|
||||
expect(root.components).toContain(`_self`)
|
||||
})
|
||||
|
||||
test('static props', () => {
|
||||
const { node } = parseWithElementTransform(`<div id="foo" class="bar" />`)
|
||||
expect(node).toMatchObject({
|
||||
|
@ -128,6 +128,12 @@ interface SharedTransformCodegenOptions {
|
||||
* Indicates that transforms and codegen should try to output valid TS code
|
||||
*/
|
||||
isTS?: boolean
|
||||
/**
|
||||
* Filename for source map generation.
|
||||
* Also used for self-recursive reference in templates
|
||||
* @default 'template.vue.html'
|
||||
*/
|
||||
filename?: string
|
||||
}
|
||||
|
||||
export interface TransformOptions extends SharedTransformCodegenOptions {
|
||||
@ -218,11 +224,6 @@ export interface CodegenOptions extends SharedTransformCodegenOptions {
|
||||
* @default false
|
||||
*/
|
||||
sourceMap?: boolean
|
||||
/**
|
||||
* Filename for source map generation.
|
||||
* @default 'template.vue.html'
|
||||
*/
|
||||
filename?: string
|
||||
/**
|
||||
* SFC scoped styles ID
|
||||
*/
|
||||
|
@ -24,7 +24,9 @@ import {
|
||||
NOOP,
|
||||
PatchFlags,
|
||||
PatchFlagNames,
|
||||
EMPTY_OBJ
|
||||
EMPTY_OBJ,
|
||||
capitalize,
|
||||
camelize
|
||||
} from '@vue/shared'
|
||||
import { defaultOnError } from './errors'
|
||||
import {
|
||||
@ -79,7 +81,9 @@ export interface ImportItem {
|
||||
path: string
|
||||
}
|
||||
|
||||
export interface TransformContext extends Required<TransformOptions> {
|
||||
export interface TransformContext
|
||||
extends Required<Omit<TransformOptions, 'filename'>> {
|
||||
selfName: string | null
|
||||
root: RootNode
|
||||
helpers: Set<symbol>
|
||||
components: Set<string>
|
||||
@ -112,6 +116,7 @@ export interface TransformContext extends Required<TransformOptions> {
|
||||
export function createTransformContext(
|
||||
root: RootNode,
|
||||
{
|
||||
filename = '',
|
||||
prefixIdentifiers = false,
|
||||
hoistStatic = false,
|
||||
cacheHandlers = false,
|
||||
@ -130,8 +135,10 @@ export function createTransformContext(
|
||||
onError = defaultOnError
|
||||
}: TransformOptions
|
||||
): TransformContext {
|
||||
const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
|
||||
const context: TransformContext = {
|
||||
// options
|
||||
selfName: nameMatch && capitalize(camelize(nameMatch[1])),
|
||||
prefixIdentifiers,
|
||||
hoistStatic,
|
||||
cacheHandlers,
|
||||
|
@ -263,7 +263,16 @@ export function resolveComponentType(
|
||||
}
|
||||
}
|
||||
|
||||
// 4. user component (resolve)
|
||||
// 4. Self referencing component (inferred from filename)
|
||||
if (!__BROWSER__ && context.selfName) {
|
||||
if (capitalize(camelize(tag)) === context.selfName) {
|
||||
context.helper(RESOLVE_COMPONENT)
|
||||
context.components.add(`_self`)
|
||||
return toValidAssetId(`_self`, `component`)
|
||||
}
|
||||
}
|
||||
|
||||
// 5. user component (resolve)
|
||||
context.helper(RESOLVE_COMPONENT)
|
||||
context.components.add(tag)
|
||||
return toValidAssetId(tag, `component`)
|
||||
|
@ -33,7 +33,7 @@ export interface SFCBlock {
|
||||
|
||||
export interface SFCTemplateBlock extends SFCBlock {
|
||||
type: 'template'
|
||||
functional?: boolean
|
||||
ast: ElementNode
|
||||
}
|
||||
|
||||
export interface SFCScriptBlock extends SFCBlock {
|
||||
@ -79,7 +79,7 @@ export function parse(
|
||||
source: string,
|
||||
{
|
||||
sourceMap = true,
|
||||
filename = 'component.vue',
|
||||
filename = 'anonymous.vue',
|
||||
sourceRoot = '',
|
||||
pad = false,
|
||||
compiler = CompilerDOM
|
||||
@ -143,31 +143,32 @@ export function parse(
|
||||
switch (node.tag) {
|
||||
case 'template':
|
||||
if (!descriptor.template) {
|
||||
descriptor.template = createBlock(
|
||||
const templateBlock = (descriptor.template = createBlock(
|
||||
node,
|
||||
source,
|
||||
false
|
||||
) as SFCTemplateBlock
|
||||
) as SFCTemplateBlock)
|
||||
templateBlock.ast = node
|
||||
} else {
|
||||
errors.push(createDuplicateBlockError(node))
|
||||
}
|
||||
break
|
||||
case 'script':
|
||||
const block = createBlock(node, source, pad) as SFCScriptBlock
|
||||
const isSetup = !!block.attrs.setup
|
||||
const scriptBlock = createBlock(node, source, pad) as SFCScriptBlock
|
||||
const isSetup = !!scriptBlock.attrs.setup
|
||||
if (isSetup && !descriptor.scriptSetup) {
|
||||
descriptor.scriptSetup = block
|
||||
descriptor.scriptSetup = scriptBlock
|
||||
break
|
||||
}
|
||||
if (!isSetup && !descriptor.script) {
|
||||
descriptor.script = block
|
||||
descriptor.script = scriptBlock
|
||||
break
|
||||
}
|
||||
errors.push(createDuplicateBlockError(node, isSetup))
|
||||
break
|
||||
case 'style':
|
||||
const style = createBlock(node, source, pad) as SFCStyleBlock
|
||||
if (style.attrs.vars) {
|
||||
const styleBlock = createBlock(node, source, pad) as SFCStyleBlock
|
||||
if (styleBlock.attrs.vars) {
|
||||
errors.push(
|
||||
new SyntaxError(
|
||||
`<style vars> has been replaced by a new proposal: ` +
|
||||
@ -175,7 +176,7 @@ export function parse(
|
||||
)
|
||||
)
|
||||
}
|
||||
descriptor.styles.push(style)
|
||||
descriptor.styles.push(styleBlock)
|
||||
break
|
||||
default:
|
||||
descriptor.customBlocks.push(createBlock(node, source, pad))
|
||||
@ -290,8 +291,6 @@ function createBlock(
|
||||
} else if (p.name === 'module') {
|
||||
;(block as SFCStyleBlock).module = attrs[p.name]
|
||||
}
|
||||
} else if (type === 'template' && p.name === 'functional') {
|
||||
;(block as SFCTemplateBlock).functional = true
|
||||
} else if (type === 'script' && p.name === 'setup') {
|
||||
;(block as SFCScriptBlock).setup = attrs.setup
|
||||
}
|
||||
|
@ -811,7 +811,7 @@ export function formatComponentName(
|
||||
? Component.displayName || Component.name
|
||||
: Component.name
|
||||
if (!name && Component.__file) {
|
||||
const match = Component.__file.match(/([^/\\]+)\.vue$/)
|
||||
const match = Component.__file.match(/([^/\\]+)\.\w+$/)
|
||||
if (match) {
|
||||
name = match[1]
|
||||
}
|
||||
|
@ -67,6 +67,12 @@ function resolveAsset(
|
||||
|
||||
// self name has highest priority
|
||||
if (type === COMPONENTS) {
|
||||
// special self referencing call generated by compiler
|
||||
// inferred from SFC filename
|
||||
if (name === `_self`) {
|
||||
return Component
|
||||
}
|
||||
|
||||
const selfName =
|
||||
(Component as FunctionalComponent).displayName || Component.name
|
||||
if (
|
||||
|
@ -53,7 +53,7 @@ window.init = () => {
|
||||
const compileFn = ssrMode.value ? ssrCompile : compile
|
||||
const start = performance.now()
|
||||
const { code, ast, map } = compileFn(source, {
|
||||
filename: 'template.vue',
|
||||
filename: 'ExampleTemplate.vue',
|
||||
...compilerOptions,
|
||||
sourceMap: true,
|
||||
onError: err => {
|
||||
@ -150,7 +150,7 @@ window.init = () => {
|
||||
clearEditorDecos()
|
||||
if (lastSuccessfulMap) {
|
||||
const pos = lastSuccessfulMap.generatedPositionFor({
|
||||
source: 'template.vue',
|
||||
source: 'ExampleTemplate.vue',
|
||||
line: e.position.lineNumber,
|
||||
column: e.position.column - 1
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user