Demystifying Typescript + Webpack + Babel

typescript-webpack-babel.png

I got into web-development around 2008. Those were simpler times. All you had to do was to write some basic HTML / javascript and then use your favorite FTP client to upload the files to your hosting companies cPanel. Times have certainly changed and web-development today has become one of the most rapidly evolving ecosystems.

In this article we will start with a TypeScript file and end up with some complied javascript. It seems simple enough but there are some really interesting things that need to happen in order for us to get our javascript. There are plenty of “boilerplate” GitHub repositories that accomplish this task so my goal isn’t to provide the code (though you can certainly copy-and-paste to your heart’s content).

The goal of this article is to make you less intimidated by these developer tools. You might’ve used them in the past, but I’ll try to add some clarity about what is really going on. Let’s begin…

Set up your folder #

Let’s first set up a folder where we’ll be doing our experimentation. Use the following shell commands.

mkdir typescript-demo # Make the folder
cd typescript-demo # Change directories
npm init -y # Init npm

TypeScript – Why would javascript need types? #

Why use TypeScript in the first place? Doesn’t vanilla javascript already offer everything you’d need to get up and running for frontend development? Yes it certainly does, but TypeScript adds a helpful feature to javascript: types. Vanilla javascript is not a typed language. That means that you can mix and match variables with different types without any consequences. TypeScript adds types to javascript and aims to prevent run-time errors by telling you about type conflicts during development. This can help prevent many awkward and preventable bugs.

Consider the following javascript:

const a = 5;
const b = '10'; // Oops this is a string not a number
transferToBank(a+b);

Here a+b becomes the string concatenation of a and b which is “510”. As a programmer when you wrote transferToBank(a+b) you likely wanted to add two numbers the result of which should be 5+10=15. Though this is a simple toy demo, it demonstrates how javascript is an extremely forgiving language and as a consequence of that it can yield some dangerous bugs.

Let’s consider the following TypeScript:

let a, b: number;
a = 5;
b = '10';
transferToBank(a + b);

You can see the above code in the TypeScript Playground. When it comes time to convert this TypeScript to regular javascript we are met with the following compile time error: Type '"10"' is not assignable to type 'number'.. Thanks to TypeScript we just prevented our banking code from losing a lot of money. 😉

It is important to realize that javascript ≠ TypeScript. Instead, javascript ⊂ TypeScript. Since browsers can not understand TypeScript, we’ll need something (like a compiler) to convert our TypeScript code into javascript that our browser can run. This is where Babel comes in.

Babel – A javascript compiler? Really? #

Babel is a neat little developer tool that lets us use tomorrow’s javascript today. Beyond that, Babel is able to digest TypeScript (.ts) or React (.jsx) files and output browser ready javascript. Let’s begin by trying to convert some basic TypeScript into javascript that we can run.

Installing Babel

Run the following command and I’ll explain what each of these installs is right bellow:

npm i -D @babel/core @babel/cli @babel/preset-env 
npm i -D typescript @babel/preset-typescript

What are these packages

Writing our TypeScript

Create a file called index.ts and place the following TypeScript inside it:

const twitter: string = "shahzebdev";
const days: number = 3;
console.log(`My twitter is @${twitter} and it took me ${days} days to write this article`);

Compiling our TypeScript

Now that we’ve written some TypeScript we need to get Babel to transform it into regular javascript.

Create a new file called .babelrc. This file basically tells Babel what features and configuration we want it to use. Let’s start with a basic .babelrc file:

{
  "presets": ["@babel/preset-typescript"]
}

Now run the following:

npx babel index.ts

(Side note about the npx command: The npx command allows you to run a node package executable. If you use npm install to install packages, you often have to run executables that are located inside the /node_modules folder. Instead of running $ ./node_modules/.bin/babel index.ts we can simply run npx babel index.ts. These two things are functionally equivalent. OK now back to the regularly scheduled programming…)

On your console you should see the following javascript output:

const twitter = "shahzebdev";
const days = 3;
console.log(`My twitter is @${twitter} and it took me ${days} days to write this article`);

🎉 Yay! We have successfully converted our TypeScript into javascript that our browser can easily run. In most cases though, we don’t want to print the javascript to the browser; we want to output a .js file from our TypeScript. Run the following command:

npx babel index.ts -o index.js

Now if we cat index.js you should see that the javascript has been outputted to a file.

Type checking

It is important that we get warnings (or errors) about any type errors that we might’ve written in our code. We can use the previously installed typescript package to accomplish this. Run the following:

npx tsc src/*.tsc

The console will now print out any warnings or errors specific to TypeScript. You can also include the -w flag in the command to watch those files.

If all you wanted was a quick way to convert TypeScript to javascript, then you don’t need to read any further. However, continue on to learn about the tricks Webpack enables us to do.

Webpack – Where does this fit into the picture? #

Let’s say you want to use TypeScript for web-development. Your setup might look something like this:

  1. Write TypeScript files
  2. TypeScript is automatically converted into javascript
  3. The web-browser you are testing your code in automatically refreshes

This is a pretty common pipeline. Webpack is a bundler and task runner which is capable of executing all kinds of pipelines. For instance you can tell it to parse your .scss files and uglify your javascript along with many other tasks. Though there are other similar products such as Gulp or Grunt, Webpack seems to be the industry standard today.

It is common practice to have a src folder where we will write our TypeScript and a dist folder (short for distribution) which will contain the generated output code. Create the folders and move the index.ts file into the src folder so that your folder structure looks like this:

├── dist
├── package-lock.json
├── package.json
├── src
     └── index.ts

Now let’s install the necessary Webpack dependencies.

npm i -D webpack webpack-cli
npm i -D babel-loader

Much like how Babel required a .babelrc configuration file, Webpack requires its own webpack.config.js. Create the file and populate it with the following contents (I’ll explain what is going on down bellow).

const path = require("path");

const DEV = true;

module.exports = {
  entry: "./src/index.ts",
  module: {
    rules: [
      {
        test: /\.ts?$/,
        exclude: /node_modules/,
        loaders: ["babel-loader"]
      }
    ]
  },
  output: {
    filename: "main.js",
    path: path.resolve(__dirname, "dist")
  },
  mode: DEV ? "development" : "production"
};

Let’s walk through what all this means line-by-line:

Oh boy, that was a lot of reading on your part. Now for the fun part – testing out Webpack. Run the following command:

npx webpack

After a successful run, you should see the following as terminal output:

Hash: 7dfc820753a6c38c2956
Version: webpack 4.41.2
Time: 1151ms
Built at: 11/26/2019 10:37:17 PM
  Asset      Size  Chunks             Chunk Names
main.js  3.81 KiB    main  [emitted]  main
Entrypoint main = main.js
[./src/index.ts] 41 bytes {main} [built]

Now if you try looking at /dist/main.js it is just going to look like a bunch of automatically generated javascript (which is exactly what it is). Let’s create a basic index.html file inside the /dist folder to test out our generated output.

<html>
    <head>
        <title>Webpack test</title>
    </head>
    <body>
        <script src="./main.js"></script>
    </body>
</html>

If we open up the index.html file inside a browser and look at the console, then we should see our console.log comment coming in. If you see this then congrats! 🎆 You have successfully configured Webpack to convert TypeScript into javascript.

Watching is key

Right now we have to run the npx webpack command every time we want Webpack to do the TypeScript conversion. Instead we can tell Webpack to watch the folder and automatically run if it notices any changes. Let’s modify our webpack.config.js file by adding the following to the module.exports.

  watch: true,
  watchOptions: {
    ignored: /node_modules/
  },

Now we only need to run npx webpack once and webpack will automatically re-run the TypeScript conversion if it detects the files in the source tree change.

A static web-server

Wouldn’t it be convenient if a static web-server served our development files on localhost and allowed for auto-refreshing of the webpage every time our code changed? Well Webpack can do that for us. Let’s install the webpack-dev-server.

npm i -D webpack-dev-server

After the dependency is installed, modify your webpack.config.js and add the following config options for the webpack server:

  devServer: {
    contentBase: path.join(__dirname, "dist"),
    compress: true,
    port: 9000,
    watchOptions: {
      ignored: [
        path.resolve(__dirname, "dist"),
        path.resolve(__dirname, "node_modules")
      ]
    }
  },

This devServer config is telling Webpack to serve static content from the /dist folder on port 9000 while ignoring to watch files in the /dist and /node_modules folder. Remember, we only need to watch the TypeScript files located in the /src folder.

Important: After this change, we will no longer run npx webpack. Instead, we will run npx webpack-dev-server. Run this command and you should see some output to the console including the main.js being output as the entry point. At this point you can visit http://localhost:9000/ and you should be able to see the console.log message in the developer tools window. I recommend editing the index.ts TypeScript file, hitting save, and noticing the page auto-reload your new javascript. This is the power of Webpack.

A bonus puzzle piece: Prettier #

Spaces or tabs?

Honestly, who cares. There is no reason to be dogmatic about programming style when modern day linters and automatic formatters can take care of all the heavy lifting for us.

This makes me think: wouldn’t it be cool if Webpack could automatically prettify the TypeScript in our /src folder at compile time? Well guess what – it can absolutely do that. Start by installing Prettier:

npm i -D prettier prettier-webpack-plugin@1

Now modify the webpack.config.js to include the Prettier plugin:

const path = require("path");
const PrettierPlugin = require("prettier-webpack-plugin");

const DEV = true;

module.exports = {
  entry: "./src/index.ts",
  module: {
    rules: [
      {
        test: /\.ts?$/,
        exclude: /node_modules/,
        loaders: ["babel-loader"]
      }
    ]
  },
  devServer: {
    contentBase: path.join(__dirname, "dist"),
    compress: true,
    port: 9000,
    watchOptions: {
      ignored: [
        path.resolve(__dirname, "dist"),
        path.resolve(__dirname, "node_modules")
      ]
    }
  },
  watch: false, // No need to watch when we are using the webpack-server
  watchOptions: {
    ignored: /node_modules/
  },
  plugins: [new PrettierPlugin()], // Added the Prettier plugin
  output: {
    filename: "main.js",
    path: path.resolve(__dirname, "dist")
  },
  mode: DEV ? "development" : "production"
};

Test out the new configuration by writing some really poorly formatted TypeScript in your index.ts file and then run npx webpack-dev-server. This should automatically format your TypeScript code using the Prettier default configuration.

Was it all worth it? (and other concluding thoughts) #

tl;dr: we understood the key selling point of TypeScript, converted our TypeScript to javascript using Babel, and made a real-world web-development pipeline using Webpack.

So was it all worth it? Are we in a much better place in the land of web-development than we were in 2008? Well, maybe. I do think that modern web-development can be quite overwhelming (especially for beginners). But I think if a little bit of time is taken to understand these tools, then the benefit provided by them becomes much more clear.

Sure, setting up these config files can feel like a headache but chances are you’re only ever going to write them once for a project. In return, these tools will provide you with cutting edge javascript features and a productive development environment with time saving features.

At times I am nostalgic for the past. Simpler days when all I needed was a simple .js file to get up and running. I have to remind myself that I can still use bare vanilla javascript if I want. Evolving with ones tools and gaining new knowledge is simply part of the process.

Thank you. #

I spent a few days writing this article. If you liked it or found it helpful then I would love to hear from you. My twitter is @shahzebdev.

Thanks to my brother Salaar for proofreading.

 
4
Kudos
 
4
Kudos

Now read this

A look at DynamoDB Key’s.

Today, coming primarily from a MySQL background, I realized that choosing the right DynamoDB data structure requires some thinking. Here are some things I wish had been clearer before I started: The situation: In an Express.js... Continue →