The Master Chef's Configuration
In our last lesson, we learned what Babel is and how it works. Today, we're diving deep into configuration - like a master chef learning to adjust recipes for different dietary requirements and preferences. We'll explore how to configure Babel for different environments, browsers, and project needs.
Babel Configuration Files
Babel can be configured in several ways. Let's understand when to use each approach:
1. babel.config.js (Recommended)
// babel.config.js
module.exports = function(api) {
// Cache the configuration based on environment
api.cache.using(() => process.env.NODE_ENV);
return {
presets: [
['@babel/preset-env', {
targets: {
node: 'current', // For Node.js projects
// OR for browsers:
browsers: ['> 0.25%', 'not dead']
}
}]
],
plugins: [],
// Environment-specific settings
env: {
development: {
plugins: ['@babel/plugin-transform-react-jsx-source']
},
production: {
plugins: ['transform-remove-console']
},
test: {
presets: [
['@babel/preset-env', {
targets: { node: 'current' }
}]
]
}
}
};
};
2. .babelrc
// .babelrc
{
"presets": ["@babel/preset-env"],
"plugins": [],
"env": {
"development": {
"plugins": ["@babel/plugin-transform-react-jsx-source"]
},
"production": {
"plugins": ["transform-remove-console"]
}
}
}
3. package.json
// package.json
{
"name": "my-project",
"babel": {
"presets": ["@babel/preset-env"],
"plugins": []
}
}
Environment-Specific Configuration
Different environments need different configurations. Let's set up Babel for development, production, and testing:
// babel.config.js
module.exports = function(api) {
// Determine the environment
const isDevelopment = api.env('development');
const isProduction = api.env('production');
const isTest = api.env('test');
// Cache configuration based on environment
api.cache.using(() => process.env.NODE_ENV);
const presets = [
['@babel/preset-env', {
targets: isTest
? { node: 'current' }
: { browsers: ['> 0.25%', 'not dead'] },
// Configure module transformation
modules: isTest ? 'commonjs' : false,
// Configure polyfills
useBuiltIns: isProduction ? 'usage' : 'entry',
corejs: 3,
// Debug information
debug: isDevelopment
}]
];
const plugins = [
// Common plugins
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-private-methods',
// Development-only plugins
isDevelopment && '@babel/plugin-transform-react-jsx-source',
isDevelopment && '@babel/plugin-transform-react-jsx-self',
// Production-only plugins
isProduction && 'transform-remove-console',
isProduction && 'transform-remove-debugger',
// Test-only plugins
isTest && 'babel-plugin-dynamic-import-node'
].filter(Boolean);
return {
presets,
plugins,
// Additional environment-specific settings
sourceMaps: isDevelopment ? 'inline' : false,
compact: isProduction,
comments: !isProduction,
// Environment overrides
env: {
production: {
presets: [
['@babel/preset-env', {
// More aggressive optimizations for production
bugfixes: true,
modules: false,
useBuiltIns: 'usage',
corejs: 3
}]
]
}
}
};
};
Browser Compatibility Configuration
One of Babel's most powerful features is its ability to target specific browsers. Let's explore different strategies:
Using Browserslist
// package.json
{
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
// Or in .browserslistrc
# Production browsers
>0.2%
not dead
not op_mini all
# Development browsers
last 1 chrome version
last 1 firefox version
last 1 safari version
Browser Query Examples
| Query | Description | Example Result |
|---|---|---|
| > 0.5% | Browsers with more than 0.5% market share | Chrome 88+, Safari 14+, Firefox 85+ |
| last 2 versions | Last 2 versions of each browser | Chrome 90-91, Firefox 88-89 |
| not dead | Browsers still receiving updates | Excludes IE, old Android |
| ie 11 | Specific browser version | Internet Explorer 11 |
| iOS >= 12 | Mobile OS versions | iOS Safari 12+ |
| node 14 | Node.js versions | Node.js 14.x |
Target-Specific Configuration
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
// Target specific browsers
targets: {
chrome: '58',
ie: '11',
firefox: '60',
safari: '11.1',
edge: '16'
},
// Or target by environment
targets: {
esmodules: true // Target browsers supporting ES modules
},
// Or target specific features
targets: {
browsers: ['chrome >= 60'],
// Only transform specific features
include: ['transform-arrow-functions'],
exclude: ['transform-regenerator']
},
// Debug output to see what transforms are applied
debug: true
}]
]
};
Polyfill Strategies
Choosing the right polyfill strategy is crucial for bundle size and browser compatibility:
at entry point] D --> G[Import only
used polyfills] E --> H[Handle polyfills
manually] F --> I[Larger bundle
Better compatibility] G --> J[Smaller bundle
Targeted compatibility] H --> K[Full control
Manual management] style B fill:#f9f,stroke:#333 style D fill:#9f9,stroke:#333
1. Entry Strategy
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
useBuiltIns: 'entry',
corejs: 3
}]
]
};
// index.js (entry point)
import 'core-js/stable';
import 'regenerator-runtime/runtime';
// Your app code...
const result = [1, 2, 3].includes(2);
const promise = Promise.resolve();
2. Usage Strategy (Recommended)
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: 3
}]
]
};
// index.js
// No manual imports needed!
const result = [1, 2, 3].includes(2); // Polyfill auto-imported
const promise = Promise.resolve(); // Polyfill auto-imported
// Babel automatically adds:
// import "core-js/modules/es.array.includes";
// import "core-js/modules/es.promise";
3. Manual Strategy
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
useBuiltIns: false
}]
]
};
// index.js
// Manual polyfill imports
import 'core-js/features/array/includes';
import 'core-js/features/promise';
// Your app code...
const result = [1, 2, 3].includes(2);
const promise = Promise.resolve();
Advanced Plugin Configuration
Let's explore advanced plugin configuration and custom plugin options:
Plugin with Options
// babel.config.js
module.exports = {
plugins: [
// Plugin with options object
['@babel/plugin-transform-runtime', {
corejs: 3,
helpers: true,
regenerator: true,
version: '^7.17.0'
}],
// Multiple configurations for the same plugin
['module-resolver', {
root: ['./src'],
alias: {
'@components': './src/components',
'@utils': './src/utils',
'@assets': './src/assets'
},
extensions: ['.js', '.jsx', '.ts', '.tsx']
}],
// Conditional plugin with options
process.env.NODE_ENV === 'production' && ['transform-remove-console', {
exclude: ['error', 'warn'] // Keep error and warn, remove others
}],
// Custom plugin function
function myCustomPlugin() {
return {
visitor: {
Identifier(path) {
// Transform identifiers
}
}
};
}
].filter(Boolean)
};
Common Plugin Options
| Plugin | Option | Description |
|---|---|---|
| @babel/plugin-transform-runtime | corejs | Core-js version for polyfills |
| @babel/plugin-transform-runtime | helpers | Use runtime helpers |
| @babel/plugin-proposal-decorators | legacy | Use legacy decorator behavior |
| @babel/plugin-proposal-class-properties | loose | Use loose compilation |
Optimizing Babel Configuration
Performance optimization is crucial for large projects. Here are strategies to improve build performance:
1. Caching
// babel.config.js
module.exports = function(api) {
// Cache based on environment variables
api.cache.using(() => {
return JSON.stringify({
env: process.env.NODE_ENV,
babel: process.env.BABEL_ENV,
target: process.env.BUILD_TARGET
});
});
return {
// ... configuration
};
};
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false, // Faster caching
cacheIdentifier: process.env.NODE_ENV
}
}
}
]
}
};
2. Selective Transpilation
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
// Only include necessary polyfills
useBuiltIns: 'usage',
corejs: 3,
// Exclude transforms for supported features
exclude: [
'transform-typeof-symbol',
'transform-regenerator',
'transform-async-to-generator'
],
// Only include necessary transforms
include: [
'transform-arrow-functions',
'transform-classes'
]
}]
],
// Ignore files that don't need transpilation
ignore: [
'node_modules',
'**/*.spec.js',
'**/*.test.js'
],
// Only transpile specific files
only: [
'src/**/*.js',
'lib/**/*.js'
]
};
3. Bundle Size Optimization
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
// Enable modern optimizations
bugfixes: true,
// Target modern browsers for smaller output
targets: {
esmodules: true // Only browsers with ES modules support
},
// Use tree-shakeable builds
modules: false,
// Optimize for specific browsers
ignoreBrowserslistConfig: false,
// Remove debug information in production
debug: process.env.NODE_ENV !== 'production'
}]
],
// Production optimizations
env: {
production: {
presets: [
['@babel/preset-env', {
// Aggressive dead code elimination
forceAllTransforms: false,
// Skip unnecessary polyfills
exclude: ['es.array.iterator', 'es.promise']
}]
],
plugins: [
// Remove development helpers
'transform-react-remove-prop-types',
'transform-remove-console',
'transform-remove-debugger'
]
}
}
};
Debugging Babel Configuration
When things don't work as expected, these debugging techniques can help:
1. Debug Output
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
debug: true // Shows which transforms are applied
}]
]
};
// Command line
BABEL_ENV=development babel src --out-dir lib --verbose
// Output example:
@babel/preset-env: `DEBUG` option
Using targets:
{
"chrome": "58",
"ie": "11"
}
Using modules transform: auto
Using plugins:
transform-template-literals { "ie":"11" }
transform-arrow-functions { "ie":"11" }
transform-classes { "ie":"11" }
2. AST Explorer
// Use https://astexplorer.net/ to visualize transformations
// 1. Select "JavaScript" as language
// 2. Select "@babel/parser" as parser
// 3. Add transforms to see the output
// Example input:
const double = (x) => x * 2;
// Example output (with arrow function transform):
var double = function double(x) {
return x * 2;
};
3. Configuration Validation
// babel.config.js
module.exports = function(api) {
// Validate configuration
api.assertVersion(7);
api.cache(true);
// Log configuration information
console.log('Babel Environment:', api.env());
console.log('Babel Version:', api.version);
return {
presets: ['@babel/preset-env'],
// Validate plugin existence
overrides: [{
test: /\.jsx?$/,
plugins: [
'@babel/plugin-transform-react-jsx',
api.env('production') && 'transform-remove-console'
].filter(Boolean)
}]
};
};
Real-World Configuration Example
Let's look at a comprehensive configuration for a modern web application:
// babel.config.js
const path = require('path');
module.exports = function(api) {
const env = api.env();
const isDevelopment = env === 'development';
const isProduction = env === 'production';
const isTest = env === 'test';
api.cache.using(() => JSON.stringify({
env,
version: process.env.APP_VERSION,
target: process.env.BUILD_TARGET
}));
const presets = [
['@babel/preset-env', {
targets: isTest ? { node: 'current' } : undefined,
modules: isTest ? 'commonjs' : false,
useBuiltIns: isProduction ? 'usage' : 'entry',
corejs: 3,
bugfixes: true,
debug: isDevelopment,
exclude: isProduction ? ['transform-typeof-symbol'] : []
}],
['@babel/preset-react', {
development: isDevelopment,
runtime: 'automatic'
}],
'@babel/preset-typescript'
];
const plugins = [
// Stage 3 proposals
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-private-methods',
'@babel/plugin-proposal-private-property-in-object',
// Transform runtime for helpers and polyfills
['@babel/plugin-transform-runtime', {
corejs: false, // We're using preset-env for polyfills
helpers: true,
regenerator: true,
version: '^7.17.0'
}],
// Module resolution
['module-resolver', {
root: ['./src'],
alias: {
'@components': './src/components',
'@hooks': './src/hooks',
'@utils': './src/utils',
'@services': './src/services',
'@assets': './src/assets',
'@constants': './src/constants',
'@types': './src/types'
},
extensions: ['.js', '.jsx', '.ts', '.tsx']
}],
// Development plugins
isDevelopment && 'react-refresh/babel',
// Production plugins
isProduction && ['transform-remove-console', {
exclude: ['error', 'warn', 'info']
}],
isProduction && 'transform-react-remove-prop-types',
// Test plugins
isTest && 'babel-plugin-dynamic-import-node'
].filter(Boolean);
return {
presets,
plugins,
// Source maps configuration
sourceMaps: isDevelopment ? 'inline' : isProduction ? true : false,
// Output configuration
compact: isProduction,
minified: isProduction,
comments: !isProduction,
// File handling
ignore: [
'node_modules',
'**/__tests__/**',
'**/*.test.js',
'**/*.spec.js'
],
// Environment-specific overrides
env: {
production: {
plugins: [
['react-remove-properties', {
properties: ['data-testid']
}],
['transform-react-class-to-function', {
memo: true
}]
]
},
test: {
plugins: [
'@babel/plugin-transform-modules-commonjs'
]
}
},
// File-specific overrides
overrides: [
{
test: /\.tsx?$/,
presets: [
['@babel/preset-typescript', {
isTSX: true,
allExtensions: true
}]
]
},
{
test: /node_modules\/.*\.js$/,
sourceType: 'unambiguous'
}
]
};
};
Practical Exercise
Let's practice creating a Babel configuration for different scenarios:
Scenario 1: React Application with TypeScript
Create a Babel configuration that:
- Supports React with automatic JSX runtime
- Handles TypeScript files
- Targets modern browsers (last 2 versions)
- Optimizes for production builds
- Includes path aliases for clean imports
Click to see solution
// babel.config.js
module.exports = function(api) {
api.cache(true);
const isProduction = api.env('production');
return {
presets: [
['@babel/preset-env', {
targets: 'last 2 versions',
useBuiltIns: 'usage',
corejs: 3
}],
['@babel/preset-react', {
runtime: 'automatic'
}],
'@babel/preset-typescript'
],
plugins: [
['module-resolver', {
root: ['./src'],
alias: {
'@': './src',
'@components': './src/components',
'@pages': './src/pages'
}
}],
isProduction && 'transform-remove-console'
].filter(Boolean)
};
};
Scenario 2: Node.js API with Modern JavaScript
Create a Babel configuration that:
- Targets Node.js 14+
- Uses ES modules
- Supports async/await without regenerator
- Handles environment variables
- Optimizes for server-side performance
Click to see solution
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
targets: { node: '14' },
modules: false,
bugfixes: true,
exclude: ['transform-regenerator']
}]
],
plugins: [
['@babel/plugin-transform-runtime', {
regenerator: false
}],
['transform-inline-environment-variables', {
include: ['NODE_ENV', 'API_KEY', 'DB_URL']
}]
],
ignore: ['**/*.test.js'],
sourceMaps: 'inline'
};
Common Configuration Patterns
Here are some common configuration patterns you'll encounter:
1. Library Configuration
// For publishing npm packages
module.exports = {
presets: [
['@babel/preset-env', {
modules: false, // Preserve ES modules
targets: {
browsers: ['> 0.25%', 'not dead']
}
}]
],
env: {
cjs: {
presets: [
['@babel/preset-env', {
modules: 'commonjs' // CommonJS build
}]
]
},
esm: {
presets: [
['@babel/preset-env', {
modules: false // ES modules build
}]
]
}
}
};
2. SSR Configuration
// For server-side rendering
module.exports = {
presets: [
['@babel/preset-env', {
targets: {
node: 'current', // Server config
browsers: ['> 0.25%'] // Client config
}
}]
],
env: {
server: {
presets: [
['@babel/preset-env', {
targets: { node: 'current' }
}]
]
},
client: {
presets: [
['@babel/preset-env', {
targets: { browsers: ['> 0.25%'] }
}]
]
}
}
};
3. Monorepo Configuration
// Root babel.config.js
module.exports = {
presets: ['@babel/preset-env'],
overrides: [
{
test: ['./packages/web/**'],
presets: ['@babel/preset-react']
},
{
test: ['./packages/mobile/**'],
presets: ['@babel/preset-react'],
plugins: ['react-native-web']
},
{
test: ['./packages/server/**'],
presets: [
['@babel/preset-env', {
targets: { node: 'current' }
}]
]
}
]
};
Summary and Best Practices
Configuration Best Practices
- Use
babel.config.jsfor monorepos and complex configurations - Cache configurations based on environment variables
- Use
useBuiltIns: 'usage'for optimal polyfill inclusion - Enable
bugfixes: truefor smaller output - Use
debug: trueduring development to understand transformations - Exclude unnecessary transformations for target environments
- Keep development and production configurations separate
- Use environment-specific overrides for optimization
Performance Tips
- Enable caching with
cacheDirectory - Exclude
node_modulesfrom transpilation - Use
browserslistto target specific browsers - Remove unused plugins and presets
- Consider using
swcoresbuildfor faster builds
Debugging Tips
- Use AST Explorer to understand transformations
- Enable debug output to see applied transforms
- Validate configurations with
api.assertVersion() - Use source maps for easier debugging
- Test configurations in isolation