Search code examples
typescriptvue.jsvue-routervuejs3vue-router4

How to declare TypeScript type interface for custom meta fields in Vue Router v4?


With Vue Router version 4, which is currently in beta.11 in vue-router-next repo, there is a documentation page about how to define meta fields custom type interface with TypeScript.

declare module 'vue-router' {
  interface RouteMeta {
    // is optional
    isAdmin?: boolean
    // must be declared by every route
    requiresAuth: boolean
  }
}

To be placed along the Vue shim module declaration. Mine is looking like:

declare module '*.vue' {
  import { defineComponent } from 'vue';

  const component: ReturnType<typeof defineComponent>;
  export default component;
}

declare module 'vue-router' {
  interface RouteMeta {
    isPublic?: boolean;
  }
}

However, this is not working. Instead this way of defining the interface seems to overwrite the interface that is shipped with the package, or rather declaring 'vue-router' module seems to do that.

What would be the correct way of defining custom meta field types?


Solution

  • Their documentation is wrong or at best incomplete.

    A Module Augmentation uses the same syntax as an Ambient Module declaration and is only considered an augmentation when it is within a module file itself. A module is defined, as per the ECMAScript specification, as a file containing one or more top level import or export statements.

    The snippet in a file that is not a module does exactly what you've noticed. It supplants any other type declarations for the 'vue-router' package instead of augmenting them. But we want to augment that package's types, not replace them.

    However, a declare module statement that is intended as a declaration, not an augmentation, must be in a file that is, conversely, not a module. That is, in a file not containing any top level import or export statements.

    To resolve this, move the declare module 'vue-router' {...} to a separate file (say, augmentations.d.ts), and make that file a module by beginning it with export {}.

    // augmenations.d.ts
    
    // Ensure this file is parsed as a module regardless of dependencies.
    export {}
    
    declare module 'vue-router' {
      interface RouteMeta {
        // is optional
        isAdmin?: boolean
        // must be declared by every route
        requiresAuth: boolean
      }
    }
    

    Now let's come back and take look at the original code in question.

    // shims-vue.d.ts
    
    declare module '*.vue' {
      import { defineComponent } from 'vue';
    
      const component: ReturnType<typeof defineComponent>;
      export default component;
    }
    
    declare module 'vue-router' {
      interface RouteMeta {
        isPublic?: boolean;
      }
    }
    

    The two declare module statements cannot exist in the same file because one of them is trying to declare a module, '*.vue', and the other to augment one. Therefore, we will leave the declare module '*.vue' {...} where it is, as it is functioning as intended.