Using React, Redux and Webpack with Dotnet Core to build a modern web frontend

As a developer, I consider myself mostly a Dotnet developer. But when building modern applications for the web, there’s no way around it: every web developer now also has to be a javascript developer. So, reluctantly, I have accepted that I also need to know javascript, and have learned to deal with it. My aim for this project is to explore next-generation technologies so I decided to finally go all-in.

This is part of a series in my exploration of a modern web architecture, RROD (React, Redux, Orleans and Dotnet). For the introduction, see here.

Architecture considerations

What Framework to use?

The battle of javascript frameworks is still very much ongoing. Frameworks go in- and out of fashion quickly and the decision on a front-end framework at the start of a new project feels like a gamble. If the selected framework goes out of fashion next year, and your project is successful, it will be harder to attract the right people to work on it, and maintaining it will get more expensive and less fun.

React

As it stands in 2017, React seems to be the most popular javascript framework. It is actively used and maintained by Facebook and has developed a cult of almost religious followers. Big applications have been built with it; it’s proven and well-understood. And it has good support for a feature that is very important to me: Isomorphic (also called Universal) rendering. This means that the page rendering can happen both server-side and client-side, which is important for Search Engine Optimization (SEO).

Redux

React is actually not a complete framework, it is just a View engine. For data handling and logic, most React projects add some additional library. There are several good options, but Redux has become mainstream, after its creator Dan Abramov introduced it with a brilliant presentation. Redux uses an event-sourcing mechanism, where events (called Actions in Redux) flow through the state object tree where they are handled by updating that state, after which React will re-render the View. The difference with traditional event sourcing architectures is that, in Redux, the event handler is a reducer function [(state, action) => state] operating on immutable data, which makes it cheap to keep references to all previous states. This, in turn, allows for “time travel”: rewind events, update code, replay the events on the updated code. It’s pretty cool.

Typescript

As a C# developer in javascript land, I tend to get myself in trouble a lot. Working with .NET ever since it was invented, I have developed some habits that I found unproductive in javascript. For instance, I’m used to quickly upgrade all project dependencies to the latest version; this usually works out fine in .NET projects: breaking changes show up in the compiler and are fixed easily. With javascript, updating dependencies results in things breaking a lot. Oftentimes there are breaking changes, and the new version is totally different. But sometimes things break subtly, at runtime. You may not notice it immediately, and finding what caused it may cost a lot of time later…

Typescript, of course, was made for this. It does not fix all of javascripts shortcomings, but it adds optional type checking, which makes maintaining a larger javascript codebase a lot less painful. Using typescript adds some extra work to maintain type declarations, but support is improving steadily, especially since typescript 2.0 introduced a much better way of importing type declarations files, and ambient module declarations made it easier to reference external javascript modules. Coming from C# or other typed languages, it feels like a no-brainer to use typescript now.

Babel

In my RROD exploration I also want to use new ES2015 javascript syntax, including async/await, the …spread operator and annotations. Those must be transpiled to ES5 (“old”) syntax before they can run in the browser and the stable version of node (native async currently needs the unstable node 7.x version). I use babel to transpile ES2015 to ES5. The typescript compiler can directly compile to ES5, without using babel, but I need babel to do some transforms on the code, especially Hot Module Reloading. So I let typescript compile .ts and .tsx files to the equivalent .js and .jsx, still in modern ES2015 syntax, then babel will take those and compile down to ES5, adding browser polyfills, the “generator runtime” (for async) and HMR transforms.

Typewriter

Even with typescript, working on server-side (.NET) code and client-side (javascript) code concurrently makes it hard to keep data structures properly synchronized between both sides. That is: until I found Typewriter, a project that lets you generate typescript declarations from .NET code. The templates for typescript code generation are part of the project, and they can be adapted to your own requirements and conventions.

AspNet JavascriptServices

A good starting point for a Dotnet Core javascript project is the AspNet JavascriptServices project, maintained by Steven Sanderson, of Knockout.js fame. This project provides Dotnet Core templates for several popular javascript frameworks, such as React, Angular 2, Vue, Knockout and Aurelia. For React, there is a template that also includes Redux and Typescript, and that is the template that I used to start.

