Setting up Typescript project with Webpack
Tags: webpack, typescript, tutorial, beginners
Published: 2020-08-16 15:05:38

In the **Typescript 101** we have talked a lot about advantages of Typescript, discussed some use-cases, practices and tricks. Therefore, today I would like to show how you can start a new Typescript project from scratch. If you are using a front-end framework, everything happens inside of the black box,  where webpack performs its magic. In this post, I would like to expose webpack configuration and demonstrate how you can spin up your next Typescript project in a couple of simple steps.

So what is the webpack? Webpack is a module bundler, a tool that gives you freedom of structuring your code however you like and takes an overhead of producing static assets, that you will be serving over the web. Webpack comes with a developed ecosystem of plugins that allow extending webpack with any additional utilities.

#### Table of content
1. [Project setup](#projectSetup)
2. [HTML templates](#HTMLtemplates)
3. [Webpack development server](#devServer)
4. [Using JS libraries](#jsLibs)
5. [Project styles](#styles)

### <a name="projectSetup"></a>Project setup

To get started, let's initialize an empty npm project: `npm init`.
Here are some minimal dependencies we will need:
```txt
npm i webpack webpack-cli typescript ts-loader -D
```

At the end of this part, you must end up with the following directory structure:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/s84ttc9xoopk3rdr606h.png)

We will start by configuring Typescript for that project. All you need to do is to include `tsconfig.json` in the project root with the following properties:

```json
{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "allowJs": false,
    "sourceMap": true
  }
}
```

Now let's continue with setting up webpack. We will place  `webpack.config.js` in the project root and add minimal configurations to get started:

```jsx
const path = require('path');

module.exports = {
  mode: "production",
  entry: path.resolve(__dirname, './src/index.ts'),
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

```

Some remarks here:
1. Default webpack mode is `production`, but it's always good to be explicit. In `production` mode webpack will minify your code
2. `entry` allows to configure entry points for your project, it can be a powerful tool should you decide to have code-splitting
3. The array of `rules` defines what actions bundler should undertake when it encounters a file of a specific type. For now, we only expect typescript files in the project, so we add only one rule, which will make use of `ts-loader`, a plugin, capable of parsing `ts` files.
4. In the `output` object one might provide a resulting file name and the directory where bundle assets will be added

That's it. Let's add an npm script to the `package.json` 

```json
...
  "scripts": {
    "build": "webpack"
  },
...
```

write some code to `/src/index.ts`

```jsx
window.addEventListener("load", () => {
  const header = document.createElement("h1");
  header.innerText = "Webpack❤️TS"

  const body = document.querySelector("body");
  body.appendChild(header);
})
```

and build the project with: `npm run build`.

You will see that once your code is compiled, a new `dist` folder is added to the project root.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/tel24s0yz1uya2txnw2v.png)

Congrats! You've built your first Typescript project! 🚀🚀

### <a name="HTMLtemplates"></a>HTML templates

It's great to have a compiled script, now you can reference it in your `html` file, and you are good to go, right? No! 😀 Luckily webpack offers a better way to deal with that setup.

Firstly, we will install some additional dependencies: `npm i html-webpack-plugin clean-webpack-plugin -D`. We will also need a new `templates` folder where we will place our `html` template:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nrkjwqly557z8fmnutz4.png)

`/src/templates/index.html`:
```html
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Webpack❤️TS</title>
</head>
<body>

</body>
</html>
```

In the `webpack.config.js` we will use newly installed plugins:
1. `html-webpack-plugin` takes care of including your bundle to the html template
2. `clean-webpack-plugin` is a simple plugin which cleans up output folder on every build.

```jsx
...
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require("clean-webpack-plugin");

module.exports = {
  ...
  output: {
    filename: 'bundle.[hash].js', // <- ensure unique bundle name
    path: path.resolve(__dirname, 'dist'),
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "./src/templates/index.html")
    })
  ]
};
```

You might notice that besides adding the plugins, we have changed the output file name. `[hash]` is a webpack keyword, that will add a hash to your bundle name. A unique name is essential to ensure that you always see up to date version in your browser window and not something cached.

Let's build again: `npm run build`. If you check out the `dist` folder now, you will see that webpack has produced a bundle with a hash in the file name as well as `index.html` which already includes you bundle in the script tag `src`.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/qjkhba2rox61dy57r2o4.png)

`/dist/index.html`:

```html
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Webpack❤️TS</title></head>
<body>
<script src="bundle.ff6beaa8d270fd22434a.js"></script>
</body>
</html>
```

That should be already sufficient for opening the html file in the browser, but can we do even better? 🤔
Sure we can!

### <a name="devServer"></a>Webpack development server
Webpack development server allows us to serve assets (such a surprise!) and enables live reload, whenever you change something in your code.

`npm i webpack-development-server -D`

While using webpack, it is common to split your configurations into several parts:
1. `webpack.common.js` - will include configurations that are shared between multiple setups. In our case `dev` vs `prod`
2. `webpack.dev.js` will include dev-specific configs such as webpack dev server, source map, etc
3. `webpack.prod.js` is responsible for configuring production build. It usually includes some optimization settings and ensures that the output is "production-ready".

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0lo306y4w5m9lca9uca2.png)

```jsx
// webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require("clean-webpack-plugin");

module.exports = {
  entry: path.resolve(__dirname, './src/index.ts'),
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  output: {
    filename: 'bundle.[hash].js',
    path: path.resolve(__dirname, 'dist'),
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "./src/templates/index.html")
    })
  ]
};

```

```jsx
// webpack.dev.js
const webpackCommon = require('./webpack.common');
const path = require('path');

module.exports = {
  ...webpackCommon,
  devtool: "inline-source-map",
  mode: "development",
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    port: 3000
  }
};
```

```jsx
// webpack.prod.js
const webpackCommon = require('./webpack.common');

module.exports = {
  ...webpackCommon,
  mode: "production",
};
```

We should also update npm script, to make sure, that a proper configuration file is used.

```json
// package.json
...
  "scripts": {
    "start": "webpack-dev-server --config webpack.dev.js",
    "build": "webpack --config webpack.prod.js"
  },
...
```

Run `npm start` - your project will be available on http://localhost:3000

### <a name="jsLibs"></a>Using JS libraries

Now, as we have all set up, using a third-party library is very straightforward.
Let's include lodash as a dependency: 
```txt
npm i lodash -S && npm i @types/lodash -D
```

And use it in the code:

```jsx
import * as _ from "lodash";

window.addEventListener("load", () => {
    const header = document.createElement("h1");
    header.innerText = "Webpack❤️TS"

    const body = document.querySelector("body");
    body.appendChild(header);

    const food = [{id: 0, name: "pizza"}, {id: 1, name: "sushi"}]

    const foodElement = document.createElement("h2")
    foodElement.innerText = _.find(food, {id: 0})?.name;

    body.appendChild(foodElement);
})
```

Easy, right? 😇

### <a name="styles"></a>Project styles
Probably the only missing piece now is the styles which we might want to include to our project. With webpack, you can use any style processor, but for the sake of the example, I will stick to the plain CSS.
As always, we will need some webpack plugins to handle the styles:
```txt
npm i css-loader mini-css-extract-plugin -D
```

Let's add them to the `webpack.common.js`:

```jsx
// webpack.common.js
...
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  ...
  module: {
    rules: [
      ...
      {
        test: /\.css$/i,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader'
        ],
      },
    ],
  },
  plugins: [
    ...
    new MiniCssExtractPlugin({filename: "styles.[hash].css"})
  ]
};
```

As webpack is a js-centric library, everything that we want to be included in the final bundle needs to be import in the `.ts` file:

```jsx
// /src/styles.css
h2 {
    color: #1976d2;
}

// /src/index.ts
import "./styles.css";
import * as _ from "lodash";
...

```

Styles will be automatically included in the html file.
Run `npm start` for development and `npm run build` to create a production build! Good luck!

You can check out full code in my [github repository](https://github.com/GlebIrovich/typescript101-webpack-starter)!

If you liked my post, please spread a word and [follow me on Twitter](https://twitter.com/gleb_irovich) 🚀 for more exciting content about web development.