Tal
Tal

Reputation: 1448

Global types in typescript

Is there a way to make a file in your typescript file that defines globally accessible types?

I like typescript but find that when I want to be truly type-safe I have to explicitly import types from all over the system. It's rather annoying.

Upvotes: 81

Views: 113023

Answers (7)

James Lam
James Lam

Reputation: 345

When using global variables in TypeScript, two problems are often encountered:

  • Cannot find name 'variable-name', which means that the variable has not been properly exposed to the global scope.
  • The global variable type is any, which means that the type has not been properly imported.

Solution

  1. First, the type of the global variable needs to be declared:
// global-types.d.ts
import type { Foo } from "package-with-types";

declare global {
  type Bar = Foo;
}
export {};

💡 A declaration file is not a module by default, so you cannot import types in a declaration file. Therefore, use export {} to make the declaration file a module, import types with import type, and then use declare global to define globally accessible types.

  1. Then, declare the global variable:
// global.d.ts
// eslint-disable-next-line  @typescript-eslint/no-explicit-any
declare const eruda: any;
declare const bar: Bar;

  1. This allows access to the global variable in the project with type hints.

References

Upvotes: 2

Aaron Beaudoin
Aaron Beaudoin

Reputation: 577

For a lot of people the detail I got myself tripped up on might be obvious. I spent 2 hours trying to figure out why my *.d.ts files were not getting picked up, and it turns out it was simply because in my tsconfig.json I had an include property like this (it was the default that came with a project template I started with):

{
  ...
  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue"
  ]
}

But to keep my src/ directory clean I had put my whatever.d.ts file was under types/. 🤦‍♂️ So in my case the fix was simply one of the following:

  1. Add **/*.d.ts or types/*.d.ts or something like that to my include property.
  2. Remove the include property entirely and let it use the default.

Upvotes: 1

Qwerty
Qwerty

Reputation: 31919

All the answers above can actually coexist and work together. The key is understanding that declaration files (non-modules) must be "somehow" discoverable by the project, since they don't contain exports. And that there is a syntax that allows any module file (any file that uses imports/exports, basically anything in your src folder) to contribute ambient declarations as well.

Ambient Declarations files (non-modules)

  • Must be included in the project (i.e. tsconfig.json "include")
  • Must not contain any import or export statements
  • To import external types, there is a special syntax
tsconfig.json
{
  "include": ["./src/global.d.ts"],
  // or wildcard
  "include": ["**/*.d.ts"],
}
src/globals.d.ts
// Global types

type AnyObject<T = any> = Record<string, T>
type AnyFunction = (...args: any[]) => any

// Contributing to existing interfaces (interface merging)

interface Window {
  console: AnyObject
}

// Importing

declare type BaseContract = import('ethers').BaseContract
declare type _MockContract = import('ethereum-waffle').MockContract
declare type Stub = import('ethereum-waffle').Stub
// Here we are re-defining an existing interface to provide better typings.
interface MockContract<T extends BaseContract = BaseContract> extends _MockContract {
  mock: {
    [key in keyof T['functions']]: Stub
  }
}

JS Modules

  • Modules are those files that contain import or export statements,
    so basically every file within your src folder.
  • Any js (module) file can provide ambient declarations.
src/app.ts
import React from 'react'

export default MyApp(props: AppProps) {
  return <div>Hi</div>
}

// Ambient declarations

declare global {
  interface Window {
    console: AnyObject
  }
}

Put this at the top of your Ambient Declarations file to avoid coworker confusion

////// -----------------------------------------------------------------------------------------------------------------
/*//// -----------------------------------------------------------------------------------------------------------------

This file is an "Ambient declarations file". The types defined here are available globally.
More info here: https://stackoverflow.com/a/73389225/985454

Don't use `import` and `export` in this file directly! It breaks ambience.
To import external types in an ambient declarations file (this file) use the following:

*//**
* @example
* declare type React = import('react')
*//*

To contribute ambient declarations from any file, even non-ambient ones, use this:

*//**
* @example
* declare global {
*   interface Window {
*     ethereum: any
*   }
* }
*//*

/*//// -----------------------------------------------------------------------------------------------------------------
////// -----------------------------------------------------------------------------------------------------------------

// Your type definitions here ...

Upvotes: 20

byyoung
byyoung

Reputation: 397

In addition to Sebastian Sebald's Answer

don't forget to

export {} 

which makes it actual module.

so like this.

this is working.

declare global {
    /*~ Here, declare things that go in the global namespace, or augment
     *~ existing declarations in the global namespace
     */
    interface String {
        fancyFormat(opts: StringFormatOptions): string;
    }
}
export {}

Upvotes: 18

Elias
Elias

Reputation: 4112

I found the accepted answer is not working (maybe it is some configuration that needs to be done?). So with some tinkering, I got it to work for me (maybe I also have some weird configuration option? Let me know if it doesn't work and I will remove this answer).

  1. Create a definition file in a suitable folder. I will be using types/global.d.ts
  2. Check your tsconfig.json and include "*": ["types/*.d.ts"] under paths. (You should also be able to directly address the created file if you care to).
  3. Put your global definitions directly into the root of the file NO declare global or similar.

Now you should be good to go to use the types declared in this file (tested with typescript 3.9.6 and 3.7.5).

Example file:

// global.d.ts
declare interface Foo {
    bar: string;
    fooBar: string;
}

What your tsconfig should look like:

[...]
"paths": {
    "*": ["types/*.d.ts"]
},
[...]

Upvotes: 56

Raid
Raid

Reputation: 825

A little late but, you can add a file.d.ts anywhere in your project as I've noticed, and it will be picked up.

For example, in my project I wanted a:

Optional<T> = T | null;

And I didn't know where to add it, so I added a common.d.ts in a folder, and added:

declare type Optional<T> = T | null;

Now it's being picked up and no errors. Didn't even need to import it. This of course being tested in vscode, not sure if it will work in your editor.

(Depending on your file include/exclude rules of course, but most projects include all *.ts)

Upvotes: 30

Sebastian Sebald
Sebastian Sebald

Reputation: 16846

Yes this is possible. You can find all information here: https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html

The important part is this:

declare global {
    /*~ Here, declare things that go in the global namespace, or augment
     *~ existing declarations in the global namespace
     */
    interface String {
        fancyFormat(opts: StringFormatOptions): string;
    }
}

Upvotes: 71

Related Questions