A couple of years ago, Sentry were having big problems with their React app. They'd pretty recently migrated it to TypeScript from JavaScript. And the app was part of a large monorepo.
But the IDE performance was slow. You'd often need to wait a couple of seconds after making a change for the TypeScript language server to update. And running tsc
would take a long time.
Now, this isn't unusual for a large TypeScript codebase. But the Sentry team had a hunch that something was wrong. The problem felt out of proportion to the size of the codebase.
It turned out that the issue, outlined by Jonas, was down to a single pattern.
#
In tons of places in Sentry's codebase, they were extending HTML types in React. For instance, defining ButtonProps
would look like this:
import React from "react";
type ButtonProps =
React.HTMLAttributes<HTMLButtonElement> & {
extraProp: string;
};
const Button = ({ extraProp, ...props }: ButtonProps) => {
console.log(extraProp);
return <button {...props} />;
};
This means that you could pass in all the props that a <button>
element could take, plus an extraProp
:
<Button
extraProp="whatever"
onClick={(e) => { }}
/>;
But it turns out that this pattern is devilishly slow. So Jonas, following the advice of the TypeScript Performance Wiki, changed each of these to use an interface
instead:
import React from "react";
interface ButtonProps
extends React.HTMLAttributes<HTMLButtonElement> {
extraProp: string;
}
Suddenly, things got a lot snappier. The TypeScript language server was faster, and tsc
ran quicker. Just from a little syntax change. Why?
#
You may have heard that interface
is slightly faster than type
. This is not quite true. In fact, interface extends
is slightly faster than &
.
In an earlier version of this article, I'd posted an explanation based on some fuzzy thinking which, thanks to my old colleague
Mateusz Burzyński, I now understand was wrong.
The problem is more complex than I realised - check out this thread for his critiques and our investigations.
Hopefully, I can update this article again with a definitive description of why this happens - but nothing is simple when it comes to TypeScript performance.
Suffice to say - interface extends
is generally faster than &
, and so it proved in this case too.