Setting up a React + TypeScript + SASS + Webpack and Babel project in 6 Steps#

warning
This article was published a long time ago. Its content may be outdated and no longer reflects the current state of technology.

In this tutorial I want to show you how to set up a bulletproof, IE 11 safe, frontend project using TypeScript, React and Sass.

Note: I will use yarn instead of npm in this tutorial. If you never heard of it, you should check it out! 😁

Initializing a blank project#

Let's start by creating an empty folder and initializing a blank node project.

mkdir awesome-app
cd awesome-app
yarn init

Which will result in the following file structure:

.
└── package.json

Installing dependencies#

Before we can start to write some of the sweet react code, we first have to set up our transpiration environment. In this case with Webpack and Babel.

But why do I need to transpile my code?

Well, there are three reasons, why we need to transpile our code:

  1. Browsers don’t support TypeScript (at the moment), so we need to transpile it to JavaScript.
  2. Not all browsers support modern JavaScript (especially IE). We need to transpile it into an “older version”, so that common browsers can interpret it correctly.
  3. We need to transpile our SASS to CSS, because (you might guessed it already) nearly no browser supports SASS.

Let’s install all the dependencies we need:

yarn add core-js react react-dom regenerator-runtime
yarn add -D @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript @types/react @types/react-dom babel-loader css-loader node-sass sass-loader source-map-loader style-loader webpack webpack-cli typescript ts-loader

Event though it looks like a lot of dependencies, we only have 4 of them which are going to be built into our application, the rest are development dependencies that are only required by our development environment.

If you are interested, here is a short description of every package:

react // React framework
react-dom // React's DOM framework
core-js // Polyfills for a lot of ECMAScript methods
regenerator-runtime // Polyfill for runtime
/* Webpack */
webpack
webpack-cli
/* Babel core and presets to transpile TypeScript */
@babel/core
@babel/preset-env
@babel/preset-react
@babel/preset-typescript
/* Type Definitions for React */
@types/react
@types/react-dom babel-loader
/* For transpiling SASS */
style-loader
css-loader
node-sass
sass-loader
/* For better debugging */
source-map-loader
/* TypeScript core package and Webpack loader */
ts-loader
typescript

Setting up Webpack#

Now that we got all our dependencies installed it is time to create our webpack.config.js file.

Webpack will manage our loaders. Loaders are software components (npm packages) that will change/transpile/extract or analyze our code.

Our webpack.config.js will look like this:

module.exports = {
mode: "development",
watch: true,
entry: "./src/index.tsx",
output: {
filename: "bundle.js",
path: __dirname + "/dist"
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"]
},
devtool: "source-map",
module: {
rules: [
{ test: /\.scss$/, use: [ "style-loader", "css-loader", "sass-loader" ] },
{ test: /\.tsx?$/, loader: "babel-loader" },
{ test: /\.tsx?$/, loader: "ts-loader" },
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
]
}
};

I think most of the options are self-explanatory, but I want to quickly talk about four of them.

watch states that Webpack will automatically recompile if it noticed a file change aka. cmd + s.

devtool: "source-map" will add source maps, which will make your developer life a lot easier, since they provide TypeScript sources to your browser devtools, so class names, interfaces and so on don’t get lost. Important: Remove this option and line 17 for a production build.

["style-loader", "css-loader", "sass"-loader"] is a loader chain. Which basically means that the output (return) from the right loader will be used as the input by the next loader and so on (right to left!). This loader chain will extract SASS from the SASS files, transpile it to CSS and finally to JavaScript.

babel-loader will transpile TypeScript to JavaScript (ES2015 in our case) based on the .babelrc file which we will configure in the next step.

Transpiling is needed because browsers can’t understand TypeScript at the moment, in addition, we need to produce “old” JavaScript to get a good browser coverage.

Setting up Babel#

Setting up babel is a piece of cake! Babel will help us transpiling our TypeScript to the right JavaScript standard. This is our .babelrc config file:

{
"presets": [
"@babel/react",
"@babel/typescript",
[
"@babel/env",
{
"modules": false,
"targets": {
"chrome": "58",
"ie": "11"
}
}
]
],
}

We basically tell Babel to use it’s React and Typescript presets and to transpile to a minimum version of IE 11 and Chrome 58.

Setting up TypeScript#

Hold tight, we are very close to writing our first line in TypeScript, but before we can do that, we need to create a file called tsconfig.json. This file will hold (you might guessed it already) some TypeScript configurations:

{
"compilerOptions": {
"outDir": "dist/",
"noImplicitAny": true,
"module": "commonjs",
"target": "es2015",
"jsx": "react"
},
"include": [
"./src/**/*"
]
}

I also think that most of the options are clear, but I want to shortly explain two of them:

noImplicitAny is a flag, which will force you to declare typed function arguments. Even though it is not required, I encourage you to use it, because otherwise you can get confused by not knowing which type your function argument was.

jsx states that we want to write web component syntax in our JavaScript, or in our case TypeScript files.

Our project structure is now looking like this:

.
├── .babelrc
├── node_modules
├── package.json
├── tsconfig.json
├── webpack.config.js
└── yarn.lock

Let’s write some React!#

Congratulations, you made it! You can start writing React now 🥳

Since this article isn’t about writing React, I won’t go into details about my React code, but I want to show you how to use SASS with your React components.

Let’s create an index.html in your project root:

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Awesome App</title>
</head>
<body>
<div id="app"></div>
<script src="dist/bundle.js"></script>
</body>
</html>

Now let’s add some Code!

src/components/Banner/Banner.scss
@import "../../styles/colors";
.banner {
background-color: $moccasin;
box-sizing: border-box;
padding: 2rem;
text-align: center;
&__text {
color: $dark-red;
}
}
src/components/Banner/Banner.tsx
import * as React from "react";
import "./Banner.scss";
interface IProps {
name: string;
}
export default class Banner extends React.Component<IProps> {
public render() {
return (
<div className="banner">
<span className="banner__text">
Hello {this.props.name}!
</span>
</div>
);
}
}
src/index.tsx
import "core-js";
import "regenerator-runtime/runtime";
import * as React from "react";
import * as ReactDOM from "react-dom";
import Banner from "./components/Banner/Banner";
import "./styles/global.scss";
ReactDOM.render(
<div>
<Banner name="Max" />
</div>,
document.getElementById("app"),
);
src/styles/colors.scss
$moccasin: moccasin;
$dark-red: darkRed;
src/styles/global.scss
html {
font-size: 10px;
}
body {
font-size: 1.8rem;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

To make my life easier I added an Webpack alias to my package.json

...
"scripts": {
"webpack:dev": "npx webpack --config webpack.config.js"
}
...

Now you can run yarn run webpack:dev and open the index.html to see the working app. 🤩

App screenshot

I hope you found this tutorial helpful, If you want to take a deeper dive into the code, I will link you the GitHub repo right down below.

Cheers, Max.

You can find the GitHub repository for this article here.