ts-node: Compilation is unbelievably slow

Hi, for some reason we have performance problems when using ts-node in our mocha tests. It takes about 500 ms to compile an empty ts file. I added some console.logs to measure the time to run this very line: const output = service.getEmitOutput(fileName) and this line takes 500 ms to run even though the content of the file is an empty string (the code variable on the line 314 is empty string). Actually, all our tests files take too long to process, while the production files take a few milliseconds to compile. So any *.spec.ts takes about 500 ms to compile, a regular *.ts file takes about 20 ms. Do you have any idea what could be the root cause or how should I debug it more?

We use latest mocha, ts-node and typescript, but we tried some old versions too and the problem persists. The tsconfig.json:

{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": ".",
    "declaration": true,
    "target": "es2017",
    "lib": ["es2017"],
    "module": "commonjs",
    "moduleResolution": "node"
  },
  "exclude": [
    "node_modules",
    "dist"
  ]
}

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 140
  • Comments: 168 (32 by maintainers)

Commits related to this issue

Most upvoted comments

If anyone can give me temporary access to a slow repo, let me know! I can quickly take a look into the issue and happy to sign any NDA, etc you need. If not, can I get a sense of peoples tsconfig.json that’s slow - are you relying on files, include, exclude, rootDir or other to compile with TypeScript?

Let’s try with reactions:

  • ❤️ files
  • 🚀 include
  • 👀 exclude
  • 😕 rootDir
  • 🎉 Other

Have you tried --transpile-only mode?

I’ve run into similar issue today. After ts-node upgrade from 7.0.1 to 8.0.1 my mocha test suite became very slow (32s vs 4s). Adding TS_NODE_TRANSPILE_ONLY=true resolved my issue. Thanks @cspotcode

That said, an ode to you @blakeembrey and the rest of the ts-node team. Your work on this free, open-source tool and your visibility/availability to its users is much appreciated.

We had to downgrade too. This is what happens in our project 💥

7.0.1

image

8.0.3

image

Reproducing the issue

I also created a project to reproduce the issue: https://github.com/lukaselmer/reproduce-slow-ts-node

image

7.0.1

git clone https://github.com/lukaselmer/reproduce-slow-ts-node reproduce-slow-ts-node-v7
cd reproduce-slow-ts-node-v7
npm i
npm start
npm start

8.0.3

git clone https://github.com/lukaselmer/reproduce-slow-ts-node reproduce-slow-ts-node-v8
cd reproduce-slow-ts-node-v8
git checkout v8
npm i
npm start
npm start

With TS_NODE_FILES=true

image

With TS_NODE_TRANSPILE_ONLY=true

image

With TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true

image

It seems that even with the additional options, 8.0.3 is still 2-7 times slower than 7.0.1.

I think I had a breakthrough. Compilation gets way faster when we implement getProjectVersion in our LanguageServiceHost. I think without that method implemented, the compiler has no choice but to routinely throw away a bunch of internal state. For example, for every required ts file, we make 2 calls back-to-back: one to get output JS, and the next to get diagnostics. Without implementing getProjectVersion, the compiler has to redo a bunch of work internally for both of those calls, which is crazy because nothing has changed.

For example, if you run the following assertion:

assert(languageService.getProgram() === languageService.getProgram())

… it fails in ts-node today, but it passes when I add an implementation of getProjectVersion().

TS source explaining getProjectVersion()

EDIT: Here’s where this happens inside the compiler: https://github.com/microsoft/TypeScript/blob/master/src/services/services.ts#L1201-L1394 When project version is not the same or not implemented, a new Program and TypeChecker must be built. Implemented and explained here: https://github.com/Microsoft/TypeScript/pull/3131/#issuecomment-103121030

Even when we don’t provide a project version, the language service internally checks if the program is up-to-date using a more CPU-intense check, looping through all files, config values, etc, to see if anything changed. This check is failing, so there must be something else we’re doing wrong. https://github.com/microsoft/TypeScript/blob/master/src/compiler/program.ts#L555-L650

