vite
vite copied to clipboard
Recommend `vite-env.ts` over `vite-env.d.ts`
Documentation is
- [ ] Missing
- [ ] Outdated
- [ ] Confusing
- [x] Not sure?
Explain in Detail
There are two types of TypeScript files: Source files (.ts, .tsx, .cts, .mts) and declaration files (.d.ts, .d.cts, .d.mts, .d.*.ts).
Declaration files describe a JavaScript file. Typically users are supposed to author source files, and if relevant, emit declaration files as a build artifact. Users are typically not supposed to author declaration files. Yet, Vite recommends authoring a file named vite-env.d.ts.
The practical problem often comes from users who don’t understand these differences and TypeScript compiler options. It’s common to enable the option skipLibCheck. This option disables type checking of declaration files. So users end up with a file named vite-env.d.ts that’s not type-checked. Another confusing aspect is TypeScript scripts vs modules. The option moduleDetection affects source files, but not declaration files.
The convention to create a file named {tool}-env.d.ts is not limited to Vite. I believe there are also astro-env.d.ts and next-env.d.ts. These frameworks suffer from the same problems.
Your Suggestion for Changes
I suggest to remove all references to vite-env.d.ts, and replace them with vite-env.ts. This file should also be explicitly marked as a module to avoid ambiguity. This includes the documentation as well as all project templates.
// vite-env.ts
/// <reference types="vite/client" />
declare global {
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
// more env variables...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
}
// Explicitly mark this file as a module.
export {}
Reproduction
No response
Steps to reproduce
No response
I checked whether it's possible to do this, and noticed that ambient module declarations do not work in .ts. This means if the user wants to add .unknown to assetsInclude, they would need to add a separate file instead of vite-env.ts.
It would be better to have type check enabled for vite-env.d.ts, but I assume there won't be many types declared there, I think it's acceptable to keep using .d.ts. It'd be nice if TypeScript allowed enabling type check per-file by adding @ts-check to .d.ts though.
According to the TypeScript handbook, ambient modules can be declared in script files, not in modules.
The following works:
// tsconfig.json
{
"compilerOptions": {
"module": "preserve",
"strict": true
}
}
// src/ambient.ts
declare module '*.unknown' {
export const unknown: string
}
// src/test.ts
import { unknown } from 'whatever.unknown'
console.log(unknown)
However, this breaks if you force enable module detection. Some things that do this are the compiler options:
"moduleDetection": "force""module": "node16""module": "node18""module": "nodenext"
Vite users should use "module": "preserve". This means they shouldn’t be affected by this, but it’s not really obvious these options are linked. If you have to use "module": "nodenext", you can still disable module detection with "moduleDetection": "legacy".
The reason the augmentation always works for in declaration files without ESM syntax, is because declaration files are not affected by the moduleDetection compiler option.
We recommend "moduleDetection": "force" and set it in the template because Vite treats all files as a module. So if there's a way to mark a single file as a script file, that would also solve our problem.
Sadly no. It’s possible to force a file into module mode by adding export {}, but not the other way around. You recommendation to force module detection does make sense, though I wonder how many users actually author files without imports or exports.
I suppose this is a matter of trade-offs. There’s not one perfect solution that serves all projects.
We're going to remove vite-env.d.ts in https://github.com/vitejs/vite/pull/20132
Nice, I like it. But that doesn’t remove vite-env.d.ts from the IntelliSense for TypeScript section.
Ah, good catch. I'll made a PR for that section once it's merged then.
FWIW the TypeScript docs mentions vite-env.d.ts and doesn't recommend against it. Personally I don't think .d.ts strictly needs to mirror .js files and we have to discourage .d.ts ambient files, unless there's official docs discouraging that.
In that section, it describes about the ambient module declaration. So it makes sense to use .d.ts.
The file in "IntelliSense for TypeScript" section does not include an ambient module declaration, so it doesn't have to be .d.ts. I think it's better to use .ts so that the type-check runs on the file even if skipLibCheck is enabled.
My point is that there's nothing wrong with manually authoring declaration files, like what the issue description has said:
Users are typically not supposed to author declaration files. Yet, Vite recommends authoring a file named
vite-env.d.ts.
So if we decide to use either .ts or .d.ts, it's not because of that but another reason, like the skipLibCheck argument here.
About using .ts so it's unaffected by skipLibCheck, I'm personally not sure as vite-env.d.ts is usually already very simple, and if we mention adding ambient module declarations because they extend assetsInclude using vite-env.d.ts (we lack this docs now but we should have), it's going to make vite-env.ts and vite-env.d.ts confusing to explain.
- "Why is there two files doing similar things?"
- "What if I put this type into the other so I can keep one file?"
- "Is there a typo in the extension? I'm going to put the types in the existing
.ts/.d.ts" and things don't work
Keeping a single vite-env.d.ts is more consistent when suggesting any sort of ambient types.