Building modular javascript applications in ES6 with React, Webpack and Babel

Tim Ermilov
Towards Data Science
10 min readMay 4, 2015

--

So, you have decided to build your new javascript application using React and you also want to use that new fancy ES6 syntax in it. In addition you want to create reusable components and publish them to NPM. How exactly can you do this today? How do you publish ES6 to NPM and use it in your project later? It took me a while to nail all the bits and pieces, so I thought I’d share my knowledge here.

If you don’t really want to read all the details and just want to see code — head straight to the last section.

Requirements

Let’s first figure out what exactly we want to do and how.
Our main target is to have a React application that is written in ES6 and uses our custom React components. The tricky part comes with custom React components. Here are the requirements for them:

  1. Custom components should also be written in ES6
  2. Custom components should be completely self sufficient (usage should be as simple as “install, import, render”)
  3. Custom components should provide their own styles (because of 2)
  4. Custom components should be installable via NPM (because bower has a lot of issues)
  5. And finally — all custom components should have tests and code coverage reports.
  6. Bonus points — custom components should not depend on each other, but should be able to interact

Additionally, we want to have nice things like code linting and source maps for debugging (since we have a compile step, this is actually a must).

Dealing with the requirements

Now that we have all our requirements, we can start figuring out how exactly we can fulfill them. So, let’s decide on a toolset and libraries we’re going to use.

Compilation

Since we are going to use ES6 and browsers do not yet fully support it, we will need a compilation step. And because we want to manage our components using NPM, we want a CommonJS compatible toolset.
There are two popular options available — Browserify and Webpack. Both will allow us to do most of the things we need, but we’re going to stick with Webpack since it’s quite much simpler to work with non-javascript files in it (e.g. styles, images, fonts). And working with styles was one of our requirements.
We could stop here and just use a Webpack, but we are going to add Gulp.js on top just to have a bit more flexibility in tasks we can perform later (e.g. unit testing, test coverage, serving static assets).

Using ES6

Now that we know how we will build our code, we need to figure out how to fulfill our first requirement — using ES6. There’s a number of ES6 transpilers we could use, most popular are: Traceur, Babel.js, TypeScript (well, kinda). But we’re going to stick with Babel.js since it has a better ecosystem (plugins, extensions, etc). Babel is a transpiler that allows you to use ES6+ code today and conveniently transpile it to ES5 that’s supported now by majority of browsers. If you haven’t heard about it, have a look at their docs — the project is pretty amazing. And as a bonus, in addition to ES6 syntax Babel also compiles JSX, so we can get rid of standard React JSX compiler in our compilation process.
If you just want to use Babel.js, I’d recommend reading their usage docs. There’s as well a great post on using it from Dr. Axel Rauschmayer (I’d recommend his blog in general as well, if you are interested in javascript).

Being self sufficient

Now that we have decided on the ES6 transpiler, let’s talk about being self sufficient. If we’re talking about pure React components — it’s relatively easy to make something that can work as a standalone thing (being reusable). There’s even an official guideline for that. But what happens when we need to provide default styles?
Of course, we can take “pure JS” kind of approach and just inline styles into our JSX like so:

Inline styling of JSX

The problem with this approach is that it’ll be almost impossible to change that style later from the parent application if we decide to.
Plus, this approach won’t work if we would need to include images or something like custom fonts. So, what’s the better way then?

Webpack to the rescue! Luckily for us Webpack allows requiring pretty much any types of files using loaders. Idea behind loaders is pretty simple — they are pluggable transformations that are applied to the files during loading. Essentially, they preprocess files. We will use a special babel-loader to transpile our ES6 code to ES5.
But the cool part here is that the loaders can work on any files. So, if we want to include styles — we just need to add style-loader! I decided to go with LESS, so it’s a bit more complicated —I’d created a chain of loaders:

  • First *.less files are processed by less-loader and converted to CSS
  • Then compiled css is processed by css-loader and passed to next step
  • Finally style-loader will include the final style into resulting code

The code below shows the entry point of the component with LESS styling:

Importing styles right into javascript with some help from webpack

As you can see, including styles is as easy as importing a file. Of course, that chain from less-loader, css-loader and style-loader must be already configured in our webpack configuration file (see example one in the next section).

One of the issues with import styles from separate components is the fact that CSS uses global variables by default. Which means there’s no scoping. Which means that if you have two components that use similarly named classes, one of them will get overwritten. How do we evade this?
The simplest way I could figure out was to create scopes artificially by using a component name as a topmost class in the markup and in the style file.
Like so:

Artificially scoped styling of the component

That works pretty well since all the components will have unique names, which means there will be no conflict once they are assembled into one application. Plus, it’s quite easy to re-style the components using the component name as a class from the application level styles.

Publishing to NPM

So, how do you publish ES6 modules to NPM? Well, you actually don’t. At least not yet. It is possible to just push your code out there, but it is generally a bad idea and you will have a hard time importing that code into your main application (or other components) later. Of course, it’s possible to say to Webpack (and to Browserify as well) to compile the code on require. But then you’ll have problems running tests unless you use Webpack (and browser) for testing too. I usually try to run tests in cli since it’s usually faster and simpler to automate later (say, when you are setting up CI).
The best way that worked for my projects so far was to ship both — original ES6 code and compiled ES5 code. Doing that is pretty easy:

  • Add build command to your package.json
  • Run build command as pre-publish step
  • Use compiled ES5 code as main file
  • Expose ES6 code through additional package.json fields

Here’s how it looks:

package.json for publishing ES6 module on NPM

Using this approach gives us a couple of advantages:

  1. We can easily import or require() this module in any ES5 code and it will work!
  2. If we for some reason need access to the module’s source code, we can easily tell Webpack to use “es6” field for requiring and import the ES6 code

Testing and coverage

You’d be surprised how easy it is to set up tests and coverage for babel (great ecosystem, as I’d already mentioned).
I’d always used mocha for all my testing needs, so I’ll be talking about it here. All you have to do to support ES6 code in mocha is to tell it to use babel pre-compiler by adding a compilers flag to mocha execution (or to mocha config file):

--compilers: jsx?:babel/register

And you are set, tests now can run directly on your ES6 code and of course can be written using that fancy ES6.

And here comes the tricky part — we have React components and we want to run tests using mocha without having to open a browser (or to spin up a PhantomJS which is quite heavy). How do we do that?
The answer is jsdom — a javascript implementation of the WHATWG DOM and HTML standards for iojs. It’s much lighter than Phantom and it gives pretty much all we need for testing React.
Here’s how the test helper file that sets up a jsdom environment with React looks:

jsdom environment for running React test on CLI

Note that I’d used import statements for jsdom and localStorage, but used require() for React. This is because import statements are hoisted and we need to make sure that React is required after we have initialized jsdom. If you try to require React before having DOM, it’ll work OK until you try to interact with your components. Then you’ll run into various errors because React assumed non-browser environment and couldn’t work with DOM.

And now that we have mocha test running properly, getting Istanbul test coverage is as easy as executing:

istanbul cover _mocha -- -R spec

Component Interactions

And here’s our bonus requirement — interaction but without inter-dependency. This one is quite unique to one of the projects I’m working on, so if you are not curious, feel free to skip this section.

If not, then let’s talk about ways of achieving this.
So, we need to make several parts of system interact without coupling them together. In a perfect world we’d prefer that they don’t even know about each other and just respond to requests. That task sounds pretty familiar, doesn’t it?
You’ve probably heard about microservices by now. Or maybe you even use them already. If not, I’d recommend watching fantastic talk on the topic by Fred George.
If you are too lazy to watch (or want to do it later), the idea of microserivces is a simple one: microservices are small, independent entities communicating with each other using a common interface. That common interface can be a variety of things including: messaging bus, REST, RPC, etc.

And since we’re doing client-side javascript application, we don’t really have too much of a choice for that interface. Luckily for us, there is an awesome library that feels like it was made specifically for this case — postal.js.
Even though under the hood it still uses callbacks, it provides a great deal of flexibility by allowing to split messages into channels and topic.

It’s easier to show how all this will work using an example.
Imagine we have an app that requires some sort of authentication and URL signing for getting data. With postal we can define auth channel that will listen to those signing requests and return signed URL in different topic, like so:

Using postal.js channels for communication between components

This works pretty well because we don’t need to know anything about other component — we only need to know about the channel and topics to use. On one hand this does means that burden of managing this falls on to developer. On other hand, this approach allows to swap the auth component with ease — today we’re using OAuth, tomorrow our custom token system, then something else, and all we need to do to support a new workflow is to swap that auth component for a different one.

Code linting and source maps

And the last tiny bit is adding code linting and enabling source maps.
Since jshint does not plays well with ES6 and React code, we’re going to use eslint. It supports both of the syntaxes and can be extended with plugins.
Using it with Webpack is quite easy, all you have to do is to add a couple of lines to your webpack config file. The snippet below shows the needed options:

Note that node_modules folder is excluded from linting, so only your actual component code will get through the procedure.
Webpack will output all the linting problems to the console after the compilation process is finished.

Turris.js — putting it all together

After combining all of the things mentioned above, I’d created Turris.js — set of helper packages and a yeoman generators to easily scaffold ES6 React applications and standalone components.

If you are not interested in generators and just want to see example code of the application and standalone component, you can find both in their github repositories: turris-example-application and turris-example-component.

Creating new application

I’d tried to make creating new applications as simple as possible. Here’s what you need to do to create one:

  • Make sure you have latest io.js and npm installed.
  • Get yeoman and turris-generator from npm:
$ npm install -g yo generator-turris
  • Create a new folder for your app, enter it and execute turris generator:
$ mkdir HelloWorld
$ cd HelloWorld
$ yo turris
_-----_
| | .--------------------------.
|--(o)--| | Welcome to the |
`---------´ | breathtaking Turris |
( _´U`_ ) | generator! |
/___A___\ '--------------------------'
| ~ |
__'.___.'__
´ ` |° ´ Y `
? Your project name: (HelloWorld)
  • Answer a couple of questions and yeoman will do all the work for you!
  • Start your new shiny app with “npm start”
  • Navigate to http://localhost:8080, open your favourite editor and start changing it to your liking

In addition to scaffolding a basic application, Turris generator provides three helper generators:

  1. Component generator — will generate a new component inside of your app. Handy for small components that you are not going to re-use.
  2. Page generator — will generate a new page and inject it into react-router for you. Nothing fancy here, just a little subgenerator to save your time.
  3. Docker generator — will generate a dockerfile that has all the needed things for running the app inside.

For more information on usage, subgenerators, project structure and that other stuff — refer to readme in the project repository.

Creating a standalone component

Creating a component is not much harder. Here’s how to do it (requirements are the same as for the main generator):

  • Get turris-generator-component from npm:
$ npm install -g generator-turris-component
  • Create a new folder for your component, enter it and execute turris component generator:
$ mkdir HelloWorld-Component
$ cd HelloWorld-Component
$ yo turris-component
_-----_
| | .--------------------------.
|--(o)--| | Welcome to the |
`---------´ | stupendous |
( _´U`_ ) | TurrisComponent |
/___A___\ | generator! |
| ~ | '--------------------------'
__'.___.'__
´ ` |° ´ Y `
? Your component name: (HelloWorld-Component)
  • Answer a couple of questions and yeoman will do all the work for you!
  • Start your new shiny component in debug mode with “npm start”
  • Navigate to http://localhost:8080, open your favourite editor and start changing it to your liking

For more information on usage and project structure and other stuff — refer to readme in the project repository.

The end

I hope you have found something useful in this text. And maybe you even tried my generators. If not — give them a shot! If yes — I’d love to hear any feedback or issues you might have. And of course, pull requests are appreciated.

--

--