EDIT2: Found where it’s failing. When any source files are omitted from getScriptFileNames the check fails because the internal HostCache doesn’t have those files. Caused by the revert made between v8.4.0 and v8.4.1 https://github.com/TypeStrong/ts-node/issues/754#issuecomment-536097118 Maybe related: https://github.com/TypeStrong/ts-loader/issues/949

I filed a bug on Typescript to see if the Program invalidation is a bug on their end. https://github.com/microsoft/TypeScript/issues/36748

Released as 8.8.0, please let me know if anyone sees a regression in other parts of the application (or errors with TypeScript as happened in 8.4.0).

--transpile-only does speed things up considerably for me. I don’t want to lose the type checking though!

@brunolm Your update on 8.4 inspired me to revisit the change in that version. Can you try https://github.com/TypeStrong/ts-node/pull/985 at all?

For now, please try using the environment flag from https://github.com/TypeStrong/ts-node#cli-and-programmatic-options - TS_NODE_FILES=true. I’ll attempt refactoring to the newer TypeScript watch API and see if it improves performance in the coming weeks.

In my case with .mocharc.js, replace: require: ["ts-node/register", ...] with: require: ["ts-node/register/transpile-only", ...] resolved my issue.

It doesn’t seem fixed with version 8.1.0 😞

@blakeembrey

https://github.com/TypeStrong/ts-node/pull/985 (npm i -S TypeStrong/ts-node#be/improve-perf-again)

  • 10s
  • 9s
  • 9s
  • 9s
  • 9s
  • 10s

Looks like you nailed it!

❤ 😄 🎉

#963 implements getProjectVersion, which should workaround microsoft/TypeScript#36748, meaning the compiler needs to typecheck 3x less than before. This will only affect you if you have not enabled transpileOnly / --transpile-only / TS_NODE_TRANSPILE_ONLY nor compilerHost / --compiler-host / TS_NODE_COMPILER_HOST.

If you’re really eager or curious, you can test it by putting this in your package.json deps: "ts-node": "https://github.com/TypeStrong/ts-node.git#ab/add-getprojectversion",

Setting TS_NODE_DEBUG=true will emit extra debug statements related to when and how often the compiler needs to rebuild the typechecker.

That’s great feedback, thanks! If I can gather another piece of positive feedback in terms of performance on a large project, I’ll look into releasing this as v9 along with other breaking changes.

@kfrajtak I’ve tried esbuild and it is unbelievably fast. I moved to it. Compile + run is much faster than ts-node. Thanks.

Edit: I’m using esbuild --watch and nodemon build/out.js now

Edit 2: I’ve tried to use other project using decorator, but its Interpolation is still different from original tsc. so I’m sticking tsc --watch in that project.

I have read over all the comments, the issue is still open and it’s been close to 9 months. The latest version still has this problem and the only solution I can see here is to role back to 7.0.1

Are there any updates on this issue and when this will be resolved.

ts-node -r tsconfig-paths/register --files run-tests.ts | faucet

Using --files helps run the tests a bit faster for my colleague but for myself they do not complete even after 11 minutes whilst I was making tea.

I use node-demon with ts-node for automatic restart of the server on code edit. I still see a consistent 10x slow down for restart, between versions v7.0.1 and v8.10.1 of ts-node. I am using Linux Mint.

I will try and create a minimal reproducible example, but I just wanted to note that the speed regression is still very clear, at least on my machine.

ts-node@8.7.0

  • 42s
  • 41s
  • 40s

ts-node@8.6.2

  • 48s
  • 47s
  • 47s

ts-node@7.0.1

  • 7s
  • 3s
  • 4s

@blakeembrey I’ve made a minimal repro at https://github.com/davidgruar/tsnode-perf-repro - please check it out and have a play. Loading just three test files with fairly few dependencies takes more than a second, and the more imports you add, the longer it takes.

