次の方法で共有


Starting a fresh Progressive Web App project from scratch with ReactJs,Redux ,Typescript ,TDD and VSCode Debugging

If you are going to start a new React Project from scratch,You may  want to have many feature sets to start with  e.g. Progressive Web Apps support , Typescript with a build system, how to do TDD ,How to debug,How to architect the codebase and the list goes on.So this basic skeleton will make sure everything is in order from start.

Easiest thing to do is start with a boiler plate project which will give you with lot of options out of the box.Some good ones are

For example,following are some of the feature-sets I want in my new project

In this we are going to start a new project from scratch with Create-React-App and then add features one by one. Create-React-App is ideal just hides/expose enough details at the same time let's you add these features one by one and once you want more advanced features,you can eject and get a full power . For completed app, Please find the source code here

Starting up

Creating a new project with Create-react-app is explained  here   but the gist of it are couple of commands

 npm install -g create-react-app

create-react-app my-app

This will create your project called my-app but do not create your project just yet, we will do that in our next section .

Under the hood create-react-app sets up a fully functional, offline-first Progressive Web App .

Progressive Web Apps (PWAs) are web applications that use advanced modern web technologies that behave like native app e.g. load instantly, regardless of the network state or respond quickly to user interactions 

PWA with TypeScript support

JavaScript's lack of static types is a major concern especially if you are used to  Java/.NET. Static type checking helps you iron out a lot of bugs at compile time and also make your app from running into any Undefined errors :)  .In this case we are going to use TypeScript 

and alsol get following features out of the box

  • PWA capabilities
  • a project with React and TypeScript
  • linting with TSLint
  • testing with Jest and Enzyme, and
  • state management with Redux

We will use create-react-app  but we will have to pass the react-scripts-ts as scripts-version argument. What this will do is add react-scripts-ts as development dependency and your underlying build system can now understand typescript whenever you test/run your project:

 create-react-app my-pwa-app --scripts-version=react-scripts-ts

So if you look closely ,you will see following lines in your packages.json file

  "scripts": {
 "start": "react-scripts-ts start",
 "build": "react-scripts-ts build",
 "test": "react-scripts-ts test --env=jsdom",
 "eject": "react-scripts-ts eject"
 }

Just to compare,without react-scripts-ts ,it will be

 scripts": {
 "start": "react-scripts start",
 "build": "react-scripts build",
 "test": "react-scripts test --env=jsdom",
 "eject": "react-scripts eject"
 }

So every time you run commands like npm start or npm build you are basically calling it though the react-scripts-ts.  

 react-scripts-ts is the magic which will make sure your project is using TypeScript at development time which is installed as development dependency

 Once you have your application created, you will have following structure



my-pwa-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ └── favicon.ico
│ └── index.html
│ └── manifest.json
└── src
│ └── App.css
│ └── App.tsx
│ └── App.test.tsx
│ └── index.css
│ └── index.tsx
│ └── logo.svg
│ └── registerServiceWorker.ts
├── tsconfig.json
├── tsconfig.test.json
├── tslint.json

In this registerServiceWorker.ts is the script which will make use of service workers  to make our application Progressive Web App.

This script registers a service worker to serve assets from local cache in production. This lets the app load faster on subsequent visits in production, and gives it offline capabilities. To learn more about the pros and cons, read this. This link also includes instructions on opting out of this behavior.

Setting up Mock API server for your backend

Normally you will be having a back-end in another project (e.g. asp.net web api )  ,Java (e.g. spring boot )  When you develop/debug/run a front end application,you have two options

  1. Run a backend everytime you run your frontend for debugging
  2. Setup a fake api which will behave like your backend but without running any of the dependent services

We will be using  json-server  to set up the fake API backend

You can install it as globally or locally as i use it for multiple projects,I install as globally

 npm install -g json-server

json-server works with a json file which we will call db.json

 {
  "blogs": [
    { "id": 1, "title": "json-server", "author": "rohith" }
  ],
  "comments": [
    { "id": 1, "body": "some comment", "postId": 1 }
  ]
}

Once you have your db.json file,you can run command

 json-server --watch db.json

Now if you go to https://localhost:3000/blogs/1, you'll get

 { "id": 1, "title": "json-server", "author": "rohith" }

Now we have to add json-server to our react project but what I need is a visual studio like experience, when I press F5 or start debugging,it should automatically start both front end and fake api backend

We will use two npm packages concurrently and cross-env and to make the job easy . concurrently can run multiple concurrent scripts (or commands ) at the same time. cross-env will make sure the command runs fine on all enviroments(whether you develop on windows or Linux or MacOS) .So to summarise,we need 3 npm packages and following configuration in our packages.json file

 "scripts": {
    "start": "concurrently --kill-others \"cross-env NODE_PATH=src react-scripts-ts start\" \"npm run server\"",
    "build": "cross-env NODE_PATH=src react-scripts-ts build",
    "test": "cross-env NODE_PATH=src react-scripts-ts test --env=jsdom",
    "eject": "cross-env NODE_PATH=src react-scripts-ts eject",
    "server": "json-server --watch --port 3001 ./src/api/db.json"
  }

