Search code examples
sveltesvelte-3svelte-component

How can I get current component tag name in Svelte?


I am developing a custom UI component library using Svelte and each component has at least a CSS class which is the same name the component tag name. Also, I have a base component which all other components are using that. Here is the simplified codes:

Base.svelte

<script>
    export let componentName
    export let tag = 'div'
</script>
<svelte:element this={tag} class={componentName}>
    <slot />
</svelte:element>  

Foo.svelte

<script>
    import Base from './Base.svelte'
</script>
<Base tag="b" componentName="Foo">
    <slot />
</Base>

Bar.svelte

<script>
    import Base from './Base.svelte'
</script>
<Base tag="h3" componentName="Bar">
    <slot />
</Base>

App.svelte

<script>
    import Foo from './Foo.svelte'
    import Bar from './Bar.svelte'
</script>

<Foo>
    <Bar>This is Bar 1</Bar>
    <Bar>This is Bar 2</Bar>
</Foo>
<Foo>
    Foo
</Foo>

In above code, I have to set the componentName prop which actually should be a constant. Also, I should always update that in case I change the component's file name.

Is there anyway to get the current component tag name and set it in Base component?

Or, a function that returns the name similar to what we have for the component:

import { current_component } from 'svelte/internal';


Solution

  • You cannot get the tag name; it is not static and components can be rendered without using a tag at all via <svelte:component> or by just using the constructor in code. There are ways to get the file name, which then could be transformed.

    I would approach this via pre-processing. Svelte already provides a way to do this via its config, e.g.

    // svelte.config.js
    import { basename } from 'path';
    
    /** @type {import('svelte/types/compiler/preprocess').PreprocessorGroup} */
    const addComponentName = {
        script: ({ content, filename }) => {
            const componentName = basename(filename, '.svelte');
            return {
                code: `const __componentName = ${JSON.stringify(componentName)};\n`
                    + content,
            };
        }
    }
    
    export default { preprocess: [addComponentName] };
    

    This adds code that sets a constant __componentName to the file name, which thus can be used in the rest of the code or the markup. You can use whatever name works for you; the underscores are there to prevent potential name collisions with the existing code.

    (If you are using TypeScript you have to declare a global for this to prevent errors, as the declaration will be missing from the source files.)