Closing because some solutions are already available (--transpiler), other aspects are tracked by other open issues (incremental builds), and unfortunately this thread has not yielded good reproductions with which we can test.

  • --transpiler with swc yields the fastest compilation when skipping typechecking, beating out even a fully-cached solution
  • Docs have a dedicated page with performance recommendations: https://typestrong.org/ts-node/docs/performance
  • #817, #1161, and #1245 track the research/work necessary to support incremental builds

Can you use the power of esbuild? https://esbuild.github.io/

@acro5piano could you explain how you do that please 🙏

@Alexandredc instead of having your nodemon config watch your .ts files and execute ts-node on the entry ts file on changes, You can instead run tsc --watch (optionally with "incremental": true in your tsconfig.json) when the .ts files change to generate the js files in the "outputDir" folder, then run node / nodemon against the generated entry .js file

@christianrondeau It’s currently on master but cannot support a use-case that used to be supported. I only discovered this after trying to test another bug in the backlog, so I haven’t released it yet. Most likely I’ll be looking to put it behind a flag as part of v8 for a bit.

@blakeembrey I’ve tried the incremental program branch and it launches our app in 10 or 11 seconds, whereas 8.4.0 takes around 16 seconds

I’ve been using 8.3.0 for a while. Tried upgrading to 8.4.1 today and startup time went from ~16s to ~75s.

also had to downgrade 8.0.3 to 7.0.1 to fix the performance issue, time to start tests was like 3 minutes vs few seconds difference, TS_NODE_FILES=true didn’t help anything

I’m just using tsc --noEmit && TS_NODE_TRANSPILE_ONLY=true ts-node index.ts in my project, and it’s 3x faster than ts-node index.ts ( there are 500+ ts files in my project. before: 96 seconds, after: 32 seconds ).

See also: #1160 which allows opting into the swc compiler via ts-node.

I’ve met same issue on developing web app/API server using express + ts-node-dev(ts-node). So, I’ve created express-lazy-router for reducing this issue. This is inspired by webpack’s lazyCompilation.

express-lazy-router help us to reduce the compilation time at start time by compiled when needed. 📝 It is for webapp using express, it is not for testing

@khaledosman @Alexandredc

My current solution is tsc + nodemon, but I’m still doing the configuration for Ava. The downside is the complex configuration. ts-node and tsconfig-paths is a simple configuration, works nicely for fs.readFile and ts path mappings and Ava. But tsc + nodemon is 2x speeds up my project.

The basics are:

# Directory structure

