React on TypeScript – Part 2

In the previous post I left some React on TypeScript build flow steps to my future self. The project is already in a pretty decent shape, TypeScript is working, there is linting, CSS modules support and unit testing. Now just adding some more loaders to Webpack to handle images and other files and Babel to add some more browser compatibility. I will continue working on the repository which I created in the previous post, check it out if you want to see how we got here.

New build flow features

The configuration which was setup is working already quite well, just want to add a few more build tasks:

use Babel for fine grained browser compatibility

use URL loader to import images

Kick start this project

At the end of this blog post you will find a new repository with my the mostly complete workflow to build React application on TypeScript. To get started I cloned the results of the previous blog post where I believe most of the heavy lifting was done already.

Update the TypeScript configuration

I’m updating the tsconfig.json file to generate ES6 code. It’s not actually breaking the configuration of course, but the generated code will have somewhat limited browser compatibility. But that’s fine, will let Babel take care of that in the next step. The new configuration:

tsconfig.json

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

{

"compilerOptions":{

"baseUrl":"./src",

"rootDir":"./src",

"sourceMap":true,

"noImplicitAny":true,

"module":"commonjs",

"target":"es6",

"jsx":"react",

"lib":[

"es7",

"dom"

]

},

"include":[

"./src/**/*"

],

"exclude":["node_modules"]

}

Start using Babel

First let’s install the node package dependencies.

Add Babel dependencies

Shell

1

yarn add-D@babel/core@babel/preset-env@babel/preset-react

Babel configuration

Create .babelrc file. I will just add some minimal configuration now, it should be fairly easy to extend it further. The browserlist configuration is similar to the CSS autoprefixer, so browser support breaks at more or less the same point for both.

,babelrc

JavaScript

1

2

3

4

5

6

7

8

9

{

"presets":[

"@babel/preset-react",

[

"@babel/preset-env",

{"targets":">1%, last 4 versions, Firefox ESR, not ie < 9"}

]

]

}

Add Babel to the Webpack configuration

Yet another simple step. awesome-typescript-loader comes with full support for Babel and at this point I only use it there, so just updated the TS loader options:

Webpack configuration with Babel added

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

{

test:/\.tsx?$/,

use:[

{

loader:require.resolve('awesome-typescript-loader'),

options:{

useBabel:true,

babelCore:'@babel/core',

useCache:true,

}

}

]

},

The expected behavior with the above configuration is, that the TypeScript code is first transpiled to ES6 and then Babel compiles it further if necessary. The difference with the current configuration will be subtle if any, it’s a lot more visible if for example I changed the tsconfig.json to generate ES2016 or ES2017 first and then let Babel work on it. But nevertheless, now we can easily add @babel/polyfill for example or use other Babel features. On a side note, turning on useChache will create a .awcache folder in the project directory, so also added that to the .gitignore file.

Bundle the images – Import images to TypeScript

To bundle the images I will use the url-loader module. It’s a great asset to Webpack which will transform the images to base64 URIs. Of course this only makes sense for small files, so it has a fallback option for larger ones. For fallback I will use the file-loader, which is the default fallback and will bundle the file and return the URI. Another option would be the responsive-loader module, which generates a set of images and returns a srcset. That is a better option in most cases, however it will require you to install some dependencies which I don’t want to cover in this post. It should be straight forward to install sharp and the responsive-loader in a real project though, so if your project uses some large images, go for it.

Install the loaders

Install the image loaders

Shell

1

yarn add-Durl-loader file-loader

Configure webpack

Add the following configuration to the module rules array:

1

2

3

4

5

6

7

8

9

10

11

12

13

{

test:/\.(png|jpg|gif)$/i,

use:[

{

loader:require.resolve('url-loader'),

options:{

limit:4096,

fallback:'file-loader',

name:'[name].[hash].[ext]'

}

}

],

},

Note, that the name option is used by the file-loader, the options of the url-loader are passed forward to the fallback. The 4k size might be a bit large or bit small depending on your project, but will do it for now.

Setup in the project

In the sample project I have created a new folder under src called assets/img and copied my website logo there. Before I can import it in TypeScript, have to create a declaration. In the same folder I created an index.d.ts file with the following content:

src/assets/img/index.d.ts

1

2

3

declaremodule'*.png';

declaremodule'*.jpg';

declaremodule'*.gif';

And added a new component which renders the logo. The Logo.tsx file looks like this now:

src/components/Logo/Logo.tsx

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

import*asReact from'react';

import*aslogoSrc from'../../assets/img/SeaBadgerWhiteLogo.png';

import*asclasses from'./Logo.css';

const logo:React.StatelessComponent<{}>=()=>{

return(

<div className={classes.Logo}>

<img src={logoSrc}alt="Logo"/>

</div>

);

};

export defaultlogo;

And added a little ugly, but functional styling in the Logo.css:

src/components/Logo/Logo.css

CSS

1

2

3

4

5

6

7

8

9

10

.Logo {

width:100%;

text-align:center;

}

.Logo img {

height:80px;

background-color:#000;

border-radius:40px40px00;

}

Since this file is fairly big (about 13k, so larger, than the 4k limit which we defined in the configuration above), you will see, that it’s copied to the destination folder and is included by the file-loader. That is the expected fallback method, so I’m happy with the results. Let’s see how it looks with a smaller file which we want to include in the bundle as base64 encoded string. For this I got a random person icon from icons8.com and will load it into the NameCard component.

The modified NameCard.tsx looks like this now:

src/components/NameCard/NameCard.tsx

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

import*asReact from'react';

import*asanonLogoSrc from'../../assets/img/person.png';

import*asclasses from'./NameCard.css';

exportinterfaceINameCardProps{name:string;age:number;}

export classNameCard extendsReact.Component<INameCardProps,{}>{

publicrender(){

return(

<div className={classes.NameCard}>

<div className={classes.Details}>

<pclassName={classes.Name}>Name:{this.props.name}</p>

<pclassName={classes.Age}>Age:{this.props.age}</p>

</div>

<div className={classes.ProfilePicture}>

<img src={anonLogoSrc}alt="Profile picture"/>

</div>

</div>

);

}

}

And the updated stylesheet in NameCard.css:

src/components/NameCard/NameCard.css

CSS

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

.NameCard {

padding:20px;

box-shadow:2px2px5px#000;

display:flex;

flex-flow:rowwrap;

}

.Details {

flex-basis:80%;

flex-grow:1;

}

.Name {

font-weight:bold;

}

.Age {

font-style:italic;

}

.ProfilePicture {

max-width:60px;

}

.ProfilePicture img {

width:100%;

height:auto;

}

As a result, the source attribute of the profile image will look like this:

1

src="data:image/png;base64,iVBORw0KGgo..."

So the image is included in the application bundle, which is exactly what I wanted to demonstrate here, so again happy with the results.

Summary

While it can be tuned further, this repository is hopefully a good start to build your React project on TypeScript. The complete repository with the sample project is available here: