You pre-empted me and my commenting, you rascal! 😉 Tbh though, whatever you've got going could work just fine as long as you eliminate the echo. That is to say, sound treating the room.
I think folks do it cuz: 1) The ".d" part of the filename makes it seems like it's where you should define types or something 2) When you open any library "d.ts" file and you'll see that it's a file full of type definitions, one can get the impression that "d.ts" files are simply files where you define types you wanna use across your project (not saying that's right, just describing the impression i had had)
That's why I generally don't like 90% of the web devs I've encountered: they assume lots of stuff and usually think others are in the wrong, not them. Web devs are the forever wanna be devs, not putting enough work to understand their flaws and wanting others to bend to their flawed necessities. And I'm a web dev.
I use .d.ts to augment some types and interfaces of the external libraries. Example: there is a leaflet map library, and there are a bunch of plugins that are written in pure js, but they add to the library's inbuilt features. So to make typescript happy, I declare those types there.
A huge smile spread on my face when i read the title of this video. About a year and a half ago i started my first frontend dev job, and on my very first task, on the very first week, i stumbled upon our app's huge usage of the .d.ts files. I tried asking several of my peers what is this ".d.ts" file, and nobody knew, one assumed that the "d" stands for "deprecated" (to which i replied in a question - "so why did you declare new types in a new .d.ts file just two weeks ago?"). So i did a bit of investigation and soon enough i understood the depth of the antipattern that is spread all over. You see, i came from Python, and in Python, as Python is, it is extremely easy to make a huge mess and chaotic code, because no typing is required. So i was very sensitive to this issue. So at the end of this first week, and actually up until today, because this thing is like cancer, my crusade is to get rid of all our .d.ts files. Good luck to me.
I have an answer to your question: Why do people use them? I can answer anecdotally with my experience: because ts docs are a mess, and you didn't yet made an explaination video about it. 2 takeaways: - ts docs should be rewritten from scratch - you are a rare gem, thanks for existing ❤
I’ve been using TS because of ReactNative for years but it is only now that I’m realizing how much separate TS knowledge I need. I’m learning so much from your channel. Thank you the breakdowns!
I prefer separating types into different files inside a "types" folder that contain types for specific things (like database types in one file, utility types in other file and some app stuff in 3rd file), I don't like the approach of putting every single type into 1 file bc especially in large applications we end up with a huge and hard to maintain types file. Also, I put types into types files only if I have to use them somewhere else, otherwise I would just define a type right in the file that will use that type
I think all depends on the scope of the project and it's nature. If it's big one with hundreds of PR's and thousands types defined having a giant single file would be ridiculous 😂 in my experience the best is a mix. If you have some types u use Everywhere - use them in global scope. If you have types you use only where you defined it or use them somewhere else where you import it - use them within the file. If you have complex use case where some interfaces are shared, some are used separately - create one file if that benefit the clarity. Also worth to discuss these as you go with your teammates to make sure that you all following the same patterns and that you all agree. But this actually should be a part of Code Review and initial phase of a project
I do the same and for the first time I disagree with Matt on this one. If you put the types in a file co-located with the items using it (sometimes to solve circular references) and don't make it a definition file then you will now have an empty module output. I usually put the types in the same file but sometimes they need to be separated out for readability of the code it's typing for the other Devs in the team. I do agree there should never be actual code in a .d.ts file but then doing that had never even occurred to me.
Yup, I type stuff coming from APIs in d.ts files because they are global and are passed around through components. Seems like the correct way to do it so I am not having to redefine types every time I use them.
I used to write .d.ts files for ambient type suggestions via jsdoc before we took the time to swap to typescript's actual runtimes. It was a big project and it took a while to have the time to do the migration.
I think it’s fine to have a “types” or “models” folder, as long as it’s one type per file. If I have a User model, that’s gonna be used in many places, so it should in a central, shared location. Otherwise you’re either going to be redeclaring it in various files, or potentially importing the “User” type from some unrelated component.
All my projects have an index.d.ts in the project root where I augment libraries, like adding properties to the express req object, for typing process.env and adding semantic alises like type IsoDate = string or type UnixTimestamp = number.
i think a better explanation is that it is for compiled ts code that has become js, not for us to write directly so you can have the actual runnable code, and the types for developers to use
Putting my internal types into a global.d.ts file caused me a lot of headaches to actually get it to work and then realize that this approach was completely stupid. Not only because Typescript interprets two different types of declaration files (one with imports and one without), but also because it's very cumbersome since you have to re-type all the used internal classes because of the type-value difference. I learned that you should not use declaration files if the compiler does not complain about missing types from external modules or unknown imported file types it cannot read.
Thanks for the revelation, Matt. I think I do this because types feel like such a specific feature of a codebase to me that it doesn't make sense for type declarations to be modular inherently. It made more sense in my head for them to be global; if TS doesn't end up in the bundle anyway, why should I care about the import statements? That's just how I thought about it, but never thought that it could be an issue.
so if you wanted to make a package that was just a type definition to publish on npm, for example an AnyStringWithAutocomplete (that weird "| (string & {})" or something like that, you just have a .ts file instead of a .d.ts file?
previously I had a terrible typing. (literally a props.d.ts file that was an interface-export for all the props in a React project) Now I learned how to learn and realised types don't need to be seperated or in .d.ts
This is one of your MOST useful videos. I have suffered the pain of this from so many teams. I stumbled trying to clearly explain WHY *.d.ts files are bad bad bad. You nailed it with this video : so clear, so very useful.
I was first confused by the title and then realized that it is about people creating .d.ts files manually. I never thought anyone would work with .d.ts files except for compiling into a package or importing typings :D
I use .d.ts files for defining types in the global scope. I used to use it a lot less, but because these files only really want to reference other globally defined types, it ends up being parasitic. So, I've gradually moved all of my type definitions to .d.ts files. This actually ends up being really convenient and save a lot of refactoring effort because when you move a folder or file around, you don't have to update the corresponding type file as well. VS Code seems to be able to handle moving a single file around but not when you move multiple interdependent files / folders. IIRC, I first started doing this because it turned a ~100 line refactor that I was having trouble double-checking to ~3 lines. Note, I also avoid specifying type annotations because TypeScript can usually although not always correctly infer the types because it saves work whenever I change the parameter or return type. I could make refactors easier by having a global location for types, but this is inconvenient because it makes navigating between type definitions and implementations more difficult (e.g. ctrl+click on a type and navigate to the adjacent file vs hover over parameter, wait for intelli-sense, copy type, search for the implementation among dozes of results) because they are no longer co-located. However, the project I'm currently working on is a game with a lot of complicated interactions, so everything has to depend on everything else. I don't think this would be nearly as relevant / beneficial for a normal web app.
And that's why tsconfig.json has the "paths" property... You make the paths relative to the workspace folder, and voila, no 100 lines "refactors" when you move files around. Plus if VSCode's automatic path modification doesn't reliably work there's something really wrong with your project's setup
I was already using paths to setup absolute file paths. It's one of the reasons why I'm using absolute files paths because it solves the issue for some of the scenarios but not all of them. Those refactors happened in spite of that. Maybe you can replicate the one of the issues I was dealing with? 1. Create a new vite ts react app. 2. Create a folder (a) with multiple files each exporting something. 3. Create a folder (b) with multiple files each importing some of those exports. 4. Without having any of the importing files opened, move the folder (b) to another sub-folder. On my setup, this results in the imports the files in folder (b) not being updated. It works if the files in folder (b) are opened, but that's not practical for obvious reasons. I believe absolute imports would solve this particular issue at the cost of introducing a different issue, but it's just one of many different issues I encounter when moving multiple files/folders around in VS Code (e.g. github.com/microsoft/vscode/issues/105110). Again, moving a single file works just fine, it's various flavors of moving multiple files and folders at once that causes issues.
Super useful! I created a monorepo with a frontend and backend, and decided to create a third project for shared types. In beginning using .d.ts seemed like logical approach. But it gave me weird problems, and was indeed not being type checked. This is because of skiplibcheck! Im going to stop using them and switch to .d.ts. Thanks so much!!
I mean, we are talking about a superset of features (because Typescript really isn’t a language) that was designed to make a scripting language do something it wasn’t designed to do. And the bandage keeps getting bigger and bigger.
I use .d.ts to declare some project specific types that I want to use everywhere in the project. That way they got into global scope so I don't need to import them in every single module that uses them. I also use namespaces, so different parts don't clash with each other. What's wrong with that?...
Found this out the hard way a while ago. Now I only use .d.ts files to declare certain modules I need to have globally available. For example, if I'm using SCSS modules, Typescript normally doesn't know or understand what they are so I declare them in my types.d.ts as declare module "*.module.scss" { const content: { [className: string]: string }; export default content; } I suppose this is what it was meant for
1. When you need to type some JavaScript 2. When you want to make alterations to a global type, like in this article: www.totaltypescript.com/how-to-properly-type-window
Well said Matt. When writing typescript code is usually have a directory "types" in which files that add my own types to the global namespace using declare global ... Good one 👍
2:40 I think that's wrong. I think that applies to any TypeScript file (ts or d.ts) that is included in a tscofnig (via include array or files array) as long as it doesn't contain any import or export statements. If you use any import or export statement in a d.ts file, types won't be availible globally anymore. At least that how it works for me, don't ask me why. Global types can be useful at times though (for example for some custom helpers like 'UnionToIntersection' or 'DeepPartial').
Can you do a video on why packages that ship with types, should include the declaration maps and source ts files in the package? It's very annoying not having `go to definition` work.
I sometimes use global types for automatic declaration merging because I need to extend types that I share with my git submodule(s). This means i can share types with my submodule, but also extend these types with project specific stuff. I don't use .d.ts files for this, instead I use "declare global" in normal module files. Before watching your vid, I would have said it doesn't make a difference, but with your skipLibCheck advice, it does actually.
I think a video demonstrating when you would transition a type from a normal module to a d.ts file would be very helpful. Some kind of simple project where a User type needs to be changed from local to global; something like that.
I think one of the points he's making in the vid is that you shouldn't use .d.ts files for your own types. They're really only used for adding types to js files (like in the case of npm packages) or weird hacky type overrides
Thanks for the information, but I haven't seen the use of .d.ts files in any tutorial project other than declaring types and interfaces. However, if you create a types.d.ts file in the root of the project, you can use types and interfaces without importing them, but just by specifying types (interfaces), which in my opinion is very convenient.
That's very helpful to know! Something else I've wondered, is should we put centralize the types, or have them distributed around the codebase. At work, we have a types folder with multiple files for each kind of category like auth.ts, billing.ts, etc. Is that an antipattern?
Hi Matt, thanks for this! Very interesting. About skipLibCheck, I thought it was about all the typings, not just custom ones. But why disabling it? I mean, wouldn't it be better to have it disabled ("false") so TS warn you are making code-smell? Would it be perhaps be better to have a tsconfig.build.json file that turns it true while on development it is turned to false? I'm used to always create two tsconfig files, one for build and one for development. What's your suggestion here? Do you agree with this way of making ts projects? Thanks!
@@mattpocockuk I make tsconfig.json to extend tsconfig.build.json, so what's really important stands in production file while things like "paths" stays in the dev
I have a related question that maybe somebody have faced before : If i create a `customTypes.ts` file, where I create and export some interfaces/types, and i put that file inside the folder where I have my `main.ts` file (where I write my code), How could I exclude that file/folder from the compilation? I'd like that the compiler do not create a `customTypes.js` when compiling. I tried to create a ` "exclude":["customTypes.ts"] ` in my `tsconfig.json`. , but since the file contains "export" declarations , the compiler walks through that file anyway. Do you have any standar solution, or a way to really exclude a file with import/export statements ?
It's a cool feature, but you will have to be careful naming your types, specially if you're using React, or any other library or framework, because you could be naming a type with a name that already being used by that library/framework, and if you're not paying attention, you could end up choosing the wrong type.
@@mattpocockuk when I try to extend Cypress.Commands in commands.ts with extra functions (frequently needed in my testing workflow), there are type errors and the tests won't run unless I add a typedef for the function to a file called global.d.ts in the following format: declare namespace Cypress { interface Chainable { addedFunction(args: string | number | whatever): Chainable; otherAddedFunction(args: whatever): Chainable; ... and so on. } } It's a qwirk of Cypress' TypeScript support.
However, the tsc command does not export *.d.ts files for output. It only exports the *.ts file as *.d.ts. Or what is the correct configuration for tsconfig to export *.d.ts files as well?
@@mattpocockuk But "declaration": true generates *.d.ts from *.ts that's fine. But how do I make TSC copy my *.d.ts files from rootDir to outDir? TSC ignores *.d.ts files and outDir does not contain my *.d.ts files. That's why I have the type files as *.ts files, because TSC compiles those for me and then creates *.d.ts in outDir. That's why we write types to *.ts files and not to *.d.ts.
What's about things like: declare global { interface Window { /*extends*/ }}? Should it be .d.ts? It's a global thing, not a module? Also, sometimes it's necessary to radicle some module typings: declare module 'universal-router' { /* some magic here */ }. What would you recommend doing in those cases? Thank in advance
What would be the correct approach when building a component library with typescript? ts, tsx files? I've seen d.ts files referenced on the types property of the package.json file
i always have a .d.ts file for my glsl files: declare module "*.glsl" { const value: string export default value } is this the correct way to do this? or should i use plain .ts?
I agree with you but then, I was wondering why npm suggests you to create a `.d.ts` file when it could have just told you to have types files ? N.B. : I am currently creating a React npm package and I just saw the npm message...
I hate how fastify forces me to make d.ts files and use declaration merging for their interfaces. I spend weeks to setup the tsconfig so my monorepo packages work when being used as a dependency.
Thanks, that exactly that info i want. I wondered why I shouldn’t store all my types and interfaces in d.ts, because it looked convenient - I don’t have to import them. But I suspected there was a catch somewhere...
I just have one .d.ts file which is to create a custom type safe axios client like this: import 'axios'; declare module 'axios' { export interface AxiosRequestConfig { retry?: number; retryDelay?: number; } } am I doing wrong? :(
I even answered in an interview that .d.ts files are for defining types and the interviewer was fine with the answer. Which means both of us dont know it
I remember reading that .d.ts files were skipped during complication which can make a significant difference in large codebases. When you create a .ts module strictly containing types, what does the compiler do? Does it just produce an empty .js file?
@@mattpocockuk So this was the cause of all problems when we tried to integrate Cypress tests in our project, but we already used Jest. And both of these libraries have types for "expect" and other functions...
Hello Matt, thank you for all these useful videos about TS. I'm currently binge-watching them. I use spatie/typescript-transformer to convert my PHP code to TypeScript (in a Laravel-Vue-Inertia stack). Let's say I have a DTO UserData with name and email as properties, this class then gets transformed into a App.Data.UserData type that's available globally without importing via .d.ts file. For me it's very convenient since I don't need to import these DTO everytime I need them and they are generated automatically (from PHP code). Is that a bad design as well? To me it ressembles all these ".d.ts" that accompany ".js" files like explained in the GitHub post you included in the description box.
Hi @mattpocockuk - I watched the video more than once and I'm confused between the title of it and its content. Can I use an env.d.ts global file in my Astro app for example to declare a type that I know for a fact I'll use throughout my codebase? Is this not recommended?
so I get this, but what I don't get is what you're supposed to do while developing a library. Surely you'd want an option that does type checking for only your own .d.ts files right? And if such an option existed then I'd see no problem with doing this in any project.
Hi, I have somewhat related question. To describe external to TS data (which came from JS) I use `declare` in .d.ts. But I can't use types from modules in there. So I have to put all types in .d.ts too. Is there any way to avoid putting all those types in global scope?
So. Do you recomend to use the d.ts files as typescript version of .env files? I mean, you don't want to ship your aplication with your configuration, right.
@@mattpocockuk I have recently have began with node.js, and I was follow a tutorial where logically they let his connection file at the main root folder (src) with all database connection information, so a google if there is a way to use as other languages the .env file or sort of file to populate with config info and import it into the typescript application, but there is a one way, the global.d.ts file, I don't really if I can change the name, but in this you can define the .env variables to use at typeorm
New audio setup arrived today - thanks for bearing with me during my failed Shotgun Mic experiment.
You pre-empted me and my commenting, you rascal! 😉
Tbh though, whatever you've got going could work just fine as long as you eliminate the echo. That is to say, sound treating the room.
@@herrpez If you're on Twitter, you'll be able to see the latest no-echo setup:
twitter.com/mattpocockuk/status/1677624336285421569
@@mattpocockuk Sounds a lot better, and is no doubt a great deal easier than modifying the environment to suit the shotgun mic. 🙂
I think folks do it cuz:
1) The ".d" part of the filename makes it seems like it's where you should define types or something
2) When you open any library "d.ts" file and you'll see that it's a file full of type definitions, one can get the impression that "d.ts" files are simply files where you define types you wanna use across your project
(not saying that's right, just describing the impression i had had)
Absolutely! I was surprised how many people got that impression.
I think the real reason is that it makes the types global with no imports necessary.
Devs love globals, no matter how bad it makes the code smell.
That's why I generally don't like 90% of the web devs I've encountered: they assume lots of stuff and usually think others are in the wrong, not them.
Web devs are the forever wanna be devs, not putting enough work to understand their flaws and wanting others to bend to their flawed necessities.
And I'm a web dev.
That is exactly me. I put all my types in .d.ts file, maybe I should stop doing that from now on. Should I just put them in a .ts file then?
@@MAXHASS-ph5ib Thanks for the suggestion!
I use .d.ts to augment some types and interfaces of the external libraries.
Example: there is a leaflet map library, and there are a bunch of plugins that are written in pure js, but they add to the library's inbuilt features. So to make typescript happy, I declare those types there.
Yep - great idea.
Nice, I hadn't thought of doing this!
Hi Matt, keep this content up, I really love improving my typescript skills with you
A huge smile spread on my face when i read the title of this video.
About a year and a half ago i started my first frontend dev job, and on my very first task, on the very first week, i stumbled upon our app's huge usage of the .d.ts files. I tried asking several of my peers what is this ".d.ts" file, and nobody knew, one assumed that the "d" stands for "deprecated" (to which i replied in a question - "so why did you declare new types in a new .d.ts file just two weeks ago?").
So i did a bit of investigation and soon enough i understood the depth of the antipattern that is spread all over.
You see, i came from Python, and in Python, as Python is, it is extremely easy to make a huge mess and chaotic code, because no typing is required. So i was very sensitive to this issue.
So at the end of this first week, and actually up until today, because this thing is like cancer, my crusade is to get rid of all our .d.ts files.
Good luck to me.
I have an answer to your question: Why do people use them?
I can answer anecdotally with my experience: because ts docs are a mess, and you didn't yet made an explaination video about it.
2 takeaways: - ts docs should be rewritten from scratch - you are a rare gem, thanks for existing ❤
I’ve been using TS because of ReactNative for years but it is only now that I’m realizing how much separate TS knowledge I need. I’m learning so much from your channel. Thank you the breakdowns!
I prefer separating types into different files inside a "types" folder that contain types for specific things (like database types in one file, utility types in other file and some app stuff in 3rd file), I don't like the approach of putting every single type into 1 file bc especially in large applications we end up with a huge and hard to maintain types file. Also, I put types into types files only if I have to use them somewhere else, otherwise I would just define a type right in the file that will use that type
I think all depends on the scope of the project and it's nature. If it's big one with hundreds of PR's and thousands types defined having a giant single file would be ridiculous 😂 in my experience the best is a mix. If you have some types u use Everywhere - use them in global scope. If you have types you use only where you defined it or use them somewhere else where you import it - use them within the file. If you have complex use case where some interfaces are shared, some are used separately - create one file if that benefit the clarity. Also worth to discuss these as you go with your teammates to make sure that you all following the same patterns and that you all agree. But this actually should be a part of Code Review and initial phase of a project
I do the same and for the first time I disagree with Matt on this one. If you put the types in a file co-located with the items using it (sometimes to solve circular references) and don't make it a definition file then you will now have an empty module output.
I usually put the types in the same file but sometimes they need to be separated out for readability of the code it's typing for the other Devs in the team.
I do agree there should never be actual code in a .d.ts file but then doing that had never even occurred to me.
Yup, I type stuff coming from APIs in d.ts files because they are global and are passed around through components. Seems like the correct way to do it so I am not having to redefine types every time I use them.
clear, consise, crisp - You have awesome voice Matt!
I thought that d.ts was just naming convention for type related files and a lot of my dev friends also, lol…
I used to write .d.ts files for ambient type suggestions via jsdoc before we took the time to swap to typescript's actual runtimes. It was a big project and it took a while to have the time to do the migration.
In my experience, in 90% of cases types.ts is also a bad idea. Just define types, where you use them.
Yes, most types should be colocated where they're used. But if you have shared types, then a types.ts file is fine.
agreed
Is there an effort to upstream this information in to the ts handbook? I think if this is so common, this should be spelled out more plainly there.
I think it’s fine to have a “types” or “models” folder, as long as it’s one type per file.
If I have a User model, that’s gonna be used in many places, so it should in a central, shared location. Otherwise you’re either going to be redeclaring it in various files, or potentially importing the “User” type from some unrelated component.
it's only true for a todo app.
lol😆
All my projects have an index.d.ts in the project root where I augment libraries, like adding properties to the express req object, for typing process.env and adding semantic alises like type IsoDate = string or type UnixTimestamp = number.
I do this as well. I thought that was the use case for them. Is there an alternative?
i think a better explanation is that it is for compiled ts code that has become js, not for us to write directly
so you can have the actual runnable code, and the types for developers to use
This is what I got from the included link.
Putting my internal types into a global.d.ts file caused me a lot of headaches to actually get it to work and then realize that this approach was completely stupid. Not only because Typescript interprets two different types of declaration files (one with imports and one without), but also because it's very cumbersome since you have to re-type all the used internal classes because of the type-value difference.
I learned that you should not use declaration files if the compiler does not complain about missing types from external modules or unknown imported file types it cannot read.
Thanks for the revelation, Matt. I think I do this because types feel like such a specific feature of a codebase to me that it doesn't make sense for type declarations to be modular inherently. It made more sense in my head for them to be global; if TS doesn't end up in the bundle anyway, why should I care about the import statements? That's just how I thought about it, but never thought that it could be an issue.
so if you wanted to make a package that was just a type definition to publish on npm, for example an AnyStringWithAutocomplete (that weird "| (string & {})" or something like that, you just have a .ts file instead of a .d.ts file?
Yes
Thanks Matt as per usual. The Typescript Don.
previously I had a terrible typing. (literally a props.d.ts file that was an interface-export for all the props in a React project)
Now I learned how to learn and realised types don't need to be
seperated
or in .d.ts
The youtube algorithm did a great job showing me this channel 🙌 subscribed
This is one of your MOST useful videos.
I have suffered the pain of this from so many teams.
I stumbled trying to clearly explain WHY *.d.ts files are bad bad bad.
You nailed it with this video : so clear, so very useful.
I was first confused by the title and then realized that it is about people creating .d.ts files manually. I never thought anyone would work with .d.ts files except for compiling into a package or importing typings :D
I use .d.ts files for defining types in the global scope. I used to use it a lot less, but because these files only really want to reference other globally defined types, it ends up being parasitic. So, I've gradually moved all of my type definitions to .d.ts files.
This actually ends up being really convenient and save a lot of refactoring effort because when you move a folder or file around, you don't have to update the corresponding type file as well. VS Code seems to be able to handle moving a single file around but not when you move multiple interdependent files / folders. IIRC, I first started doing this because it turned a ~100 line refactor that I was having trouble double-checking to ~3 lines. Note, I also avoid specifying type annotations because TypeScript can usually although not always correctly infer the types because it saves work whenever I change the parameter or return type.
I could make refactors easier by having a global location for types, but this is inconvenient because it makes navigating between type definitions and implementations more difficult (e.g. ctrl+click on a type and navigate to the adjacent file vs hover over parameter, wait for intelli-sense, copy type, search for the implementation among dozes of results) because they are no longer co-located.
However, the project I'm currently working on is a game with a lot of complicated interactions, so everything has to depend on everything else. I don't think this would be nearly as relevant / beneficial for a normal web app.
And that's why tsconfig.json has the "paths" property... You make the paths relative to the workspace folder, and voila, no 100 lines "refactors" when you move files around.
Plus if VSCode's automatic path modification doesn't reliably work there's something really wrong with your project's setup
I was already using paths to setup absolute file paths. It's one of the reasons why I'm using absolute files paths because it solves the issue for some of the scenarios but not all of them. Those refactors happened in spite of that.
Maybe you can replicate the one of the issues I was dealing with?
1. Create a new vite ts react app.
2. Create a folder (a) with multiple files each exporting something.
3. Create a folder (b) with multiple files each importing some of those exports.
4. Without having any of the importing files opened, move the folder (b) to another sub-folder.
On my setup, this results in the imports the files in folder (b) not being updated. It works if the files in folder (b) are opened, but that's not practical for obvious reasons.
I believe absolute imports would solve this particular issue at the cost of introducing a different issue, but it's just one of many different issues I encounter when moving multiple files/folders around in VS Code (e.g. github.com/microsoft/vscode/issues/105110). Again, moving a single file works just fine, it's various flavors of moving multiple files and folders at once that causes issues.
Such a pet peeve. I see it in Discords ALL the time... "just put it in a declaration file because I can't be bothered to educate you".
How to handle the npm lib which doesn't support TS. That's the reason i disabled the lib type check in config.
can you talk more about module augmentation?
What do you want to know?
Super useful! I created a monorepo with a frontend and backend, and decided to create a third project for shared types. In beginning using .d.ts seemed like logical approach. But it gave me weird problems, and was indeed not being type checked. This is because of skiplibcheck! Im going to stop using them and switch to .d.ts. Thanks so much!!
I mean, we are talking about a superset of features (because Typescript really isn’t a language) that was designed to make a scripting language do something it wasn’t designed to do. And the bandage keeps getting bigger and bigger.
Thanks, will change my project structures now and credit this!
I use .d.ts to declare some project specific types that I want to use everywhere in the project. That way they got into global scope so I don't need to import them in every single module that uses them. I also use namespaces, so different parts don't clash with each other. What's wrong with that?...
Found this out the hard way a while ago. Now I only use .d.ts files to declare certain modules I need to have globally available. For example, if I'm using SCSS modules, Typescript normally doesn't know or understand what they are so I declare them in my types.d.ts as
declare module "*.module.scss" {
const content: { [className: string]: string };
export default content;
}
I suppose this is what it was meant for
Absolutely!
So when should you use it - specifically when you have a .js with types?
1. When you need to type some JavaScript
2. When you want to make alterations to a global type, like in this article: www.totaltypescript.com/how-to-properly-type-window
Well said Matt. When writing typescript code is usually have a directory "types" in which files that add my own types to the global namespace using declare global ...
Good one 👍
At least 40% of the devs following you have still a lot to learn, including me. Good for you!
2:40 I think that's wrong. I think that applies to any TypeScript file (ts or d.ts) that is included in a tscofnig (via include array or files array) as long as it doesn't contain any import or export statements. If you use any import or export statement in a d.ts file, types won't be availible globally anymore. At least that how it works for me, don't ask me why. Global types can be useful at times though (for example for some custom helpers like 'UnionToIntersection' or 'DeepPartial').
Hello, Matt. Thank you so much for your videos!
I'm happy to follow these TS discussions here! Thx
Can you do a video on why packages that ship with types, should include the declaration maps and source ts files in the package? It's very annoying not having `go to definition` work.
I sometimes use global types for automatic declaration merging because I need to extend types that I share with my git submodule(s). This means i can share types with my submodule, but also extend these types with project specific stuff. I don't use .d.ts files for this, instead I use "declare global" in normal module files. Before watching your vid, I would have said it doesn't make a difference, but with your skipLibCheck advice, it does actually.
So the files should be named the same, but with different extantions. In this case we will not need to write explicit import?
I think a video demonstrating when you would transition a type from a normal module to a d.ts file would be very helpful. Some kind of simple project where a User type needs to be changed from local to global; something like that.
If it's your code you probably shouldn't be doing it. You should be using it to type /alter types from imported modules
I think one of the points he's making in the vid is that you shouldn't use .d.ts files for your own types. They're really only used for adding types to js files (like in the case of npm packages) or weird hacky type overrides
@@johnnyhane6337 That's not the only case. In that situation you'd be using define module.
@@PhatOof That isn't the case at all. There are plenty of times when you want global types in a project.
Thanks for the information, but I haven't seen the use of .d.ts files in any tutorial project other than declaring types and interfaces. However, if you create a types.d.ts file in the root of the project, you can use types and interfaces without importing them, but just by specifying types (interfaces), which in my opinion is very convenient.
Thanks so much for this info Matt! Really helpful!
But if you put your types without "export" keyword into .d.ts files, tsc fails compiling. (Cannot find name "...") Am I missing something ?
That's very helpful to know! Something else I've wondered, is should we put centralize the types, or have them distributed around the codebase. At work, we have a types folder with multiple files for each kind of category like auth.ts, billing.ts, etc. Is that an antipattern?
Here you go!
www.totaltypescript.com/where-to-put-your-types-in-application-code
@@mattpocockuk thanks!
So define types in .d.ts files for the sole purpose of not to manually import them else where is a bad idea?
Your keyboard sound is awesomr. What is its brand and model?
What keyboard do you use? It sounds really nice.
Hi Matt, thanks for this! Very interesting. About skipLibCheck, I thought it was about all the typings, not just custom ones. But why disabling it? I mean, wouldn't it be better to have it disabled ("false") so TS warn you are making code-smell?
Would it be perhaps be better to have a tsconfig.build.json file that turns it true while on development it is turned to false?
I'm used to always create two tsconfig files, one for build and one for development. What's your suggestion here? Do you agree with this way of making ts projects? Thanks!
You definitely want the same rules in your tsconfig in dev and production. Much easier maintenance and keeps things more secure in the long run.
@@mattpocockuk I make tsconfig.json to extend tsconfig.build.json, so what's really important stands in production file while things like "paths" stays in the dev
And why not keep types in the component itself when they are not reuseable?
I’m running to fix my code before my tech manager watched this video
This litterly blew my mind. Thank you so much!
clear explanation. I like your videos. keep producing Matt.
I have a related question that maybe somebody have faced before :
If i create a `customTypes.ts` file, where I create and export some interfaces/types, and i put that file inside the folder where I have my `main.ts` file (where I write my code), How could I exclude that file/folder from the compilation?
I'd like that the compiler do not create a `customTypes.js` when compiling.
I tried to create a ` "exclude":["customTypes.ts"] ` in my `tsconfig.json`. , but since the file contains "export" declarations , the compiler walks through that file anyway.
Do you have any standar solution, or a way to really exclude a file with import/export statements ?
Hi, I didn't know that .d.ts files push the types in the global scope. Really love that feature. Love the video as well 🙏
It's a cool feature, but you will have to be careful naming your types, specially if you're using React, or any other library or framework, because you could be naming a type with a name that already being used by that library/framework, and if you're not paying attention, you could end up choosing the wrong type.
Unfortunately Cypress forces me to do this for extending Cypress.Commands with chainable functions but all of my other types are in separate files.
Why does it force you to do this?
@@mattpocockuk when I try to extend Cypress.Commands in commands.ts with extra functions (frequently needed in my testing workflow), there are type errors and the tests won't run unless I add a typedef for the function to a file called global.d.ts in the following format:
declare namespace Cypress {
interface Chainable {
addedFunction(args: string | number | whatever): Chainable;
otherAddedFunction(args: whatever): Chainable;
... and so on.
}
}
It's a qwirk of Cypress' TypeScript support.
Hello, you're awesome. Great content dude!
Hehe, i actually was wondering about the best practices on this one and found your blog post. Nice to see the video too.
What about when you want ambient types? Like when you need to extend or define a type for a library?
Absolutely fine - global changes are what d.ts files are for!
However, the tsc command does not export *.d.ts files for output. It only exports the *.ts file as *.d.ts. Or what is the correct configuration for tsconfig to export *.d.ts files as well?
declaration: true
@@mattpocockuk But "declaration": true generates *.d.ts from *.ts that's fine. But how do I make TSC copy my *.d.ts files from rootDir to outDir? TSC ignores *.d.ts files and outDir does not contain my *.d.ts files. That's why I have the type files as *.ts files, because TSC compiles those for me and then creates *.d.ts in outDir.
That's why we write types to *.ts files and not to *.d.ts.
What's about things like: declare global { interface Window { /*extends*/ }}? Should it be .d.ts? It's a global thing, not a module? Also, sometimes it's necessary to radicle some module typings: declare module 'universal-router' { /* some magic here */ }. What would you recommend doing in those cases? Thank in advance
What would be the correct approach when building a component library with typescript? ts, tsx files? I've seen d.ts files referenced on the types property of the package.json file
what do you suggest we do for javascript legacy code?
i always have a .d.ts file for my glsl files:
declare module "*.glsl" {
const value: string
export default value
}
is this the correct way to do this? or should i use plain .ts?
Great info! Thank you
So we need to use .d.ts like .h files for c/c++ code? 😳
Thanks for confirming the I've been doing it right this whole time 😂
Excellent guidance Matt, thanks 🎉
awesome dude, i was not sure about this but now its pretty clear
I agree with you but then, I was wondering why npm suggests you to create a `.d.ts` file when it could have just told you to have types files ?
N.B. : I am currently creating a React npm package and I just saw the npm message...
i have to like before watching 🎉
enough for me to see you publishing new content
I hate how fastify forces me to make d.ts files and use declaration merging for their interfaces. I spend weeks to setup the tsconfig so my monorepo packages work when being used as a dependency.
Would you advise against using d.ts to store global type helpers?
No - globals is what .d.ts files are for!
I didn't even know people does this. I am a good developer, yay!
What is wrong is to use the `export` key inside the types.d.ts file, right?
Correct!
Thanks, that exactly that info i want.
I wondered why I shouldn’t store all my types and interfaces in d.ts, because it looked convenient - I don’t have to import them.
But I suspected there was a catch somewhere...
I only use d.ts files to declare env variables or global things
I just have one .d.ts file which is to create a custom type safe axios client like this:
import 'axios';
declare module 'axios' {
export interface AxiosRequestConfig {
retry?: number;
retryDelay?: number;
}
}
am I doing wrong? :(
Nope, that's a global alteration - not a file containing types!
Just found this video as I am learning TS. Well, time to refactor my code, while it is still fresh. :D
What keyboard is that? The sound is heavenly!
Ty, my coworkers will listen to me now
The confusion arises because devs think that ".d.ts" are like "if you want a file purely for Typescript stuff, here you go"
I even answered in an interview that .d.ts files are for defining types and the interviewer was fine with the answer. Which means both of us dont know it
If we have a shared protocol file with interfaces, is it a good idea to use d.ts ? Or is it only for types?
What do you mean by a 'shared protocol file'?
And by types, I'm referring to types declared using the 'type' keyword or the 'interface' keyword.
If your large code base has js files that are typed with .d.ts files then surely you want skipLibCheck to be false correct?
Yes, sadly
Thanks. I've not known what's the .d.ts for previously. Now I know.
I remember reading that .d.ts files were skipped during complication which can make a significant difference in large codebases. When you create a .ts module strictly containing types, what does the compiler do? Does it just produce an empty .js file?
Yep!
Or, more likely, it'll ignore it.
I also think maybe some people think of these like .h files in C. It's a place to put the type definitions and function definitions....
So the types in d.ts will be available everywhere in a project without the need to explicitly import them, correct?
Correct.
@@mattpocockuk So this was the cause of all problems when we tried to integrate Cypress tests in our project, but we already used Jest. And both of these libraries have types for "expect" and other functions...
@@kirillzlobin7135 With that, you can use two different tsconfig.json files. One for cypress, one for jest.
@@mattpocockuk You are the TS legend. I hope this is a good idea for one of your next videos, please please please :). Thank you very much
Hello Matt, thank you for all these useful videos about TS. I'm currently binge-watching them.
I use spatie/typescript-transformer to convert my PHP code to TypeScript (in a Laravel-Vue-Inertia stack). Let's say I have a DTO UserData with name and email as properties, this class then gets transformed into a App.Data.UserData type that's available globally without importing via .d.ts file.
For me it's very convenient since I don't need to import these DTO everytime I need them and they are generated automatically (from PHP code).
Is that a bad design as well? To me it ressembles all these ".d.ts" that accompany ".js" files like explained in the GitHub post you included in the description box.
I use .d.ts files to add type declarations for regular js dependencies that do not export types, is this wrong?
Absolutely fine.
@@mattpocockuk Thanks, another question: is there a way to bypass that file when I ctrl+click on the module import, and send me to the node_modules?
Hi @mattpocockuk - I watched the video more than once and I'm confused between the title of it and its content. Can I use an env.d.ts global file in my Astro app for example to declare a type that I know for a fact I'll use throughout my codebase? Is this not recommended?
Globals are just a bit iffy - but I suppose a global type is basically fine.
so I get this, but what I don't get is what you're supposed to do while developing a library.
Surely you'd want an option that does type checking for only your own .d.ts files right? And if such an option existed then I'd see no problem with doing this in any project.
Damn. You live you learn.
Hi, I have somewhat related question. To describe external to TS data (which came from JS) I use `declare` in .d.ts. But I can't use types from modules in there. So I have to put all types in .d.ts too. Is there any way to avoid putting all those types in global scope?
You can use declare global in a .ts file, might be easier if most of your types are in modules anyway.
@@mattpocockuk Thank you. Am I understand it correctly, that I'll have to put that declare global in each .ts file using those data directly?
@@w01dnick I'd need more info to answer you properly. Can you ask in mattpocock.com/discord ?
why big or known libraries like react are doing this? I just went and looked at the react's "index.d.ts" file and see a bunch of types declared there
Those declaration files are describing JavaScript files, which is what they're meant for.
That keyboard sounds delightfully thocky. Which brand/model is that?
Keychron Q3
I liked your new scenario 😮
So.
Do you recomend to use the d.ts files as typescript version of .env files?
I mean, you don't want to ship your aplication with your configuration, right.
What do you mean?
@@mattpocockuk I have recently have began with node.js, and I was follow a tutorial where logically they let his connection file at the main root folder (src) with all database connection information, so a google if there is a way to use as other languages the .env file or sort of file to populate with config info and import it into the typescript application, but there is a one way, the global.d.ts file, I don't really if I can change the name, but in this you can define the .env variables to use at typeorm
@@snithfferx .d.ts files aren't used at runtime. They can only be used to provide global types, not global runtime values.
@@mattpocockuk OK. thanks. To keep loking for a way to do it then.
My types go right before the variable, where they belong.