repo/
   |- tsconfig.json
   |- package.json
   |- src/
   |    `- index.ts
   `- build/
        `- index.js
// tsconfig.json

{
  "compilerOptions": {
    "outDir": "./build",      // <-- important
    "noEmit": false,     // <-- important
    "target": "es2019",
    "sourceMap": true,
    "incremental": true,
    "isolatedModules": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}
// package.json

  "scripts": {
    "dev": "concurrently yarn:dev:*",
    "dev:nodemon": "nodemon build/index.js",
    "dev:tsc": "tsc -w"
  },

And the last thing is to add deps:!

yarn add -D concurrently nodemon typescript

edited: You may also needs source-map-support

I just tried 9, and altough it feels somewhat faster on the initial compilation, it recompiles at almost the same speed as the initial on file changes, which overall makes it much less useable than v7 (need to wait 4 mins to recompile after changing a single character in code). I switched back to v7.

Whether or not the original slowness issue has been solved, I’m happy to leave this ticket open a bit longer, because the discussion is still useful. Ultimately the goal with these tickets is to help us make ts-node better, and it’s still serving that purpose for me.

Where individual issues are being identified, we’re tracking and fixing them as separate tickets. Additionally, the recent change in 8.8.0 may need to be reverted since it triggers the regression test from #884 So it’s possible we’re not out of the woods yet.

See also: #996, which links to the other relevant tickets.

Also, specifying whether or not you’re on Windows is very helpful! Since it seems we may have a windows-only performance problem related to / vs \ paths.

Big kudos to you @blakeembrey for sticking with this problem this far! I have not tried out 8.8.0 yet, but the reactions that people have made about it so far assure me that you have indeed solved the problem.

Ẹ ṣeun pupọ! Thank you very much!

Please try ts-node v8.7.0. It’s been hanging out on the next dist-tag but I just promoted it to latest.

https://github.com/TypeStrong/ts-node/releases/tag/v8.7.0

The LanguageServiceHost::getProjectVersion implementation should help some projects quite a bit.

I created a very minimal reproduction here: https://github.com/TypeStrong/ts-node-repros/tree/754 It looks like require-ing each file with typechecking turned on eats up about 60ms, even if the file is basically empty.

Possibly related: discussion here about how getting diagnostics per file is apparently slow no matter what: https://github.com/typescript-eslint/typescript-eslint/issues/1335 But I don’t know if I understand the details well enough.

Using the new incremental compilation by TypeScript would be stabler and I’d love to see that implemented, I think that would resolve this thread!

Passersby interested in implementing that, here’s the announcement linking to PR & docs: TS 3.6 APIs to support --build and --incremental.

@gruckion If you want it to be improved, you’ll need to submit a PR. Based on this discussion I’m unclear of how to improve the performance - the only possible configs are --files or --transpile-only. The only major gripe I can understand from comments is that people liked the file system cache a lot, which could be re-enabled but not as default because it causes subtle hard-to-debug issue. Using the new incremental compilation by TypeScript would stable and I’d love to see that implemented, I think that would resolve this thread!

Hi – for what it’s worth –

We have a rather large code base (no way to know really…)

It is on the order of 450 Mocha tests.

7.x was not “snappy” to startup - but it was tolerable.

I upgraded from 7.0.2 to 8.0.1 and the tests will actually never finish on Windows. They work fine on Linux (like 7.x).

After reading through this, I was trying to use the --files true flag. On a smaller codebase it made a big different (the tests ran).

On the code base with 450 tests… it simply never finished. The strange thing is if I set the environment variable TS_NODE_FILES – set TS_NODE_FILES=true ; the tests start almost immediately.

If it helps:

mocha --recursive --require ts-node/register -R spec ./**/*.spec.ts --no-timeouts --exit

tsconfig.json

{
  "compilerOptions": {
    "target": "es6",
    "outDir": "dist",
    "rootDir": "src",
    "moduleResolution": "node",
    "module": "commonjs",
    "declaration": true,
    "importHelpers": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "sourceMap": true,
    "listFiles": false,
    "traceResolution": false,
    "pretty": true,

    "strictNullChecks": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitAny" : true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,

    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2018",
      "es6",
      "dom",
      "dom.iterable"
    ]
  },
  "include": [
    "src/**/*.ts"
  ],
  "exclude": [
    "node_modules/**"
  ],
  "compileOnSave": false
}

package.json

    "ts-node": "^8.0.2",
    "typescript": "3.3.3"
    "tslib": "^1.9.3",

When host doesn’t have resolveModuleName it creates the cache per program. So whenever program changes the new resolution cache is created. (Note this is different from internal resolution cache that tsc and tsserver use that are made aware of which files have changed and hence which resolutions to invalidate) I don’t think ModuleResolutionCache that compiler has, is equippd to handle the changes and hence its per program. I think better option would be to use our WatchAPI instead as that is equipped to handle changes in file and hence invalidating partial resolutions.

@blakeembrey Hi, I also created an example: https://github.com/lukashavrlant/ts-node-perf But I guess it is similar to the previous example. Basically compiling TS files to JS files and then running mocha tests using the generated JS files is faster then using ts-node directly. See readme for more details please. The more imports I use is test files, the slower it gets. For the sake of example I used zero imports in tests files.

@eyalroth

FYI swc seems to be incompatible with sequelize-typescript

Yeah, I believe only tsc (and ts-node without SWC) can handle emitDecoratorMetadata correctly. I tried almost all solutions such as babel, esbuild, swc but they doesn’t emit decorator metadata correctly. I checked it by reading compiled code and it seems impossible.

Thus, I stopped using decorators. Moved from mikro-orm to objection, Type-GraphQL to Nexus, remove tsyringe, then moved from ts-node to esbuild. Now things became much better.

@thousandsofraccoons ts-node’s “swc” integration is the recommended solution here. It skips typechecking and uses the blazing-fast swc transpiler. You can use it today, check the docs links below for instructions.

https://typestrong.org/ts-node/docs/performance#skip-typechecking https://typestrong.org/ts-node/docs/transpilers#bundled-swc-integration

It is promoted out of “experimental” status in our next, upcoming release. #1536

The new features mentioned above, --transpiler and --show-config, have been released in v10

https://github.com/TypeStrong/ts-node/releases/tag/v10.0.0

There is a dedicated discussion thread linked if you need assistance with the upgrade.

I investigated esbuild when implementing #1160 but there were performance limitations in per-file compilation. #1160 gives the same performance benefits, and people are already successfully using it, for example https://github.com/TypeStrong/ts-node/discussions/1276#discussioncomment-590910 I recommend checking it out and giving feedback in the discussion thread.

#1160 implements pluggable transformers.  The swc transformer is built-in, but anyone can write a plugin to use any other transformer. This means your existing TS project configuration is all you need to use a different transformer, and you get the same benefits as ts-node: sourcemap support, ESM loader, etc.

Also, for many people, transpileOnly is all they need.  Often they’ve made a configuration mistake: they believe transpileOnly is enabled, but it actually is not. We implemented #1243 to make it easier to debug configuration mistakes, enabling more people to use transpileOnly and to better understand their configuration.

@acro5piano could you explain how you do that please 🙏

I have one big project, that unfortunately I can’t share. The entry point is “start”

"start": "ts-node src/index.ts"

At the top of this file (after many imports) I have a console.log(new Date())

Then I run

date; npm start

And I get the diff from there.


ts-node@8.4.0

  • 10s
  • 9s
  • 9s

I use nodemon and tsnode

I updated to “ts-node”: “^8.6.2”, and on each restart takes a lot to compile!

With TS_NODE_FILES=true did not show any kind of improvement.

I will try downgrade to previous version “ts-node”: “^7.0.1” or 8.4.0 as @christianrondeau mentioned from 8.4.1 version breaks performance.

@kaiza set this environment variable -> set TS_NODE_FILES=true

It should make your tests ‘go fast’

Unfortunately this definitely seems to be the case. I’m not 100% sure why in the language services this is so expensive. It appears that changing rootFiles results in the “project out of date” and it starts re-resolving all types and traversing node_modules. This is very bad in the test loading case - I need to find a way to use the previously cached resolutions over trying to re-resolve everything when the root files change. cc @TypeStrong/typescript-team @weswigham in case I’m doing something wrong.

That’s intereseting, files: false is definitely meant to make things faster rather than slower. Does anyone have a large enough project that’s running slow and want to either 1. investigate if the reason @davidgruar mentions appears true or 2. allow me temporary access to look into further myself?

Ideally files: false loads a single file and spiders out so it is possible that this is a slight issue with performance. It’s also possible that the previous version was hitting the cache heavily, if you upgraded from v7 to v8 (v8 removed the cache because of type issues). We could also investigate adding caching back but it needs to be much smarter than the previous version to solve transitive type changes.

The strangest thing happened. I undid all the configuration changes I made yesterday, npm ci-ed, added--transpile-only alone, and now it seems that the tests start running much faster.

Sorry for all the fuss.

Awesome, thank you! Feel free to share it here or create a separate issue if that would help to focus the conversation.

It’s understandable that many users cannot share a proprietary codebase. But they might be able to create a fake codebase with just as many lines of code, using the exact same configuration files, but all the code is copy-pasted nonsense. If a fake codebase exhibits similar performance, it gives us something concrete to look at.

Of course! I’m planning on creating such a fake repo. I just need time 😃

Compilers such as babel, swc, and esbuild do not understand type information. If type information from one file affects the emitted code from another file, it will fail to emit the code you’re expecting using these tools. TypeScript has an option isolatedModules which will raise typechecking errors to warn you that your code is incompatible with these tools.

https://www.typescriptlang.org/tsconfig/#isolatedModules

This is an important detail to understand: these errors are raised when using TypeScript to typecheck, not when using babel, swc, or esbuild to compile your code! The assumption here is that you run typechecking as part of your automated testing even when you use a non-typechecking compiler. Think of the typechecker as a lint step.

Unfortunately, the reason I closed this issue still applies here: Without any reproductions for us to look at, we can’t keep guessing at what may or may not be happening in someone’s project. Given a reproduction, we can take a look.

We’ve seen a lot of cases where someone thought they were using transpileOnly, but actually they weren’t. So that’s always a possibility whenever it doesn’t seem like transpileOnly is making much impact: perhaps it’s not even turned on.

@cspotcode I tried all the options – cli argument, environment variable and tsconfig.json 🤷

Is this an issue with cross-file metadata, in other words, is it compatible or incompatible with isolatedModules?

I… have no idea. Looks like @acro5piano knows more about this 😄

We’ve seen a lot of cases where someone thought they were using transpileOnly, but actually they weren’t. So that’s always a possibility whenever it doesn’t seem like transpileOnly is making much impact: perhaps it’s not even turned on.

Is this an issue with cross-file metadata, in other words, is it compatible or incompatible with isolatedModules?

Closing because some solutions are already available (--transpiler), other aspects are tracked by other open issues (incremental builds), and unfortunately this thread has not yielded good reproductions with which we can test.

FYI swc seems to be incompatible with sequelize-typescript. I found the issue sec#1160 which mentions this, but it’s possible that there are more undocumented issues preventing the two from working together.

Running with --transpile-only alone doesn’t seem to help much for our tests execution.

Could esbuild-register be an alternative solution?

https://github.com/egoist/esbuild-register

I’m thinking to switch plain tsc -w and nodemon combination with "incremental": true.

I love to use ts-node, but it tasks 8 seconds to start our API, whereas 3 seconds for plain tsc.

@DanielSchaffer caching was removed once upon a time. There are plans to re-add it, and issues tracking that work.

@cspotcode thanks for the insight. I did some digging. It is working fine on linux. The issue is only on windows.

I believe the difference in behavior between the 2 typescripts version come from this. https://github.com/microsoft/TypeScript/pull/36011 especially https://github.com/microsoft/TypeScript/blob/0b38a9a2b03d3c651822bc2a20d381545384f0f5/src/compiler/program.ts#L566

When i track it down it is rebuilding because when typescript does !arrayIsEqualTo(program.getRootFileNames(), rootFileNames) it found that C:\project\fileA.ts is not equal to C:/project/fileA.ts

Any suggestion for a fix?

@sylc I don’t know exactly why it’s slower compared to the previous version of typescript. But I can offer some insight as to how the compiler works.

The Program is immutable. Whenever a file is added to compilation, a new Program needs to be created to include the new file. The old ASTs can be reused so that the compiler doesn’t need to re-parse them.

However, the typechecker must be thrown away and a new one constructed. This means every time a file is added to the program, typechecking must be redone from scratch. (at least as I understand it) This is necessary because any file might include global type declarations or declaration merging that affects any other file of the program.

If you haven’t specified the "types" array in your tsconfig, it’s possible that the compiler has to load many, many @types declarations in addition to your codebase. This can add up to a ton of typechecking.

Any time it says “rebuilt Program instance” this is happening.

We can avoid this by not typechecking via --transpile-only mode. We can run tsc --noEmit separately to handle typechecking instead of ts-node.

Another option is using ts-node’s --files flag to eagerly load all your code at the beginning, so that the Program instance and typechecker don’t need to be rebuilt. If more files are loaded at the beginning, then they won’t be new to the Program when they’re require()d, and typechecking won’t need to be recomputed. If you manage to get this working, most of the “rebuilt Program instance” lines should go away.

Hi @brunolm ,

How do you do to measure execution time?

8.4.0 works fine for me , I do not notice difference with 7.0.1 , 8.6.2 is veeeery slow. I didn’t tried the v8.7.0 yet

I’m digging into the codepath that uses the incremental compiler API. It was really slow for my reproduction (link), like a second per require(), because for every single change to the Program, it was re-parsing all SourceFile instances. Our non-incremental codepath uses a LanguageService, which uses a DocumentRegistry to cache these.

For the incremental compiler, we have to pass a CompilerHost, which has a getSourceFile method. This was always returning a new SourceFile instance. I don’t know if this is intended behavior or if there’s a way to cache SourceFile instances. (or if I’m doing something wrong in my experiments)

I’m sorry, that’s our normal start script, I did add the --transpile-only argument to that when I was testing this.

I may have found a bug in the new incremental API implementation that causes it to drop back to the non-incremental API?

builderProgram is created via ts.createIncrementalProgram https://github.com/TypeStrong/ts-node/blob/master/src/index.ts#L590-L605 …but here, it’s reset with the legacy ts.createEmitAndSemanticDiagnosticsBuilderProgram https://github.com/TypeStrong/ts-node/blob/master/src/index.ts#L625-L632

When I upgraded our project’s ts-node from 4.1.0 to 8.5.2, I also faced the above problem in that our app’s startup time significantly slowed down. As advised above, using --transpile-only did significantly speed up the process, but I really didn’t want to lose type-checking. So while I am waiting for a later version of ts-node to be released that will make our app build fast without having to use --transpile-only, I have downgraded to 7.0.1. That version gives me fast build times (because, for one, it caches parts of previous build results).

I’m avoiding the issue by splitting compilation and the repl, as I still want type information:

package.json (cropped)

    "scripts": {
        "build:dev": "NODE_ENV=development tsc --watch",
        "start": "NODE_ENV=development nodemon -q node -r tsconfig-paths/register distribution/index.js --watch"
    },

and tsconfig.json (cropped)

        "pretty": true,
        "noEmitOnError": true,

then I run npm run build:dev in one terminal split, npm start in the other.

That’s my workaround until this is fixed 🤷‍♂️

@blakeembrey looks good when using this in our package.json:

  "ts-node": "https://github.com/TypeStrong/ts-node.git#be/incremental-program"

I can confirm a big performance difference between 8.4.1 and 8.4.0

my start script

cross-env NODE_ENV=development nodemon --inspect --watch src -r dotenv/config -r ts-node/register src/index.t

and tsconfig

{
"compilerOptions": {
	"esModuleInterop": true,
    "target": "es6",
    "module": "commonjs",
	"outDir": "./dist",
	"sourceMap": true,
    "noImplicitAny": true,
	"typeRoots" : [
        "node_modules/@types",
        "./src/types"
    ]
},
"include": [
    "./src/**/*"
],
"exclude": [
    "node_modules"
]
}

It’s not a solution, but downgrade to 6.2.0 helped me currently with the test performace issues.

Ok, perfect. It does look like TypeScript makes 4x the number of requests to the filesystem when files: false. Looking into whether there’s some issue with how TypeScript is functioning here and how this could be improved.