In this post I will show steps to rewrite my resume page (resume.lukaszsadlocha.pl). It was created some time ago in asp.net core + cshtml razor views but now it is time for change its Front-end to React. I will take the page, move it to one huge React component and then split its part to separated component to show how it can be done.
As a first Step I created new asp.net core with react app. In my code folder I run a command in CMD:
dotnet new react -o lukaszsadlocha_onlineresume_react
Then I open it in Visual Studio, build and get nice looking sample application with navigation and 3 examples:
Step 2 – as usual – I added it to github so it is here github.com/lukaszsadlocha/lukaszsadlocha_onlineresume_react if you want to skip this post and just get a code.
Step 3 – Let’s look how this application is being built. It is worth to remember that it is a still dotnet Core on back-end so let’s look into startup.cs
file to see how it is configured. Inside this file we can see that it is still a MVC application with a routing configured pretty much in the same way as other asp.net core application – there is no magic here.
Same situation is in Program.cs
. Main method starts WebHost. So what exactly happen when application appeared in by browser under http://localhost:54147
? As I noticed in Startup.cs
HomeController is a default controller and Index is a default action. HomeController->Index
action returns just a view and whole React stuff starts inside Index.cshtml
There is one div tag and script section with main.js
loaded
@{ ViewData["Title"] = "Home Page"; } <div id="react-app">Loading...&lt;/div> @section scripts { &lt;img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22~%2Fdist%2Fmain.js%22%20asp-append-version%3D%22true%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&amp;lt;script&amp;gt;" title="&amp;lt;script&amp;gt;" /> }
So everything connected with React starts in ~/dist/main.js
. Once you open it you can see that it is auto-generated file. To be more precisely it is bundle via webpack and its definition is in webpack.config.js
. Inside this file you can see that entry point is pointing to ClientApp/boot.txt
and inside ClientApp
there are files and logic that I will edit to make the page looking as desired.
Let’s open boot.tsx
file
import './css/site.css'; import 'bootstrap'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { AppContainer } from 'react-hot-loader'; import { BrowserRouter } from 'react-router-dom'; import * as RoutesModule from './routes'; let routes = RoutesModule.routes; function renderApp() { // This code starts up the React app when it runs in a browser. It sets up the routing // configuration and injects the app into a DOM element. const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!; ReactDOM.render( <AppContainer> <BrowserRouter children={ routes } basename={ baseUrl } /> </AppContainer>, document.getElementById('react-app') ); } renderApp(); // Allow Hot Module Replacement if (module.hot) { module.hot.accept('./routes', () => { routes = require<typeof RoutesModule> ('./routes').routes; renderApp(); }); }
It uses extra package for routing and hot reloading. As my resume page is just a static single page I will remove these packages + references and navigation component.
Files that have been removed
ClientApp/components/Layout.tsx ClientApp/components/NavMenu.tsx ClientApp/routes.tsx
And modified boot.tsx
import './css/site.css'; import 'bootstrap'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Home } from './components/Home' function renderApp() { const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!; ReactDOM.render( <Home />, document.getElementById('react-app') ); } renderApp();
So basically now Home is my main and the only one component on the page
Beside boot.tsx
also Home.tsx
was modified because I do not want to use routing package:
import * as React from 'react'; export class Home extends React.Component{ public render() { return <div> ... </div> ; } }
Step 4 – Now I will move html markup from previous version of my Online Resume and replace return output from Home component.
Important changes that need to be done in converting html
Remove comments Rename class -> className All returning html need to be wrapped into one div Self-closing html tags like br, hr, img need to be closed Custom tags as t, sm are not allowed. I needed to replace it e.g. with span and adjust css
Once it is done page has the content but styling and images are missing
Step 5 – So how actually should I add images to top react component? Together with dotnet core React sample app there is a definition for url-loader in webpack
module: { rules: [ { test: /\.tsx?$/, include: /ClientApp/, use: 'awesome-typescript-loader?silent=true' }, { test: /\.css$/, use: isDevBuild ? ['style-loader', 'css-loader'] : ExtractTextPlugin.extract({ use: 'css-loader?minimize' }) }, { test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' } ] },
It will take care of the images that are used inside components. If this is a small image then content is provided by base64 or as regular image file in other case.
So I created images
folder under ClientApp and copied there one graphic with Sitecore logo. sitecore_big.png
. Inside Home.tsx
I went into the line where img tag is defined and just replace
<img className="img-responsive" src="~/images/sitecore_big.png" alt="" />
to
<img className="img-responsive" src={require("../images/sitecore_big.png")} alt="" />
Important thing to notice here is that you cannot access http://localhost:54147/images/sitecore_big.png
– this file is not there. Instead of this, webpack moved the image to OutputPath defined inside webpack.config.js
and according to its logic the file is available under – http://localhost:54147/dist/9675e6cf679b022fb9eb5332f6706a3e.png
. It also accessible via Visual Studio under wwwroot/dist
folder.
Ok, images are there but styles are missing. I took content of main.css
file from my previous resume application and replace a content of ClientApp/css/site.css/
. I saved it and got the error on the server:
Module not found: Error: Can't resolve '../images/header-bg.jpg' in 'C:\Code\lukaszsadlocha_onlineresume_react\ClientApp\css'
yes, it makes sense – there is a reference to one more image that is missing. I’ve copied header-bg.jpg
into image
folder and refreshed the browser. Styles appeared – that’s cool. Notice that name of header-bg.jpg
if different after css and images were processed by webpack.
Ok, the page looks better now, but all icons and small graphics are missing. That’s because I forgot about fonts-awesome
. It is not difficult to add in into the application. Basically font-awesome
adds extra styles to simple html tags and adds content on css ::before pseudo-element. So what I need here is a css added to my component. Firstly I need to get font-awesome
from npm. I’ve added it to package.json
(while I was writing this post the latest stable version was 4.7.0)
.... "file-loader": "0.11.2", "font-awesome": "^4.7.0", "isomorphic-fetch": "2.2.1", "jquery": "3.2.1", ...
Once the package is there I needed to bundle it with webpack. Because I have css rules already I didn’t need to bother with ‘style’ part of font-awesome
but inside css there are references to many icons/fonts and other files. To make a new rule to handle this files I’ve added couple lines to webpack.config.js
rules: [ { test: /\.tsx?$/, include: /ClientApp/, use: 'awesome-typescript-loader?silent=true' }, { test: /\.css$/, use: isDevBuild ? ['style-loader', 'css-loader'] : ExtractTextPlugin.extract({ use: 'css-loader?minimize' }) }, { test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }, { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader" }, { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader" } ]
And last thing is just to import font-awesome
css to the page. I’ve added one line in boot.tsx
import './css/site.css'; import 'font-awesome/css/font-awesome.css'; import 'bootstrap'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Home } from './components/Home' function renderApp() { const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!; ReactDOM.render( <Home />, document.getElementById('react-app') ); } renderApp();
and now there are nice awesome fonts on the page
Step 6 in this step I wanted to focus on front-end design but My Resume page is a single page application mostly static – so splitting it into components is rather an exercise then a huge need. Nevertheless, let’s start with something really easy – Technologies section. There are 3 similar elements where on the left there is an image and on the right a header and some text. I will call it TechnologyBlock
and move it to separated file. So I added a file TechnologyBlock.tsx
inside components folder and created simple React (function) component:
import * as React from 'react'; export const TechnologyBlock = function () { return ( <div>Hello Technology component</div> ) }
and in Home.tsx
I imported it and added next to Technology section to see if it works:
import * as React from 'react'; import { TechnologyBlock } from './TechnologyBlock'; export class Home extends React.Component { public render() { return <div> <div id="section-topbar"> .... <div className="col-lg-2 col-lg-offset-1"> <h5>TECHNOLOGIES</h5> <TechnologyBlock /> </div> ....
It is there so I can change a content and pass few props inside component. I used advantage of typescript and instead of passing props I’ve declared type with properties needed inside this component
import * as React from 'react'; type TechnologyBlockProps = { image: string, header: string, text: string } export const TechnologyBlock = function ({ image, header, text }: TechnologyBlockProps) { return ( <div> <div className="col-lg-5 col-lg-offset-3"> <img className="img-responsive" src={image} alt="" /> </div> <div className="col-lg-4"> {header} {text} </div> </div> ) }
and replaced static content of Home.tsx
with new components
<div className="container desc"> <div className="row"> <div className="col-lg-2 col-lg-offset-1"> <h5>TECHNOLOGIES</h5> </div> </div> <div className="row"> <TechnologyBlock header="SITECORE" image={require("../images/sitecore_big.png")} text="I've been connected with Sitecore... " /> <TechnologyBlock header="SHAREPOINT" image={require("../images/sharePoint.jpg")} text="I worked with SharePoint 2007, 2010... " /> <TechnologyBlock header="OTHER TECHNOLOGIES" image={require("../images/other.png")} text="As a big enthusiast of new technologi..." /> </div> </div>
It looks better now.
At this this post is going to its end. Basically there is no point of splitting static page into more and more ‘static’ React components as this is not what react was design for and based on `TechnologyBlock` component I believe that everyone would be able to create new components
All the code is available on GitHub: lukaszsadlocha_onlineresume_react