]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(sfc): allow sfcs to recursively self-reference in template via name inferred...
authorEvan You <yyx990803@gmail.com>
Mon, 30 Nov 2020 17:30:35 +0000 (12:30 -0500)
committerEvan You <yyx990803@gmail.com>
Mon, 30 Nov 2020 17:30:51 +0000 (12:30 -0500)
e.g. A file named `FooBar.vue` can refer to itself as `<FooBar/>`. This gets rid of the need for the `name` option.

packages/compiler-core/__tests__/transforms/transformElement.spec.ts
packages/compiler-core/src/options.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-sfc/src/parse.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/helpers/resolveAssets.ts
packages/template-explorer/src/index.ts

index 33cfe51dd7e63b4f9d41bd17b431a0d612ce76ca..37c7c7b5f4923c4125be2c8d9685c3a3a0c6e785 100644 (file)
@@ -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({
index 02ee406838d3d1e1a9778476ddcae69df4599154..2850da196b1328f451c7ad55f110f44b1c9afb15 100644 (file)
@@ -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
    */
index b21bb73a8c993b4934ced3b99f47c020a481b96a..474f491595c2fbeef3522416e4a9af6ac68b53f4 100644 (file)
@@ -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,
index ca1ae9d1be32158e1cd00a2a1ab0e4769b73b9d8..53b148b42bbd20b118f8b08ffb4c52b5c7520dfd 100644 (file)
@@ -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`)
index 1df52721ac7b9812889ae1643a3157008693b5f9..9a065a457e82c251a785caec8f77ac50f04f46c1 100644 (file)
@@ -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
       }
index 9b1fdcef18f12d21502080a6b3e7b7f01fc44286..9e416586fe05ee04d0d99582ee3639e3d892be31 100644 (file)
@@ -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]
     }
index 2ed49fba085dfb3cee7ccb876f3d7ab82cb718d8..d94422c32fdc9f4b3c5874dbce641aa9b0765af3 100644 (file)
@@ -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 (
index e1328913ab821bc170a0d30166a758466391a003..ee5ed750ba60dee69224ad6399f832b5ea94a243 100644 (file)
@@ -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
         })