So I added another another script called server where i specified the port as 3001 and also put the db.json file n the api folder

 "server": "json-server --watch --port 3001 ./src/api/db.json"

and start has been modified to run two commands concurrently using concurrently 

 "start": "concurrently --kill-others \"cross-env NODE_PATH=src react-scripts-ts start\" \"npm run server\""

Now every time we do npm start, it will start our fake api and also start the react app

VSCode Debugging support

We will try to setup Visual Studio Code to debug our ReactJS project with following feaures

  • Setting breakpoints, including in source files when source maps are enabled
  • Stepping, including with the buttons on the Chrome page
  • The Locals pane
  • Debugging eval scripts, script tags, and scripts that are added dynamically
  • Watches
  • Console

First You need to install a Visual Studio Code Extension VS Code -Debugger for chrome  then add the following launch.json to your project .vscode/launch.json

 {
 "version": "0.2.0",
 "configurations": [{
 "name": "Chrome",
 "type": "chrome",
 "request": "launch",
 "url": "https://localhost:3000",
 "webRoot": "${workspaceRoot}/src",
 "sourceMapPathOverrides": {
 "webpack:///src/*": "${webRoot}/*"
 }
 }]
 }

Once you have the launch.json, now

  • Start your app by running npm start
  • start debugging in VS Code by pressing F5 or by clicking the green debug icon

put a breakpoint in any tsx or ts file and debug to oblivion :)

How to BDD/TDD/E2E Testing

Create React App uses Jest as its test runner . To learn more  follow the running tests

In our App, We will try to do 3 types of tests

  • Unit Testing
  • Component testing
  • End to End Testing(E2E)

Unit testing

Unit Testing is used testing the smallest possible units of our code, functions.  Let's work with a Hello World in a Typescript and Jest 

We will create a directory called common inside src and add all common domain objects here

 

helloworld in typescript with Jest unit testing

add a main.ts inside the common directory

[js]
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}

class Calculator {
add(a:number,b:number) {
return a+b;
}

sub(a:number,b:number) {
return a-b;
}
}
export {Greeter,Calculator}
[/js]

 

And a main.test.ts

[js]<br data-mce-bogus="1">

import { Greeter, Calculator } from './main';

it('greets the world', () => {
let greeter = new Greeter("World");
expect(greeter.greet()).toEqual("Hello, World");
});
it('add/substract two numbers', () => {
let calc = new Calculator();
expect(calc.add(2, 3)).toEqual(5);
expect(calc.sub(3, 2)).toEqual(1);
});

[/js]

 

When we run the test using npm test, it will give

 
 E:\Projects\my-pwa-app>npm test

> my-pwa-app@0.1.0 test E:\Projects\my-pwa-app
> react-scripts-ts test --env=jsdom

PASS src\common\main.test.ts
 √ greets the world (4ms)
 √ add/substract two numbers (1ms)

Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.234s
Ran all test suites related to changed files.

Component Testing

Component testing let's you test your components using one level deep (Shallow Rendering ) or test components and all it's children

I have a component called Header which is using Link  component from react-router .But using ShallowRendering we can test just  Header component

[js]

import * as React from "react";
import { Link } from 'react-router';

export interface HeaderProps {
type: string,
id?: string
}

export default class Header extends React.Component<HeaderProps, object> {

public renderLinks(): JSX.Element {
const { type } = this.props;
if (type === "merchants_index") {
return (
<ul className="nav nav-pills navbar-right">
<li style={{ paddingRight: '10px' }} role="presentation">
<Link className="text-xs-right" style={{ color: '#337ab7', fontSize: '17px' }}
to="/merchant/new">New Merchant</Link>
</li>
</ul>
);
}
else
return (
<div></div>
);
}
public render() {
return (
<div>

<nav className="navbar navbar-default navbar-static-top">
<div id="navbar" className="navbar-collapse collapse">
<div className="container">
<ul className="nav nav-pills navbar-left">
<li style={{ paddingRight: '10px' }} role="presentation">
<Link className="text-xs-right"
style={{ color: '#337ab7', fontSize: '17px' }} to="/">Home</Link>
</li>
</ul>
{this.renderLinks()}
</div>
</div>
</nav>
</div>
);
}

}

[/js]

 

Now lets add required packages ,we will be using Enzyme and the shallow API  to test

 npm install --save enzyme enzyme-adapter-react-16 react-test-renderer

[js]</div>
<pre>import * as React from 'react';
import * as enzyme from 'enzyme';
import Header from './Header';
import * as Adapter from 'enzyme-adapter-react-16';

enzyme.configure({ adapter: new Adapter() });

