š
Whenever I start a new project, I owe to myself to learn something new from it, especially if that project is an easy one.
This time I will describe step by step how to start a react application using Webpack and Typescript. Weāre going to learn together.
Letās get started!
Javascript was introduced by Netscape Communications in 1995 as a weakly typed scripting language, with similar syntax with Java. Reason behind this decision was the addition of more features to the already existing HTML, something thatāll make it more reactive. Its purpose was intended to complement HTML and satisfy some immediate needs like easiness of use by web designers.
In 1996 Netscape Communications submitted JavaScript to ECMA International to create a standard specification for browsers to adopt and this led to ECMAScript as a language specification one year later.
Fast forward 20 years and big browsers now support version 5 of ECMAScript, ES5. Although ES continues to improve (ES9 in 2019), browsers are not always that fast in adopting new features and usually the aim of developers is to make sure they end up having ES5 code when the project is done.
TypeScript aims to be a strongly typed JavaScript. Its main aim is to instruct mature IDEās to flag common errors in the code and therefore help with debugging and development process. You can see the introductory video from 2012 here. Compile-time type checking is something that is still very useful in large projects.
Webpack is a āstatic module bundler for modern JavaScript applicationsā. Very simply put, it does two things:
index.js
bundle.js
Make sure youāve got npm installed, then go ahead and create a folder and inside it run:
npm init && mkdir src
This will initialise our npm project and also create a folder src
that will contain all our source code.
Folder src
will be our working folder, we will write code here and every file created here will be
processed in one way or another to generate the projectās final state.
At this point, we should make sure we configure our module bundler. Webpack uses loaders
as helpers to do its job.
We will install a few of them and I will explain what each of them does:
npm install --save-dev webpack webpack-cli typescript webpack-dev-server style-loader css-loader sass-loader ts-loader html-loader url-loader file-loader clean-webpack-plugin html-webpack-plugin
webpack
is the Webpack core librarywebpack-cli
is the cli executable for Webpacktypescript
is the Typescript core librarystyle-loader css-loader sass-loader
are style loaders, the first one knows how to inject
ts-loader
allows Webpack to understand and map / pack .ts or .tsx files written with Typescripthtml-loader
allows Webpack to manipulate .html filesurl-loader
allows Webpack to transform small size files into base64 URIs, this is useful for images and fontsfile-loader
is going to be used as a fallback to url-loader
when the files are larger than usual and this loader will make sure that when we use import
for images or files, these get transformed into proper URLs in the code and also get exported to the output folderclean-webpack-plugin
itās a Webpack plugin that will allow Webpack to clean the build folder before any subsequent buildhtml-webpack-plugin
is a nice Webpack plugin that will clone out index.html file from ./src folder and put it inside
the distribution folder after each buildI think weāve got all we need for now. Letās configure our project.
Iām going to split this section in 3 parts: Typescript configuration, Webpack configuration and NPM configuration.
Typescript reads its configuration from a file called tsconfig.json
. Below is a simple config, enough to start a project,
but if youāre curious you can see all options here.
{
"compilerOptions": {
"outDir": "./dist/",
"module": "es6",
"target": "es5",
"jsx": "react",
"strict": true,
"allowSyntheticDefaultImports": true
},
"include": [
"./src/**/*"
],
"exclude": [
"./node_modules"
]
}
In big lines, the configuration file tells TypeScript the following:
Webpack configuration file is webpack.config.js
, letās create this file and add the following code:
module.exports =(env) => {
return require(`./webpack.${env}.js`)
}
What the above code does is read the āenv cli parameter from webpack and require the necessary files based on it.
For example, if we run webpack --env dev
then itāll include webpack.dev.js
configuration file. Reason we do this is,
based on the environment we run the project on, we will have different bundling configuration files that will instruct
webpack to do different things.
Now, you might think that some things from one config file, might also be needed into the other. How do we respect the
DRY principle in this case ? Well, for this, we will use a package called webpack-merge
. Letās install it first:
npm install --save-dev webpack-merge
Now, we will create a file called webpack.common.js
which will include common configuration options that will be applied
throughout all other environment configurations, then weāll make sure we merge this file inside every environment
configuration file.
Hereās what we have in webpack.common.js
:
const path = require("path");
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: "./src/index.tsx",
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: 'ts-loader',
exclude: /(node_modules)/
},
{
test:/\.(s*)css$/,
use:['style-loader','css-loader', 'sass-loader']
},
{
test: /\.html$/,
use: 'html-loader'
},
{
test: /\.(png|jpg|ttf|)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192
}
}
]
}
]
},
resolve: { extensions: [".tsx", ".ts", ".js", ".jsx"] },
output: {
path: path.resolve(__dirname, "dist/"),
publicPath: "/",
filename: "bundle.js"
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'src/index.html'
})
]
};
In big lines, it does the following:
index.tsx
as an entry script to build the dependency mapts-loader
for transpiling (some kind of a fancy word for source-to-source compiling) all ts and tsx filesstyle-loader, css-loader, sass-loader
for css, scss importshtml-loade
for html files manipulation/dist/bundle.js
file that will be included in our src/index.html
In webpack.dev.js
we merge the common configuration and add some other configurations needed only for DEV:
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const webpack = require("webpack");
const path = require("path");
module.exports = merge(common, {
mode: "development",
devServer: {
contentBase: path.join(__dirname, "dist/"),
port: 3000,
publicPath: "http://localhost:3000/",
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
});
In addition to what common config does, this one only configures the webpack-dev-server to find index.html
inside
dist
folder and hot reload every change it detects in the code files.
The webpack.production.js
configuration file looks like this:
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: "production"
});
Ok, I think weāre done with Webpack, letās move on to the last bit, NPM configuration.
The hard part is over, we only need to create a few scripts in package.json so we can easily start a webpack dev server, build a dev environment and a production one. Under the āscriptsā key in your package.json, letās add the following lines:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack-dev-server --mode development --env dev",
"build:dev": "webpack --env dev",
"build:prod": "webpack --env production"
}
And there we have it, three commands to do the following:
npm start
will start our working dev server that will be served on http://localhost:3000npm run build:dev
will build a fully working development project inside dist
folderbpm run build:prod
will build a fully working production code inside the dist
folderAnd we still havenāt written any piece of code, have we ? Letās install React:
npm install --save react react-dom @types/react @types/react-dom
react
is the core React libraryreact-dom
allows us to hook react into our DOM and contains methods like render()
@types
packages are in fact declaration files; Typescript uses those to understand the structure of a given library
codebase, in this case the react packages; usually their extensions are ā.d.tsā and they also help in not misusing or
misunderstanding libraries and IDE auto completion; as a general rule, if youāre using a certain npm package and that
package doesnāt have the declarations already, @types/package-name
should be the other thing you needWe will work in the src
folder, therefore letās create a simple application there:
src/index.html
, letās add this code:<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>React Starter</title>
</head>
<body>
<div id="root"></div>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
</body>
src/index.tsx
, letās add:import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(<App />, document.getElementById("root"));
src/App.tsx
, letās add:import React, { Component} from "react";
class App extends Component{
render(){
return(
<div className="App">
<h1> Yo! </h1>
</div>
);
}
}
export default App;
Fire it up with npm start
, go to http://localhost:3000
and if you donāt see your app on your page then youāre lying
to me .
Ok, I know, this has been a long one, but hey, at least now weāre one step closer towards understanding all that magic behind projects like create-react-app and the like. Now, we could improve this, of course. For one, we miss uglifier for our js code, we havenāt used minifiers for our css code, but I will let you check it out in Webpack Guides. Also, Webpack configuration files are all over the place, we can structure them in a separate folder. Tests have not even been mentioned and that might be the topic for a full new article.
There is though one aspect that we should discuss, itās the elephant called Babel.