What if TypeScript libraries published just .ts
sources to npm instead of .js
and .d.ts
files? This might already be tempting for Bun-only libraries, but how will that impact users? This is easy to answer by experimenting on existing libraries that ship .js
, .d.ts
, and .ts
files.
RxJS ships .js
and .d.ts
files, but also .ts
files for debugability purposes. By tweaking its package.json "exports"
, we can compare tsc
performance on this file with imports resolving to .d.ts
files vs .ts
source files:
import {} from "rxjs";
import {} from "rxjs/ajax";
import {} from "rxjs/fetch";
import {} from "rxjs/operators";
import {} from "rxjs/testing";
import {} from "rxjs/webSocket";
.d.ts | .ts | |
---|---|---|
Files | 268 | 317 |
LOC | 49k | 62k |
Instantiations ❗ | 11k | 46k |
Memory | 92 kB | 118 kB |
Parse time | .76 s | .91 s |
Check time ❗ | 1.48 s | 3.02 s |
Total time ❗ | 2.58 s | 4.43 s |
Obviously .ts
files look really bad here, which is what I was expecting. I did expect the memory/parse difference to be more dramatic, and the check difference to be less, though! RxJS already uses isolatedDeclarations
-compliant style, so this is likely the best case check penalty.
Do not skip .d.ts
emit if you care about your users’ DX, even in a world where all your users can consume TS directly.
- typescript 5.2.2
- Node.js 14.21.3 (working on something old recently)
- tsconfig:
noEmit
,"module": "nodenext"
- test file:
index.ts
(CommonJS format) - Apple M2, 16 GB RAM
- stats from
tsc --extendedDiagnostics
- times average of three runs
Modifications to node_modules/rxjs/package.json
took the form
"exports": {
".": {
- "types": "./dist/types/index.d.ts",
+ "types": "./src/index.ts",
"node": "./dist/cjs/index.js",
"require": "./dist/cjs/index.js",
"es2015": "./dist/esm/index.js",
"default": "./dist/esm5/index.js"
},
for each "exports"
subpath (except "./internal/*"
which was not directly imported from the test file).
Isn't a major downside of libs only publishing TS source rather than declaration files that every project would effectively have to share one tsconfig? Since your compilation context becomes the TS files, you would need to get your configuration to match across your entire dependency tree? Or am I missing something?