One of the ways to speed up TypeScript compilation

One of the ways to speed up TypeScript compilation

Source data: Compiling a NodeJS project consumes almost 2GB of memory. It didn't bother me on my work computer, but on my laptop, I periodically encountered an unpleasant OutOfMemory error.

I began to investigate why such a small project was consuming so much memory. Quite quickly, I found an unknown to me before compiler option sc --listFiles on Google, which outputs the list of used files: there were 4500 of them! Too many. A quick look at the list showed that the mostly used files were from libraries: googleapis, @hubspot, @redis-client. I saved the list to a file with the command npx tsc --listFiles > and began measuring:

cat | grep redis | wc -l     491
cat | grep google | wc -l   907
cat | grep hubspot | wc -l   1682


The library provides methods to access the API of this service. There are many methods, but we only use 6 of them. I create a file hubspot.js with one line: export {Client} from "@hubspot/api-client"; and rewrite the imports to this file. Great, I got rid of one and a half thousand files! But without typing, you can make many mistakes. Therefore, I add a file hubspot.d.ts next to it, in which I specify types only for the necessary APIs.

import {IHttpOptions} from "@hubspot/api-client/lib/src/services/http/IHttpOptions";
import IConfiguration from "@hubspot/api-client/lib/src/configuration/IConfiguration";
import {PromisePipelinesApi} from "@hubspot/api-client/lib/codegen/crm/pipelines/types/PromiseAPI";
import {PromiseCoreApi} from "@hubspot/api-client/lib/codegen/crm/properties/types/PromiseAPI";
import {PromiseSearchApi} from "@hubspot/api-client/lib/codegen/crm/contacts/types/PromiseAPI";

export declare class Client {
    constructor(config?: IConfiguration);
    apiRequest(opts?: IHttpOptions): Promise<import("node-fetch").Response>;
    crm: {
        deals: {
            searchApi: PromiseSearchApi
        contacts: {
            searchApi: PromiseSearchApi
        properties: {
            coreApi: PromiseCoreApi;
        pipelines: {
            pipelinesApi: PromisePipelinesApi;


We check with the command npx tsc --listFiles | grep hubspot | wc -l and get the result of 112. Good enough.


Google also provides APIs to their services, which, I think, are much more than those of Hubspot. But Google's library is more thought-out and allows importing each service separately:


Instead of:

import { google } from 'googleapis';

We need to write:

import { cloudresourcemanager } from 'googleapis/build/src/apis/cloudresourcemanager'

Bundle size reduction

This is a well-known way to reduce bundle size, but on the backend, people usually don't bother with it, while on the frontend, a bundler can remove unused files from the bundle. Checking: npx tsc --listFiles | grep google | wc -l gives us 288.


Almost 500 files, wow! I probably don't know a lot about the incredible capabilities of this database. I started to study typing in @redis/client and quickly got confused because it's so sophisticated. I could take another library, but I don't know what pitfalls it will bring. Instead, I just copied the typing of the used methods and simplified it a bit, removing the ability to use Buffer instead of string:

export type RedisClientType = {
    on(type: 'error', cb: (err: Error) => void | any);
    on(type: 'end', cb: (err: any) => void | any);
    connect(): Promise<void>;
    set(key: string, value: string | number, options?: SetOptions): Promise<boolean>;
    del(keys: string | Array<string>): Promise<void>;
    get(key: string): Promise<string>;
    publish(channel: string, message: string): Promise<void>;
    subscribe: (channels: string | Array<string>, listener:  (message: string) => unknown) => Promise<void>;

export declare function createClient(config: {
    url: string;
    password: string;
}): RedisClientType;

declare type MaximumOneOf<T, K extends keyof T = keyof T> = K extends keyof T ? {
    [P in K]?: T[K];
} & Partial<Record<Exclude<keyof T, K>, never>> : never;
declare type SetTTL = MaximumOneOf<{
    EX: number;
    PX: number;
    EXAT: number;
    PXAT: number;
    KEEPTTL: true;
declare type SetGuards = MaximumOneOf<{
    NX: true;
    XX: true;
interface SetCommonOptions {
    GET?: true;
export declare type SetOptions = SetTTL & SetGuards & SetCommonOptions;


The number of used files has been reduced from 4576 to 1397, memory consumption has halved to 1GB, and compilation time on the laptop has been significantly reduced. The changes affected only 24 files in the project, so the code review will be simple.

Some libraries allow you to import separate parts - use this even if bundle size is not important to you. For other libraries, separate types can be used to speed up compilation. Unfortunately, there are still popular libraries where typing is so bad that it's better not to have it. When choosing a library, it's worth paying attention to this.

The main advantage I see is the ability not to worry about OutOfMemory on the laptop, and let all machines do a little less work, I hope they will spare me for this during the uprising.

In case you have found a mistake in the text, please send a message to the author by selecting the mistake and pressing Ctrl-Enter.

Comments (0)

    No comments yet

You must be logged in to comment.

Sign In / Sign Up

  • 5 Ways To Speed Up Software Development

    Growing companies in software development are always looking for ways to push their business forward. One thing they try to avoid is anything that could slow th...

    Alex · 02 February · 57 · 2