The React ecosystem can be daunting. Let Ania explain how the puzzle of modern javascript tools fit together.
ReactJS has become very popular recently, the community is growing fast and more and more sites use it, so it seems like something worth learning. That’s why I decided to explore it.
There is so many resources and so many examples over the internet that it’s difficult to wrap your head around it all, especially when you are new to the modern frontend stack.
There are examples with ES6 syntax, without ES6 syntax, with old react-router syntax, with new react-router syntax, examples with universal apps, with non-universal apps, with Grunt, with Gulp, with Browserify, with Webpack, and so on. I was confused with all of that. It was hard to establish what is the minimal toolset needed to achieve my goal.
And the goal was: to create a universal application with development and production environments (with minified assets in production).
This post is the first of the series describing my journey while learning modern Javascript tools. It has the form of a tutorial how to create a universal app using bare react, then Flux and lastly Redux.
The easiest way to create a ReactJS app is just to have an index.html file with ReactJS library included as regular Javascript file.
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8" /> | |
<title>Hello React!</title> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.1/react.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.1/react-dom.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script> | |
</head> | |
<body> | |
<div id="example"></div> | |
<script type="text/babel"> | |
ReactDOM.render( | |
<h1>Hello, world!</h1>, | |
document.getElementById('example') | |
); | |
</script> | |
</body> | |
</html> |
It seems easy, so why have I seen example applications which have their own frontend servers? I started wondering why would I even need the server if I can just have a simple HTML file.
And the answer is: sure, you can create a modern dynamic application just by using simple HTML file, but you need to keep in mind that it’s content will be rendered on the client side only.
It means that if you view the page source in the browser, or make a curl request to your site, all you will see is your main div where the app is injected, but the div itself will be empty.
If the above doesn’t convince you, then perhaps this will: Google bots won’t see the content of your app if it’s only rendered on the client side. So if you care about SEO, you should definitely go with a universal app – an app which is not only rendered dynamically on the client side but also on the server side.
To achieve this you need a separate server for frontend.
You can see people referring to these kinds of apps as isomorphic. Universal is just a new, better name.
My goal was to create a separate frontend app with following characteristics:
After looking at many examples on the Internet, my mind looked like this:
I didn’t know what all of these tools do exactly and which of them I needed. E.g. do I need “browser-side require() the Node.js way” if I already decided to use ES6? Do I need Bower if I already have npm? Do I need Gulp at all?
After lots of reading I finally managed to group the tools:
ES6 is the new Javascript syntax, standardised in 2014. Although it’s not implemented in all browsers yet, you can already use it. What you need it to somehow transform it to currently implemented Javascript standard (ES5). If you are familiar with CoffeeScript, it’s the same process – you write using one syntax and use a tool, e.g. Babel to translate it to another. This process has a fancy name – transpilation.
As ES6 introduces lots of convenient features which will soon be implemented in browsers, in my opinion there is no need to use CoffeeScript right now. That’s why I choose to use ES6.
One of many convenient features of ES6 is the ability to define modules in a convenient and universal way.
Javascript didn’t have any native mechanism capable of managing dependencies before. For a long time the workaround for this was using a mix of anonymous functions and the global namespace:
// calculator.js | |
(function(root) { | |
var calculator = { | |
sum: function(a, b) { return a + b; } | |
}; | |
root.Calculator = calculator; | |
})(this); | |
// app.js | |
console.log(Calculator.sum(1, 2)); // => 3 |
Unfortunately, it didn’t specify dependencies between files. The developer was responsible for establishing the correct order of included files by hand.
As you can suspect, it was very error prone.
That’s why the CommonJS committee was created with a goal to create a standard for requiring modules.
It was implemented in Node.js. Unfortunately, this standard works synchronously. Theoretically it means that it’s not well adapted to in-browser use, given that the dynamic loading of the Javascript file itself has to be asynchronous.
To solve this problem, a next standard was proposed – Asynchronous Module Definition (AMD).
It has some disadvantages, though. Loading time depends on latency, so loading dependencies can take long too.
Incoming HTTP/2 standard is meant to drastically reduce overhead and latency for each single request, but until that happens, some people still prefer the CommonJS synchronous approach.
While setting up Babel you can choose which module definition standard you want to have in the transpiled output. The default is CommonJS.
So when you define you module in new ES6 syntax:
// calculator.js | |
export function sum(a, b) { | |
return a + b; | |
}; | |
// app.js | |
import calculator from "calculator"; | |
console.log(calculator.sum(1, 2)); |
It will be translated to the chosen standard.
If you’ve chosen CommonJS the above module would be transpiled to:
// calculator.js | |
module.exports.sum = function (a, b) { | |
return a + b; | |
}; | |
// app.js | |
var calculator = require("calculator"); | |
console.log(calculator.sum(1, 2)); |
And for AMD:
// calculator.js | |
define({ | |
sum: function(a, b) { | |
return a + b; | |
} | |
}); | |
// app.js | |
define(["./calculator"], function(calculator) { | |
console.log(calculator.sum(1, 2)); | |
}); |
Having standards for defining modules is one thing, but the ability to use it in the Javascript environment is another.
To make it work in the environment of your choice (browser, Node.js etc.) you need to use a module loader. So module loader is a thing that loads your module definition in the environment.
There are many available options you can choose from: RequireJS, Almond (minimalistic version of RequireJS), Browserify, Webpack, jspm, SystemJs.
You just need to choose one and follow the documentation on how to define your modules.
For example, RequireJS supports the AMD standard, Browserify by default CommonJS, Webpack and jspm support both AMD and CommonJS, and SystemJS supports CommonJS, AMD, System.register and UMD.
Your app usually depends on some libraries. You could just download and include all of them in your files, but it’s not very convenient and quickly gets out of hand in larger projects.
There are a few tools for dependency management. If you use Node.js, you are probably familiar with it’s package manager – npm.
Another very popular one is Bower.
Since I needed to use Node.js to implement the frontend server, I decided to go with npm.
In npm, all libraries are exported in the same format. But, of course, it can happen that the library you want to use is not available via npm, but only via Bower.
In such chase remember that some of the libraries may be exported in a different format than what you’re using in your application (e.g. as globals).
In order to use those libraries, you need to wrap them in some kind of adapting abstraction. This abstraction is called a shim.
Please check your module loader documentation how to do shimming.
If you use npm you can define simple tasks in your top-level package.json file.
It’s convenient as a starting point, but if your app grows it may not be sufficient anymore. If you need to specify many tasks with dependencies between them, I recommend one of popular task runners such as Gulp or Grunt.
Template engines are useful if you need to have dynamically generated HTML. They enable you to use Javascript code in HTML.
If you are familiar with erb you can use ejs. If you prefer haml, you would probably like Jade.
Last but not least I need a server. Node.js has a built-in one, but there is also Express.
Is Express better? What is the difference? Well, with Express you can define routing easily:
var express = require('express'); | |
var app = express(); | |
app.get('/', function (req, res) { | |
res.send('Hello World'); | |
}); | |
app.listen(3000); |
It looks really good, but I’ve also seen many examples using routing specific to ReactJS – implemented with react-router.
I wanted to use react-router too, as it seems more ‘ReactJS way’. Fortunately there is a way to combine react-router with Express server by using match method from react-router.
Summing up, here are my choices matched with characteristics that I defined at the begging of this post:
Additionally I chose Ejs for the layout template and since I’m using npm and Webpack we don’t really need to bother with grunt or Gulp task runners.
But of course, you can choose differently since there is a lot of other combinations:
Now that we know what we want to use, in the next post we will move on to creating the app. See you next week!
Update: Here is the next post.
We don’t just build software, we understand your business context, challenges, and users needs so everything we create, benefits your business.