Deep dive into using ES6 in browsers
JavaScript grows very fast. Fast enough, that once a year Ecma International publish new specification which extends JavaScript with new features. It also happens that old features are becoming deprecated or removed from language specification. Before publication new specs are discussed by TC39 committee which contains representatives from the biggest web browsers. When the new standard is published, web browsers almost immediately start implementing these features to be available for developers. Unfortunately there are plenty of web browser versions and naturally the older ones won’t support these new features. There are also web browsers (i.e. Internet Explorer) which are not supported by their creators and even that they are still in use by users. The question appeared — how to use new JavaScript standard to make it works correctly in all (older and newer) web browsers?
Nowadays, when we mostly use React, Angular and Vue.js, we don’t need to care about that problem — their default configurations contain almost everything to support new standards. Let assume, for a while, that we have to care about compilation of our code. For this post purpose, we will write a small script in native JavaScript and we will use it in web browser.
I have created really simply calculator who can add, subtract, multiply and divide. The app was created by using ES6 standard elements (like import and export). I also created index.js to make calculations with using the calculator.
### Calculator.jsconst sum = (a, b) => a+b;
const subtract = (a, b) => a-b;
const multiply = (a, b) => a*b;
const divide = (a, b) => a/b;
const Calculator = (type = null, a, b) => {
if(!a || !b) new Error('Missing numbers');
switch(type){
case 'sum':
return sum(a, b);
case 'subtract':
return subtract(a, b);
case 'multiply':
return multiply(a, b);
case 'divide':
return divide(a, b);
default:
throw new Error('Invalid Math type');
}
};
export default Calculator;### index.jsimport Calculator from "./Calculator";
Calculator('sum', 1, 2);
Let’s try to run our script. Before we run it, we need to create simply index.html and put our script in the end of <body> tag. Simply just put <script> tag that links to index.js. We expect that after opening our web browser we will see our result in browser console. Instead of that we get error saying that our import is used outside a module.
Until recently we were not be able to import dependency between the JavaScript files. With the development of JavaScript, software engineers proposed a concept where single file is treat like a module — it doesn’t have any characteristics in common (it is encapsulated) but it allows to export their own functions to use outside, by other files. The most popular modular ecosystems are CommonJS and ES Modules.
Modular ecosystems are not liked by browsers because each of them propose their own solutions and it is hard to find any similarities. ES6 standard presented ES Modules which became as a standard in web browsers.
OK, let’s get back to our problem. ES Module system allows us to import and export functions between modules in a friendly format. There are also a few types of export and import, giving developers more possibilities to manage their modules. This system, like others, is not supported by all web browsers.
### Way how we export and import using ES Modules// Example import
import {dependencyName} from '../path/to/dependency';
// Example export
export default MyComponent;
At present, we can use modules directly in HTML file. We have to import our scripts with a type attribute set to module. This solution is not supported by older browsers. It is still recommended to use compilers and bundlers due to performance things with this solution.
Error which appeared in our application is caused by not supported ES6 elements used in our code. Let assume that we translated our code to standard which understood by web browsers. Even that, it won’t fix our problem because we still have imports to other JavaScript files. Now we know that we have two problems instead of one that we assumed before.
To solve our problems we will use two things. First one is Babel — a compiler which rewrite our code (created in new standard) according to our configuration. Commonly, the transpilation is executed to ES5 standard. That rewrited code will be combine into one file using Webpack.
Disclaimer: further part of this post assumes that you have Node.js and npm (or yarn) installed on your machine.
Babel (ES6 -> ES5)
Our compiler works with many tools — bundlers, testing tools, frameworks and so on. Because of we are creating plain project, we will use official Babel CLI. Except that we will also need Babel dependency itself.
### Babel installationnpm install --save-dev @babel/core @babel/cli
To make the usage of new tool easier we will add build script to our package.json. The script will read files from src directory, transpile them and save them to dist directory.
### Adding script to transpile our code"scripts": {
...
"build": "babel src -d dist",
...
}
Last thing that we need to add is configuration file .babelrc in project main directory. It is in it we need to tell our compiler which version of ES standard we want to support. We want to transpile to ES5+ so we can download ready-to-use preset which contains all we need.
Preset is a set of bundled extensions to use certain ES standard. preset-env contains all dependencies needed to transpile our code to ES5+. That’s all what we need.
### @babel/preset-env installationnpm install --save-dev @babel/preset-env
In our newly created .babelrc file we need to initialize the usage of installed preset. Of course we can set everything by ourselves, installing all plugins one by one. I encourage you to experiment with the configuration based on official documentation.
### Preset initialization in .babelrc{
"presets": ["@babel/preset-env"]
}
Now we only need to run our build script (npm run build) and replace the script url in index.html to link to our index.js in dist directory. It is worth to look into Calculator.js after transpilation to see how our code was rewrited.
When we run our application in web browser we will se next error (which I mentioned a while ago). Our browser doesn’t know what is require method. What is more, if we run our calculator in Node.js environment all will work perfectly. Our goal is to combine all files into one file.
Babel transpiles code to CommonJS system by default which appeal to other modules (files) by using require() method. CommonJS is also used in Node.js implementation by default.
Webpack
Our goal is simple — take transpiled files and put them together in one file. To achieve this we need to use bundler — tool which transform (i.e. minify) our files and combine them into one output file. The most popular bundlers are Webpack, Parcel and RollUp.
In this post we will use Webpack. Let’s begin with package installation.
### Webpack installationnpm install --save-dev webpack webpack-cli
Webpack is a tool with many powers. Due to its flexibility it can transform files with different extensions. Scripts can be minified, add them source maps or removing unused variables/functions. The same is with styles — there are many available loaders which automatically improve our code. That is a drop in the ocean so I encourage you to read the documentation.
Loader is a script which perform an tranformation on indicated files. All rules based in configuration file can use many loaders at once to make several transformations. We can modify loader by setting different options.
Now, when we know what loaders are, our intuition should suggest us that we will need loaders to solve our problem. We will use the fact that to transpilation we are using Babel and we will install a loader which add missing part to things we have already done. Let’s install babel-loader.
### babel-loader installationnpm install --save-dev babel-loader
OK, we have everything we need. Now let’s create Webpack configuration file in our main project directory and name it webpack.config.js. The config file is an object and we need to add a rule to handle all .js extension files. In addition to indicating the extension we also need to use babel-loader. Let’s put in configuration one more thing — we want to tell Webpack not to including files from node_modules directory. Our completed configuration is below.
### Basic Webpack configurationmodule.exports = {
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
]
}
};
You can wonder what is this weird /\.js$/ entry is. This is a regular expression which consists a chain of symbols. The chain can also contains special symbols. In our case we use few special symbols:
/ — initialize regular expression. All symbols putted inside two forward slash is treat as symbols chain,
\ — precedes a special symbol and make it to be treat like normal symbol,
$ — means that all symbols before dollar symbol have to end the chain (so in our case .js has to be at the ending)
We need to define our output in configuration. Let’s add output object which describe destination to save the file and the file name. We don’t need to define our source directory because by default it is set to src.
### Configuration for outputmodule.exports = {
output: {
publicPath: "/dist/",
filename: "scripts.js"
},
module: {
...
}
};
Our basic configuration is ready. Because we decided to use bundler we no longer need to use @babel/cli. We can delete it.
### Removing @babel/clinpm uninstall @babel/cli
We also need to change our build script in package.json to use webpack to build our project.
### New build script in package.json"scripts": {
...
"build": "webpack",
...
},
Now after we run build (npm run build) we can notice that in our dist directory we have only one file — index.js. When we run our application we will see our calculator result in browser console.
Congratulations. We managed to solve our problems and now our code is written in ES6 standard and can be run in the web browsers.
That’s all. Happy coding!