myHotTake

Tag: custom types

  • How Does typeRoots Optimize Your TypeScript Project?

    If you’ve enjoyed this story, feel free to like or share!


    Let me take you on a brief journey through a airport terminal. I’m a pilot named Captain TypeScript, and my task is to navigate the skies efficiently. In the world of TypeScript, the typeRoots option in the tsconfig.json file serves as my flight plan. It tells me where to find my passengers, the types, so I can ensure a smooth flight.

    Picture typeRoots as the specific gates in the airport where my passengers are waiting. Without a clear plan, I might end up wandering through the terminal, wasting time and resources trying to find my passengers. But with typeRoots, I have a precise map, guiding me directly to the gates where my essential passengers are waiting. This way, I can quickly gather all necessary types from specific folders, ensuring that I don’t pick up any unwanted stowaways from other random places in the airport.

    As I prepare for takeoff, I check my flight plan. It lists several gates like node_modules/@types. These are the default gates where most passengers (types) are located. But sometimes, I have special passengers waiting at private gates. By configuring typeRoots, I can include additional custom gates, ensuring that all my passengers are on board and ready for the journey.

    So, typeRoots is my navigational tool, helping me efficiently gather the right types for my TypeScript project without unnecessary detours. With a clear flight plan, I, Captain TypeScript, ensure that my journey is smooth and efficient, delivering a successful flight every time.


    I’ve successfully navigated the skies with my precise flight plan, and now it’s time to see how this looks in the real world of code. In our tsconfig.json file, the typeRoots option is like setting the gates at the airport. Here’s a simple example:

    {
      "compilerOptions": {
        "typeRoots": ["./custom_types", "./node_modules/@types"]
      }
    }

    In this setup, I’ve specified two gates: ./custom_types and ./node_modules/@types. By doing this, I’m telling TypeScript to look for type definitions in these two directories. The first one, ./custom_types, is like a private gate for special passengers—custom types I’ve defined for my project. The second, ./node_modules/@types, is a standard gate where third-party types can be found.

    Suppose I have a type definition for a custom library that I don’t want mixed in with the rest of my node_modules. I can place it in the ./custom_types directory:

    /custom_types
      |-- myLibrary
          |-- index.d.ts

    This separation ensures that my TypeScript compiler only picks up the types I explicitly want, avoiding any extra baggage from other sources.

    Key Takeaways

    • Purpose of typeRoots: It tells TypeScript where to find all its type definitions. By specifying custom directories, you can have better control over which types are included in your project.
    • Customizability: You can define multiple paths in typeRoots, allowing for both default and custom type definitions to coexist in harmony.
    • Efficiency: By narrowing down the search path for type definitions, you avoid potential conflicts and reduce the likelihood of mistakenly including unwanted types.
  • How to Handle Libraries Without TypeScript Definitions?

    If you enjoy this story, feel free to like or share it!


    I’m an explorer venturing into a mysterious forest. This forest is full of unknowns, much like a third-party library without TypeScript definitions. Now, I don’t have a detailed map of this forest, but I have a trusty compass and my instincts as my guide. In the world of JavaScript and TypeScript, this compass represents my understanding of JavaScript, while my instincts are the TypeScript skills I’ve honed over time.

    As I step into the forest, I encounter a river. This river is like a function from the library with no type definitions. I need to cross it, but without knowing its depth or current, I must proceed cautiously. Here, I fashion a makeshift bridge using sticks and vines, much like how I might create a custom TypeScript definition using a declaration file. This declaration file acts as a bridge, helping me safely navigate through the library’s functions and features.

    Occasionally, I meet fellow explorers who have journeyed through parts of this forest before. They share their own bridges and paths, akin to finding community-made TypeScript definitions online. These shared resources can be incredibly helpful, allowing me to traverse areas of the forest with more confidence and ease.

    Sometimes, I come across a particularly tricky part of the forest where my makeshift solutions are not enough. In these instances, I have to make educated guesses about the terrain, just as I might use the any type in TypeScript as a temporary fix until I can gather more information. It’s not ideal, but it allows me to keep moving forward.

    As I spend more time in the forest, I become more familiar with its quirks and secrets. Similarly, as I work more with a third-party library, I gradually refine my type definitions, enhancing my understanding and making future journeys smoother.

    So, dealing with third-party libraries without TypeScript definitions is a bit like exploring an uncharted forest. With patience, creativity, and the help of a community, I can navigate the unknown and make it a part of my well-traveled world. If you enjoyed this story, feel free to like or share it!


    The Makeshift Bridge: Creating Declaration Files

    In the forest, I crafted a bridge to cross a river. In JavaScript, this is like creating a TypeScript declaration file to provide type definitions for a third-party library. Here’s a simple example:

    Suppose I’m using a library called mysteriousLibrary that lacks TypeScript support. I can create a declaration file, mysteriousLibrary.d.ts, like so:

    declare module 'mysteriousLibrary' {
      export function exploreTerrain(area: string): boolean;
      export const adventureLevel: number;
    }

    This file acts as my makeshift bridge, allowing TypeScript to understand the types of the functions and variables within mysteriousLibrary.

    The Fellow Explorers: Community Definitions

    Sometimes, others have already mapped parts of the forest. Similarly, I might find community-contributed TypeScript definitions, often hosted on DefinitelyTyped, a large repository of community-maintained TypeScript type definitions.

    For example, if mysteriousLibrary had a community definition, I could simply install it:

    npm install @types/mysteriouslibrary

    This is akin to using a pre-built bridge from fellow explorers, saving time and effort.

    The Educated Guesses: Using the any Type

    Sometimes, the terrain is unknown, and I must proceed with caution. In TypeScript, this means using the any type when necessary, while aiming to refine it later.

    import { exploreTerrain } from 'mysteriousLibrary';
    
    const result: any = exploreTerrain('denseForest');

    Using any is like cautiously stepping into the unknown, but it’s a temporary measure until I can gather more information.

    Final Thoughts

    Navigating the unknown parts of a third-party library without TypeScript definitions is not unlike exploring a dense forest. With patience and resourcefulness, I can build makeshift solutions, leverage community contributions, and use temporary measures to continue my journey.

    Key Takeaways:

    1. Declaration Files: Create custom TypeScript declaration files to define types for libraries without them.
    2. Community Resources: Leverage community-contributed type definitions to save time and effort.
    3. Temporary Solutions: Use the any type as a temporary workaround, but aim to gather more information for better type safety.
  • How Do Mapped Types Enhance Flexibility in TypeScript?

    If you enjoy this story and find it helpful, feel free to give it a like or share it with others who might appreciate it too!


    I’m a costume designer, and I specialize in crafting custom outfits for a wide variety of clients. Each client comes to me with a specific list of requirements, like the type of fabric, color, and style they want for their outfit. Now, in order to keep my workflow efficient, I’ve developed a special technique that allows me to quickly convert these requirements into the perfect pattern for each client.

    In the world of TypeScript, this special technique is similar to what we call “mapped types.” Picture a mapped type as my tailoring pattern that can take the requirements for any outfit and transform them into a ready-to-sew pattern. It’s like I have a universal pattern template, and all I need to do is feed in the specific details for each client. The magic happens when I use a “key” from a client’s requirements to map out the exact pattern pieces I need.

    For instance, suppose a client wants a jacket with specific sleeve length and pocket style. I take my base pattern and, using the mapped type, I adjust the sleeve length and pocket style according to the keys provided in the client’s list. This way, I don’t have to reinvent the pattern for each client; I simply adapt my universal pattern using their specific instructions.

    This technique not only saves me time but also ensures that each outfit is precisely tailored to fit the client’s needs. In TypeScript, mapped types allow me to do the same thing with my code, taking an object type and creating a new type by transforming its properties according to a specific set of rules. It’s my way of ensuring that every piece of code fits just right, just like how every outfit I create is perfectly tailored to each client.

    So, as I continue crafting custom outfits in my marketplace, I lean on the power of mapped types to keep my tailoring process seamless and adaptable, ensuring every client walks away with a perfect fit.


    I have a base pattern for outfits, defined as a TypeScript interface:

    interface OutfitRequirements {
      sleeveLength: string;
      pocketStyle: string;
      fabricType: string;
    }

    This is like a checklist for each client’s requirements. Now, suppose I want to create a new pattern that marks each requirement as optional for some clients who want a more flexible outfit design. In my tailoring shop, this is akin to having a base pattern where I can choose to include or exclude certain features. Here’s how I can use a mapped type to achieve this:

    type FlexibleOutfit = {
      [Key in keyof OutfitRequirements]?: OutfitRequirements[Key];
    };

    In this code snippet, FlexibleOutfit is a mapped type that takes each key from OutfitRequirements and makes it optional using the ? modifier. This is like saying, “For this particular client, I might or might not include the sleeves or pockets, depending on their preference.”

    Now, let’s say I want to ensure that all the properties are read-only, so once the outfit is designed, it can’t be altered. I can create a mapped type for that too:

    type ReadOnlyOutfit = {
      readonly [Key in keyof OutfitRequirements]: OutfitRequirements[Key];
    };

    With ReadOnlyOutfit, every property is locked in place, just like a completed outfit that’s ready for delivery and can’t be modified.

    Key Takeaways:

    1. Mapped Types as Tailoring Patterns: Mapped types allow me to transform existing types in TypeScript, similar to how I adapt my base patterns for different clients in my tailor shop.
    2. Customization and Flexibility: By using mapped types, I can create flexible and adaptable type definitions, such as optional or read-only properties, to suit different coding needs.
    3. Efficiency and Precision: Just as my tailoring process becomes more efficient and precise with mapped types, so does my coding, as it reduces redundancy and enhances type safety.