Demistifying Modern Javascript Tools
The goal
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.
Why universal? What does it mean? Do I need this?
The easiest way to create a ReactJS app is just to have an index.html file with ReactJS library included as regular Javascript file.
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.
Modern Javascript tools
My goal was to create a separate frontend app with following characteristics:
- Server side Javascript rendering. So we need a server for this.
- JS scripts written in EcmaScript6 syntax. So we need something to transpile ES6 to ES5 (ES6 is not fully supported in browsers yet).
- Stylesheets written in Sass. So we need something to transpile SASS into CSS.
- All Javascript bundled in one file and all stylesheets bundled in another file. So we need a file bundler of some sort.
- Assets (js, css) minified for production.
- A mechanism to watch for changes and transpile on the fly in development mode, to speed up work flow.
- Something to handle external dependencies.
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:
EcmaScript6 (ES6)
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.
Module definitions
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:
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.
CommonJS
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.
AMD
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:
It will be translated to the chosen standard.
If you’ve chosen CommonJS the above module would be transpiled to:
And for AMD:
Module loaders
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.
Dependencies
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.
Shimming
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.
Task runners
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
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.
Server
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:
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.
Choices
Summing up, here are my choices matched with characteristics that I defined at the begging of this post:
- Server side Javascript rendering – Express as the frontend server
- JS scripts written in EcmaScript6 syntax – transpiling ES6 to ES5 using Babel loaded through Webpack
- Stylesheets written in Sass – transpiling SASS into CSS using sass-loader for Webpack
- All Javascript bundled in one file and all stylesheets bundled in another file – Webpack
- Assets (js, css) minified for production – Webpack
- A mechanism to watch for changes and transpile on the fly in development mode, to speed up work flow – Webpack
- Something to handle external dependencies – npm
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.