DEV Community

Jacopo
Jacopo

Posted on • Updated on

React + Typescript + Webpack

Updates

2021/09/27: Specify html-webpack-plugin version (4.5.2) because the latest versions don't work with listed dependencies in this project.

Ensure you have Node.js installed on your system.

Step #01

Setup πŸ—‚

First things first, create a directory and init the project

mkdir react_typescript_webpack
cd react_typescript_webpack
npm init -y
Enter fullscreen mode Exit fullscreen mode

Create a second directory inside the first one and call it src, here we are going to place our source code

mkdir src
touch src/index.tsx
Enter fullscreen mode Exit fullscreen mode

Create also an entry point for the application called index.tsx.

Dependencies β›“

A dependency is another code, usually in the form of a library, that we need to have in order to create a project.
Roughly speaking there are two types of dependency:

  • Runtime dependencies: that one we need at runtime, usually we interact with them in the code.
  • Build dependencies: all that tools that we use to build the project. Most of the time we only feed configurations to this type of dependency.

I use npm as a package manager so copy-paste this code in the package.json

{
  "name": "react_typescript_webpack",
  "version": "1.0.0",
  "description": "Build UI that scales",
  "scripts": {
    "build": "webpack --config webpack.config.js"
  },
  "keywords": [
    "react",
    "typescript",
    "webpack"
  ],
  "author": "bonta.jacopo@gmail.com",
  "license": "ISC",
  "dependencies": {
    "react": "^16.12.0",
    "react-dom": "^16.12.0"
  },
  "devDependencies": {
    "@types/react": "^16.8.24",
    "@types/react-dom": "^16.0.5",
    "@types/webpack": "4.1.4",
    "ts-loader": "^6.2.1",
    "typescript": "^3.4.3",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10"
  }
}
Enter fullscreen mode Exit fullscreen mode

This file keeps a list of the dependencies of the project.

We set our runtime dependencies (react and react-dom) in the "dependencies" object while all the build dependencies in the "devDependencies" object.

Note on @types: they are declaration files used for describing the shape of an existing JavaScript object to TypeScript.

Note also the "build" script that runs the webpack command using a specific configuration file.

To install the dependencies run the command

npm install
Enter fullscreen mode Exit fullscreen mode

Step #02

Configuration files 🧾

TypeScript 🎒

Let's starts with TypeScript and create a tsconfig.json file in the project root directory:

touch tsconfig.json
Enter fullscreen mode Exit fullscreen mode
{
  "compilerOptions": {
    "outDir": "./dist",
    "target": "es5",
    "module": "es6",
    "jsx": "react",
    "noImplicitAny": true,
    "allowSyntheticDefaultImports": true
  }
}
Enter fullscreen mode Exit fullscreen mode

This file tells the transpiler how to transpile TypeScript code to JavaScript.

Webpack πŸ“¦

Webpack puts your code with all its dependencies in a single file called bundle.
It does this by looking at your code and resolving all dependencies starting from the entry point (index.tsx) and recursively in any file for each import statement encountered.

Create a configuration file for webpack

touch webpack.config.js
Enter fullscreen mode Exit fullscreen mode
const path = require('path');

module.exports = {
    mode: 'none',
    entry: {
        app: path.join(__dirname, 'src', 'index.tsx')
    },
    target: 'web',
    resolve: {
        extensions: ['.ts', '.tsx', '.js']
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: '/node_modules/'
            }
        ],
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    }
}
Enter fullscreen mode Exit fullscreen mode

The ts-loader tells webpack to run typescript for each .ts or .tsx file so it is converted to JavaScript before it gets into the final bundle.

Step #03

The code πŸ“

Now, the fun part!

App πŸ€–

All starts from index.tsx, the app entry point.
The only place where we really need react-dom.

import React from 'react';
import ReactDOM from 'react-dom';

import App from './App';

ReactDOM.render(<App />, document.querySelector('#root'));
Enter fullscreen mode Exit fullscreen mode

Basically we are saying Render the App component in an HTML Element with id = 'root'.

We don't have already the App component so let's create one.
Always in your src directory

touch App.tsx
Enter fullscreen mode Exit fullscreen mode
import React from 'react';

export default function App()
{
    return <h1>Hello, world!</h1>
}
Enter fullscreen mode Exit fullscreen mode

A simple components that display "Hello, world!" inside an H1 tag.

At this point we have almost done, we can run the build command and see the bundle app.js appear in a directory called dist.

npm run build
Enter fullscreen mode Exit fullscreen mode

Now we have a file in plain JavaScript of our app that can be included inside a web page.

Webpage 🌎

Let's create it in the src direcotry:

touch index.html
Enter fullscreen mode Exit fullscreen mode
<!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>React Typescript Webpack</title>
</head>
<body>
    <!-- React app root element -->
    <div id="root"></div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Note that no script was referenced. This is because this file act as a template for every build using a plugin for webpack.

html-webpack-plugin πŸ”Œ

A plugin for webpack that automatically includes an html page with a reference to the bundle in the output folder.

npm install --save-dev html-webpack-plugin@4.5.2
Enter fullscreen mode Exit fullscreen mode

and edit the webpack.config.js like this

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    mode: 'none',
    entry: {
        app: path.join(__dirname, 'src', 'index.tsx')
    },
    target: 'web',
    resolve: {
        extensions: ['.ts', '.tsx', '.js']
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: '/node_modules/'
            }
        ],
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(__dirname, 'src', 'index.html')
        })
    ]
}
Enter fullscreen mode Exit fullscreen mode

Done!

Now hit

npm run build
Enter fullscreen mode Exit fullscreen mode

and you are ready to go.

You only need a server to serve your web app through the dist directory.
A dirty but fast solution would be

npm i -g live-server
cd dist
live-server
Enter fullscreen mode Exit fullscreen mode

It is nice because it auto-reloads on every build.

Remember, you have an entire src directory for writing and structure your dream app ✨ For me is useful to put all the app components inside a directory called components to keep things clear.

I hope you enjoyed!


References

Top comments (5)

Collapse
 
devagency profile image
devagency.codes • Edited

Big thanks for this setup! I did notice that I get an error related to "webpack_modules" saying something like, "process is not defined"

Looks like you have to either use dotenv or webpack.DefinePlugin in the webpack.config.js file to make it compile and display on the webpage without error.

const dotenv = require('dotenv');
dotenv.config();

Enter fullscreen mode Exit fullscreen mode

be sure to run yarn add or npm install dotenv, and create an .env file (add to .gitignore)!

Collapse
 
jacopobonta profile image
Jacopo • Edited

Thanks for pointing out but probably you were using a different version of the dependencies, is this possible? I just tried to recreate the project and I'm able to print the process.env variable.

I have had a similar issue in other projects that were using webpack@5 and this answer on StackOverflow provides yet another solution.

You are right anyway, since Webpack bundles everything together you need a way to pass your environment variables (that lives where you build the app) to your .js bundle (that will run in a separate context, usually a browser somewhere on the internet) and for this, you need the DefinePlugin (or better, the EnvironmentPlugin) plus a variable loader such dotenv - personally I prefer using env-cmd: you create the dotenv file and then you can edit your build command to something like this build: env-cmd webpack --config webpack.config.js.

Hope this can help future readers :)

p.s.: Recreating the project I have noticed that the latest version of html-webpack-plugin doesn't work with the listed dependencies so I have updated the post to install a previous version. Without your comment probably a lot of people would have gone crazy about this xD

Collapse
 
wimdenherder profile image
wimdenherder • Edited

Running node version 17 returns error

When building (npm run build) I got this error "Error: error:0308010C:digital envelope routines::unsupported"
and fixed it by running node version 16 (instead of 17):

npm view node versions
// look up a version starting with 16, for example 16.11.0
nvm install 16.11.0
nvm use 16.11.0
npm run build
// now it works

Collapse
 
wimdenherder profile image
wimdenherder

If you use pnpm to install the package.json (first step in tutorial), you'll also have to use pnpm to install the plugin, otherwise you get this error: npm ERR! Invalid package name ".pnpm": name cannot start with a period

Collapse
 
ricardorp profile image
Ricardo Romero

This solved all my questions. Now to setup webpack-dev-server