Creating a Modern Front-End Development Environment (2018): ES6
2018. 7. 10.

The previous article in this series covered Webpack 4 and created a ready-to-use environment. This installment will use that same environment to add an ES6 development setup. I believe ES6 is mature enough for general use now. If you're thinking, "I'm not a front-end developer, and not all browsers support it, so I can't use ES6 yet," I hope this article changes your mind. In fact, I recommend using not just ES6, but up to ES8. While versions below IE11 have little consideration for ES6 or later, all modern browsers, including Edge (MS Edge), natively support most ES8 (ECMAScript 2017) specifications (practically 100% for useful purposes). Modern browsers are diligently keeping up with these specifications[7]. If your project needs to support browsers below IE11, it's time to use our weapon: Babel[4]. Babel is a transpiler that converts ES6+ versions into ES5[2][4][7]. You can maintain a readable and concise codebase with ES6, while the code actually used in browsers is the ES5 code transpiled by Babel[4]. If needed, various optimizations can be applied during the transpiling process. Debugging in the browser is also seamless thanks to source maps. A vast number of projects are already being developed with ES6 code using Babel without any issues[4][7]. While the transpiling process could potentially introduce errors, such cases are extremely rare; it's safe to say you'll likely never encounter such problems. If these concerns are holding you back from adopting ES6, I urge you to start right now.
Babel
As introduced earlier, Babel is a transpiler that converts code from a specific ECMAScript version to an older one[4][5]. Those who have used CoffeeScript might be familiar with transpilers. However, Babel follows the ECMAScript standard, unlike CoffeeScript. It doesn't have many extra features, so its configuration and usage are relatively simple. Let's go through it step by step.
npm install --save-dev babel-cli
First, install babel-cli
. If you followed the previous article, I recommend continuing with the project you created. Installing babel-cli
sets up the minimum environment to run Babel within your project[5].
// es6test.js
const myconst = 123;
let mylet = 456;
Create a file like the one above and run the following in your terminal:
npx babel ./es6test.js
Babel will print the result of converting the original code to ES5 in the terminal. It looks like this:
It was supposed to be converted, but it just prints the original code without any changes. Why is this happening? babel-cli
only provides the core transpiling functionality; actual code conversion requires feature-specific plugins[4][10]. Simply put, to transpile arrow functions, you need to install an additional plugin (transform plugin) for arrow functions. However, ES6 alone has quite a few added features, and installing plugins individually for each feature would be tedious. This inconvenience can be solved by installing presets[8][10]. Think of presets as sets that group necessary plugins by version[5][8]. As new ECMAScript versions are released each year, corresponding plugins and presets are continuously developed.
npm install --save-dev babel-preset-env
babel-preset-env
is a module that collects and manages such presets and plugins[5][8]. Previously, preset modules existed separately for each version, requiring individual installation. Now, those presets are all deprecated. Even after installing babel-preset-env
and running Babel again, the output remains the original code, not ES5. If you install a preset, you need to tell Babel which preset to use via options. The option for specifying presets is --presets
[5]. This time, let's also use the --out-file
option to save the transpiled file instead of printing it to the screen[5].
npx babel ./es6test.js --out-file es5.js --presets=es2015
If you open the es5.js
file, you'll see it has been converted to ES5 code as shown below.
// es5.js
"use strict";
var myconst = 123;
var mylet = 456;
"use strict"
has been added. According to the specification, ES6 module code always runs in strict mode. Since it was converted to ES5, "use strict"
was added. Conversely, using "use strict"
in ES6 code is incorrect usage and might be flagged by static analysis tools. Besides the addition of strict mode, the const
and let
keywords have been changed to var
. Since the ES6 code used only const
and let
simply, there's little overall change. If it only changes to var
, it seems the specifications of const
and let
aren't fully implemented. Let's add code that modifies the value of the const
variable and run Babel again with the same options.
const myconst = 123;
let mylet = 456;
myconst = 555;
It outputs an error message and doesn't transpile, as shown above. This way, Babel helps detect code used against the specification during the transpilation phase. Let's look at another case.
// es6test.js
const myconst = 123;
let mylet = 456;
if (myconst) {
let mylet = 1;
mylet += 1;
}
let
has block scope. Therefore, the mylet
inside the if
block and the mylet
outside the block are physically different variables and shouldn't affect each other. That is, after exiting the if
statement, the value of mylet
should still be 456. In ES5, which uses the var
keyword for variable declaration, variables have function scope, so the value of mylet
would become 2. How does Babel convert this code to ES5? Let's run Babel again.
// es5.js
"use strict";
var myconst = 123;
var mylet = 456;
if (myconst) {
var _mylet = 1;
_mylet += 1;
}
It changes the variable name entirely to achieve the same effect as block scope. In this way, depending on how the target code is used, Babel converts it to ES5 code that behaves identically according to the specification. Not just const
and let
, but even for the same feature, the content of the converted code is managed efficiently based on how it's used. Sometimes, when examining the transpiled ES5 code out of curiosity, one can be impressed by the various tricks and implementations.
babelrc
Babel allows you to register project-specific Babel settings in a file named .babelrc
[10]. You can also add settings under a babel
key in package.json
, but using .babelrc
is preferred[8]. While options can be passed via the command line every time babel-cli
is run, Babel options are usually project-specific, so they are saved in a configuration file for management and environment sharing[10]. Most command-line options can be saved in the configuration file. In fact, when integrated with Webpack, you won't need to run Babel from the command line[6][10]. The command-line options discussed earlier can be simply configured as follows:
{
"presets": ["es2015"]
}
Options for the target file to transpile and the output file (--out-file
) cannot be saved in the configuration file. You can find descriptions of available Babel options in the Options part of the API page, but due to preparations for version 7, only the link with the 'old' subdomain seems valid; the link on the current official page is broken (here). Since usually only settings related to the ECMAScript spec are saved in .babelrc
and the rest is handled by Webpack, there's rarely a need for complex configurations[9][10].
{
"presets": ["env"]
}
Setting the preset to "env"
uses the latest available preset, often called babel-preset-latest
, and provides options to manage polyfills and necessary transform plugins based on the project's supported browsers[5][8]. Since using Babel often means using the latest ECMAScript features, development has shifted towards options that optimize runtime code for target browsers rather than explicitly setting the spec version[8].
{
"presets": [
["env", {
"targets": {
"chrome": 52
}
}]
]
}
As shown above, you can define the supported browser version under the targets
option[9]. For modern browsers, rather than targeting a specific version, policies supporting the latest version or a few versions back from the latest are more useful. Babel uses browserslist, which allows defining supported browsers using string queries, enabling definitions like "last 2 versions".
A configuration supporting the last 2 versions while excluding IE versions 10 and below is as follows:
{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "not ie <= 10"]
}
}]
]
}
You can combine various conditions in an array format. Interestingly, there's an option to configure based on global browser usage statistics. For example, you could set it to "support browsers with more than 5% global usage," but I personally don't recommend this as it makes it unclear which browser versions your project supports. A list of available queries for browserslist
can be found on their GitHub page. However, the examples used above should suffice for most cases.
Furthermore, new specs not yet included in the official version can be added via the plugins
array.
{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "not ie <= 9"]
}
}]
],
"plugins": ["transform-object-rest-spread"]
}
object-rest-spread
is a spec named Rest/Spread Properties
currently in Stage-4
and scheduled for inclusion in ES2018. Simply configuring it won't work; you need to install the plugin additionally.
npm i babel-plugin-transform-object-rest-spread --save-dev
Specs still under development and not yet part of the official version can be used by adding them individually as plugins like this, but it's recommended to only use specs at Stage-3
or higher.
A list of available plugins can be found here. Specs already included in official versions are applied collectively via presets, so there's no need to use them as separate plugins. Plugins for specs not yet in the official version are listed under the Experimental section.
Webpack Integration
If the .babelrc
configuration is set up correctly, integrating with Webpack is not difficult[8][10]. First, install babel-loader
to enable Webpack to work with Babel[3][6][8].
npm i babel-loader --save-dev
Then, add the loader to the module.rules
option in your Webpack configuration[3][6][8].
module.exports = (env, options) => {
const config = {
entry: {
app: ['./src/index.js']
},
//... omitted ...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
}
//...
Briefly explaining the loader configuration: test
defines the files the loader applies to, and exclude
defines files to exclude using a regular expression[6][8]. exclude
is set to /node_modules/
, which excludes everything under the /node_modules/
directory[6][8]. The rules
option defines loaders to be applied based on specific file conditions. You can define one loader per rule, or if multiple loaders apply to specific files, use the use
option with an array to avoid duplicate definitions[3][6].
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader'}
]
}
]
}
A crucial setting to add to Webpack is babel-polyfill
. Babel polyfill adds built-in objects or methods included in the required ECMAScript version. In essence, it creates the runtime environment for a specific version. For example, it provides Promise
in browser environments that lack it. If you intend to use Babel-transpiled code, the polyfill must be loaded in the browser before the bundled file. While babel-polyfill
is needed for Babel regardless of Webpack, it's typically bundled using Webpack. How dependency code is managed in Webpack can vary depending on project needs, but as introduced in the previous article, it's common practice to bundle dependency code separately. Babel polyfill is included in this separate file. Although you could load Babel polyfill directly in your service code without changing the final Webpack config from the previous article, there's no reason to import it directly in service code. Therefore, it's hidden from the service code and included in the Webpack entry point.
module.exports = (env, options) => {
const config = {
entry: {
app: ['babel-polyfill', './src/index.js']
},
// .... omitted ....
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
}
With this setup, the polyfill will be bundled along with other project dependencies into the vendors.bundle.js
file. Compared to the configuration code from the previous article, the only change here is adding 'babel-polyfill'
to the entry.app
array.
Conclusion
Now you have an environment ready for developing with ES6++. If you're not yet familiar with using ES6++, you can get a good grasp of its usage and discover many useful features just by looking through the code examples here. Many of these features were introduced to improve upon JavaScript's shortcomings, and most are not difficult, making them easy and beneficial to apply to your projects.
with kakaopay
Recommend Post
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.