Well, the good news is the ecosystem is starting to slow down. Projects are merging. Best practices are starting to become clear. People are building on top of existing stuff instead of building new frameworks.
As a starting point, here’s my personal picks for most pieces of a modern web application. Some choices are likely controversial and I will only give basic reasoning behind each choices. Keep in mind they’re mostly my opinion based on what I’m seeing in the community and personal experiences. Your mileage may vary.
Core library: React
The clear winner right now, is React.
- Components all the way down makes your application much easier to reason about.
- The learning curve is very flat. The important APIs would fit on one page.
- It is the natural match for Flux and Redux (more on that later).
- The React community is amazing, and produced many best of breed tools such as Redux (also more on that later).
- Writing high quality data flow is much easier in large applications than dealing with 2 way data binding (eg: Knockout)
- If you ever need to do server side rendering, React is where it’s at.
There’s plenty of monolithic frameworks like Ember, Aurelia and Angular that promise to take care of everything, but the React ecosystem, while requiring a few more decisions (that’s why you’re reading this!), is much more robust. Many of these frameworks, such as Angular 2.0, are playing catch up with React.
Picking React isn’t a technology decision, it’s a business decision.
Bonus points: Once you start working on your mobile apps, you’ll be ready for it thanks to React Native.
Application life cycle: Redux
Now that we have our view and component layer, we need something to manage state and the lifecycle of our application. Redux is also a clear winner here.
Alongside React, Facebook presented a design pattern for one way data flow called Flux. Flux largely delivered on its promise of simplifying state management, but it also brought with it more questions, such as how to store that state and where to do Ajax requests.
To answer those questions, countless frameworks were built on top of the Flux pattern: Fluxible, Reflux, Alt, Flummox, Lux, Nuclear, Fluxxor and many, many more.
One Flux-like implementation eventually caught the community’s attention, and for good reasons: Redux.
In Redux, almost all of the moving parts are pure functions. There is one centralized store and source of truth. Reducer functions are responsible for manipulating data that makes up the store. Everything is much clearer than in vanilla Flux.
More importantly, learning Redux is a snap. Redux’s author, Dan Abramov is a fantastic teacher, and his training videos are fantastic. Watch the videos, become a Redux expert. I’ve seen a team of engineers go from nearly zero React experience to having a production ready application with top notch code in a few weeks.
One thing to be careful is the natural instinct to try and abstract away the Redux boilerplate. There’s good reasons behind all those pieces. Make sure you tried it and understand the “why” before trying to blindly improve on it.
Language: ES6 with Babel. No types (yet)
Avoid CoffeeScript. Most of its better features are now in ES6, a standard. Tooling (such as CoffeeLint) is very weak. Its community is also rapidly declining.
ES6 is a standard. Most of it is supported in the latest version of major browsers. Babel is an amazing “pluggable” ES6 compiler. Configure it with the right presets for your target browsers and you’re good to go.
EDIT: It was brought up that TypeScript does have things like union types which cover a lot of use cases. I only meant that I think when it comes to type system in UX-land, you go all the way or not at all.
Flow can be much more powerful, catching a wider variety of bugs, but it can be hard to setup. It’s also behind Babel in terms of language features and has poor Windows support.
I’ll say something controversial: Types are not nearly as critical to front end development as some will have you believe (the whole argument will have to be in a future blog post). Wait until the type systems are more robust and stick to Babel for now, keeping an eye on Flow as it matures.
Linting & style: ESLint with Airbnb
You do have to configure it with your style preferences. I highly recommendAirbnb’s styleguide, most of which can be enforced via the ESlint airbnb config. If your team is the kind that will argue on code style, just use this style guide as gospel and the end all be all of arguments. It isn’t perfect, but the value of having consistent code is highly underestimated.
Once you’re comfortable with it, I’d suggest enabling even more rules. The more that can be caught while typing (with an ESlint plugin for your favorite editor), the less decision fatigue you’ll have and the more productive you and your team will be.
Dependency management: It’s all about NPM, CommonJS and ES6 modules
This one is easy. Use NPM. For everything. Forget about Bower. Build tools such as Browserify and Webpack brings NPM’s power to the web. Versioning is handled easily and you get most of the Node.js ecosystem. Handling of CSS is still less than optimal though.
One thing you’ll want to consider is how to handle building on your deployment server. Unlike Ruby’s Bundler, NPM uses wildcard versions, and packages can change between the time you finish coding and you start deploying. Use a shrinkwrap file to freeze your dependencies (I recommend using Uber’s shrinkwrap to get more consistent output). Also consider hosting your own private NPM server using something like Sinopia.
Babel will compile ES6 module syntax to CommonJS. You’ll get a future proof syntax, and the benefits of static code analysis, such as tree shaking when using a build tool that supports it (Webpack 2.0 or Rollup).
Build tool: Webpack
Unless you fancy adding hundreds of script tags to your pages, you need a build tool to bundle your dependencies. You also need something to allow NPM packages to work in browsers. This is where Webpack comes in.
After trying them all, I highly recommend Webpack:
- It is more opinionated yet can be configured to handle even the craziest scenarios.
- All main module formats (AMD, CommonJS, globals) are supported.
- It has features to fix broken modules.
- It can handle CSS.
- It has the most comprehensive cache busting/hashing system (if you push your stuff to CDNs).
- It supports hot reload out of the box.
- It can load almost anything.
- It has an impressive list of optimizations.
Webpack is also by far the best to handle extremely large SPA applications with built in code splitting and lazy loading.
Be warned that the learning curve is brutal! But once you get it, you’ll be rewarded with the best build system available.
But what about Gulp or Grunt? Webpack is much better at processing assets. They can still be useful if you need to run other kind of tasks though (usually you won’t). For basic tasks (such as running Webpack or ESlint), I recommend simply using NPM scripts.
Testing: Mocha + Chai + Sinon (but it’s not that simple)
My criteria for a test framework are as follow:
- It should work in the browser for ease of debugging
- It should be fast
- It should easily handle asynchronous tests
- It should be easy to use from the command line
- It should let me use whatever assertion and mock library I want
The first criteria knocks out AVA (even though it looks awesome) and Jest (auto-mocking isn’t nearly as nice as it sounds, and is very slow anyway).
You can’t really go wrong with Jasmine, Mocha or Tape. I prefer Chai asserts because of all available plugins and Sinon’s mocks to Jasmine’s built in construct, and Mocha’s asynchronous test support is superior (you don’t have to deal with done callbacks). Chai as Promised is amazing. I highly recommend using Dirty Chai to avoid some headaches, though. Webpack’smocha-loader let’s you automatically run tests as you code.
I really enjoy Mocha’s features and support. If you want something more minimalist, read this article about Tape.
Edit 3/11/2016: Since I posted this, Facebook posted this article on how they make Jest scale. Probably a bit too complex for most people, but if you have the resources and don’t care about running things in the browser, you can probably make it awesome.
Additionally, a bunch of people thought I dismissed AVA too quickly. Don’t get me wrong, AVA is amazing. One of my criteria, however, is full browser support, so people can run them straight from any browser (to test cross browser support) and debug them easily. If you don’t care about that, you can use the fantasticiron-node for debugging. Coupled with the bundled power asserts, it would easily beat my Mocha setup.
Utility library: Lodash is king, but look at Ramda
Lodash is by far the king and contains the entire kitchen sink. It is also one of the most performant, with features such as lazy evaluation. You don’t have to include the whole thing if you don’t want to, either: Lodash lets you include only the functions you use (pretty important considering how large it has become). As of 4.x, Lodash also natively supports an optional “functional” mode for the FP geeks among us. See how to use it here.
Http requests: Just use fetch!
Many React applications don’t need jQuery at all anymore. Unless you’re working on a legacy application or have 3rd party libraries that depend on it, there’s no reason to include it. That means you need to replace $.ajax.
I like to keep it simple and just use fetch. It’s promise based, it’s built in Firefox and Chrome, and it Just Works ™. For other browsers, you’ll need to include a polyfill. I suggest isomorphic-fetch, to ensure you have all your bases covered, including server side.
There are other good libraries such as Axios, but I haven’t needed much beyond fetch.
For more details about why promises are important, see my post onasynchronous programming.
Styling: Consider CSS modules
LESS does not suffer from these issues, but has fallen out of favor due to lacking many of Sass’ features.
PostCSS is much more promising, allowing you to kind of “make your own CSS processor”. I’d recommend using it on its own, or even in ADDITION to your preferred processor for things such as AutoPrefixer instead of importing a big library like Bourbon.
One thing worthy of attention though, are CSS modules. CSS modules prevents the “cascading” part of CSS, allowing us to keep our dependencies explicit, and prevents conflict. You’ll never have to worry about overriding classes by accident or having to make ultra explicit names for your classes. It works great with React, too. One drawback: css-loader with CSS modules enabled is REALLY slow, so if you plan on having hundreds of kilobytes of CSS, you may want to avoid it until it gets better.
If I was to start a large project from scratch today, I’d probably just use PostCSS along with pre-compiled versions of my favorite CSS libraries.
Regardless of what you choose, you may want to look at my post on CSS performance with Webpack, especially if you go with Sass.
If you are building a B2C (Business to Customer) website, such as an e-commerce website, you may not have a choice but to go that route. For internal web or B2B (Business to Business) applications however, that kind of initial performance may not be required. Discuss with your product manager to see if the cost:benefit ratio is worth the trouble.
The API: There’s still no true answer.
It seems everyone lately is asking themselves what to do for API. Everyone is jumping in the RESTful API bandwagon, and SOAP is a memory of the past. There are various specifications such as HATEOAS, JSON API, HAL, GraphQLamong others.
GraphQL gives a large amount of power (and responsibility) to the client, allowing it to make nearly arbitrary queries. Along with Relay, it handles client state and caching for you. Implementing the server side portion of GraphQL is difficult and most of the documentation is for Node though.
Netflix’s Falcor looks like it will eventually give us a lot of what GraphQL/Relay offers, with simpler server requirements. It is however only a developer preview and not ready for prime time.
All the well known specifications have their quirks. Some are overly complex. Some only handle reads and don’t cover update. Some stray significantly from REST. Many people choose to make their own, but then have to solve all the design problems on their own.
I don’t think any solutions out there is a slam dunk, but here’s what I think your API should have:
- It should be predictable. Your endpoints should follow consistent conventions.
- It should allow fetching multiple entities in one round trip: needing 15 queries to fetch everything you need on page load will give poor performance.
- Make sure you have a good update story: many specifications only covers reads, and you’ll need to update stuff sometimes.
- It should be easy to debug: looking at the Chrome inspector’s network tab should easily let me see what happened.
- It should be easy to consume: I should be able to easily consume it with fetch, or have a well supported client library (like Relay)
I haven’t found a solution that covers all of the above. If there is one, let me know.
Consider looking at Swagger to document your API if you go the standard RESTful path.
Desktop applications: Electron.
Electron is the foundation of the great Atom editor and can be used to make your own applications. At its core, it is a version of Node that can open Chrome windows to render a GUI, and has access to the operating system’s native APIs without a browser’s typical security sandboxing. You’ll be able to package your application and distribute it like any other desktop application, complete with an installer and auto-updates.
This is one of the easiest ways to make an application that can run on OSX, Windows and Linux while reusing all the tools listed above. It is well documented and has a very active community.
You may have heard of nw.js (formerly node-webkit) which has existed longer (and does almost the same thing), but Electron is now more stable and is easier to use.
Take a look at this great boilerplate to play around with Electron, React and hot reload. You’ll probably want to start from scratch if you’re serious about making your own application so you understand how all the pieces work.