Real-World JavaScript: Rangle.io's Standard Tech Stack

Written by Seth Davenport

standard tech stacks - image

JavaScript is evolving rapidly, with new approaches to application development appearing almost on a monthly basis. As a training firm, one of the services we provide is staying on top of these changes and advising our clients on contemporary thinking.

On the other hand, as a consultancy, we also need to start up new projects frequently and quickly, balancing up-to-date technology with production-level expertise.

This presents us with some specific challenges:

  • We constantly need to help teams get started with React or Angular, typically on short notice.
  • Some of our projects have short time-frames: each team can’t afford to spend time fussing with tools.
  • The JavaScript ecosystem's panoply of tools can make it hard for newcomers to put together coherent toolchains and architectures.
  • Cross-functional teams sometimes struggle to keep up with the pace of change.

As a senior developer and architect at Rangle, I often find myself asking the same three questions:

  1. How do we save time on tooling at the start of each new project?
  2. How do we make sure we keep our best practices up-to-date?
  3. How do we efficiently spread this technical know-how across our scrum teams and course instructors?

A Partial Solution - Documented Best Practices

In the early days of the company, we kept a lot of this knowledge in internal documentation. These efforts attempted to provide guidance for all developers on coding, architecture, and dev ops; it was maintained by our senior 'support squad' developers with input from the experiences of our scrum teams.

This helped a lot when we were a smaller company but didn't really scale well. It's too easy to simply read a document and then forget it, and each scrum team still had to put together their toolchain each time.

We found that scrum teams were solving the same problems over and over:

  • How to minify and bundle for production.
  • How to access APIs on other domains (CORS and proxying).
  • How to run unit tests in our continuous integration system.
  • How to write and run end-to-end tests in our CI system.
  • How to turn on developer tools in dev mode and disable them in production.

And at each step of this process came the slew of ever-changing decisions:

  • ES6? ES5? Flow? TypeScript?
  • Webpack? SystemJS? JSPM? Gulp? Grunt?
  • Flux? Redux? RxJS? BaconJS?
  • Inline styles? SCSS? Less? Radium? PostCSS? CSSNext?
  • ... etc.

Wouldn’t it be better to just let our coders code?

A Better Solution - Standard Tech Stacks

About 6 months ago we changed our approach. We now maintain a small number of 'Standard Rangle Tech Stacks', or 'starter projects' which we use for almost all new projects.

Each of these stacks provides a fully-functional toolchain that's ready to go; letting our developers focus on delivering business value for our clients.

Production build tools are already set up: solutions for minification, bundling and serving over HTTP work out of the box.

Quality tools are already set up: standardized linter rules enforce company code conventions; unit testing toolchains are ready to go and end-to-end (E2E) testing with Selenium (using the Robot framework) is up and running.

Attention has also been given to styling and maintainable CSS.

The starters are compatible with our internal continuous integration and deployment tools to enable true Agile delivery.

Finally, these starters are actively maintained by our senior developers as new ideas prove worthwhile, and as toolchain issues are encountered and solved by our project teams.

Benefits

This approach has had many benefits.

Each new project team now starts with our latest best practice thinking. Their production and debug flows are ready to go, and teams don't waste time on solved problems at the start of the project.

We can set up a scrum team with a repo and a full continuous deployment pipeline in about 10 minutes.

In addition, the starter project's sample app shows new hires “what ‘good’ looks like”: helping support squad maintain the stacks has proven to be excellent training for new hires, and gives them a bird's-eye view of all our common tools.

The Stacks

The standard stacks, while tuned to our business needs, are all open source (MIT license) so the community can benefit and even contribute.

You can access the latest setups from our landing page in git-hub: https://github.com/rangle/rangle-starter.

Each stack is also set up with CI and auto-deployment, currently on Heroku.

Current Tools

Frameworks

