
When you’re writing JavaScript, there’s a painful reality that hits you sooner or later: not all browsers behave the same way. It’s like you’re in a constant battle with different engines trying to render your code. You might think you’ve nailed it with a slick ES6 feature, but then you realize that Internet Explorer 11 is still lurking in the shadows, waiting to ruin your day.
Consider the following simple piece of code that uses the fetch API, a modern way to make HTTP requests:
function fetchData(url) {
fetch(url)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
}
This works beautifully in most modern browsers, but throw it into IE, and you’re met with a brick wall. The fetch API is not supported there, so you need to find a workaround. One option is to use the old XMLHttpRequest, but who wants to write that verbose code when you can use something cleaner?
To tackle this, you can include a polyfill, which is basically a piece of code that provides the functionality that browsers lack. A popular choice is the whatwg-fetch polyfill. You can include it like this:
This way, you can keep your modern code while ensuring that older browsers can still function correctly. But it doesn’t stop there; you also have to deal with things like arrow functions, classes, and template literals. Each one of these features has its own set of compatibility issues that you need to address.
Here’s a cheeky little example using an arrow function:
const add = (a, b) => a + b; console.log(add(5, 3));
This works great in modern environments, but in older browsers, you’ll need to transpile it down to something they understand. You guessed it-time to bring in Babel.
The pain doesn’t just stop at syntax either; different browsers have varying levels of support for APIs and features that we take for granted. You might find that your JavaScript code behaves differently depending on whether it’s running in Chrome or Firefox. It’s enough to make any developer pull their hair out.
To help mitigate these issues, a good practice is to use feature detection. Libraries like Modernizr can help you check if a certain feature is available in the user’s browser, allowing you to provide fallbacks or alternative implementations:
if (Modernizr.fetch) {
fetchData('https://api.example.com/data');
} else {
// Fallback to older AJAX method
alert('Fetch API not supported, using fallback...');
}
By employing strategies like polyfills, transpiling, and feature detection, you can reduce the headaches that come with cross-browser compatibility. Though it’s not a perfect solution, it does help to keep your codebase cleaner and more maintainable. The reality is that writing JavaScript that works across all browsers is a necessary evil, but it’s a skill worth mastering. So, let’s dive into how to use Babel to help you
Apple 2026 MacBook Neo 13-inch Laptop with A18 Pro chip: Built for AI and Apple Intelligence, Liquid Retina Display, 8GB Unified Memory, 256GB SSD Storage, 1080p FaceTime HD Camera; Indigo
$559.55 (as of June 3, 2026 23:09 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Getting your hands dirty with the Babel command line
get your hands dirty with the Babel command line. First, ensure you have Node.js installed on your machine, as Babel runs on top of it. Once you have that, you can set up Babel in your project by creating a package.json file if you don’t have one already. You can do this by running:
npm init -y
Next, you’ll want to install Babel and its CLI tool. You can do this with the following command:
npm install --save-dev @babel/core @babel/cli
Now that you have Babel installed, you need to configure it. Create a file named .babelrc in the root of your project. This file will tell Babel how to transpile your code. A simple configuration might look like this:
{
"presets": ["@babel/preset-env"]
}
The @babel/preset-env preset allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed for your target environment. You can also specify which browsers you want to support directly in this preset, which is a neat way to avoid unnecessary code bloat.
Now, let’s say you have a directory called src where your ES6 JavaScript files live. You can transpile your code by running the following command:
npx babel src --out-dir lib
This command tells Babel to take everything from the src directory, transpile it, and output the results into a new directory called lib. If you look inside lib after running this command, you’ll find your ES6 code transformed into ES5, ready for older browsers.
But that’s just the beginning. You can also automate the build process using npm scripts. Open your package.json file and add a script like this:
"scripts": {
"build": "babel src --out-dir lib"
}
Now, whenever you want to transpile your code, you can just run:
npm run build
This keeps your command line tidy and lets you focus on writing code rather than remembering the exact Babel command. You can even go further and add a watch flag to keep an eye on your files and automatically transpile them whenever they change. To do this, modify your script like so:
"scripts": {
"build": "babel src --out-dir lib",
"watch": "babel src --out-dir lib --watch"
}
Now, running npm run watch will keep Babel running in the background, watching for changes and updating your transpiled files on the fly. This is especially useful during development, making it easier to see changes reflected instantly.
To really make your build process suck less, you can explore using additional presets and plugins that Babel offers. For example, if you find yourself using React, you might want to add the @babel/preset-react preset. Install it using:
npm install --save-dev @babel/preset-react
Then, just add it to your .babelrc:
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
With this setup, you can seamlessly integrate JSX into your JavaScript files without worrying about compatibility issues. Babel will handle all the heavy lifting for you, allowing you to focus on building your application.
As you can see, getting started with Babel is straightforward, and it provides a powerful way to write modern JavaScript while ensuring compatibility across different browsers. But there’s more to explore, like how to optimize your setup further with plugins and advanced configurations…
Making your build process suck less with presets and npm scripts
Manually adding every single Babel plugin you need is a recipe for disaster. You’ll end up with a .babelrc file a mile long, and you’ll spend more time managing your configuration than writing actual code. This is where presets come in. A preset is just a pre-configured bundle of plugins. You’ve already seen @babel/preset-env, but let’s talk about why it’s so great. It’s not just a dumb collection of all the latest JavaScript syntax transforms; it’s smart. It looks at the target environments you want to support and only includes the plugins needed to make your code work in those environments.
You can configure these targets right in your .babelrc file. Let’s say you only care about supporting browsers that have more than 0.25% market share and haven’t been dead for years. You can tell @babel/preset-env to do just that.
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": [
"> 0.25%",
"not dead"
]
}
}
]
]
}
This is fantastic because it means you’re not shipping a bunch of unnecessary transpiled code to modern browsers that don’t need it. The output is leaner, and your users get a faster experience. This kind of configuration is what separates a slapped-together build process from a professional one.
Now, let’s make our npm scripts a little more robust. Right now, running npm run build just overwrites files in the lib directory. What if you rename a file in src? The old file will still be hanging around in lib, which can lead to some very confusing bugs. A better approach is to clean the output directory before every build. A great cross-platform tool for this is rimraf. First, install it.
npm install --save-dev rimraf
Now you can add a clean script and use npm’s built-in prebuild hook to run it automatically before the build script. Your package.json scripts section would look something like this:
"scripts": {
"clean": "rimraf lib",
"prebuild": "npm run clean",
"build": "babel src --out-dir lib",
"watch": "babel src --out-dir lib --watch"
}
Now, when you run npm run build, npm will first execute prebuild, which in turn calls our clean script. The lib directory gets wiped clean, and then Babel runs, creating a fresh set of transpiled files. No more stale artifacts. This is a simple change, but it makes your build process significantly more reliable.
As your project grows, you might want to run multiple tasks at once. Maybe you want to run a linter at the same time you’re building your code. You could open two terminal windows, or you could use a tool like npm-run-all to manage it for you. Let’s install it.
npm install --save-dev npm-run-all
With npm-run-all, you can run scripts in parallel or in sequence. For example, let’s imagine you have a lint script. You could create a new script that runs the linter and the build process in parallel to save time.
"scripts": {
"clean": "rimraf lib",
"lint": "eslint src",
"build:babel": "babel src --out-dir lib",
"build": "npm-run-all --parallel lint build:babel",
"watch": "babel src --out-dir lib --watch"
}
Here, we renamed the original build script to build:babel and created a new build script that uses npm-run-all to execute both lint and build:babel at the same time. This is a huge time-saver in larger projects where build steps can take a while. By composing small, single-purpose scripts, you can create a powerful and flexible build system without needing a complex tool like Gulp or Webpack just yet.
