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 that, we are going to use the Babel project. It's a JavaScript compiler that is going to do all the heavy lifting for us.
Dependencies #
Just as with webpack, Babel dependencies can 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 specifiedbabel-loader- webpack loader for running Babel within the webpack execution lifecycle
npm i @babel/core @babel/preset-env babel-loader -D --save-exact
At a 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 our case, instead of defining all the polyfills for non‑modern browsers manually, we are using a preset (@babel/preset-env) that comes with built‑in logic to include 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 the 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 supportcore-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/clipackage 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.
Browserslist #
@babel/preset-env plugin takes advantage of the browserslist project. Given a Browserslist‑compatible configuration, it includes all the necessary polyfills in 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 others have to be 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 applicationentry- adds polyfills directly into the application entry (before the application's main entry)corejs- specifies exact imports from the core-js project. In our case, we are using core-js version 3.
Plugins #
@babel/plugin-transform-runtime:
corejs- specifies the 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-envpreset.useESModules- use ES modules helpers instead of preserving CommonJS semantics. Allows for smaller builds.
Webpack #
It is time to configure webpack itself to take advantage of all the Babel setup 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 can 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 setup. 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 Regexloader- 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.config.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 the index.js file as the application's main entry point. There are also two polyfill imports at the top of the file:
core-js/stable- polyfills all the ESstablefeatures and standardsregenerator-runtime/runtime- polyfills generators as well as async/await syntax