⚠️ Do not use this unless you understand the risks ⚠️
You’re probably better off using ts-expose-internals directly.
ts-expose-internals-conditionally
A simple fork of the excellent ts-expose-internals, but only activates when compiled with:
// tsconfig.json
{
"compilerOptions": {
"module": "nodenext", // or "node16" or "bundler" - must support package.json "exports"
"customConditions": ["ts-expose-internals"]
}
}
Why?
If you are publishing a library that uses ts-expose-internals in its implementation, and exposes some of TypeScript’s non-internal types in its API, your own declaration files will end up referencing ts-expose-internals:
import { CompilerOptions } from "typescript";
export function doSomethingWrappingTypeScript(
fileName: string,
typeScriptOptions: CompilerOptions
): unknown;
Even though CompilerOptions
is part of TypeScript’s public API, ts-expose-internals is a module augmentation that re-declares and augments everything in the typescript
module. So when tsc --declaration
notices that the CompilerOptions
you’re referencing in your own compilation is declared in "typescript"
but augmented in "ts-expose-internals"
, it adds an explicit reference to "ts-expose-internals"
to make sure everyone using your library sees the same CompilerOptions
that you do.
Normally, this is a very desirable behavior. Without it, users could end up with types in their node_modules
that reference things that don’t exist in their own compilation, leading to errors. But in the case of ts-expose-internals, you probably don’t want to expose all of TypeScript’s internals for all of your users. You want the internals when compiling your own implementation code, but your public API can be consumed without them.
By using ts-expose-internals-conditionally
and compiling with "customConditions": ["ts-expose-internals"]
in your tsconfig.json, your compilation will include the ts-expose-internals module augmentation, but your users’ compilations won’t (assuming they don’t also have "customConditions": ["ts-expose-internals"]
).
Of course, this is not safe, because if you accidentally reference anything internal that makes it into your declaration files, your users will get errors:
import { ModeAwareCache } from "typescript";
To be safe, you should re-check your output declaration files without any customConditions
to ensure they’re portable.
This must go in dependencies
, not devDependencies
Since tsc --declaration
will emit a reference to "ts-expose-internals-conditionally"
in your declaration files, you need to ensure that reference actually resolves (to an empty file) for your users, which means they will need this package installed, which means it should go in your dependencies
, not your devDependencies
.
Why is this a fork and not an intermediate dependency?
I thought I would get away with making a package with two tiny files:
But it turns out that tsc --declaration
still emits a reference directly to "ts-expose-internals"
, even when it’s referenced transitively through this proxy package. This behavior actually makes sense, so I don’t think there was a way to do this without publishing an independent package.