it("renders 'new merchant' link in the header when type is merchants_index",()=>{
const header=enzyme.render(<Header type="merchants_index"/>);
expect(header.find(".text-xs-right").text()).toContain("New Merchant")
});
[/js]

End to End (E2E) testing

for E2E testing,we will be using testcafe and also the typescript support is good. You basically have to install testcafe and testcafe-react-selectors .

You can find more details on this here  or here

 

Support for Routing

There are many routing solution but ReactRouter is the most popular one,to add it to our project

npm install --save react-router-dom

First we have to add the require wiring up in index.tsx , you can see this in index.tsx inside the repository

[js]
<pre>import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Router, browserHistory } from 'react-router';
<strong>import routes from './routes';</strong>
import registerServiceWorker from './registerServiceWorker';
import './index.css';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/css/bootstrap-theme.css';

import { createStore } from 'redux';
import { merchantsReducer } from './reducers/index';
import { StoreState } from './types/index';

const store = createStore<StoreState>(merchantsReducer);

ReactDOM.render(
<Provider store={store}>
<strong><Router history={browserHistory} routes={routes} /></strong>
</Provider>,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();</pre>
[/js]

And we will create a our Routes in Routes.tsx file

[js]
import * as React from 'react';
import { Route,IndexRoute } from 'react-router';

import App from './App';
import MerchantIndex from './components/MerchantsIndex';
import HomePage from './components/Jumbotron';
import MerchantDetailsContainer from './containers/MerchantDetailsContainer'

export default (
<Route path="/" component={App}>
<IndexRoute component={MerchantIndex}/>
<Route path="merchants/new" component={HomePage}/>
<Route path="merchants/:id" component={MerchantDetailsContainer}/>
</Route>
)
[/js]

Using  Redux (Or Mobx)

Here also you can choose to use Redux or MobX and both has it's pros and cons  or checkout this comparison .But in this case we are going to use Redux .But with redux comes a bunch of other requirements :

  • Defining our app's state
  • Adding actions
  • Adding a reducer
  • How to use Presentation and container Components

We have State in TypeScript

[js]
<pre>import {Merchant} from '../common/Merchant';

export interface MerchantsList
{
merchants:Merchant[];
error:any;
loading:boolean;
}
export interface MerchantData
{
merchant:Merchant|null;
error:any;
loading:boolean;
}
export interface StoreState
{
merchantsList:MerchantsList;
newMerchant:MerchantData;
activeMerchant:MerchantData;
deletedMerchant:MerchantData;
}</pre>
Now we will have a directory named actions created  and inside that I will have
<pre>import axios, { AxiosPromise } from 'axios';
import * as constants from '../constants'
import { Merchant } from '../common/Merchant';
import { ROOT_URL } from './index';
//import { RESET_ACTIVE_MERCHANT } from '../constants';

export interface FetchMerchant {
type: constants.FETCH_MERCHANT,
payload: AxiosPromise<any>
}

export interface FetchMerchantSuccess {
type: constants.FETCH_MERCHANT_SUCCESS,
payload: Merchant
}

export interface FetchMerchantFailure {
type: constants.FETCH_MERCHANT_FAILURE,
payload: any
}

export interface ResetActiveMerchant {
type: constants.RESET_ACTIVE_MERCHANT
}

export type MerchantDetailAction = FetchMerchant | FetchMerchantSuccess | FetchMerchantFailure | ResetActiveMerchant;

export function fetchMerchant(id:string): FetchMerchant {
const request = axios({
method: 'get',
url: `${ROOT_URL}/merchants/${id}`,
headers: []
});
return {
type: constants.FETCH_MERCHANT,
payload: request
};
}

export function fetchMerchantSuccess(merchant: Merchant): FetchMerchantSuccess {
return {
type: constants.FETCH_MERCHANT_SUCCESS,
payload: merchant
};
}

export function fetchMerchantFailure(error: any): FetchMerchantFailure {
return {
type: constants.FETCH_MERCHANT_FAILURE,
payload: error
};
}

export function resetActiveMerchants(): ResetActiveMerchant {
return {
type: constants.RESET_ACTIVE_MERCHANT
};
}
[/js]

I also have Presentational and Container components created and you can see all this action in github project https://github.com/rohithkrajan/react-redux-ts

 

Hope this helps!

Comments

  • Anonymous
    March 21, 2018
    "and a bunch of other things." That is exactly how you'll dissuade people. Web development is hard enough as it is. When people see "bunch of other things", they run for the hills. We are tired of configuring a "bunch of other things" -- especially when we cannot identify what their use is within the first few seconds of reading. I am telling you this because in order to make an appealing blog post, you have to sell well. Web dev can be complex, sure -- but does it have to be? If does -- make sure you explain why.
    • Anonymous
      March 21, 2018
      You are right, it is overwhelming to start with the all the best practices so a title like that was confusing so I changed it for now. Thank you for the feedback