Getting Typescript 3.7 To Work With Webpack and ts-loader

At the time of writing this post, TypeScript 3.7 is in beta. Eventually, this post will become irrelevant. But, for the moment if you are trying to get TypeScript 3.7 Beta or any of the RC builds working with Webpack and ts-loader you might have encountered a bunch of red text in your console.

In my case, I had target: "esnext" set in my tsconfig.json file which the ts-loader plugin should read and set the appropriate settings. And yet, TypeScript 3.7 Beta was not working despite making sure everything was up to date.

It turns out at present, ts-loader does not seem to work with esnext as the target value (hopefully, this changes when TypeScript 3.7 is released). To get things working, all you need to do is change your target value in tsconfig.json to es2018 like this: "target": "es2018"

In my case, that fixed the issue and I could use the exciting new features TypeScript has to offer such as Nullish Coalescing and Optional Chaining. Happy days.

Mocking Default Imports In Jest With TypeScript

If you are writing tests using Jest and you use TypeScript, there is a good chance you have encountered an error along the lines of TypeError: defaultsDeep_1.default is not a function or TypeError: myClass.default is not a constructor when trying to test a file that is using a default import from a module.

You most likely have read countless StackOverflow questions, but none of the solutions will solve the issue. You’ve read the Jest documentation (which is quite extensive), but still no mention of mocking default module imports with TypeScript.

In my case, I had this error when trying to import a Lodash function defaultsDeep and another when importing the Input Mask module. My imports look like the following.

import defaultsDeep from 'lodash/defaultsDeep';
import Inputmask, { Options, Instance } from 'inputmask';

Inside of my test which will be testing this specific file, I use jest.mock to mock the specific modules and their implementations. The important thing to note here is I am returning default from within my mocks. This is because of how default imports are transpiled within TypeScript.

The Lodash mock is more simplistic:

jest.mock('lodash/defaultsDeep', () => {
  return {
    default: jest.fn()
  };

In the case of Input Mask, I needed to mock an instance which has a method on it. The usage in the actual file highlights what we want to achieve. The input mask plugin is newable, it then exposes a mask method which we supply with an element.

this.im = new Inputmask(options);
this.im.mask(element);

This is how we mock the above module and accommodate for the usage:

jest.mock('inputmask', () => {
  return {
    default: jest.fn().mockImplementation(() => {
      return {
        mask: jest.fn()
      };
    })
  };
});

The convenient thing about the solutions presented is they will work for all default imported modules. Have fun.

Module ES2015 and TypeScript 2.4 Dynamic Imports

Introduced in TypeScript 2.4 is support for the ECMAScript dynamic imports feature. If you didn’t see the announcement or read it properly, you’re probably here because you’re getting the following error.

In my case I use Webpack and I was trying to add in some dynamic import goodness and getting this error: Dynamic import cannot be used when targeting ECMAScript 2015 modules.

TypeScript 2.4 dynamic imports error

You’re probably thinking, this is crazy considering dynamic imports are an ECMAScript feature, not a TypeScript one. The tell is in the error, if your module is set to es2015 you’re targeting ES6 and dynamic imports are a feature not due for release until ECMAScript 2018.

Funnily enough, the TypeScript team did reveal this in their official announcement but if you’re like me, you missed it the first time and hit this issue.

The fix is simply setting the module value in your tsconfig.json file to esnext, like this: "module": "esnext". If you’re using Visual Studio Code, you might get a squiggly in your tsconfig.json file telling you it’s not a valid value, but ignore it because it is.

Configuring Git Pre Commit Hook And TSLint (automatically)

If you’re a TypeScript user and you’re reading this, then you’re using TSLint (most likely). Recently, a situation at work arose where even though TSLint warnings were being thrown in the editor as well as terminal output, some developers (tsk tsk) were still committing these warnings.

Naturally, a pre-commit Git hook is the right candidate for this. Being able to run TSLint to ensure that before a developer can even commit let alone push, only valid code conforming to the tslint.json file can be pushed.

This poses another problem. You can’t automatically add pre-commit hooks into the repository and have everyone automatically pull them down. This is for security reasons, could you imagine if someone committed a hook that deleted a bunch of files/folders?

If you’re using a task runner like Gulp or Grunt, then you can create a clever task that copies a file to the .git/hooks directory for you.

Firstly, let’s create a pre-commit hook. In the root of your application create a new folder called hooks and a new file called pre-commit (with no file extension):

#!/bin/bash

TSLINT="$(git rev-parse --show-toplevel)/node_modules/.bin/tslint"

for file in $(git diff --cached --name-only | grep -E '\.ts$')
do
        git show ":$file" | "$TSLINT" "$file"
        if [ $? -ne 0 ]; then
                exit 1
        fi
done

Git hooks are actually bash scripts and can be quite powerful. We are creating a path to TSLint in our local application (some Git clients like Github for Windows require this) and using that to call TSLint on our files.

Secondly, let’s create our task. I personally use Gulp, but you can easily adapt the following to any task runner:

var gulp = require('gulp');

gulp.task('install-pre-commit-hook', function() {
    gulp.src('hooks/pre-commit')
        .pipe(gulp.dest('.git/hooks'));
});

gulp.task('default', ['install-pre-commit-hook']);

Running gulp or gulp install-pre-commit-hook will copy over our pre-commit hook and put it into the .git/hooks directory. It is possible on Unix based operating systems that you need to adjust the file permissions using chmod which the fs module offers a method for, but possibly not needed.

Now, throw that task into your Npm build script and anyone else who has the latest changes will get the pre-commit hook every time the task runs. No more warnings from code written by others clogging up your terminal or editor.

Dealing With Tslint Errors/Warnings In Third Party Files

This might be a bit of an edge case for some, but recently I needed to use a third-party script in my Aurelia TypeScript application that wasn’t installable through Npm.

I could have carefully changed it to conform to my TSLint guidelines, but that would have been more effort than I wanted to spend. I just wanted to include the file in a vendor folder and then import it without worrying how it’s written, whether it uses single or double quotes or what indentation setting it uses.

Enter TSLint rule flags.

All you need to do is put /* tslint:disable */ at the top of your file and TSLint will ignore everything after that. You can also selectively ignore certain parts of your file by using /* tslint:disable */ and /* tslint:enable */ to turn parts of your file off and then back on.

You can use TSLint rule flags to ignore certain rules as well, for cases where the rules don’t apply.

Getting Visual Studio Code To Work With Next Versions of TypeScript

Recently whilst helping out a client with an Aurelia TypeScript project, I encountered a situation where the latest development version of TypeScript 2.0 was being used, but some of the newer features like filesGlob support were not being picked up. Module resolution and other things were also an issue.

Turns out you can configure Visual Studio Code to use a local version of TypeScript through a setting directive inside of a project settings file.

Firstly, make sure you have TypeScript installed in your project, I installed via npm install typescript@next -D which saves it as a development dependency.

The best way to make sure this works in your project is to create a folder (if it doesn’t already exist) called .vscode and inside there create a file (if it doesn’t already exist) called settings.json and put the following inside of the curly braces:

"typescript.tsdk": "./node_modules/typescript/lib"

This will then ensure that your Visual Studio Code editor uses your locally installed version of TypeScript opposed to an internal version of it which could be out of date.

String Enums In TypeScript

Sometimes you want to specify in your application multiple allowed values. For example, you might have a method which allows you to perform server requests and the only allowed values are; GET, POST, UPDATE, DELETE and PUT.

Ideally you want to prevent any developer from calling a method with an invalid value. Normally you would use an enum for this, most languages support the use of an enum which might be used for this purpose.

Unfortunately, in TypeScript, enums only support numeric values, not strings. Although, there is an issue here which is calling for it to be extended to strings and other types, like other languages such as C++ allow.

In an ideal world it would be nice to be able to do this in TypeScript:

enum stateEnums {
    "on",
    "off"
}

export class MyClass {
    private componentState: stateEnums;

    on() {
        this.componentState = "on";
    }

    off() {
        this.componentState = "off";
    }
}

Except, because we can’t use an enum in this way, we can use a type instead, the end result isn’t too different:

type stateEnums = "on" | "off";

export class MyClass {
    private componentState: stateEnums;

    on() {
        this.componentState = "on";
    }

    off() {
        this.componentState = "off";
    }
}

Not only that, but you can also add in a boolean and possibly other values into the mix as well. You are essentially creating a map of values that will be checked when the value utilising the type is changed. Akin to a “only allow these values and none other”

type stateEnums = "on" | "off" | boolean;

export class MyClass {
    private componentState: stateEnums;

    on() {
        this.componentState = "on";
    }

    off() {
        this.componentState = "off";
    }

    onTrue() {
        this.componentState = true;
    }

    offFalse() {
        this.componentState = false;
    }
}

Conclusion

You probably already knew the above, but if not, hopefully this helped you. I am quite knew to the TypeScript side myself, so if there is an even better way of achieving the above (perhaps changing how enums work), then feel free to leave a comment and enlighten us.