Setting up React, Babel, Webpack, & TypeScript Without Create-React-App👾

rinconcamilo

Camilo Rincon

Posted on September 11, 2022

Setting up React, Babel, Webpack, & TypeScript Without Create-React-App👾

Today, we will be looking at how to integrate TypeScript into our existing, manually set-up project of React, Babel, and Webpack that has been done without the create-react-app command.

This article is the second part of a series on how to set up a front-end environment. Of course, this article will have a quick, and brief set-up guide. If you would like a more in-depth guide on how to create a project without create-react-app, visit the previous article here.

Versions Being Used:

  • React 18
  • Babel 7
  • Webpack 5
  • TypeScript 4

Quick Set-Up

In order to quickly initialize the project, run the following commands:

a. Create package.json file

npm init -y
Enter fullscreen mode Exit fullscreen mode

b. Install React, Babel, Webpack, and other needed packages

npm install --save-dev --save-exact react react-dom @babel/core @babel/preset-env @babel/preset-react babel-loader webpack webpack-cli webpack-dev-server html-webpack-plugin style-loader css-loader file-loader
Enter fullscreen mode Exit fullscreen mode

c. Make sure to create a file called .babelrc and paste the following code:

{
  "presets": [
    "@babel/preset-env",
    [
      "@babel/preset-react",
      {
        "runtime": "automatic"
      }
    ]
  ]
}
Enter fullscreen mode Exit fullscreen mode

d. Finally, at the root of the project, create a webpack.config.js file and write the following:

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

module.exports = {
  entry: "./src/index.js",
  mode: "development",
  output: {
    filename: "bundle.[fullhash].js",
    path: path.resolve(__dirname, "dist"),
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
    }),
  ],
  resolve: {
    modules: [__dirname, "src", "node_modules"],
    extensions: ["*", ".js", ".jsx", ".tsx", ".ts"],
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: ["babel-loader"]
      },
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: ["style-loader", "css-loader"]
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        exclude: /node_modules/,
        use: ["file-loader"]
      }, 
    ],
  },
};
Enter fullscreen mode Exit fullscreen mode

e. Create a folder called "src" with the following files:

  • App.js
const App = () => (
  <div>
    <h1>Hello, World!</h1>
  </div>
);

export default App;
Enter fullscreen mode Exit fullscreen mode
  • index.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

const rootElement = document.querySelector('#root');
if (!rootElement) throw new Error('Failed to find the root element');
const root = createRoot(rootElement);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode
  • index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>React</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

f. Creating start and build scripts
Now, in the package.json file, add these lines to your scripts section:

"start": "webpack serve --open",
"build": "webpack --config webpack.config.js --mode production"
Enter fullscreen mode Exit fullscreen mode

Now that we have all of the above set-up, this quick set-up should be able to run. If you have any questions or would like to learn more on what is going on above, visit my previous article here.

Implementing TypeScript

Background Information

Before installing and configuring TypeScript into our application, let us first understand how we plan on integrating and using both TypeScript and Babel together.

Babel: We will be utilizing Babel for transpiling our code only. Furthermore, since we are using Babel 7 we do not need to install the ts-loader since Babel 7 understands TypeScript with the babel-loader; and the additional plugins & preset we will install and set in the .babelrc will add the remaining functionality.

TypeScript: We will be utilizing TypeScript for type-checking only!

Having these two technologies focus on one task we are able to clearly define the different roles each will accomplish. Making the configuration side of things more maintainable. This means that we will have to type-check then transpile the code.

1. Installing TypeScript

In order to install TypeScript, we have to install the following packages:

npm install --save-dev --save-exact typescript @types/react @types/react-dom @babel/preset-typescript
Enter fullscreen mode Exit fullscreen mode

2. Modifying Webpack.config.js

We need to make some modifications to the Webpack configuration file. The first modification is pointing the entry path to a .tsx file like so:

entry: "./src/index.js",
Enter fullscreen mode Exit fullscreen mode

to

entry: "./src/index.tsx",
Enter fullscreen mode Exit fullscreen mode

Then, we need change the rule using the babel-loader under module to:

      {
        test: /\.(js|ts)x?$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
Enter fullscreen mode Exit fullscreen mode

This change is to help webpack resolve:

  • plain JavaScript - js
  • JavaScript with React enabled - jsx
  • TypeScript with JavaScript - ts
  • TypeScript with React enabled - tsx

The final webpack.config.js should look like the following:

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

module.exports = {
  entry: "./src/index.tsx",
  mode: "development",
  output: {
    filename: "bundle.[fullhash].js",
    path: path.resolve(__dirname, "dist"),
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
    }),
  ],
  resolve: {
    modules: [__dirname, "src", "node_modules"],
    extensions: ["*", ".js", ".jsx", ".tsx", ".ts"],
  },
  module: {
    rules: [
      {
        test: /\.(js|ts)x?$/,
        exclude: /node_modules/,
        use: ["babel-loader"]
      },
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: ["style-loader", "css-loader"]
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        exclude: /node_modules/,
        use: ["file-loader"]
      }, 
    ],
  },
};
Enter fullscreen mode Exit fullscreen mode

