Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to configure volar so that it resolves global / auto-imported components when they are not explicitly imported inside <script> tag? #889

Closed
maninak opened this issue Jan 22, 2022 · 11 comments
Labels
question Further information is requested

Comments

@maninak
Copy link

maninak commented Jan 22, 2022

Hello,

I'm using Nuxt v2 ("nuxt-edge": "^2.16.0-27335535.777a4b7f") with Composition API so I'm also using Volar v0.31.1.

Unfortunately, since today it seems that a lot of my (own) components, as well as components from libraries (e.g. <NuxtImg>), seem to not be resolved which results in them shown red and related IntelliSense not working.

image

All those vue components are auto-imported via the components property of nuxt.config.js which is a built-in feature of nuxt and everyone is using it by default.

image

Some other components are also auto-imported via unplugin-auto-import/nuxt

image

Is there a way to configure volar so that it resolves those imports when they are not explicitly specified inside <script>?

To be more specific, if the following code is all that I put in a .vue file (without even using a <script> tag), vue and nuxt compile and show it just fine on the page, but Volar cannot resolve the import of my components/FancyButton.vue.

<template>
  <div>
    <FancyButton>Click me</FancyButton>
  </div>
</template>

Thank you so much for this amazing extension!

@maninak maninak changed the title How to configure volar so that it resolves auto-imported components when they are not explicitly specified inside <script> tag? How to configure volar so that it resolves global / auto-imported components when they are not explicitly imported inside <script> tag? Jan 22, 2022
@maninak
Copy link
Author

maninak commented Jan 22, 2022

One thing I just tried is adding a components.d.ts file in my repo like so:

declare module '@vue/runtime-core' {
  export interface GlobalComponents {
    FancyButton: typeof import('components/FancyButton.vue').default
  }
}

export {}

But there are two issues I still have:

  1. Am I doing something wrong here? While Volar doesn't show my component's name in red in the template anymore, when hovering over it, it shows it's imported as : any.
  2. Is there a better way to import all .vue files under the /components directory so that I don't have to manually add all components in this file and maintain it over time?

@johnsoncodehk
Copy link
Member

Actually I'm not familiar with nuxt and I'm not sure about nuxt best practices. But I see that in your screenshot: dts: 'types/auto-imports.d.ts'

I guess this file will be create when you run build command, and it may have types that volar needed. If you already have this file but global component types still missing, you may need to define "types": ["types/auto-imports.d.ts"] in compilerOption from your tsconfig.

If it still not working, nuxt discord server is better to ask for help: https://discord.com/invite/ps2h6QT

@johnsoncodehk johnsoncodehk added the question Further information is requested label Jan 23, 2022
@rchl
Copy link
Collaborator

rchl commented Jul 19, 2022

Nuxt (v2) doesn't auto-generate types for ./components/*. Is someone expected to manually add components to GlobalComponents whenever adding components in ./components/*?

@ubugeeei
Copy link
Member

ubugeeei commented Nov 11, 2022

I have created a package that generates global component type definitions by specifying a directory.
Please use it if you like! 😄
https://www.npmjs.com/package/vue-global-type-gen

@rchl
Copy link
Collaborator

rchl commented Mar 29, 2023

@johnsoncodehk do you think it would be possible to dynamically generate those types in memory through the plugin infrastructure in the future (or now already)? Or it's not likely?

@johnsoncodehk
Copy link
Member

@rchl Already supported experimentally by #2267.

@rchl
Copy link
Collaborator

rchl commented Mar 29, 2023

Cool!

But you said in that PR:

Note that this option is only available when the TypeScript Plugin or Takeover mode is enabled.

Which is a bit of a roadblock for me because I wouldn't want to use that mode necessarily.
And also, is that even relevant for that specific case where we want to generate type definitions for Vue compoents? Doesn't sound like that should require takeover mode?

@johnsoncodehk
Copy link
Member

If TypeScript Plugin or Takeover mode is not enabled, Volar cannot make the type generated in memory affect *.ts, but only affect *.vue.

On the other hand, when Nuxt is starting the dev server, it generates real .d.ts files instead of memory files, so there are no such limitations.

@rchl
Copy link
Collaborator

rchl commented Mar 29, 2023

Nuxt 3 generates types but not Nuxt 2.

The intention here is to have type augmentations added for local Vue components so that those are recognized when used in other local Vue components. So there are no *.ts files involved in that (unless you are talking about internal, virtual *.ts files).

@rchl
Copy link
Collaborator

rchl commented Mar 29, 2023

This actually works (without takeover mode):

/** @type {import('@volar/language-core').LanguageModule} */
module.exports = {
    createFile() {
        return undefined;
    },
    updateFile() {},
    proxyLanguageServiceHost(host) {
        const ts = host.getTypeScriptModule();
        const vueTypesScript = {
            projectVersion: '',
            fileName: host.getCurrentDirectory() + '/generated-component-types.d.ts',
            _version: 0,
            _snapshot: ts?.ScriptSnapshot.fromString(''),
            get version() {
                this.update();
                return this._version;
            },
            get snapshot() {
                this.update();
                return this._snapshot;
            },
            update() {
                if (!this._snapshot) {
                    return
                }
                if (!host.getProjectVersion || host.getProjectVersion() !== this.projectVersion) {
                    this.projectVersion = host.getProjectVersion?.() ?? '';
                    const newText = this.generateText();
                    console.error({newText})
                    if (newText !== this._snapshot.getText(0, this._snapshot.getLength())) {
                        this._version++;
                        this._snapshot = ts?.ScriptSnapshot.fromString(newText);
                    }
                }
            },
            generateText() {
                const projectFileNames = host.getScriptFileNames().map(fileName => fileName.replace(host.getCurrentDirectory() + '/', ''));
                const vueFiles = projectFileNames.filter(fileName => (fileName.startsWith('pages/') || fileName.startsWith('components/')) && fileName.endsWith('.vue'));
                const components = vueFiles
                    .map(path => {
                        const filename = path.split('/').pop()
                        if (!filename) {
                            return
                        }
                        const [basename, _] = filename.split('.')
                        return {
                            name: basename,
                            path,
                        }
                    })
                    .filter(Boolean)
                return `
declare module '@vue/runtime-core' {
    export interface GlobalComponents {
        ${components.map(component => `${component.name}: typeof import('${component.path}').default`).join(('\n'))}
        ${components.map(component => `Lazy${component.name}: typeof import('${component.path}').default`).join(('\n'))}
    }
}
`;
            },
        };

        return {
            getScriptFileNames() {
                return [
                    ...host.getScriptFileNames(),
                    vueTypesScript.fileName,
                ];
            },
            getScriptVersion(fileName) {
                if (fileName === vueTypesScript.fileName) {
                    return String(vueTypesScript.version);
                }
                return host.getScriptVersion(fileName);
            },
            getScriptSnapshot(fileName) {
                if (fileName === vueTypesScript.fileName) {
                    return vueTypesScript.snapshot;
                }
                return host.getScriptSnapshot(fileName);
            },
        }
    },
};

The only issue I see is that the "go to definition" doesn't work. Is that something that can be made to work with some extra code in the module?

@johnsoncodehk
Copy link
Member

johnsoncodehk commented Mar 29, 2023

The only issue I see is that the "go to definition" doesn't work. Is that something that can be made to work with some extra code in the module?

This is possible, but I can't check it for now. Can you open a feature request?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants