{"id":13792769,"url":"https://github.com/tapjs/tsimp","last_synced_at":"2025-05-16T10:08:55.183Z","repository":{"id":204532870,"uuid":"704723890","full_name":"tapjs/tsimp","owner":"tapjs","description":null,"archived":false,"fork":false,"pushed_at":"2024-10-22T15:13:34.000Z","size":733,"stargazers_count":529,"open_issues_count":21,"forks_count":14,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-13T13:11:56.362Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://tapjs.github.io/tsimp/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tapjs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["isaacs","tapjs"]}},"created_at":"2023-10-13T23:05:48.000Z","updated_at":"2025-05-12T06:55:48.000Z","dependencies_parsed_at":"2023-11-14T18:43:45.217Z","dependency_job_id":"cee25a1d-2cd7-4f3a-b931-98f76afcc1c5","html_url":"https://github.com/tapjs/tsimp","commit_stats":null,"previous_names":["tapjs/tsimp"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tapjs%2Ftsimp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tapjs%2Ftsimp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tapjs%2Ftsimp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tapjs%2Ftsimp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tapjs","download_url":"https://codeload.github.com/tapjs/tsimp/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254509477,"owners_count":22082892,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T22:01:15.781Z","updated_at":"2025-05-16T10:08:50.169Z","avatar_url":"https://github.com/tapjs.png","language":"JavaScript","funding_links":["https://github.com/sponsors/isaacs","https://github.com/sponsors/tapjs"],"categories":["JavaScript","Loaders / Execution Tools"],"sub_categories":[],"readme":"# tsimp 😈\n\nA TypeScript IMPort loader for Node.js\n\n## What It Is\n\nThis is an importer that runs Node.js programs written in\nTypeScript, using the official TypeScript implementation from\nMicrosoft.\n\nIt is designed to support full typechecking support, with\nacceptable performance when used repeatedly (for example, in a\ntest suite which spawns many TS processes).\n\n## Why Is It\n\nThere are quite a few TypeScript loaders and compilers available!\nWhich one should you choose, and why did I need to create this\none?\n\n- [swc](https://swc.rs) is a TypeScript compiler implementation\n  in Rust\n- [tsx](https://npmjs.com/package/tsx) is a zero-config\n  TypeScript executer that aims to be a drop-in replacement for\n  node, powered by esbuild.\n- [ts-node](https://npmjs.com/package/ts-node) is probably the\n  most established of these, with a huge feature set and support\n  for every version of node and TypeScript you could possibly\n  want.\n\nHow this differs:\n\n- It uses the TypeScript implementation from Microsoft as its\n  compiler. No shade towards swc and esbuild, they're fast and\n  can do a lot, but the goal of `tsimp` is strict consistency\n  with the \"official\" `tsc` program, and just using it is the\n  simplest way to do that.\n- It supports the `--import` and `Module.register()` behavior\n  added in node v20.6, only falling back to warning-laden\n  experimental APIs when that's not available.\n- Type checking is enabled by default, so no need to run an extra\n  `tsc --noEmit` step after running tests, using a persistent\n  [sock daemon](https://isaacs.github.io/sock-daemon) and a\n  generous amount of caching to make it performant.\n- It's just a module loader, not a bunch of other things. So\n  there's no repl, no bundler, etc. Pretty much all it does is\n  make TypeScript modules in Node work.\n\n## USAGE\n\nInstall `tsimp` with npm:\n\n```\nnpm install tsimp\n```\n\nRun TypeScript programs like this in node v20.6 and higher:\n\n```\nnode --import=tsimp/import my-typescript-program.ts\n```\n\nOr like this in Node versions prior to v20.6:\n\n```\nnode --loader=tsimp/loader my-typescript-program.ts\n```\n\nOr you can use `tsimp` as the executable to run your program (but\nthe import/loader is ~100ms faster because it doesn't incur an\nextra `spawn` call):\n\n```\ntsimp my-typescript-program.ts\n```\n\nNote that while `tsimp` run without any arguments will start the\nNode repl, and in that context it will be able to import/require\nTypeScript modules, it does _not_ include a repl that can run\nTypeScript directly. This is just an import loader.\n\nIn Node v20.6 and higher, you can also load `tsimp` in your\nprogram, and from that point forward, TypeScript modules will\nJust Work.\n\nNote that `import` declarations happen in parallel _before_ the\ncode is executed, so you'll need to split it up like this:\n\n```js\nimport 'tsimp'\n// has to be done as an async import() so that it occurs\n// after the tsimp import is finished. But any imports that the\n// typescript program does can be \"normal\" top level imports.\nconst { SomeThing } = await import('./some-thing.ts')\n```\n\nBy comparison, this won't work, because the imports happen in\nparallel.\n\n```\nimport 'tsimp'\nimport { SomeThing } from './some-thing.ts'\n```\n\nCommonJS `require()` is patched as well. To use `tsimp` in\nCommonJS programs, you can run it as described above, or\n`require()` it in your program.\n\n```js\n//commonjs\nrequire('tsimp')\n// now typescript can be loaded\nrequire('./blah.ts')\n```\n\nIn Node version 20.6 and higher, this will also attach the\nrequired loaders for ESM import support. In earlier Node\nversions, you _must_ use `--loader=tsimp/loader` for ESM support.\n\n## Configuration\n\nMost configuration is done by looking to the nearest\n`tsconfig.json` file at or above the module entry point in the\nfolder tree.\n\nYou can use a different filename by setting\n`TSIMP_PROJECT=\u003cfilename\u003e` in the environment.\n\nIf there is a `tsimp` field in the tsconfig json file, then that\nwill override anything else in the file. For example:\n\n```json\n{\n  \"compilerOptions\": {\n    \"rootDir\": \"./src\",\n    \"declaration\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"inlineSources\": true,\n    \"jsx\": \"react\",\n    \"module\": \"nodenext\",\n    \"moduleResolution\": \"nodenext\",\n    \"noUncheckedIndexedAccess\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": false,\n    \"sourceMap\": false,\n    \"strict\": true,\n    \"target\": \"es2022\"\n  }\n  \"tsimp\": {\n    \"compilerOptions\": {\n      \"skipLibCheck\": true,\n      \"strict\": false\n    }\n  }\n}\n```\n\nSourcemaps are always enabled when using `tsimp`, so that errors\nreference the approriate call sites within TypeScript code.\n\n### Config File Changes and `extends` Options\n\nIf the `tsconfig.json` file used by tsimp changes, then it will\nautomatically expire its memory and disk caches, because new\noptions can result in very different results.\n\nHowever, while `extends` is fully supported (if `tsc` can load\nit, so can `tsimp`, because that's how it loads config), any\nextended config files will _not_ be tracked for changes or cause\nthe cache to expire.\n\nWhen in doubt, `tsimp --restart` will reload everything as\nneeded.\n\n### `\"module\"`, `\"moduleResolution\"`, and other must-haves\n\nThe ultimate resulting module style for tsimp _must_ be something\nintelligible by Node, without any additional bundling or\ntranspiling.\n\nTowards that end, the `module` and `moduleResolution` settings\nare both hard-coded to `NodeNext` in tsimp, regardless of what is\nin `tsconfig.json`.\n\nAlso, the following fields are always hard-coded by tsimp:\n\n- `outDir` Because tsimp isn't a build tool, but rather a module\n  importer, it doesn't actually write the emitted JavaScript to\n  disk. (Ok, technically it does, but only as a cache.) So, the\n  `outDir` is hard-coded to `.tsimp-compiled`, but this is never\n  used.\n- `sourceMap` This is always set to `undefined`, because:\n- `inlineSourceMap` is always set to `true`. It's just much\n  simpler and faster to have the sourcemap inline with the\n  generated JavaScript output.\n- `inlineSources` is always set to `false`. There is no need to\n  bloat the output, when the input is definitely present on disk.\n- `declarationMap` and `declaration` are always set to `false`,\n  because type declarations are not relevant.\n- `noEmit` is always set `false`, because the entire point is to\n  get the JavaScript code for Node to run. That said, the \"emit\"\n  is fully virtual, and nothing is written to disk (except to\n  avoid compiling the same code multiple times).\n\n### File Extensions, Module Resolution, etc.\n\nThe same rules for file extensions, module resolution, and\neverything else apply when using `tsimp` as when using `tsc`.\n\nThat means: if you're running in ESM mode, you need to write your\nimports ending in `.js` even though the actual file on disk is\n`.ts`, because that's how TS does it when `module` is set to\n`\"NodeNext\"` and the target dialect is ESM.\n\n### Compilation Diagnostics\n\nSet the `TSIMP_DIAG` environment variable to control what happens\nwhen there are compilation diagnostics.\n\n- `TSIMP_DIAG=warn` (default) Print diagnostics to `stderr`, but\n  still transpile the code if possible.\n- `TSIMP_DIAG=error` Print diagnostics to `stderr`, and fail if\n  there are any diagnostics.\n- `TSIMP_DIAG=ignore` Just transpile the code, ignoring all\n  diagnostics. (Similar to ts-node's `TS_NODE_TRANSPILE_ONLY=1`\n  option.)\n\n## How fast is it?\n\nIf the daemon is running, it's very fast, even if type checking\nis enabled. If the daemon is running and its previously compiled\nthe file you're running, it's _zomg extremely_ fast, like \"so fast\nyou'll think it's broken\" fast, outperforming TypeScript\ncompilers written in Rust and Go, since it literally doesn't have\nto do anything except check some file stats and then hand the\ncached results to Node. (In fact, since it caches in memory as\nwell as to disk, it might even be _faster_ in many cases than\nrunning plain old JavaScript, if the program is large.)\n\nAnd, this is with full type checking, which is sort of the point\nof using TypeScript. No matter how fast your compiler is, if\nyou're then running `tsc --noEmit` to check your types, then it's\nnot actually gaining much.\n\nIf the daemon is _not_ running, and it's a cold start with no\ncache, it's pretty slow, comparable with ts-node, especially if\ntype checking is enabled.\n\nAn exceptionally not scientific example comparison:\n\n\u003cpre style=\"color:#eeeeee;background:#222222;position:relative\" title=\"tapjs/tsimp main - tapjs/tsimp\"\u003e\n$ time node --loader @swc-node/register/esm hello.ts\n(node:89220) ExperimentalWarning: `--experimental-loader` may be removed in the future; instead use `register()`:\n--import 'data:text/javascript,import { register } from \u0026quot;node:module\u0026quot;; import { pathToFileURL } from \u0026quot;node:url\u0026quot;; register(\u0026quot;%40swc-node/register/esm\u0026quot;, pathToFileURL(\u0026quot;./\u0026quot;));'\n(Use `node --trace-warnings ...` to show where the warning was created)\nhello, world\n\nreal\t0m0.268s\nuser\t0m0.255s\nsys\t0m0.033s\n\n$ time node --import=tsx hello.ts\nhello, world\n\nreal\t0m0.135s\nuser\t0m0.126s\nsys\t0m0.020s\n\n$ time node --import=./dist/esm/hooks/import.mjs hello.ts\n\u003cspan style=\"color:#00ffff\"\u003ehello.ts\u003c/span\u003e:\u003cspan style=\"color:#ffff00\"\u003e2\u003c/span\u003e:\u003cspan style=\"color:#ffff00\"\u003e18\u003c/span\u003e - \u003cspan style=\"color:#ff3030\"\u003eerror\u003c/span\u003e\u003cspan style=\"color:#404040\"\u003e TS2322: \u003c/span\u003eType 'string' is not assignable to type 'boolean'.\n\n\u003cspan style=\"color:#222222;background:#eeeeee\"\u003e2\u003c/span\u003e const f: Foo = { bar: 'hello' }\n\u003cspan style=\"color:#222222;background:#eeeeee\"\u003e \u003c/span\u003e \u003cspan style=\"color:#ff3030\"\u003e                 ~~~\n\n\u003c/span\u003e  \u003cspan style=\"color:#00ffff\"\u003ehello.ts\u003c/span\u003e:\u003cspan style=\"color:#ffff00\"\u003e1\u003c/span\u003e:\u003cspan style=\"color:#ffff00\"\u003e14\n\u003c/span\u003e    \u003cspan style=\"color:#222222;background:#eeeeee\"\u003e1\u003c/span\u003e type Foo = { bar: boolean }\n    \u003cspan style=\"color:#222222;background:#eeeeee\"\u003e \u003c/span\u003e \u003cspan style=\"color:#00ffff\"\u003e             ~~~\n\u003c/span\u003e    The expected type comes from property 'bar' which is declared here on type 'Foo'\n\nhello, world\n\nreal\t0m0.126s\nuser\t0m0.110s\nsys\t0m0.022s\n\u003c/pre\u003e\n\n## How is it so fast?\n\n![meme comic \"We need this to run faster\" \"rewrite it in rust\" \"rewrite it in zig\" \"use basic caching and work skipping\" guy gets thrown out window](https://github.com/tapjs/tsimp/raw/main/faster.jpg)\n\nBasic caching and work skipping.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftapjs%2Ftsimp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftapjs%2Ftsimp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftapjs%2Ftsimp/lists"}