Skip to content

Instantly share code, notes, and snippets.

@andrewbranch
Last active May 31, 2024 20:54
Show Gist options
  • Save andrewbranch/6f11e6e0c3fb9a294590a061249264b0 to your computer and use it in GitHub Desktop.
Save andrewbranch/6f11e6e0c3fb9a294590a061249264b0 to your computer and use it in GitHub Desktop.
tsc performance consuming .ts vs .d.ts files in npm packages

Original Tweet

tsc performance consuming .ts vs .d.ts files in npm packages

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";

Results

.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.

Takeaway

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.

Test details

  • 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).

@benjamind
Copy link

benjamind commented Sep 25, 2023

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?

@andrewbranch
Copy link
Author

Yes, performance isn’t the only potential problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment