JavaScript at Scale

How thousands of engineers at big tech companies write JavaScript.

Author
Yangshun Tay
9 min read
Dec 21, 2023

JavaScript at Scale

As web development continued to evolve, the demand for more advanced and modern features led to the development of ECMAScript 6 (ES6), also known as ECMAScript 2015. Released in June 2015, ES6 introduced a range of new features, including arrow functions, classes, template literals, and destructuring assignments, among others.

Today, JavaScript is a ubiquitous language used not only for client-side web development but also for server-side development (Node.js) and mobile app development (React Native). The language continues to evolve, with ongoing efforts to enhance performance, introduce new features, and address the needs of modern web development.

Use latest (stable) language features

After the release of ES2015 / ES6, the ECMAScript specification transitioned to a yearly release cycle, allowing for a more agile development process and quicker incorporation of new features. Subsequent releases, such as ECMAScript 2016 (ES7), ECMAScript 2017 (ES8), and so on, brought additional improvements and features to the language.

Using the latest JavaScript language features in web development is crucial for several reasons. First and foremost, it ensures compatibility with modern browsers and leverages performance improvements introduced in newer language versions. This not only enhances the user experience but also aligns development efforts with the evolving standards of the JavaScript ecosystem. Furthermore, modern features contribute to enhanced productivity, readability, and maintainability of code. Syntax improvements, abstractions, and functionalities like arrow functions, async/await, and class syntax make code more concise, organized, and easier to understand. By staying current, developers gain access to a broader ecosystem of APIs, libraries, and community support, fostering a better development experience and future-proofing their code. Keep your developers happy by enabling the latest technologies and language features.

A combination of JavaScript compilers and polyfills allow developers to use the latest ECMAScript features. JavaScript compilers like Babel transpile newer language syntax into an older version while polyfills like core-js provide implementations for newer JavaScript APIs and they work together so that older browsers that do not support the features can still run the code. Google uses their homegrown Google Closure Compiler while Meta uses a combination of Babel and Flow.

TypeScript, which offers type checking on top of enabling modern language features, is both a compiler and type checker, so you might not need Babel at all. Read on to find out more.

Type-safety first

Type-safety is extremely important in large companies and large codebase. Type-safety helps to eliminate an entire class of bugs during development that will not make it into production.

Meta places such a high importance in type-safety that they have developed type-safe versions of / type checkers for every dynamic-typed language they use:

Other big tech companies like Microsoft have developed TypeScript and Google has developed Dart, which further highlights the importance of using type-safe languages when developing at scale. These languages / type checkers are open sourced and available for all to use.

Beyond catching and preventing type errors, type-safe languages are also easier to read and maintain. In VS Code, you can hover over a symbol to know its type. After all, code is read much more than it is written! The benefits of increased readability and clarity outweighs the learning curve in the long term. IDEs can also improve the developer experience by providing better autocompletion suggestions, and showing type errors inline.

Although Flow is open sourced, the Flow team has publicly stated that open source support is not a priority. In the open source ecosystem, there are other alternatives for writing type-safe web applications like ReScript and Reason, which also have their roots in Meta, but they have lost traction in recent years.

Automate via linters and formatters

There are multiple ways to write and format code, and in a large codebase built by a large team, inconsistent coding styles across different files and developers make the code difficult to read and maintain. This lack of standardization can also hinder collaboration and slow down development, as more time is spent understanding and debugging the code instead of adding new features or improvements.

Linting is the automated process of analyzing source code to detect errors, enforce coding conventions, and identify potential issues. Linters, or linting tools, play a crucial role in improving code quality by catching syntax errors, promoting consistent coding styles, and enforcing best practices. They contribute to error prevention, enhance code readability, and establish a uniform coding style across a project.

Meta uses ESLint with eslint-plugin-flowtype for linting JavaScript. If you're using TypeScript, the recommendation would be ESLint with typescript-eslint. Configuring ESLint rules individually can be a chore, so it is recommended to use popular open source ESLint configs like eslint-config-airbnb as a starting point. Meta's internal ESLint config can be found at eslint-config-fbjs.

It is recommended to add ESLint rules around sorting imports, sorting object keys alphabetically, sorting React component props alphabetically, etc. This reduces the chances of merge conflicts when developers edit the same file. Christoph Nakazawa, ex-manager on Meta's JavaScript infrastructure and React Native team published @nkzw/eslint-config which has a high degree of overlap with Meta's ESLint configuration.

Coding style is another aspect that can be automated and benefits from consistency. Prettier is the de facto choice for formatting. In 2018, Christopher Chedeau ran a format-athon at Meta where he rallied around 20 engineers across the company to format the codebase with Prettier.

Linting should ideally be executed during development so that errors and warnings are reflected within the IDE instantly. The fast feedback loop increases productivity as the developer can fix the issues on-the-spot while the context is still fresh. Autofix lint issues and format the code on save by adding to VS Code's settings.json:

{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}

Runtime

A JavaScript runtime is an environment where JavaScript code is executed. JavaScript was originally designed to be run in the browser, but these days JavaScript can also be run on the server using Node.js, which is the most popular server-side JavaScript runtime built on Google Chrome's V8 engine.

Meta uses Hermes for server-side rendering of React, although Hermes was originally designed to run React Native apps on mobile platforms.

There isn't much of a choice to debate about here. In the wild, most companies use Node.js and it has gotten really good in recent years. Deno and Bun are rising stars in the JavaScript runtime space and both offer TypeScript support out-of-the-box. Deno has a high focus on security by restricting access to sensitive runtime APIs by default, while Bun is extremely fast and includes a package manager, bundler, and test runner.

Deno and Bun are still considered new so the risk is yours to bear if you decide to go with them.

Officially-endorsed/supported libraries

While the JavaScript language is rapidly evolving, in the last decade, there were huge gaps where the language left in terms of providing solutions to common product needs like functional utilities, datetime formatting and manipulation, etc. As a result, a rich ecosystem of utilities and libraries that cater to various development needs have emerged:

Engineering leadership or a front end infrastructure team should make a decision on which libraries to use for various purposes. This prevents different libraries serving the same purpose being shipped in the same app and users end up paying the cost of downloading the JavaScript twice. Resources should also be committed to supporting internal developers facing issues with them and performing periodic codebase-wide upgrading of the library versions being used.

Package management

Finally, the last piece of JavaScript development to discuss is package management. Package management in JavaScript refers to the process of managing, distributing, and installing JavaScript libraries and tools within a project. A package manager is a tool that simplifies these tasks by automating the installation, versioning, and dependency resolution of external code packages.

Back in 2016, Meta created Yarn as an improvement over npm, it introduced the concepts of lockfiles, fast installs, global offline caches, workspaces, etc. A key feature of a scalable package manager is locking down the version dependencies and an offline mirror. You don't want to be blocked from deployment if the npm registry goes down. At Meta, node_modules are installed via Yarn v1, scanned for vulnerabilities, and checked into the monorepo.

These days, npm has mostly caught up with Yarn in terms of feature parity and Yarn has also undergone many changes since its initial launch. As of writing, Yarn is now v4, introduces Yarn Plug'n'Play, and is no longer maintained by Meta. Personally, my goto package manager these days is pnpm because it has the most features, has an amazing development experience, and is frequently updated.

Conclusion

TypeScript by Microsoft is now the de facto way to write type-safe web applications. Community support (learning resources, library type declarations) and developer tooling support (linters, IDE integration) for TypeScript is also extremely strong; new JavaScript runtimes like Deno and Bun also support TypeScript out-of-the-box. Even Google uses TypeScript as the primary language for Angular development, a UI framework created by them.

You will benefit from choosing TypeScript, even if the size of your codebase is small.


JavaScript scalability checklist

JavaScript at Scale List

  • Use modern language features, syntax, and APIs.
  • Use a type-safe language like TypeScript.
  • Leverage linting and formatting to enforce coding style and educate best practices.
  • Officially-endorsed/supported libraries.
  • Use features or modern package managers, check in your node_modules / use an offline mirror.
  • Upgrade runtime and package versions periodically.