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
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
c. Make sure to create a file called .babelrc
and paste the following code:
{
"presets": [
"@babel/preset-env",
[
"@babel/preset-react",
{
"runtime": "automatic"
}
]
]
}
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"]
},
],
},
};
e. Create a folder called "src" with the following files:
- App.js
const App = () => (
<div>
<h1>Hello, World!</h1>
</div>
);
export default App;
- 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>
);
- 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>
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"
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
-
typescript
- Adds optional types to JavaScript.
-
@types/react
- Type definitions for react.
-
@types/react-dom
- Type definitions for react-dom.
-
@babel/preset-typescript
- Allows Babel to understand typescript. Once we have these packages installed, we need to begin modifying our configurations and file names.
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",
to
entry: "./src/index.tsx",
Then, we need change the rule using the babel-loader
under module
to:
{
test: /\.(js|ts)x?$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
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"]
},
],
},
};
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,
}
...
}
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
andtarget: 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"]
}
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 thestrict: 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"
]
}
6. Run Type Checking
Now in the package.json file, add this line to your scripts section:
"tscheck": "tsc"
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.
Top comments (1)
Cool, Thanks!