Full Stack JavaScript Nightmare

JavaScript in a Nutshell

At the beginning, JavaScript was introduced for client-side scripting for the Web. JavaScript is based on ECMAScript (ECMA: European Computer Manufacturer's Association) standards. ECMAScript is the language, and JavaScript is a dialect.

Versions of JavaScript

Being part of the top 10 most used languages[*], JavaScript is not immune to the pressure of change by its developers. Developers who have previously worked with other languages expect to be able to write the code in the same way in every language, due to their lazy nature. Language adoption is much easier if developers don’t have to completely restart to learn when they switch languages.

One piece of evidence of this is the concept of class. JavaScript is prototype-based, but developers are used to classes, so the keyword class was added. It's not a real class, it's just a sugar syntax.

When it comes to JavaScript versions, nowadays, we would refer to ES5 (ECMAScript 5), ES6 or[*]. Although, ECMAScript was first based on JavaScript.

Browser Support Hell Matrix

Once we get into understanding how many versions of JavaScript exist, and mix that with the different implementations and versions of browsers available, we can easily see how this becomes a matrix of hell. It is very important to understand which browsers you intend to support in order to know what can or cannot be used in your code. To help us, many libraries are taking this matter into their own hands. Libraries like underscoreJs or lodash offer the developer a single api, and the library will use native code if it is supported, or emulate it if not. Another useful library is Modernizr, which allows the developer to detect what feature is available on the user browser.

NodeJS, the missing piece

For a very long time, JavaScript was something that ran on the browser (front-end) and gave the option to make a page dynamic. The full stack developers were required to write JavaScript to get the browser interactivity, and then would have to choose another language to use on the server (back-end). NodeJS is the answer for the full-stack JavaScript developer. NodeJS is a runtime build of Chrome's V8 JavaScript engine. It is now possible to use node (just like python or ruby) in a shell and get the work done.

With this, came the nightmare of managing the code. Just like other languages, NodeJS has its own package manager (npm). npm is to node what pip is to Python or rubygem is to Ruby. To be able to manage per project dependencies, NodeJS also has nvm to manage versions, like virtualenv for Python or rvm for Ruby.

Although it is possible to write JavaScript in any version (ES6 for instance), the developer needs to take into consideration if the code will be run on the server-side or on the client-side. JavaScript is not a compiled language (we distribute the source code, not a binary version of it). To be able to write the code in the latest version, but run on a browser, the developer needs to "transpile" the code with tools like BabelJS (although it claims to be a compiler, it is in fact a transpiler).

Grunt/Gulp to the rescue

Because life (or projects) is a repetition of tasks, Grunt and Gulp are tools that use plugins to enable the developers to do common tasks like linting, minifying, uglifying code, or run tests. This allows repeatable tasks to offer a greater assurance of quality before committing code to the version controlling repository.

Webpack to the rescue

Many developers who focus on the front-end are now using tools like Webpack to do most of the tasks that were done with Grunt/Gulp plugins. One thing I would comment on this tool is that webpack is focused for the web, and it doesn't really make sense to use it for server-side code. Webpack can be used with Grunt/Gulp to transpile/uglify/minify/bundle the web portion of the code, instead of replacing Grunt/Gulp, but it is not as useful for the server-side code.

Continuous Integration

Working as a team, or as many teams on a single code base, will prove difficult to make sure everything works as it should. Even with as many precautions as possible, it's always possible that we are testing on uncommitted code, on the wrong branch, with manually added, outdated code, or modified config files that are on our local development system. Continuous integration is a practice where committed code is regularly checked to allow early error detection. We can use automation tools like Jenkins to help.

Ideally, a continuous integration environment should replicate the production environment as close as possible. Developers are on equipment with a variety of operating systems, versions of those operating system, and even versions of tools. This situation will potentially introduce issues like files not accessible in production because the developer was using a case-aware system, instead of a case-sensitive one.

Even after all this setup, one thing many developers forget is that even if NodeJS is supposed to be able to run on many operating systems, NodeJS also uses other libraries, and those could be required to be compiled. It is important to use the continuous integration system to create the artifacts that will be used in production.

Lessons Learned

As much as developers want to use the latest and greatest tools, the most important part is to keep everything maintainable and clear for anyone, including yourself, when you need to fix a bug many months from the time you wrote that code.

It is also hard to find the complete toolset that was available for what we used a few months back, and equivalent tools for the new and shiny toys. This does not only apply to new tools, but also to new version of a given tool.