Rules to Better TypeScript

​​

Hold on a second! How would you like to view this content?
Just the title! A brief blurb! Gimme everything!
  1. Do you avoid using “any”?

    TypeScript’s any keyword is a blessing and a curse.  It is a type that can be anything, where every possible property and method exists and also returns any. It can be casted to and from anything and is how you tell the compiler to get out of your way.

    However, it’s easy to use it as a crutch, and as a result, miss out on handy intellisense, refactoring support and compile-time safety – the main benefits of TypeScript!

    Aim to use any in the same way that you use the dynamic keyword in C# - that is, sparingly, and with careful consideration.

    any-bad.png
    Figure: Bad example – I can pass anything into this method, so I get bad output at run time (“undefined undefined”)
    any-good.png
    Figure: Good example – using types means I get errors and intellisense support
  2. Do you describe types sparsely?

    This comes down to personal preference, but there are only a few times when you must define a type in TypeScript, for example:

    1. When initializing a variable with an ambiguous value (eg. null)
    2. Function parameters​

    Of course, there are also times when you may want to be more explicit – you may want to have an interface as a function return value instead of the class, for example.​

    The rest of the time, rely on TypeScript to infer the type for you.

    describe.png
    Figure: Except for the input parameter, TypeScript can infer all the types for this function​
  3. Do you follow good Object-Oriented design patterns?

    ​TypeScript gives us a reasonably full-featured object-oriented system, and we should use it as such. Following the SOLID and DRY​ principles are encouraged.​​​​​​​​

    Write code that you’d be proud to see in C#, because there are no longer any excuses.

  4. Do you have good TypeScript configuration?

    TypeScript is a powerful language that transpiles to JavaScript, and provides much desired type-safety and IDE refactoring support.  But without good configuration, a lot of the benefits can be lost.

    Use tsconfig.json

    Putting a “tsconfig.json” file in your project tells the typescript compiler where the root of your project is, and provides a centralized place to configure the compiler.  This config is read by IDEs and the compiler and can be utilised by the build scripts to ensure configuration is consistent.

    goodtypescriptconfig1.png
    Figure: A tsconfig.json file with great configuration

    Disable implicit “any”

    The primary benefit of TypeScript is type-safety, and attempting to escape from the type-safety should be a conscientious decision by the developer.  So ensure that noImplicitAny is true, and keep your code type-aware and able to be refactored.

    Exclude external files

    By default, the compiler will compile everything ending in .ts.  This means things inside node_modules and even typings will be parsed and included.  Ensure you exclude these files to reduce your compile time and, more importantly, reduce your reported errors. 

    Don’t rely on TypeScript for bundling

    TypeScript should compile in-place, and a single file input should produce a single file output.  This reduces compile time, and puts bundling in the hands of a system that knows more about the modules – the module loader. 

    Hide generated files from your IDE

    Files generated from typescript get in the way – you don’t want to scroll through .d.ts, .js and .js.map files all the time.  So hide them in the IDE.
    In VSCode this can be done via the “files.exclude” key in the settings.json file.  For a shared experience across the team, check this file into source control.

    goodtypescriptconfig2.png
    Figure: VSCode settings.json file that hides generated files
  5. Do you only export what is necessary?

    Each file in TypeScript is a module, and each module can export whatever members it wants.  However, if you export everything, you run the risk of having to increment major versions (when using semantic versioning), or having your module used in unintended ways.

    ​​Only export the types necessary to reduce your API surface.  Often, this means exporting interfaces over implementations.

  6. Do you use package managers appropriately?

    Advice like this can be a minefield, and is constantly in flux, but there are some rules-of-thumb that can make life simpler.

    package1.jpg
    Figure: Default ASP.NET Core project is package management done wrong
    package2.jpg
    Figure: Project using good package management

    Bower is dead

    package3.jpg
    Figure: Bower is dead

    File-New Project in Visual Studio comes with bower packages, and there are a lot of old blog posts that  recommend bower for client side libraries, but bower is dead. Angular2 is discouraging its use, and npm has all the same packages, and more. Prefer npm over bower, even for client-side​ dependencies.

    Use a single package manager

    For client side libraries, avoid mixing npm, jspm and manually copy/pasted files. Life will be simpler if you stick with just 1. 

    Right now, this is npm, but watch out for jspm, which shows a lot of promise if you can get past the steep learning curve.

    Be careful with versioning

    Theoretically, everything in npm is using semantic versioning.  In practice, people aren’t that diligent, so it pays to be careful with your version numbers. It’s not rare for versions to disappear from npm, or for a build-servers internet connection to be flaky.  If these issues are happening, consider using npm-shrinkwrap  to lockdown dependency versions.

    Track your dev dependencies and dependencies separately​

    All package managers distinguish between those used for development, and those used for the application.  Use this feature – it will save you time.