The RROD Implementation

Based on these considerations, I created the javascript front-end for my project, implementing all these technologies. Complete code for this project is here on GitHub. In this section I will highlight a few interesting parts of my implementation and share some of my learning.

Starting a Dotnet Core Javascript project

The easiest way to setup this type of project is documented on the JavascriptServices site at github. You use npm and Yeoman to scaffold a completely functional starter website. I selected the “React with Redux and Typescript” variant.

What you get, on top of a normal Dotnet Core MVC project are:

  • A reference to the nuget package Microsoft.AspNetCore.SpaServices. This references the Microsoft.AspNetCore.NodeServices package that makes it possible to execute javascript code from Dotnet. Configuration for these is in the ConfigureServices function in Startup.cs.
  • A ClientApp folder with a starter website, written in typescript. In my case, this is an Isomorphic web application with entry points for both server and client, using webpack to drive the compilation and bundling process. The application references the aspnet-webpack, aspnet-prerendering and aspnet-webpack-react npm modules, that support specific features of the integration between Dotnet and Javascript.
  • A razor viewpage Index.cshtml that “boots” the ClientApp.
  • Build targets in the .csproj project file to run npm install and webpack as part of the Dotnet build and publish commands.

Typewriter — generating typescript from C# code

To assist in synchronizing C# and Typescript code, specifically Redux State and Actions, and data passed via Web API models, I used Typewriter. Typewriter works as a Visual Studio plugin, and the generated typescript code is exported at development time in a “server” folder under ClientApp (where all client code is), so it can be referenced by the rest of the typescript client code.

I created two templates:

  • Models.tst — for all classes with a name ending in “Model” or “Result”, this template generates a typescript interface.
  • Redux.tst — For all classes with a name ending in “Action” this template converts a Redux Action on the server (where the Type is build-in) to the equivalent typescript interface, with the type as a string property on the object. I also implemented the equivalent, reflection based back-and-forth conversion in .NET. It also generates a string constant for the action name, and generates an interface for classes with a name ending in “State”.

With this in place, I can be sure that models are always correctly translated between server and client, without duplicate work. This works very well, and the type safety of typescript avoids a lot of problems before they happen.

React Components in Typescript

The React Components in the project follow the pattern of AspNet Javascript Services: they are built using TSX syntax, the typed equivalent of JSX. I’m a long-time fan of the Bootstrap CSS framework, so I’m using that. However, I have replaced the default bootstrap code (that uses jQuery) for react-bootstrap, which is a React-based re-implementation of bootstrap that doesn’t depend on jQuery. Typescript typings are available, so you also get code auto-completion. This leads to a very nice developer experience; for instance, here is the bootstrap-based navigation menu:

I think that is very clean. It’s like ASP Web Forms, made cool again :-)

Initializing Javascript Services

AspNet JavascriptServices is initialized in Program.cs like this:

The AddNodeServices() is optional, starting it with LaunchWithDebugging = true makes it possible to attach a debugger. To do that with VS Code, configure a Node.js launch task using “Legacy Protocol” to attach to the node debugger on port 5858 (which is the default). Debugging transpiled ES2015 is not perfect, but I was able to set breakpoints and understand how node was executing my typescript code server-side.

This setup handles requests using normal Mvc routing rules, the home page going to the Index action on HomeController. If no matching route is found, the “spa-fallback” route is invoked, sending the request to the same home page action. This passes the request to the javascript router.

The Controller Action for a JavascripServices web application does nothing unusual. The magic happens in the View Index.cshtml: that will execute the main-server entrypoint using the asp-prerender-module tag helper and also reference the compiled main-client javascript bundle for the client-side follow-up execution:

<div id=”react-app” asp-prerender-module=”ClientApp/dist/main-server”>Loading…</div>
<script src="~/dist/main-client.js" asp-append-version="true"></script>

This statement executes the server-side entrypoint in Node.js, and writes the resulting string inside the div element. This means the page content is pre-rendered server-side, so the page will display even for users who have javascript disabled and, more importantly, for search indexers such as GoogleBot.

Webpack

The app.UseWebpackMiddleware(), only executed in the development environment, is used to instruct AspNet JavascriptServices to start a watcher that will recompile the javascript code, using Webpack, whenever it detects a code-change. The HotModulePlacement = true instructs Webpack to insert extra code that automatically reloads any javascript imports when their source is changed. The middleware does this by manipulating the configuration of webpack before executing it. It takes your webpack.config.js and then dynamically inserts the plugin webpack.HotModuleReplacementPlugin that makes Hot Module Reloading (HMR) work. This means it is important to NOT add those modules in your webpack.config.js yourself, as having them twice results in hard-to-understand conflicts.

The ReactHotModuleReplacement = true does the same, but with extra smarts for React Components, using the react-transform-hmr babel transform. This transform is deprecated and I tried to use the newest way of doing this (React Hot Loader 3), but the new method is, as of this writing, in beta, and I had nothing but problems getting it to work properly. Let Steven Sanderson fix this, then I’ll copy his work. See, I’m learning this javascript stuff already.

Warning: danger ahead

It’s important to know that the webpack, babel and npm configurations are all very finicky. You can very easily break things by changing something in the webpack configuration or in package.json. Things may even break without any participation on your part: when minor new versions of dependencies are published, a new typescript version comes out, you run it on another machine that has another version of node, anything basically…

webpack.config.js

Also, there are many ways to specify a webpack configuration: loaders can be specified as a string, as an array of strings, as an object, as an array of objects, in the (deprecated) .loader section or in the (new) .use section. Parameters can be specified with a querystring, as an options field, using the .babelrc config file, inside the package.json, etc. It is entirely possible to have a valid webpack configuration structured in a way that AspNet JavascriptServices does not understand, resulting in HMR silently failing. Do not touch it unless necessary, and when you do, do it step-by-step, testing every change before going further.

If you manage to get all this to work without breaking anything you get rewarded with Hot Module Reloading. That means that modifying any typescript (.ts or .tsx) file while running in Development mode results in an automatic refresh of the browser screen, usually without even losing state. You can install Chrome plugins for React and Redux that can show you all the Actions that have happened in the past and lets you change them. You can use a slider to undo Actions, change code, and replay those Actions on the updated code. It is very cool.

Passing data from Dotnet to Javascript

It took me some time to understand how to pass data from the Dotnet side over to javascript. For instance, I wanted to pass the XSRF token that Dotnet Core generates and the Dotnet authentication state into the Javascript-rendered DOM and the Redux State.

This turns out to be straigtforward. Just pass the data from the Controller into the serverside View Index.cshtml and then either render it in the DOM and read it client-side with javascript or pass it in the asp-prerender-data parameter, from where it is passed to the boot-server.tsx component using the param object.

Still another way to pass initial data to the client is by sending it in a client-readable cookie.

Optimization

The average page size on the internet currently stands at 3Mb. That is terrible, but when building a web application, using npm and webpack, adding a few cool libraries to your project, it’s easy to see how pages can grow to that size.

Being selective about importing libraries is important, but an easy tactic to limit bundle size is to use pre-compression. I let webpack generate a .gz compressed version of the javascript and .css assets, and have installed a custom middleware in dotnet to modify incoming requests for static assets so they return the pre-compressed version, if available.

You can get the CompressedStaticFilesMiddleware from NuGet. This simple method reduces bundle size by a factor of 3, without adding any load to the CPU, because the files are pre-compressed.


Conclusion

It took me some time to grok the whole javascript thing, and I still curse the language and tools regularly when something seemingly simple breaks again. But now that I understand most of it I really like React and Redux. The webpack configuration is sensitive to changes. Using React serverside rendering can also be challenging, many components and examples you find online are not really tested for that scenario.

The dynamic nature of javascript feels dangerous and exciting at the same time: the lack of basic language features and the endless number of half-baked solutions to solve the same problem can cost a lot of time. On the other hand: Hot Module Reloading brings a big productivity boost and makes it easy to develop new features.

And Redux is definitely a good idea. So much that I wanted to also use that idea in Dotnet and use Redux on the Server. More on this in the final part of this series, where I’m going to dig into the Microsoft Orleans 2.0 based back-end of RROD, and connect the javascript front-end with back-end Actors in real-time.