3. Changing File Names

Time to change all the file extensions from .js to .tsx. This would be two of the following files:

  • App.tsx
  • index.tsx

With these file names changed, we can now move on to configure TypeScript.

4. Configuring TypeScript

In order to configure TypeScript, we need to create a tsconfig.json file at the root of our project.

Background Information: Within this file, we can implement different rules to run at compile time. You can learn more about tsconfig.json here.

Some of the basic fields are called the "Root Fields":

  • Files
    • List the files to include in the program. An error will occur if any of the listed files are not found.
  • Extends
    • A string that contains a path to another configuration file to inherit.
  • Include
    • Will specify an array of filenames or patterns that will be included within the program.
  • Exclude
    • Will specify an array of filenames or patterns to exclude from the program.
  • References
    • A way to structure TypeScript programs into smaller pieces. This will help improve the following: build, editor interaction times, enforce logical separation, etc.

Setting Up tsconfig.json: Since we are only using TypeScript as a type-checker, we make sure the following configurations are set in order for both Babel to work properly:

{
  "compilerOptions": {
    // Emit Configuration
    "noEmit": true,

    // Modules Configuration
    "module": "ES2022",

    // Language and Environment Configuration
    "target": "ES2022",
    "jsx": "react-jsx",

    // Interop Constraints Configuration
    "esModuleInterop": true,
  }
...
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • noEmit: true
    • Is set to true because we do not want to emit any files since Babel will be in charge of transpiling our code.
  • module: ES2022 and target: ES2022
    • We are are not transpiling our code using TypeScript. This means that we do not need to worry about browser compatibility in this area of configuration.
  • esModuleInterop: true
    • Is set in order to have default import in TypeScript since this is allowed within Babel.
  • jsx: react-jsx
    • Will allow for React's JSX transform.

Here is my tsconfig.json that I use, if you have any suggestions please drop a comment!

{
  "compilerOptions": {
    // Emit Configuration
    "noEmit": true,

    // Type Checking Configuration
    "allowUnreachableCode": false,
    "allowUnusedLabels": false,
    "exactOptionalPropertyTypes": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitThis": true,
    "noPropertyAccessFromIndexSignature": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitOverride": true,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    // Strict Rules - v4.8
    "alwaysStrict": true,
    "strictBindCallApply": true,
    "strictFunctionTypes": true,
    "strictNullChecks": true,
    "strictPropertyInitialization": true,
    "useUnknownInCatchVariables": true,

    // Modules Configuration
    "baseUrl": "./",
    "module": "ES2022",
    "moduleResolution": "node",

    // Language and Environment Configuration
    "target": "ES2022",
    "jsx": "react-jsx",

    // JavaScript Support Configuration
    "allowJs": true,
    "checkJs": true,

    // Interop Constraints Configuration
    "esModuleInterop": true,
    "isolatedModules": true
  },
  "include": ["src/**/**/*"],
  "exclude": ["node_modules"]
}
Enter fullscreen mode Exit fullscreen mode

Here are some explanation over a few configurations:

  • compilerOptions
    • Covers how the language should work.
    • If upgrading TypeScript, the strict rule will add any additional rules automatically. For personal reasons, I like to keep the "magic" of things happening automatically to a minimum which is why I excluded from using the strict: true.
  • outDir
    • Where the compiler will emit compiled JavaScript files.
    • Since we are using TypeScript as just a type-checker we will not emit any files, hence the noEmit: true.
  • module
    • Defines output module resolution system that will be used. This is based on what your JavaScript engine is capable of parsing.
  • target
    • Changes which JavaScript features are down-leveled and which are left intact.

You can find a complete list of all configurations for tsconfig.json here.

5. Configuring TypeScript with Babel

In order to configure TypeScript with Babel, we have to go to the .babelrc file and make sure it contains the following:

{
  "presets": [
    "@babel/preset-env",
    [
      "@babel/preset-react",
      {
        "runtime": "automatic"
      }
    ],
    "@babel/preset-typescript"
  ]
}
Enter fullscreen mode Exit fullscreen mode

6. Run Type Checking

Now in the package.json file, add this line to your scripts section:

"tscheck": "tsc"
Enter fullscreen mode Exit fullscreen mode

This will type check all of the files that were selected in the tsconfig.json file.

View everything together in Github.

If you found this helpful or just enjoyed reading the article, consider grabbing me a cup of coffee.

💖 💪 🙅 🚩
rinconcamilo
Camilo Rincon

Posted on September 11, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related