Skip to content
This repository has been archived by the owner on Dec 11, 2021. It is now read-only.

Latest commit

 

History

History

tooling

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

Tooling Recommendations for JavaScript Projects

This is an overview of tools you may use in your project and (rather opinionated) guide to choosing the right set of options.

tl;dr

It Is Dangerous To Go Alone, Take One Of These

Package Managers

When it comes to package management in JavaScript, there is npm (which isn’t an acronym for “node package manager”) and then there are others. You will use npm to install all Node-based tools, including other package managers. However, it is perfectly okay to use npm for front-end dependencies (i.e. for browsers) – it is even officially endorsed.

npm packages are “CommonJS modules:” every module requires dependencies and exports stuff to be consumed by other modules. Thus your code has an obvious dependency graph and you don't need to pollute global namespace with your functions. The only disadvantage of npm is that you need to bundle your modules to be consumed by browsers, even while you are developing the application. There are more details in separate section.

Consider Yarn

Yarn is a new dependency manager originally started by Facebook. The awesome thing about Yarn is full compatibility with npm. It works with package.json and it downloads all the npm packages you need. In addition, it has also following advantages:

  • It is very fast,
  • It has nicer CLI (for example yarn add works like npm install --save, saving you a few keystrokes),
  • It manages a lockfile (yarn.lock) for reliable and deterministic dependency management.

The yarn.lock file contains exact version and checksum of installed packages. This way the whole team has the exact same versions of dependencies and the deployments are safer.

You will likely use npm for global package installation or for npm run; these functions are supported by Yarn but may behave a bit differently. However, for project-level package management Yarn is strongly recommended.

Avoid Bower

Another popular package manager specifically for front-end projects is Bower. It is strictly a dependency manager: it will just download your dependencies to bower_components directory and the rest is up to you; usually you will place <script> into your HTML files for every dependency you want to use. This is fine for very small projects, but it can get out of hand very quickly.

  • Dependencies between packages are not explicit, you need to manage them yourself,
  • it won't help you to split your code into multiple modules,
  • since there is no dependency graph, you will usually resort to passing dependencies through the global space and/or by modifying global objects (popular with jQuery plugins),
  • for code minification and concatenation you will end-up with some crazy magic comments.

On the other hand, Bower does not require you to bundle your code and does not assume anything about your modules, which gives you more freedom. It is still better than manually downloading JS files and placing it into your repository, but only marginally. If you have only a few dependencies or you want to avoid compile step, you may as well use libraries from CDN.

Recomended Reading

Module Bundlers

Once you decide to divide your code into neat modules or tap into npm’s huge registry, you will need to decide how to deliver your code to browsers.

Node.js supports CommonJS by default, so your server-side code will work out of the box. However, browsers don't understand require or exports in your modules. This is where module bundler comes to play: it will load your entry file, traverse your dependency graph and spits out a single JS file, bundle, ready to be served to browsers.

Webpack

Webpack is the most popular and mature solution these days. It handles all assets, including CSS files and images. Webpack takes a “batteries-included” approach: it has a specific ecosystem and it is less composable than Browserify, but handles common use cases more easily. The possible issue with Webpack is a tight coupling with your code; since Webpack encourages you to import everything to your JS, including CSS files, the code won't work without compile step in Node. This is especially visible with its overloading of require by specifying loaders directly within the code. However, this practice is discouraged and not used much in practice; the recommended way is to specify your configuration in webpack.config.js file outside of your code.

On the other hand, Webpack brings some significant advantages:

Browserify

For smaller and JS-only projects consider Browserify; it’s very simple to use, can be nicely extended and plays well with existing Node ecosystem. While developing project, you may want to use Watchify, which monitors changes in your files and rebuilds the bundle for you.

Recommended Readings

Build Tools

Now that you have some tools in your toolbelt, you surely need a task runner to manage are your build needs! Choose yours: Grunt, Gulp, Broccoli? Or how about Mimosa, Gobble, Brunch, Jake, Cake, Gear.js or Fly?