Currently, we have standard stacks for React with Babel, and Angular ([1 (https://github.com/rangle/angular-redux-starter) or 2) with TypeScript.

They're all set up with our favourite state-management solution: Redux. Redux is a variant of the Flux architecture which we have found significantly reduces application complexity at scale. It also boasts some first-rate developer debugging tools.

Finally, each stack has an optional, minimal NodeJS /Express HTTP server for production deployments.

Build

All of our stacks use webpack for production minification and bundling. We have found Webpack to handle these tasks with significantly less configuration or code-writing than other tools such as JSPM, SystemJS, or gulp.

Transpilation

Webpack's import/loader model also makes transpilation a breeze.

Transpilation is very much part of current JavaScript practice; the benefits offered by next-generation dialects like ES6 or TypeScript are simply too compelling to ignore. As such, our ES6 starters are transpiled with Babel and our TypeScript starters with Microsoft's TypeScript compiler.

Finally, all our build scripts and our NodeJS servers run on the current long-term support (LTS) version of NodeJS.

Styles

Related to transpilation, we've taken a similar approach to CSS, courtesy of postcss. This helps us solve a couple of persistent issues that have dogged designers and developers alike since CSS was first introduced:

  1. The global nature of styles
  2. Extremely inconsistent browser support

Global CSS in a Component-Oriented World

As modern development converges more and more on atomic design and component-oriented architecture, the global nature of CSS is becoming a huge problem. We can encapsulate the structure (HTML, JSX) and behaviour (JavaScript) of our UI widgets at the component level, but we're still stuck with the presentation attributes being global.

This leads to CSS class namespace collisions, conflicting style definitions across libraries, and huge difficulty maintaining CSS code at scale.

The React community has favoured inline CSS as a solution to this, but that suffers from a couple of issues:

  1. It requires your designers to learn React and/or Angular
  2. It requires us to handle browser incompatibilities and vendor prefixing manually.

Other projects have traditionally scoped their CSS using naming conventions such as BEM, but this requires training and discipline.

Browser Whack-a-Mole

CSS implementations in even major browsers are notoriously fickle. Bugs, vendor prefixes, and conflicting specifications all conspire to make manual cross-browser support a frustrating game of whack-a-mole.

This is something that should not be handled manually, but rather with CSS transformations like autoprefixer.

Having your CSS and Eating it Too

In our case we have opted for an approach to CSS based on transpilation, which is enabled by postcss and webpack. Using various build-time transformations we can import component-specific CSS files which are:

All this and your designers can write component-level CSS files just as they've always done.

Quality

Our starters come with several quality tools baked in. Code linting from eslint (ES6) or tslint (TypeScript) catches common errors at build-time.

Unit tests are run against browser engines using
the Karma test runner; unit tests themselves are written with mocha / chai / sinon (for React and Angular 1) and
Jasmine (for Angular 2).

Finally, in collaboration with our BQA group we have rolled out automated end-to-end tests using a python-based selenium wrapper called Robot. We prefer Robot for its ability to let BQAs write automated tests using a natural-language syntax:

*** Test Cases ***
Valid Login  
    Open Browser To Login Page
    Input Username    user
    Input Password    pass
    Click Login
    Verify user is successfully logged in

Username is empty - Invalid Login  
        Open Browser To Login Page
    Input Password    Pass
    Click Login
    Verify Username Validation  Username is required.

Deployment

Our starters ship with a minimal NodeJS/Express server that's set up to handle the most common production-related mistakes; thought has been given to:

  • Proxying APIs to remote servers
  • Proper error handling
  • Basic XSS protections via HelmetJS
  • Basic request logging.

However you can also deploy statically: npm run build will compile and output the application bundle to /dist; put this folder behind the HTTP server of your choice or upload it to Amazon S3.

Developer Experience

Developer experience is something we take seriously, because of its benefits for productivity. Running npm run dev for any of our starters will enable various developer tools, including:

  • webpack-dev-server's live reload
  • webpack-dev-server's hot module replacement (React only)
  • Redux DevTools (via the Chrome extension)
  • Support for the latest version of Augury (Angular 2 only).

Browser Support

Generally, we'll make an effort to support the earliest browsers supported by the tech stack's frameworks.

In practice, this currently means IE9+ and the last two versions of Firefox and Chrome.

Conclusion

So, that’s a peek at how we do JavaScript at Rangle. We’ve found value in these stacks as:

  1. Living best-practices guides.
  2. Teaching tools for newcomers.
  3. A quick way to set up new project teams.
  4. A playground for experiments.

They are all open source! Discussions, contributions, bug reports and ideas are always welcome.

Resources

Sign up to our newsletter to gain insights on our best practices and processes: