[转] 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:
// 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:
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.
Previously in the adventures series
In the last post we decided to use following tools:
- Server side Javascript rendering – Express as the frontend server
- JS 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
- To minify assets (js, css) for production – Webpack
- A mechanism to watch for changes and transpile on the fly in development mode, to speed up workflow – Webpack
- Something to handle external dependencies – npm
Now we’ll learn how to set them up.
Idea
We will be creating a simple application for rating submissions. This is a really simplified version of the application we used for evaluating submissions for a RailsGirls event.
We need a form for creating new submissions:
We will display pending, evaluated and rejected submissions in separate but similar listings. All listings will have “first name” and “last name” columns, evaluated submissions will additionally have a “mark” column and rejected will have a “reason” column.
The last view that we need is the detailed submission view with the rating.
Dependencies
Firstly, let’s create package.json with the application dependencies:
Take a look at the ‘scripts’ key, it’s where we define the application tasks:
- babel-node – to be able to write server.js file in ES6
- start – for starting the server in development mode
- build – for building production assets
- production – for starting the server in production mode
To install the specified dependencies run npm install from the console, in the project directory.
To start the server execute npm start.
To run in production mode execute npm run build first and then npm run production.
Server
As you can see, we are running the server by executing server.js. We need to create it then:
Now let’s understand what different parts of this code do. This line creates an Express application:
Which we’ll configure later:
And then we start the actual server:
Index
By default Express looks for the view to render in the views directory, so let’s create our index.ejs there:
There are two important things going on here. Firstly, this is the div where all of our app will be injected:
Secondly, we attach bundle.js (and bundle.css) only in development:
It’s important to do it only for development because in production we’ll have our assets minified with fingerprints (e.g. bundle-9bd396dbffaafe40f751.min.js). We’ll use the Webpack plugin to inject javascript and stylesheet bundles for production.
Webpack
Development config
We included bundle.js, but we don’t have it yet, so let’s configure Webpack. Create a webpack directory and inside add the file development.config.js:
- entry – defines entry points, the places from which Webpack starts bundling your application bundles (see the actual value in the shared config below – two entry points, one for stylesheets, one for javascript)
- output – defines where the output file will be saved, how it will be named and how you can access it from the browser
- module – defines loaders (for transpiling ES6, sass, etc.)
- plugins – defines plugins (e.g. we use ExtractTextPlugin to extract the stylesheets to a separate output file)
Some parts will be shared between development and production, so I extracted them to default.config.js:
As you can see, here we configure:
- how our bundle will be named,
- on which port our server will start,
- where our static assets will be served from (we use it in server.js),
- entries which are the starting points for bundling,
- loaders which we want to use:
- babel-loader for ES6,
- css-loader for ExtractTextPlugin
- sass-loader for Sass
Production config
As I mentioned, for production we want assets to be minified and attached in HTML with fingerprints. That’s why we need a separate config:
Entry points
We specified entry points to our application as: src/application.js, css/application.scss – but we don’t have them yet. Let’s add them!
Create application.scss in the css directory:
Also download these two css files and save them in the css directory: main.scss, normalize.css. Then create an application.js file in the src directory:
This file is the entry point for our client side application. Notice the render method – it’s responsible for injecting your component tree into the specified element. For us, it’s the div with “app” id.
Routes
In application.js we imported the routes.js file that we don’t have yet.
Let’s create only two routes for now:
This means that when we go to /submissions/new, the SubmissionFormPage component will be rendered. But notice that the route is nested in the / route, which is assigned to the Main component.
It’s because we want Main to be some kind of layout component, with the menu, which will be visible all the time.
And all its child routes will be rendered inside the Main component thanks to the this.props.children directive:
And in SubmissionFormPage we would have the actual form:
Create the above components in src/components directory. As you can see, each ReactJS component has a render method which defines the HTML to be rendered. It’s not pure HTML, it’s HTML in Jsx syntax, to make it easy to write HTML in Javascript code.
Connection to API
In the above file, you could also notice that when submitting the form we make a request to the backend API. We will use Axios to do this. Let’s create src/lib/Connection.js:
Displaying submissions
To check if everything works, it would be convenient to be able to see the pending submissions list, so let’s create PendingSubmissionsPage:
As you can see here, in componentDidMount we load submissions from the API and assign them to the local component state. Then we pass them to the SubmissionsList component which is responsible for rendering the table. SubmissionsList:
Backend
To have some kind of backend, you can clone and setup this very simplified backend app. Just follow instructions in the README.
Starting the app!
Now we can finally test if everything works. Run npm start in the console, and go to http://localhost:3000 in your browser.
Rating
Now we can implement the rating feature itself.
Let’s add SubmissionPage:
Again, in componentDidMount we load particular submissions from the API and assign them to the local component state. But the most important part is this:
We pass performRating handler as props to the Rate component:
And again pass performRating further, to the RateButton component, where we have actual rate value defined.
Here, finally, we have it bound to the onClick event because only here do we know the particular value for a rating – this.props.value
Thanks to that, when a user clicks a rate button, the performRating method defined in SubmissionPage is called and a request to the API is made.
Let’s add a route to the src/routes.js to be able to access the view:
That’s all!
We just created a simple application using bare React.
The important thing to notice is that we hold the state of the app in
many places. In a more complicated application, this can cause a lot of
pain :)
In the next post, we’ll update our app to use a more structured pattern for managing the state – Flux.
For now, you can practise a bit by adding the missing EvaluatedSubmissionsPage and RejectedSubmissionsPage.
The full code is accessible here.
See you next week!