Well, you don't need any of these. Use npm scripts.

Probably the main promise of these build tools is an easy composability; you can combine your transpiler with code minification, copy assets, publish packages etc. from one place. No need to hunt for commands with long, complicated arguments.

However module bundlers do most of that for us already; in the end, your fancy task runner is just a verbose way to pass configuration options to Browserify or Webpack.

Put commands you run often into "scripts" section of your package.json, for example:

...
"scripts": {
  "test": "tap test/*.js",
  "build": "npm run build:js && npm run build:css",
  "build:js": "browserify index.js -o dist/index.js",
  "build:css": "cp index.css dist/index.css",
  "watch:js": "watchify index.js -o dist/index.js"
  ...
}

You can run these scripts with npm run <script_name>, e.g. npm run build.

If you need something complex the bundler won't do for you, like copy arbitrary files to dist/ directory, just write a shell script.

The only advantage of task runners is a portability: the same task will work on Mac OS X, Linux, and Windows (hopefully…). Your Bash scripts won't run on Windows out of the box. The solution is to write your scripts in JavaScript as well. ShellJS provides a familiarity of *nix commands with a portability of JavaScript, use it.

It's a Unix system, you know this!

Put your custom scripts into script/ folder. Name them in package.json:

"scripts": {
  "something-complicated": "node script/something-complicated.js",
  ...
}

If you need to run some tasks in parallel, for example a development server and a modules bundler, try npm-run-all.

Recommended Readings

Transpilers

If you think there's too many JavaScript task runners, just take a look at the list of languages that compile to JS. Honestly, most of them don't matter.

One problem with many of these languages is that they require you to learn a new syntax in addition to JavaScript; it is a leaky abstraction and you might end up debugging the resulting JavaScript code. Furthermore the syntax of these languages tends to be very opinionated and your team members might have quite different preferences. And most of these options lack a proper tooling, like linter or static analysis tools.

Instead of hunting for syntactic sugar for JavaScript, focus on JavaScript and its new features, especially ES2016. Use Babel to transpile ES2016 code for backward compatibility with today browsers. Eventually these features will gain native support and we can move to transpiling future versions of JavaScript.

As usual, there are exceptions to these rules. Perhaps you have a project in other language and you want to share some parts of existing codebase with browser or just reduce the mental overhead of switching between languages. Or your project has specific needs. If you are adventurous, we recommend checking out these languages which transpile to JavaScript:

  • LiveScript – An indirect descendant of CoffeeScript with nice functional programming features.
  • Elm – Purely functional language similar to Haskell with direct support for GUI programming, time travelling debugger and other goodies.
  • Opal – Ruby to JavaScript compiler.
  • ClojureScript – Clojure to JavaScript compiler.

Babel Recommendations

Babel is more than just ES2016 to ES5 transpiler, it turned into a kind of code transpilation framework where all features are plugins. It is up to you which features you want to use in your project, but generally you want to use plugin presets.

Recommended Readings

Type Checking

Type checking can help in a larger code base and it can also remove need for some kinds of tests we usually do to make up for the lack of strict typing. If you'd like to incorporate type annotations, check out these projects:

  • Flow – Focused on soundness; you can add annotations gradually to your project, plus the checker can infer many type checks on its own.
  • TypeScript – Focused on tooling and general pragmatism. Has a large collection of external type definitions useful for working with external JavaScript code.

Recommended Reading

Live Reloading

Front-end development incorporates a lot of visual checking. Save your changes, switch to browser, click refresh, check the result, repeat. To reduce clicking on refresh button, you may want to use some live reload solution.

  • Browsersync watches HTML, JS, and CSS files and reloads the browser when there is a change. It can also act as proxy for non-JavaScript projects (i.e. Java or Ruby server-side sites) and synchronize your interaction (so you can test on multiple devices at once!).
  • beefy is a development server for Browserify with live reloading.
  • webpack-dev-server is a similar solution for Webpack.

