Skip to main content

Babel

This section explains the possibilities of using modern JavaScript dialects to write application source code, yet still being able to execute it within browsers with different compatibility levels. To accomplish something like that, we are going to be using babel project. It's a JavaScript compiler that is going to do all the heavy lifting for us.

Dependencies

Just as with the webpack, babel dependencies could be split into core and satellite. For the core dependencies we are going to need:

  • @babel/core - main dependency, the engine itself
  • @babel/preset-env - defines a bunch of syntax transforms/polyfills according to the browser support specified
  • babel-loader - webpack plugin for running babel within the webpack execution life-cycle
npm i @babel/core @babel/preset-env babel-loader -D --save-exact

At the higher level, architecturally babel represents a compiler with some basic capabilities and a bunch of plugins to extend those capabilities. The rest depends on a particular project configuration. For example, in out case, instead of defining all the polyfills for non modern browsers manually, we are using a preset (@babel/preset-env) that comes with the built in logic of including different syntax transforms based on the selected browsers and the language features used.

Satellite dependencies extend babel capabilities even further, but are not strictly necessary. Their usage depends on a final project target build.

  • @babel/plugin-transform-runtime - makes babel inject helper methods into the bundle in one place, reduces generated code size
  • @babel/runtime-corejs3 - a production dependency of @babel/plugin-transform-runtime with core-js 3 support
  • core-js - library for polyfilling modern JavaScript features
  • @babel/plugin-proposal-class-properties - provides private property initializers syntax
  • @babel/plugin-proposal-object-rest-spread - provides support for rest and spread syntax
npm i @babel/plugin-transform-runtime @babel/runtime-corejs3 core-js regenerator-runtime \
@babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread -D --save-exact

One line setup

npm i @babel/core @babel/preset-env babel-loader \
@babel/plugin-transform-runtime @babel/runtime-corejs3 core-js regenerator-runtime \
@babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread -D --save-exact

@babel/cli package could be used to run babel from the command line bypassing webpack. For the projects where build target represents both website and a library, it could be included as well.

Browser list

@babel/preset-env plugin takes advantage of the browserlist project. Given browserlist compatible configuration it includes all the necessary polyfills into the bundle.

.browserslistrc

>= 1%

Here we are specifying all the browsers that have at least 1% of market share.

Configuration file

babel.config.js

module.exports = api => {

api.cache(true);

return {
presets: [
["@babel/preset-env", {
"useBuiltIns": "usage",
corejs: 3,
}]
],

plugins: [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread",
["@babel/plugin-transform-runtime", {
corejs: 3,
useESModules: true
}]
]
}
}

The file consists of two major parts, presets and plugins. Some entries just need to be listed because they don't provide any configuration parameters, while overs have to configured with specific options.

Presets

@babel/preset-env configuration:

  • useBuiltIns - specifies how polyfills are included within the rest of the bundle.
  • usage - adds polyfills based on the usage of the language features in the application
  • entry - adds polyfills directly into the application entry (before application main entry)
  • corejs - specifies exact imports from core-js project. In our case we are using core-js version 3

Plugins

@babel/plugin-transform-runtime:

  • corejs - specifies core-js project version to be used to polyfill babel helpers. Often, the value for this parameter corresponds to the same parameter for the @babel/preset-env preset. useESModules - do not preserve commonjs module semantics semantics. Allows for smaller builds

Webpack

It is time to configure the webpack itself to take advantage of all the babel set up we just did.

Parts

webpack/parts.ts

import path from 'path';
import webpack, { Entry, Output, Node, Plugin, Module } from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import HtmlWebpackHarddiskPlugin from 'html-webpack-harddisk-plugin';
import { CleanWebpackPlugin } from 'clean-webpack-plugin';

export const distFolder = () => path.resolve(__dirname, '../dist')

export const getParts = () => ({
context: path.join(__dirname, '../src', 'app'),

entry: {
main: './index',
} as Entry,

output: {
path: path.resolve(distFolder(), 'js'),
filename: '[name].bundle.js',
publicPath: '/js'
} as Output,

node: {
fs: 'empty'
} as Node,

module: {
rules: [{
// Include js files.
test: /\.js$/,

exclude: [ // https://github.com/webpack/webpack/issues/6544
/node_modules/,
],
loader: 'babel-loader',
}]
} as Module,

plugins: [
new webpack.EnvironmentPlugin(['NODE_ENV']),
new HtmlWebpackPlugin({
chunksSortMode: 'auto',
filename: '../index.html',
alwaysWriteToDisk: true
}),
new HtmlWebpackHarddiskPlugin(),
new CleanWebpackPlugin({ verbose: true })
] as Plugin[]
})

We introduced a new section module with the subsection called rules. In webpack, different file types could be handled by different loaders. Loaders define source code transformations of a module. Every import statement in the code tells webpack to inspect the source file for it, and every inspected file gets transpiled according to its extension (file path regex pattern) in the config file set up. With babel, adding a loader means introducing a new rule for it:

  • test - specifies Regex for files to be handled by the rule.
  • exclude - ignore loader transforms for specific file path Regex
  • loader - specifies loader name

Dev, Prod

For the config files themselves, we just need to specify the new module property from parts.ts

webpack/webpack.config.dev.ts, webpack/webpack.dev.ts

// ...
module: parts.module,
// ...

Sources

src/app/index.js

import 'core-js/stable'
import 'regenerator-runtime/runtime'

function* func () {
yield console.log('test')
}

const f = func()
f.next()
f.next()

We treat index.js file as the application main entry point. There are also two polyfill imports at the top of the file:

  • core-js/stable - polyfills all the ES stable features and standards
  • regenerator-runtime/runtime - polyfills generators as well async/await syntax

Final version

Reference implementation