Webpack, React, Typescript, React Hot Loader
Paul Allies
Posted on July 26, 2020
So in this post I show how to configure your next react project to use React Typescript and Webpack.
Initialise Project
Create project folder, npm init, git init and open project in vs code.
mkdir myproject && cd myproject && yarn init -y && git init && code .
Create an appropriate .gitignore file
node_modules
dist
Install Webpack Dev Dependencies and Configure Webpack
To run webpack bundler we need the webpack tools
yarn add -D webpack webpack-cli
Let's add a script in our package.json
{
"name": "myproject",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "webpack"
},
"devDependencies": {
"webpack": "^4.44.0",
"webpack-cli": "^3.3.12"
}
}
When you run "yarn build", there will be an error message:
ERROR in Entry module not found: Error: Can't resolve './src'
Let's create an index.js in a "src" folder
src/index.js
console.log("Hello");
Let's run "yarn build" and see that a "dist/main.js" was created. Webpack without config looks for a "src/index.js" file and compiles to a "dist/main.js". To further control the configuration of webpack we need to create a webpack.config.js
webpack.config.js
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
}
}
Create Home page
To run the bundle.js file within the browser, we need an index.html page. Create one in the src folder: "src/index.html"
src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
Note: We've purposely left out the src reference. We'll use webpack to inject the compiled bundle.js the file. To do this install the HtmlWebpackPlugin. Webpack plugins are utilities used after compilation.
yarn add -D html-webpack-plugin
Update the webpack.config.js file to include the plugin
webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js"
},
plugins: [new HtmlWebpackPlugin({
template: "src/index.html",
hash: true, // This is useful for cache busting
filename: '../index.html'
})]
}
After build you'll now notice that a "dist/index.html" was created which includes the link to the bundle.js file
dist/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="bundle.js?d645258de977f9a9c7c4"></script></body>
</html>
Let's configure our app for react.
Install React
yarn add react react-dom
Let's change our "src/index.js":
src/index.js
import React from "react";
import { render } from "react-dom";
import App from "./App";
const root = document.getElementById("root");
render(<App />, root);
src/App.js
import React from "react";
const App = () => {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
const interval = setInterval(() => {
setCount((count) => count + 1);
}, 500);
return () => clearInterval(interval);
}, []);
return <h2>Count: {count}</h2>;
};
export default App;
If you build again you'll get an error:
ERROR in ./src/index.js 6:16
Module parse failed: Unexpected token (6:16)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|
| const rootEl = document.getElementById('root');
> ReactDOM.render(<App />, rootEl);
For webpack to compile react we use Babel. So install the babel dependencies
yarn add -D @babel/core @babel/preset-env @babel/preset-react babel-loader
To use babel we need a config file ".babelrc.js"
module.exports = {
presets: ["@babel/preset-react", "@babel/preset-env"]
}
Here we inform babel to use the env preset and the react preset.
The @babel /preset-env is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s). This both makes your life easier and JavaScript bundles smaller!
Once again we need to update the webpack.config.js file
webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require("path");
module.exports = {
entry: "./src/index.js",
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
]
},
output: {
filename: "bundle.js"
},
plugins: [new HtmlWebpackPlugin({
template: "src/index.html",
hash: true, // This is useful for cache busting
filename: '../index.html'
})]
}
After running build we now can run the application from dist.
Install Typescript
yarn add -D typescript @types/react @types/react-dom awesome-typescript-loader
To use typescript we need a tsconfig.json file. Generate on with
tsc --init
Edit the tsconfig.json to allow typescript to handle react jsx. Change "jsx": "preserve" to "jsx": "react"
tsconfig.json
{
"compilerOptions": {
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
"strict": true /* Enable all strict type-checking options. */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}
Update the webpack.config.js file
webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require("path");
module.exports = {
mode: "development",
devtool: "source-map",
entry: './src/index.tsx',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.(ts|tsx)?$/,
loader: "awesome-typescript-loader",
exclude: /node_modules/
},
]
},
resolve: {
extensions: ['.ts', '.js', '.json', ".tsx"]
},
output: {
filename: "bundle.js"
},
plugins: [new HtmlWebpackPlugin({
template: "src/index.html",
hash: true, // This is useful for cache busting
filename: 'index.html'
})]
}
Rename all react files to *.tsx extension and build app again. Update App.tsx with typescript code
import React from "react";
const App = () => {
const [count, setCount] = React.useState<number>(0);
React.useEffect(() => {
const interval = setInterval(() => {
setCount((count) => count + 1);
}, 500);
return () => clearInterval(interval);
}, []);
function displayCount(message: string): string {
return message;
}
return <h2>{displayCount(`Count: ${count}`)}</h2>;
};
export default App;
Lastly setup a dev environment to hot reload our changes as we update our components during coding.
Install web dev server and react-hot-load
yarn add react-hot-load
yarn add -D webpack-dev-server
Update webpack.config.js for web-dev-server and hot reload
webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require("path");
module.exports = {
mode: "development",
devtool: "source-map",
entry: './src/index.tsx',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.(ts|tsx)?$/,
loader: "awesome-typescript-loader",
exclude: /node_modules/
},
]
},
resolve: {
extensions: ['.ts', '.js', '.json', ".tsx"]
},
output: {
filename: "bundle.js"
},
devServer: {
port: 4000,
open: true,
hot: true
},
plugins: [new HtmlWebpackPlugin({
template: "src/index.html",
hash: true, // This is useful for cache busting
filename: 'index.html'
})]
}
Update your App.tsx file for hot reload
src/App.js
import { hot } from "react-hot-loader";
import React from "react";
const App = () => {
const [count, setCount] = React.useState<number>(0);
React.useEffect(() => {
const interval = setInterval(() => {
setCount((count) => count + 1);
}, 500);
return () => clearInterval(interval);
}, []);
function displayCount(message: string): string {
return message;
}
return <h2>{displayCount(`Testing Count: ${count}`)}</h2>;
};
export default hot(module)(App);
Lastly to run the web dev server in dev, add the a script
package.json
...
"scripts": {
"build": "webpack",
"dev": "webpack-dev-server"
},
...
Run "yarn dev", update some code in App.tsx and watch your changes hot reload without losing state.
Cheers for now!
Posted on July 26, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.