Hot Module Replacement

Most live reload servers can inject CSS without reloading the whole page. What if you could do the same thing with your JavaScript code? Ideally your application would maintain exactly the same state and only replace the modules which were changed. This would be very hard without knowing where the state is located in your application. But if your code is stateless and you keep your state contained, hot module replacement may be easy to implement for you. React makes this more feasible since components have an explicit state.

Check out these solutions:

Minification

Code minification can improve load speed by removing comments and whitespace from your code, rename variables to shorter names, eliminate dead code etc. It should be a part of your build process before deploying code to production.

Use code minifier for the module bundler of your choice. Minifyify for Browserify or minimize option for Webpack. Both of them use UglifyJS2 for minification, which you can also use on its own.

Closure Compiler may give you better results if you annotate the code for it. But unless you properly document all your code (or you use some type checker), it is probably not worth it.

Linters

ESLint is our favourite solution. It is extensible, configurable, and supports JSX and ES2016. We have linting rules for it. Do not accept substitutes!

Testing

Preferred test runners are AVA and Tape. Both have simple API, AVA is more feature packed and Tape is simpler.

AVA supports ES2016 syntax by default, includes Power Assert for descriptive error messages and runs code in parallel. Unfortunatelly it doesn't run in browsers – yet.

Tape works both in Node and in browser and goes well with any package bundler (especially Browserify). It is the easiest option if you would like to run your tests against various browsers with testling-ci. Tape produces TAP output which can be further used with some nice summarizers. For headless testing with Tape check out Prova.

Another popular option, especially for React applications, is Jest. PayPal team has a nice write-up about migration from AVA to Jest.

There are many testing frameworks, libraries and test runners for JavaScript. They are very similar in features and style, but each takes a bit different approach. Some of them are browser-only and some of them are very opinionated about your testing needs.

For a rough idea, there are:

  • Assertion Libraries, need to be paired with framework or runner, provide helper functions for assertions and mocking:
    • Power Assert – acts like a standard assert, but adds extremely descriptive error messages; it works through code transformation with plugins for bundlers and Babel,
    • Chai – supports various assertion styles,
    • Sinon.js – mocking, stubbing, and test spies,
    • Should.js – supports “BDD” syntax should(x).be…
    • Expect.js – like should, but with expect(x)
  • Testing Frameworks, cover unit testing:
    • Mocha – runner and “outer shell” for tests; bring your own assertion library,
    • Jasmine – similar to Mocha, but with own assertions library,
    • QUnit – XUnit-style framework, originally for jQuery,
    • Unit.js – Still kinda new, but with interesting features and focused on interoperability with runners and other frameworks.
  • Test Runners: Make some aspects of testing easier, like running with headless browsers, automatic mocking and code injection, …:
    • Jest – Built on top of Jasmine, mocks dependencies and DOM, runs tests in parallel,
    • Karma – Browser-only testing which launches (headless) browser against integrated test server,
    • Intern – Buzzword-heavy, name-dropping runner with support for both unit tests (Node and browser) and headless functional tests. Uses Chai for assertions by default.

The favourite combination seems to be Mocha, Chai and Karma, but make your own research. However, keep things simple; tests don’t provide real functionality and you should not spend too much thinking about your test stack.

Functional Testing

Functional tests simulate user interaction and it’s a world of its own. Most projects build upon Selenium WebDriver, which lets you automate pretty much any browser. You can automate desktop browsers or special headless browsers like PhantomJS and SlimerJS.

Given the indirection of WebDriver protocol, you can write functional tests in most languages with a proper driver. The following projects run under Node, so you can automate interaction with JavaScript.

You may also want to check out projects with support for PhantomJS.

Recommended Readings

Hosted Services

If you publish your project as open-source on GitHub or in the npm registry, consider using these useful services: