How I fixed some trouble importing types in .d.ts files

By

How I fixed Astro env.d.ts types not being picked up after a top-level import, using an inline import() type, plus the declare global way to do it today.

~~~

I had some trouble making something work in my Astro site.

I used Astro locals and I had to type a variable I shared using locals.

So I went and added that to the src/env.d.ts, as the docs say.

But my types weren’t picked up.

My code looked like this:

/// <reference types="astro/client" />
import { sometype } from 'somelib'

declare namespace App {
  interface Locals {
    somevariable: sometype
  }
}

What I discovered thanks to this SO answer is, we can’t do imports like this in .d.ts files.

So I imported the type in another file, and then I imported the type from that file, like this:

typesforenvdts.ts

import { sometype } from 'somelib'

export { sometype }

env.d.ts

/// <reference types="astro/client" />

declare namespace App {
  interface Locals {
    somevariable: import('./typesforenvdts').sometype
  }
}

Don’t ask me why, but now it works.

Why it works

Looking back, “you can’t import in a .d.ts file” isn’t quite right. Imports are allowed. The catch is that a top-level import turns the declaration file into a module, and a namespace written at the top level of a module is no longer a global augmentation. So the App namespace I wrote never reached Astro’s global one.

import('./typesforenvdts').sometype is an inline import type. It doesn’t turn the file into a module, so the namespace stays global, and that’s why my fix worked.

The helper file wasn’t even needed. You can point the inline import type straight at the library:

declare namespace App {
  interface Locals {
    somevariable: import('somelib').SomeType
  }
}

That first line in my files came from Astro 4’s default env.d.ts. Astro 5 and later don’t create env.d.ts for you anymore. The same types come from the generated .astro/types.d.ts file, and the tsconfig.json you extend already includes it. So if you add an env.d.ts yourself today, you can leave the reference line out.

The other way: declare global

If you’d rather keep a normal import at the top, wrap the namespace in declare global:

/// <reference types="astro/client" />

import type { SomeType } from 'somelib'

declare global {
  namespace App {
    interface Locals {
      somevariable: SomeType
    }
  }
}

export {}

import type is erased from the emitted JavaScript, and declare global makes it explicit that App belongs to the global scope. The export {} keeps the file a module even if you remove the import later, because declare global only works inside a module.

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about typescript: