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.

Disable Webpack 4 Native JSON Loader

Now that Webpack 4 is out, it supports a plethora of new things and features, one of those is the native handling of JSON. In theory, this is great, but in a particular application I am working with which is JSON heavy, the native JSON loading caused a trove of errors.


Obligatory photo of some code

Fortunately, inside of your module.rules section of webpack.config.js you can disable the native JSON loader and use the json-loader package which seems to be more reliable at present.

Make sure you yarn add json-loader -D (or Npm equivalent) and then add the following rule. This will tell Webpack to use the json-loader plugin.

{
    test: /\.json$/,
    loader: 'json-loader',
    type: 'javascript/auto'
},

This got me out of a pickle until I can work out what the real issue is (maybe some invalid JSON). Whatever it is, this fixes it.

The Curious Case of Webpack 4 Production Mode and Function.name

I have migrated over to Webpack 4 for Built With Aurelia and I am using the fantastic Aurelia Store plugin.

During development, everything worked fine, but when I would do a production build the state management aspect would fall apart, complaining about something to do with the function that notifies Redux Dev Tools about the change (what the action name and state value was).

It took a lot of trial and error (a solid day) for me to work out what was going on.

My logic which registers my actions looked like this:

this.store.registerAction(loadProjects.name, loadProjects);

This takes the function name (a string) and uses it as the name of the action being registered. This was to save time creating separate constants or manual string names, it made perfect sense.

It turns out when you enable mode: 'production' in Webpack 4, it uglifies your code using UglifyJS. Unless told otherwise, Uglify will mangle your class and function names but it will not populate the name property on the prototype itself with the newly mangled name, as someone else encountered in this issue.

Inspecting the minified production source code, I could see the action name was provided to the send method was an empty string. This resulted in not only the app breaking but also red errors in the console complaining about the postMessage API and something else ambiguous.

My first instinct was to disable function name mangling and it would have fixed it. However, I just ended up abandoning the idea of using the Function.name value as the name of the action and reverting to using constants as the action name.

const LOAD_PROJECTS = 'loadProjects';

this.store.registerAction(LOAD_PROJECTS, loadProjects);

This is the kind of pattern you see promoted in other state management solutions and it kind of makes sense why you would do it this way.

If you use Function.name in your application regardless of whether or not it’s an Aurelia app, you will encounter this issue with Webpack 4 production mode enabled or even in Webpack 3 with UglifyJS and no explicit mangle settings.

Polyfilling Promises Using Bluebird in Webpack 2

Surprisingly, whilst determining the best way to polyfill promises in a Webpack build and using Bluebird I came across such mixed results.

A lot of the posts out there don’t mention Bluebird at all, opting for es6-promise when it has been proven Bluebird has such great performance, it’s even faster than native promises in some browsers.

A lot of the results I found were from 2015 and a couple in 2016. As you know, front-end development moves rapidly and 2015 is 100 years in front-end land.

I wanted to specifically know the best way to polyfill promises using Bluebird in Webpack 2.

The solution I am proposing might sound familiar, using imports-loader and exports-loader in combination with the Provide plugin to override native API’s.

Another solution is to conditionally load polyfills depending on what the browser supports, but that is for another time.

The approach we are taking won’t pollute the global namespace, whenever a reference to a module is found Webpack will automatically rewrite its reference to be the supplied module.

As you can see, we are polyfilling Promise, which means if you open up your application in a browser without native Promise support, typing Promise into the console will result in undefined. However, any code in your app referencing Promise will get the polyfill version.

Install the dependencies

npm install --save-dev exports-loader imports-loader
npm install --save bluebird

Configuring your Webpack

Once you have your required dependencies, inside of the plugins section of your webpack.config.js file:

plugins: [
    new webpack.ProvidePlugin({
        Promise: "imports-loader?this=>global!exports-loader?global.Promise!bluebird"
    }),
]

Now, whenever your application requests to use a Promise, you’ll get Bluebird instead. Swell.

Bonus: jQuery and whatwg-fetch polyfill

If you are using jQuery in your application, then using the ProvidePlugin you can also shim certain variables to modules.

plugins: [
    new webpack.ProvidePlugin({
        Promise: "imports-loader?this=>global!exports-loader?global.Promise!bluebird",
        fetch: "imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch",
        $: "jquery",
        jQuery: "jquery"
    }),
]