Use Gulp but want React Hot Module Replacement? Here’s how

Share the joy
  •  
  •  
  •  
  •  

At JustGiving we use Gulp in our newer microsites to handle front-end build tasks. On the continuous integration (C.I.) server for production we run an npm run build script which kicks off a gulp build task. In the microsite’s gulpfile, the production build task is usually a subset of development build tasks. Generally we either run the same Gulp tasks in production as we do for development, working locally from minified code using sourcemaps for debugging, or fork build tasks to an environment using a Gulp --type dev flag. Our newer sites make heavy use of ES6 using Browserify as our default Javascript module resolver, with a Babelify transform and for development a Watchify wrapper. In my current team I’m working on a rebuild of our donation checkout – a fairly standard eCommerce checkout flow. The application is being rebuilt from scratch, using React and Redux in the client, running off a HAL API on the server. Initially our standard gulpfile worked perfectly on this project, with Babelify handling our JSX compilation out of the box. The only change we had to make to the build was to switch our default test runner from Jasmine to Mocha due to some Jasmine async weirdness around React’s test utils Simulate method, and this was a simple one-line change to our Karma conf.

However, as we moved from building React view wireframes to wiring in state, it became clear that React’s Hot Module Replacement (HMR) would give us a decent development boost. We wanted to take advantage of HMR*, most commonly used as a Webpack plugin, but without significantly changing the footprint of our current Gulp build setup, either locally or on the C.I. server. This post outlines what we changed to achieve this.

* Although Browserify can integrate HMR, given the current standard of Webpack as the module resolver of choice for React projects, I opted for integrating Webpack into our current Gulp build over integrating HMR with Browserify.

Original Gulp setup: JS module bundling and localhost server

Our original Gulp setup used Browserify to bundle Javascript modules (run twice – once for each bundle required) under two gulp modules:*-js tasks. These tasks used a Gulp environment flag (--type dev) to output minified or non-minified JS bundles with sourcemaps into our build/js directory from which both the localhost and the production server deploy the javascript bundles. We used Gulp connect as a localhost server, serving from build/ as the root.

Webpack integration: what we changed

The first change we need make to our build is to replace our Browserify Javascript build task with a Webpack build task so we can access Webpack’s HMR feature. Second, because Hot Module Replacement depends on Webpack’s webpack-dev-server, we need to swap our Gulp connect task with a webpack-dev-server task. And finally, because webpack-dev-server does not write its bundles to disk we need a second Webpack task that writes bundles to disk for our Gulp production build. To configure our Webpack builds we need to create awebpack.config.js. In theory this should be all the code we need to get our Webpack with HMR build in place without changing our gulpfile’s public API.

This would be the end of this post were it not for two small issues with this setup. The first is that the flags we need to pass to Webpack to opt in to HMR do not work reliably when passed in via webpack.config.js. Instead, for whatever reason, we must pass them via Webpack’s command line interface. Second, the HMR setup requires an hmre Babel preset which we must add to our .babelrc file. But this throws errors when we run up our unit tests in Karma, because Karma uses the same .babelrc file for the test transforms without having access to the preset. Luckily there is a simple way to fix both these issues.

In our package.json we set up a new task under the scripts property which we have namedwebpack:dev. This task kicks off the development Webpack build using the required inline andhot flags for HMR (we keep the rest of the Webpack config in webpack.config.js). As a bonus,npm passes the task name through the node build chain, including to Babel, as an environment variable. This means we can now set an environment property in our .babelrc limiting the hmrepreset to the to webpack:dev environment, keeping it away from our unit tests. We can also use this same variable to fork our webpack.config.js for development and production builds. From a development point of view, the primary change is that, in addition to running our gulp --type dev task we must now run up a second task npm run webpack:dev. Since both tasks watch, I run up each in separate shell tabs. We also keep our tests running as we develop, but we have always kept this process separate from the Gulp Javascript watch task since it significantly slows on-the-fly rebundling, so we have a third shell tab running gulp test:dev.

From the C.I. server’s point of view, nothing has changed because we only needed to swap the Browserify task under gulp modules:*-js with a gulp-webpack task. Our final build setup is shown below.

  • The .babelrc file now contains an environment-specific HMR preset

  • The package.json runs a two scripts, the original build task used by the C.I. server and the new webpack:dev task with the inline and hot flags passed in, used in development

  • We create a webpack.config.js with a common config, then merge in environment-specific config for export

  • The gulpfile is now heavily simplified because the Browserify task is swapped with a production-only webpack bundle task whose config is stored in webpack.config.js

And that’s it! If you’re starting a project from scratch and have no build tooling constraints you might prefer to use Webpack to handle all your build tasks rather than split them across Webpack and Gulp. That said, Webpack and Gulp are not competing concerns and having Gulp available in your development stack is always going to be handy. As a final note, the setup described above only features React HMR. Webpack can also be set up to handle CSS reloads in the same way and many more features.


Share the joy
  •  
  •  
  •  
  •  

About the author

Leanne T

Senior Front-End Developer

View all posts

Leave a Reply

Your email address will not be published. Required fields are marked *