How to make types, classes, helper functions, etc. available in all components in Vue 3 with TypeScript without polluting the global scope?

Viewed 573

Context

I'm building a Vue app (using the Composition API) in TypeSript and I'm running into an issue that I'm sure has been explained many times, but I just can't find a solution or even know how to best formulate the question. (In case that matters, I'm using Vite and VS Code.)

I have a TS file that contains types, interfaces, enums, classes and functions that are meant to be used globally.

Currently, in every single other .vue or .ts file, I have a lengthy import statement that imports everything I need from that file. As you can imagine, this is quite unwieldy.

Is there a way to make all the contents of that helper file usable everywhere, without having to import it?

Things I've tried

  1. Using Vue's global properties

This isn't recommended or documented for projects using the Composition API and script setup, which is what I'm doing. Besides, it doesn't work for enums, types or classes, as far as I could tell.

  1. Using Vue's provide/inject or composables features

This is equivalent to using imports, it just adds an extra step. Unless I'm missing something.

  1. Importing the file in main.ts, then augmenting the global scope using declare global {} so that TS doesn't complain

I don't know if this is a good practice (probably not) or if it even is supposed to work. I didn't manage to get it to behave. In particular, I didn't know how to get this to behave with classes, I kept getting Declaration or statement expected or No implementation allowed in ambient contexts errors.

  1. Using option 3 for types only, and importing everything else

That's what I ended up doing and it makes it sliiiightly more manageable as types is what I need a majority of the time, but I really wish I could find a way to do this for enums as well at the very least.

To sum up...

Is there a best practice for making all kinds of things from a single file available to an entire project without having to rely on import statements?


Edit to clarify:

I know polluting the global object is bad. I'm not asking how to do that! I'm looking for solutions to not do that, while also not having to add 20 lines of imports to each file.

I know it's doable:

  • Rollup's inject plugin automatically adds import statements everywhere needed based on a configuration file you create once
  • unplugin-auto-import, as the name suggests, auto-imports APIs based on a list of presets

These two tools enable you to use the stuff you need everywhere without having to worry about making it available. (I actually use the latter, but didn't manage to get the former to work with Vite.) How can I do the same thing with helpers and utilities that exist in a file locally?

2 Answers

If what you're looking for is an alternative to Vue2's Vue.prototype, here's a useful resource, authored by @skirtle. It covers all techniques you've already tried, and then some, outlining specific differences, pros and cons of each.


As a rule of thumb, the best practice in Composition API is not to have globals.

The general concept of Composition API is to have granular control over the variables exposed to each <template> and not to expose anything other than what's actually being used in the current component/template.

In other words, wherever you need stuff, you have to import it:

import { useStuff } from './wherever'

const stuff = useStuff();

If you're inside a <script setup>, that should be enough.
If you're inside a setup(), inside a defineComponent(), you have to add stuff to the object returned by setup(), before you could use it in the <template>.

By definition, what import means is that it is not global. You have to import it before you can use it.


Addendum: When our projects grow in complexity, the import section of each component inevitably grows and, as far as I can tell, this is the "problem" you're trying to solve.
Here's how I tackle it:

  • I re-export all my stores from an index.ts file in the src/store folder and then I can import all stores needed in a component using a single import:
    import { useAuth, useLayout, useSomethingElse } from '@/store';
  • I do the same thing with my types, using @/types folder
  • Sometimes I also keep a @/config folder, as most of my apps are configuration driven

I'm not necessarily recommending the pattern, as I'm aware others might prefer different ones and I wouldn't want to start an argument over opinionated code architecture. What I can tell you is that my import sections throughout my projects no longer look messy.
Notably, when I Ctrl/Cmd + click a resource imported from a barrel import, my IDE takes me directly to the definition source, by-passing the re-export step.

You can use Vue mixins - feel free to check the documentation!

Related