Isomorphic JavaScript with ASP.NET MVC, ReactJS and Browserify

25 Jul 2014

It has been a few months since I started using Facebook’s React in my projects. I have to say that it shifted the way I’m thinking about implementing web UIs. After grasping basic concepts like programming against the virtual DOM and declarative approach, React worked well for me and let me increase my productivity, moving many potential headaches out of the way. The big advantage of the library is that it actually simplifies the programming model for DOM-based UIs. There are some useful comparisons between React and Angular.js here (although Angular is a more comprehensive framework and covers more use cases; React focuses rather only on V in MVC).

One great feature of React is its support for pre-rendering UI components server-side. This may be beneficial for a page load speed or SEO purposes. This sort of code sharing between the server and the browser ties in with the concept of isomorphic JavaScript.

React.JS and ASP.NET

React.js team released lately a great toolkit ReactJS.NET which eases integration with ASP.NET MVC by providing bundling and optimization tools for JSX scripts. One of its neat features is support for rendering React components inside of ASP.NET Razor views. Internally it uses MsieJavaScriptEngine – a .NET wrapper for working with the Internet Explorer's JavaScript engines.

Quick look from Node perspective

I took my first steps with React on a side-project based on Node.js and I can definitely say that both technologies fit really good together. Essentially, part of the modular server-side code can be reused on the client with the help of Browserify bundles. Writing React components as Node modules and bundling them up for the client allows for easy code sharing between the browser and the server. Tasks like transformations of React JSX code or bundle generation can be easily automated with one of available build frameworks.

As I wanted to continue with React on ASP.NET projects, my first thought was to that it would be cool to carry on with the same smooth approach I experienced with Node stack. What I mean by this is to write modular JavaScript and be able to easily share part of my React components between the browser and server-side views. Let's see how we can get down to it.

Tweaking ASP.NET with Node tools

Employing some of the Node-based tools into ASP.NET development workflow may be beneficial for a number of reasons:

  • Using NPM for handling script dependencies.
    Why? Public npm registry offers by far more web asset packages than NuGet. What’s more, NPM versions are often more up-to-date as their respective NuGet counterparts; Finally, Node Package Manager allows to define a dependency which does not necessarily is a published package. It can be for example a URL to Github repository (see documentation for reference).

  • Defining streamlined and flexible build process for web assets
    Node-based build frameworks with their abundance of community plugins have a lot to offer when it comes to automation of repetitive build steps. Multi-step processes can be defined as a set of tasks piped together through data streams, contributing to a clean and quick build.

  • Writing well-organized modular JavaScript with automatic dependency resolution
    Modular code is easier to maintain and modify. With Browserify we can write fine-grained Node-style modules for the browser and get them bundled using its dependency resolution mechanism.

  • Simplifying bundle configurations
    When using a common bundling mechanism known from ASP.NET we would need to list all of dependencies explicitly and assure their correct order to satisfy potential dependencies between them. Such imperative bundle configuration may become fragile, hard to maintain and prone to errors as the application gets bigger. By leveraging Browserify our .NET bundle configuration contains only entry level script(s) without dependencies. If using server-side pre-rendering , it applies also to React bundle.

Ok, so let’s start with the set-up of our environment. Key tools we are going to use are NPM, Gulp.js (+plugins) and Browserify.

  • Node & Node Package Manager
    NPM requires Node.js installed on your system. Checkout readme for more details.
    Issuing npm init command prepares project to be handled by npm. It creates a config file named package.json in the projects root, which holds configuration for package dependencies.

  • Gulp.js
    Gulp is a build system with two main goals in mind: speed and simplicity. Its approach relies on Node streams, enabling to define a build process as a series of smaller processes chained by their streams. It has a lightweight API and the build tasks are defined in plain JavaScript, so the learning curve is rather gradual. Gulp has already gained an active community and a large amount of plugins. Gulp command line utility should preferably be installed globally: npm install –g gulp
    Additionally at the root of the project you should install gulp module as a development dependency:
    npm install --save-dev gulp
    By default Gulp reads a file named gulpfile.js which should be created at the root of the project. I recommend this great introduction to Gulp to understand it better.

  • Browserify
    Browserify makes it possible to use CommonJS modules in the browser. Any dependencies are automatically resolved basing on require() calls in modules code. Browserify can be used either from the CLI or through an API. Simply install it at the root of your project as a development dependency:
    npm install browserify –save-dev

Putting the pieces together

To have a tangible example to work on, let’s reuse a piece of example from the official React tutorial. Our application will allow a user to store a TODO list. Text can be formatted using markdown syntax. There will also be a small UI component shared between the user panel and the TODO list, showing time elapsed since some event. This component will use a utility library for handling date-time operations in JavaScript - Moment.js. Finally we want some parts of the UI to be pre-rendered on the server.

A raw version of the app would look more or less like this (sources are available on GitHub):

There are a few related entities here, which can be mapped as modules:

*red modules will be pre-rendered server-side

We’ll start with installing both of external libraries, Markdown.js and Moment.js:
npm install moment –save
npm install markdown --save

Let’s skip the implementation details of React components (sources can be found here) and focus on how to provide them to ReactJS.NET to use them sever-side. (To learn how to think in React I recommend this great tutorial from the official website.) ReactJS.NET offers a very straightforward interface to call React component from ASP.NET Razor view. The sample snippet below pre-renders our TODO list with some initial entries:

/Views/Home/Index.cshtml:

<div id="main">
  @{
    var props = new
    {
      entries = new[] {
        new {Text = "my todo 1", CreatedAt = DateTime.Now - TimeSpan.FromDays(35) },
        new {Text = "my todo 2", CreatedAt = DateTime.Now - TimeSpan.FromDays(13) },
        new {Text = "my todo 3", CreatedAt = DateTime.Now - TimeSpan.FromDays(5) }
      }
    };
  }
  @Html.React("Todo", props)
</div>

Before we’ll be able to see the pre-rendering magic working, we have to feed ReactJS.NET with our components. In order to do that we will define a Browserify bundle which provides all the necessary code required on the server. If we were using a common ASP.NET bundle mechanism we would have to list all our scripts and their dependencies assuring their correct load order. We can get most of the job done for us by letting Browserify to handle the dependency resolution. For that we will define a Gulp build task.

Setting up build with Gulp.js

First of all, it’s a good pracise to define project structure for our build process and later on use this configuration object throughout the build tasks. It basically contains all constant project paths, file names, file filters, etc.

/gulpfile.js:

var paths = {
  src: {
    jsx: 'Scripts/App/Components/*.jsx',
    app: 'Scripts/App/main.js',
    scripts: 'Scripts/**/*.js'
  },
  dest: {
    bundles: 'Scripts/dist',
    bundlesFilter: '!Scripts/dist/**/*.js',
    serverBundle: 'serverBundle.js',
    clientBundle: 'clientBundle.js',
    jsx: 'Scripts/App/Components'
  }
};

Transforming JSX files

Our components are written using JSX, so before bundling them up, we have to transform them into regular JS. Here's a gulp task for that:

/gulpfile.js:

var react = require('gulp-react');
gulp.task('react', function () {
  return gulp.src(paths.src.jsx)
    .pipe(react())
    .pipe(gulp.dest(paths.dest.jsx));
});

The whole process consists of three steps: read all JSX files, compile them to regular Javascript (using a gulp plugin gulp-react [link]) and save the output files in the same directory.

Bundle generation

We are going to generate two bundles – one to use by ReactJS.NET server-side and one for the browser. Let’s start with the one for server.

Bundle for ReactJS.NET

One of Reacts top-level API methods - React.renderComponentToString() is intended to be used on the server to generate an initial HTML of a component. Calling React.renderComponent() on the client will preserve this initial markup and only attach necessary event handlers. Good news is that ReactJS.NET automates this activity for us. For each component rendered on the server ReactJS.Net will add a <script> tag at the end of the <body> containing the code for “reactifying” the static HTML markup. It’s quite convenient. This is how such auto-generated script looks like:

<body>
  ...
  <script>
  React.renderComponent(
    Todo({"entries":[{"Text":"Make to do list","CreatedAt":"2014-06-17T23:20:13+02:00"}]}),
    document.getElementById("react1"));
  React.renderComponent(UserPanel({"name":"Guest"}), document.getElementById("react2"));
  </script>
</body>

Bundle for React needs to include all components which are going to be pre-rendered. Let’s define a simple JSON configuration holding a list of corresponding modules. As it’s JSON, it will be easy to consume from the Gulp task.

reactServerConfig.json

{
  "expose": [
    { "path": "./Scripts/App/Components/Todo", "name" : "Todo" },
    { "path": "./Scripts/App/Components/UserPanel", "name" : "UserPanel" }
  ]
}

The next step is to tell Browserify to include those modules into the bundle and expose them in the way that they will be accessible from the outside of the bundle. Adding a module to the bundle and exposing is basically a one-liner:

browserify.require("./Scripts/App/Components/Todo", { expose: "Todo" });

Finally, we need to assign respective component classes to global variables, so that they can be called from ASP.NET code using @Html.React()

The whole bundling task could look somewhat like the snippet below:

var source = require('vinyl-source-stream');
var streams = require('memory-streams');
var CombinedStream = require('combined-stream');

var createServerBundle = function (browserify, configPath) {
  function parseConfig(config) {/*...*/}

  function exposeModule(exposedVariables, name, path) {
    browserify.require(path, { expose: name });
    exposedVariables.append('var ' + name + ' = require("' + name + '");');
  }

  if (configPath === undefined) {
    configPath = './reactServerConfig.json';
  }
  var config = require(configPath);
  var serverComponents = parseConfig(config);
  if (serverComponents) {
    var exposedVariables = CombinedStream.create();
    exposedVariables.append(';');
        //expose React by default
    exposeModule(exposedVariables, 'React', 'react')
    //expose configured components
    for (var name in serverComponents) {
      var path = serverComponents[name];
      browserify.add(path);
      exposeModule(exposedVariables, name, path);
    }
    var bundleStream = CombinedStream.create();
    bundleStream.append(browserify.bundle());
    bundleStream.append(exposedVariables);

    return bundleStream;
  }
}

gulp.task('server-build', function () {
  var bundle = createServerBundle(browserify());
  return bundle
    .pipe(source(paths.dest.serverBundle))
    .pipe(gulp.dest(paths.dest.bundles));
});

As you may notice, this code relies heavily on Node streams (as most of the code written for Gulp). Additional task dependencies: ‘vinyl-source-stream', ‘memory-streams', 'combined-stream' are there to ease stream manipulations.

Once we are ready with the bundle for ReactJS.NET, the only thing that’s left is to let ReactJS.NET know where the bundle is:

/App_Start/ReactConfig.cs:

public static class ReactConfig
{
    public static void Configure()
    {
        ReactSiteConfiguration.Configuration = new ReactSiteConfiguration();
        ReactSiteConfiguration.Configuration
            .AddScript("~/Scripts/dist/serverBundle.js");
    }
}

Bundle for the browser

Assuming that the client code starts off from main application script (in our case main.js) we can simply pass this file to Browserify constructor and let it recursively bundle up all the required modules.

However, we want also to assure that React correctly initializes our pre-rendered components (described in previous chapter). Basically auto-generated initialization scripts require global variables representing component classes (Todo, UserPanel) and the React class itself. That is exactly what we’ve already implemented for the server bundle. Browserify will assure that modules were added uniquely to the bundle. In the end the code looks like this:

/gulpfile.js:

var gulpClientBundle = function() {
  var b = browserify(paths.src.app); 
  var bundle = createServerBundle(b);
  return bundle
    .pipe(source(paths.dest.clientBundle))
    .pipe(gulp.dest(paths.dest.bundles));
}
gulp.task('client-build', ['react'], function () {
  return gulpClientBundle();
});

In case of release build we could enhance the process with minification step before saving the output.

Automating the build

The biggest strength of Gulp is its flexibility in defining build tasks and dependencies between them. To assure that our bundling process operates on up-to-date sources we can make the server-build task dependent on the prior compilation of JSX components. This can be easily done using a task dependency:

gulp.task('server-build', ['react'], function () {
  return gulpServerBundle();
});

Another thing we can do is to let Gulp watch for changes and automatically rebuild the corresponding assets. It will come in handy in order to keep the browser bundle always up-to-date. This is where another gulp plugin: gulp-watch comes in. In general, a call to gulp-watch will listen for changes on a set of files that can be defined by a series of path filters. In response to a change we can either directly process the stream of modified files or run another already defined Gulp task. In our case we can divide the whole watch process into two separate calls – one watching for JSX files modifications and compiling them and the second watching for modification of any *.js files (but filtering out the generated bundle files) and issuing the bundling process in response.

var watch = require('gulp-watch');
gulp.task('watch', function () {
  watch({glob: paths.src.jsx}, function(files) {
    return files.pipe(react())
      .pipe(gulp.dest(paths.dest.jsx));
  });

  watch({glob: [paths.src.scripts, paths.dest.bundlesFilter]}, function() {
    return gulpClientBundle();
  });
});

At last, we can set our watch task as a default gulp task:

gulp.task('default', ['watch']);

That way, we can simply run >gulp in the root folder of the project to rebuild our client assets on any change.

As to React server bundle, it would be useful to sync its creation with the build of ASP.NET project. We easily set it up in the pre-build event of our .NET project:

/ReactDotNetBrowserify.csproj:

<PreBuildEvent>
    cd $(ProjectDir)
    gulp server-build
</PreBuildEvent>

Wrap up

Building and maintaining larger web applications requires appropriate tools. React may help to reduce complexity in web UI programming. On the other hand in order to streamline development and build process it may be a good idea to reach for some valuable tools coming from Node.js community. One interesting possibility is to combine ReactJS.NET toolkit with Browserify bundles and be able to write modular Javascript, which can be partially reused on ASP.NET server without much maintenance overhead.

comments powered by Disqus