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

Show examples of env.d.ts as a module #10945

Open
thegatesdev opened this issue Feb 12, 2025 · 5 comments · May be fixed by #10810
Open

Show examples of env.d.ts as a module #10945

thegatesdev opened this issue Feb 12, 2025 · 5 comments · May be fixed by #10810
Labels
improve or update documentation Enhance / update existing documentation (e.g. add example, improve description, update for changes)

Comments

@thegatesdev
Copy link

thegatesdev commented Feb 12, 2025

📚 Subject area/topic

TypeScript support, middleware, environment variables

📋 Page(s) affected (or suggested, for new content)

https://docs.astro.build/en/guides/environment-variables/#intellisense-for-typescript
https://docs.astro.build/en/guides/middleware/#middleware-types

📋 Description of content that is out-of-date or incorrect

The documentation examples show the env.d.ts file as being a normal TypeScript file, not a module.
This caused two problems for me:

1: To get the types in this file to actually apply, I had to add the "src" directory to the tsconfig "include" paths:

- "include": [".astro/types.d.ts", "**/*"],
+ "include": [".astro/types.d.ts", "**/*", "src"],

It looks like non-module scripts are not automatically included otherwise. Although I often see "src/**/*" being used, which also solves this problem, using "**/*" is the default in Astro.

2: You cannot import types from other modules using the import { ... } from syntax. This is not really a problem for environment variable definitions, because they don't include complex types. But it's a different story with context locals:

namespace App {
  interface Locals {
    myLocalType: LocalType<unknown>;
  }
}

Having the above definition and using Astro.locals.myLocalType fails to get the correct type (and thus gives me ESLint warnings) because it isn't imported.

I suggest changing the examples so that this file becomes a module by default, just like the following example:

import type { LocalType } from "@bar/foo";

declare global {
  interface ImportMetaEnv {
    readonly MY_VARIABLE: string;
  }

  interface ImportMeta {
    readonly env: ImportMetaEnv;
  }

  namespace App {
    interface Locals {
      myLocalType: LocalType<unknown>;
    }
  }
}

Thank you!

@ArmandPhilippot
Copy link
Member

ArmandPhilippot commented Feb 12, 2025

Hi! Thanks for taking the time to report this issue.

I had to add the "src" directory to the tsconfig "include" paths:

Are you sure this is needed? It seems weird to me since **/* should match src as well... And, without imports, using "include": [".astro/types.d.ts", "**/*"] works fine for me.

Don't you think this is your other change that fixes your issue?

as being a normal TypeScript file, not a module.

This is not a normal Typescript file, but an ambient module declaration. So, the problem with your approach is that your env.d.ts will no longer be an ambient module but a regular module because of the top-level import. That's why you need to add declare global, which can be used in any normal Typescript file.
So I don't think this is the approach we want to recommend.

But, you're right, we should show how to use an import in your env.d.ts since it seems to be a common use case. And in fact, we have an open PR (#10810) to update the Middleware page!
Thanks for pointing out that this is also relevant for Environment page!

So the example will look more like:

interface ImportMetaEnv {
  readonly MY_VARIABLE: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

namespace App {
  interface Locals {
    myLocalType: import("@bar/foo").LocalType<unknown>;
  }
}

This way the file remains an ambient module.

@ArmandPhilippot ArmandPhilippot added the improve or update documentation Enhance / update existing documentation (e.g. add example, improve description, update for changes) label Feb 12, 2025
@thegatesdev
Copy link
Author

I see, thanks for the explanation.

The src import (or making it a module) was required for ESLint to not complain in my case, but I don't really have time to reproduce that. If it works on your end it might have been a mistake on my part.

What is the difference between making env.d.ts a module vs an ambient module? I personally prefer using the static import, is that also a valid option or does that break things?

@ArmandPhilippot
Copy link
Member

What is the difference between making env.d.ts a module vs an ambient module? I personally prefer using the static import, is that also a valid option or does that break things?

I'm not gonna lie, I'm not an expert on how these things work. Last time I didn't immediately notice the difference... But I can try to explain it.

An ambient module is used to inform a compiler that the declarations exists in a different place. This means you don't show the actual implementation. These files are not converted to JavaScript.

Using a top level import turns that file into a normal module. Any code defined in a module is scoped to that module, and are not added to the global scope. That's why you need declare global as you would do within your "regular" code. These files are converted to Javascript.

So yes, you can use a top-level import with declare global. It works. However, I can't say for sure that it won't lead to side effects. I don't think so if you use import type { Foo } and not import { type Foo }, but I'm not sure. In doubt, I think it's preferable to stay with the ambient format because we are sure that these files will not be transpiled.

I hope this helps a bit to better understand the difference...

@thegatesdev
Copy link
Author

Yeah, I see the difference, it seems to be a difference only in intent.
I can't seem to find any resources comparing the actual effects...
Some stackoverflow answers suggesting using a module declaration is completely fine, although the original intent of ambient modules seems to match the usage...
I'll personally keep using a normal module, top level imports are nice 😉

Thanks for the responses, can I close this Issue off?

@ArmandPhilippot
Copy link
Member

Hi, sorry for the delay. I think we can keep this open since it's still an issue. I'll link this issue to the open PR so we can close this issue once merged!

@ArmandPhilippot ArmandPhilippot linked a pull request Feb 15, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
improve or update documentation Enhance / update existing documentation (e.g. add example, improve description, update for changes)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants