You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Why dual-emit is not supported and sometimes impossible
Future work? Browser, import maps, URLs?
Guides
Choosing compiler options
Troubleshooting dependency issues
Publishing a library
Reference
Module syntax
ESM (link elsewhere?)
CJS in JavaScript
CJS in TypeScript
Type-only imports and exports
Ambient module declarations & augmentations?
Module emit and checking
module
commonjs
es2015+
node16/nodenext
CJS/ESM interop details
The default problem
__esModule marker
allowSyntheticDefaultExports
esModuleInterop
verbatimModuleSyntax
Module resolution
moduleResolution
node16/nodenext
bundler
allowJs and maxNodeModuleJsDepth
package.json
"main", "types"
"typesVersions"
"exports"
"type"
"imports"
Customization compiler options
paths
baseUrl
resolvePackageJsonImports
resolvePackageJsonExports
customConditions
allowImportingTsExtensions
resolveJsonModule
rootDirs
typeRoots
moduleSuffixes
Reacted by J M Rossy, Thomas Chetwin, Trey Brisbane, Emmanuel Meric de Bellefon, Justin Grant, hardfist, Patrick Kerschbaum, Jude Agboola, xfr0ntier, prcodes and 2 more
I'm not sure which section this would go in, but there is a semi-obscure question/choice I'd like to get an authoritative answer on, so I'm kinda hijacking this issue as a place to resolve it.
Question
Is it legitimate for a project to store hand-written utility types in a local *.d.ts source file instead of using a *.ts source file?
It's common to have local files in which you might store utility types. Maybe you put them in a directory called util-types/. So which file extension type should you put in this directory?
A full-fat *.ts file would work, but at best will result in an empty *.js file being emitted, and at worst might deviate from programmer intent over time if someone accidentally inserts value-land code.
Whereas a *.d.ts enforces this intent and guarantees to have zero JS emit - it only is needed during checking.
The answer to this question is unsatisfying to me. I would say it’s discouraged or even harmful (though it won’t be “deprecated”). At best, it’s a technique with serious pitfalls that can be leveraged by people who understand them enough to set up additional tooling and safeguards to make it viable. Because .d.ts files only occur “naturally” as a pair with a .js file, together as outputs of a .ts file, a .d.ts file always implies the existence of a .js file. So the potential harm is readily apparent: if you hand-author only-types.d.ts and then write import {} from "./only-types.js", this resolves and is legal in all settings, but in verbatimModuleSyntax, the import will be preserved and crash at runtime. While TypeScript has type-only imports and exports, it lacks the analogous concept of a type-only module, one which exists for type information purposes but is known to not exist at runtime, though I’ve casually suggested multiple times that such a concept could be useful.
But the main reason I personally avoid this is just because it doesn’t copy into outDir. For my purposes, I’d rather just eat the cost of the empty JS file (which also protects you from crashes should you accidentally import it at runtime).
Reacted by Rob Palmer, Jakub Mazanec, GabenGar, Ethan Resnick, Davis Ford, Ryan Cavanaugh, Anurag Hazra, Nico Jansen, Wout Mertens, Devin Rhode and 93 more
Reacted by lillallol
Reacted by Butter fly, Anser Waseem, xfr0ntier, Alpha Vylly, Kunal Jain, Miguel Montalvo, Tim Elsener, Glenn Mendoza, Thisoe, ShinMini and 1 more
Reacted by Andrew Branch, Ethan Resnick, Ryan Cavanaugh, Emmanuel Meric de Bellefon, Alan Pierce, Andy Edwards, Justin Grant, Nico Jansen, Kevin Elliott, Pham Minh Hoang Henry and 4 more
I'm not sure if this is too much, but guidance on how to navigate incompatible packages when migrating a TS project from CJS to ESM would be helpful. I can file tickets on projects to fix their package.json and build processes all day, but ultimately it can be very hard to know precisely what is broken, what to tell library authors, and what patch I need to apply to the package to hold me over in nodenext + CJS mode to prepare to go to nodenext + ESM mode.
Quick update: this is getting fairly close to being done and anticipate getting a PR up to put this on the website in the coming weeks. I just pushed a fairly large update to the gist.
Reacted by Mark Erikson, GabenGar, Dawid Kopys, Marco Beier, Guillaume Humbert, J M Rossy and MajorTanya
I’m getting ready to merge this. There’s still more to do, but it’s complete enough to publish and iterate on.
Feedback is welcome, so I’ll mention the things I already have on my immediate to-do that I won’t get to before merging
Better introduction?
Glossary
Add moduleDetection, allowSyntheticDefaultImports, esModuleInterop, and verbatimModuleSyntax to the reference section. (These are discussed in other places, but they deserve detailed reference-style sections along with module and moduleResolution values.)
Emphasize declaration file extensions’ impact on module format in theory section
Cross-link to this content from other places in the website (there are redirects from deleted sections, so some will work already, but more can be added—website PRs welcome to help with this!)
Reacted by Guillaume Humbert, Josh Ghoulberg 👻 and Mark Erikson
One suggestion for now: have you considered putting the Theory section after Guides and References? In my experience most learners will not have the time or patience to read through all of it - they just want to learn how to fix a broken build or type errors. The guides could get them where they need to go quickly while forward-linking to references and theory as needed.
As far as typescript concerned package.json#imports has no relation to paths, because the latter operates on source files, while the former - on the output files. Bundlers muddy the ground there again, since they obviously bundle the output into whatever so they are free to interpret imports whatever they want. This is not the case if you want to output nodejs ESM modules, however.
The section shows how the the browser scripts were written in ye olde times, specifically how they all shared the same global scope. And there was no type=module back then.
The intent of these sections seems to be explanatory and not solely limited to TS hence remarks about import maps. I don't think adding more ways for understanding nuance like the fact that paths differs from package.json#imports is harmful; in fact, your comment could be a great addition to the documentation since it is something coming up here under some tension.
Fair enough for the script tags, but it doesn't explain how this differs from the modern day by showing the modern example.
i am havin a serious challenge, from my tsconfig.json code " {
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "ESNext", "DOM.Iterable"],
},
"include": ["src//*.ts", "src//.tsx", "src/types/**/.d.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}
" but there is error " Cannot find type definition file for 'node'.
The file is in the program because:
Entry point of type library 'node' specified in compilerOptionsts
Cannot find type definition file for 'react'.
The file is in the program because:
Entry point of type library 'react' specified in compilerOptionsts
Cannot find type definition file for 'react-dom'.
The file is in the program because:
Entry point of type library 'react-dom' specified in compilerOptionsts
JSON schema for the TypeScript compiler's configuration file
No quick fixes available " and for this reason other module Cannot find module 'react' or its corresponding type declarations.ts(2307)
No quick fixes available. any help will be appreciated
Activity
andrewbranch commentedon Feb 3, 2023
Proposed, very ambitious outline, subject to change:
verbatimModuleSyntaxawaitallowArbitraryExtensions"exports""type""exports"conditionsnoEmitandallowImportingTsExtensionsmodulecommonjses2015+node16/nodenextdefaultproblem__esModulemarkerallowSyntheticDefaultExportsesModuleInteropverbatimModuleSyntaxmoduleResolutionnode16/nodenextbundlerallowJsandmaxNodeModuleJsDepth"main","types""typesVersions""exports""type""imports"pathsbaseUrlresolvePackageJsonImportsresolvePackageJsonExportscustomConditionsallowImportingTsExtensionsresolveJsonModulerootDirstypeRootsmoduleSuffixesfatcerberus commentedon Feb 3, 2023
Approximately everybody 😄
RyanCavanaugh commentedon Feb 3, 2023
Another section we'll need is effectively "What is the right thing to put on DefinitelyTyped?"
DanielRosenwasser commentedon Feb 4, 2023
What module settings should my new project use?
andrewbranch commentedon Feb 4, 2023
Daniel Rosenwasser (@DanielRosenwasser) please refer to section 3.i
fatcerberus commentedon Feb 4, 2023
All of them
jmrossy commentedon Feb 5, 2023
Andrew Branch (@andrewbranch) Two more fields worth covering in 4.iii.c:
moduleandbrowserrobpalme commentedon Feb 6, 2023
I'm not sure which section this would go in, but there is a semi-obscure question/choice I'd like to get an authoritative answer on, so I'm kinda hijacking this issue as a place to resolve it.
Question
*.d.tssource file instead of using a*.tssource file?It's common to have local files in which you might store utility types. Maybe you put them in a directory called
util-types/. So which file extension type should you put in this directory?*.tsfile would work, but at best will result in an empty*.jsfile being emitted, and at worst might deviate from programmer intent over time if someone accidentally inserts value-land code.*.d.tsenforces this intent and guarantees to have zero JS emit - it only is needed during checking.Here's some example usage in a multi-file playground.
Potential Answers
I'm looking for a steer on whether the use of
*.d.tsfiles for storing hand-written utility types is:The answer will help inform whether we should internally force the use of
*.tsfiles for this purpose by banning hand-written*.d.tsfiles.andrewbranch commentedon Feb 6, 2023
The answer to this question is unsatisfying to me. I would say it’s discouraged or even harmful (though it won’t be “deprecated”). At best, it’s a technique with serious pitfalls that can be leveraged by people who understand them enough to set up additional tooling and safeguards to make it viable. Because
.d.tsfiles only occur “naturally” as a pair with a.jsfile, together as outputs of a.tsfile, a.d.tsfile always implies the existence of a.jsfile. So the potential harm is readily apparent: if you hand-authoronly-types.d.tsand then writeimport {} from "./only-types.js", this resolves and is legal in all settings, but inverbatimModuleSyntax, the import will be preserved and crash at runtime. While TypeScript has type-only imports and exports, it lacks the analogous concept of a type-only module, one which exists for type information purposes but is known to not exist at runtime, though I’ve casually suggested multiple times that such a concept could be useful.But the main reason I personally avoid this is just because it doesn’t copy into
outDir. For my purposes, I’d rather just eat the cost of the empty JS file (which also protects you from crashes should you accidentally import it at runtime).benasher44 commentedon Mar 1, 2023
Never been so hyped for documentation
RyanCavanaugh commentedon Mar 3, 2023
How to make dual-mode packages? #53045
benasher44 commentedon Mar 23, 2023
I'm not sure if this is too much, but guidance on how to navigate incompatible packages when migrating a TS project from CJS to ESM would be helpful. I can file tickets on projects to fix their package.json and build processes all day, but ultimately it can be very hard to know precisely what is broken, what to tell library authors, and what patch I need to apply to the package to hold me over in nodenext + CJS mode to prepare to go to nodenext + ESM mode.
2 remaining items
refactor: better use of typescript types
andrewbranch commentedon Aug 24, 2023
Quick update: this is getting fairly close to being done and anticipate getting a PR up to put this on the website in the coming weeks. I just pushed a fairly large update to the gist.
chore: module-scoped type definitions, see: microsoft/TypeScript#5259…
andrewbranch commentedon Oct 18, 2023
I’m getting ready to merge this. There’s still more to do, but it’s complete enough to publish and iterate on.
Feedback is welcome, so I’ll mention the things I already have on my immediate to-do that I won’t get to before merging
moduleDetection,allowSyntheticDefaultImports,esModuleInterop, andverbatimModuleSyntaxto the reference section. (These are discussed in other places, but they deserve detailed reference-style sections along withmoduleandmoduleResolutionvalues.)JoshuaKGoldberg commentedon Oct 18, 2023
Amazing work, congrats Andrew Branch (@andrewbranch)! 👏
One suggestion for now: have you considered putting the Theory section after Guides and References? In my experience most learners will not have the time or patience to read through all of it - they just want to learn how to fix a broken build or type errors. The guides could get them where they need to go quickly while forward-linking to references and theory as needed.
Faithfinder commentedon Oct 18, 2023
I wonder if an appendice about

baseUrlandpathsbelongs in there somewhere? Really made me think about it when readingbmeck commentedon Oct 18, 2023
Just a couple of feedback points:
https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options.html#im-writing-es-modules-for-the-browser-with-no-bundler-or-module-compiler likely could also mention
package.json#importssince it also can map to work with a large variety of tooling often more thanpathsincluding import maps: https://codepen.io/bradleymeck/pen/YzdmQRNhttps://www.typescriptlang.org/docs/handbook/modules/theory.html#scripts-and-modules-in-javascript likely should include
type=modulein the script tags or disambiguate Script vs Module. I think just putting type=module makes less complexity but could cause confusion when thinking there is only ESM and CJS and it not working.GabenGar commentedon Oct 19, 2023
package.json#importshas no relation topaths, because the latter operates on source files, while the former - on the output files. Bundlers muddy the ground there again, since they obviously bundle the output into whatever so they are free to interpretimportswhatever they want. This is not the case if you want to output nodejs ESM modules, however.type=moduleback then.bmeck commentedon Oct 19, 2023
GabenGar
pathsdiffers frompackage.json#importsis harmful; in fact, your comment could be a great addition to the documentation since it is something coming up here under some tension.Updated the used types to ensure seamless compilation, refer to: micr…
Updated the used types to ensure seamless compilation, refer to: micr…
Abolaji1 commentedon Sep 12, 2025
i am havin a serious challenge, from my tsconfig.json code " {
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "ESNext", "DOM.Iterable"],
},
"include": ["src//*.ts", "src//.tsx", "src/types/**/.d.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}
" but there is error " Cannot find type definition file for 'node'.
The file is in the program because:
Entry point of type library 'node' specified in compilerOptionsts
Cannot find type definition file for 'react'.
The file is in the program because:
Entry point of type library 'react' specified in compilerOptionsts
Cannot find type definition file for 'react-dom'.
The file is in the program because:
Entry point of type library 'react-dom' specified in compilerOptionsts
JSON schema for the TypeScript compiler's configuration file
No quick fixes available " and for this reason other module Cannot find module 'react' or its corresponding type declarations.ts(2307)
No quick fixes available. any help will be appreciated