38 Compilation and Packaging Through Webpack Babel for Compilation and Packaging

38 Compilation and Packaging Through Webpack Babel for Compilation and Packaging #

Hello, I am Shichuan.

When upgrading JavaScript from ES5 to ES6, many developers wanted to experience the new features in advance, but faced the problem of lacking environment support as most browsers had not yet supported the new version of JavaScript. To solve this problem, some compilers targeting JavaScript developers, such as Babel.js, were born. It allows developers to write programs in the syntax of ES6 and then compile and transform the code into a version compatible with ES5.

Of course, later on, with the support of new versions of JavaScript by browsers, Babel.js not only solved the issue of using ES6, but also added many new features to support various compilation needs. Today, let’s take a look at the use of Babel in JavaScript development.

Compilation of JavaScript #

We know that JavaScript is an important milestone with the release of the ES6 version. It has been 6 years since the previous ES5 version, and after the release of ES6, JavaScript has been updated every year. So ES6 can be seen as a “major version” update, which includes many new features and syntax sugars.

Faced with inconsistent support for ES6 in different browsers, there are two ways to handle this: compilation and polyfill. There is no clear boundary between these two, but the general difference is that compilation will transform the code into a lower version before running it. Polyfill, on the other hand, checks if a browser supports a feature at runtime and only uses the patch code if it does not support it; otherwise, it uses the native functionality.

Today, we will focus on the compilation approach. For a long time, Babel has provided a compilation tool that helps developers write code using the next generation of JavaScript versions in advance. It can compile ES6+ code into backward-compatible versions. You may ask, isn’t compilation the job of the browser? Why do we say Babel is a compiler?

In fact, Babel is a compilation from source code to source code, not from source code to machine code. Therefore, to differentiate it from JavaScript engines in browsers, Babel is also called a “transpiler”. Since its launch in 2014, Babel has allowed web developers to use ES6 and higher versions of new language features when many browsers did not yet support ES6.

Some ES6 language features can be easily converted to ES5, such as function expressions. But some language features, such as the class keyword, require more complex transformations. Usually, the code output by Babel is not necessarily readable by developers, but it can be mapped to the positions of the source code, which is very helpful for debugging at runtime after compilation.

As mainstream browsers increasingly support ES6+ versions and IE has exited the stage, the demand for compiling arrow functions and class declarations has greatly reduced. However, Babel can still provide assistance for newer language features. Like most other tools we described earlier, you can install Babel using NPM and run it using NPX.

You can install Babel with npm install --save-dev @babel/core, and then import and use it directly in your code. However, this approach adds the compilation work to the client-side, and a more reasonable approach is to integrate Babel into the development workflow.

To incorporate Babel into the development workflow, you can follow these steps:

  1. Install Babel and the CLI package:
npm install --save-dev @babel/core @babel/cli
  1. Run Babel on the source code to generate the compiled code:
./node_modules/.bin/babel src --out-dir lib

Afterward, you can include presets. There are two ways to add presets: installing all the compilation plugins at once, or installing specific plugins for certain features such as arrow functions.

To install all the plugins at once:

npm install --save-dev @babel/preset-env
./node_modules/.bin/babel src --out-dir lib --presets=@babel/env

To install the arrow functions plugin:

npm install --save-dev @babel/plugin-transform-arrow-functions
./node_modules/.bin/babel src --out-dir lib --plugins=@babel/plugin-transform-arrow-functions

If you install the arrow functions plugin mentioned above, Babel will transform the code into a version compatible with ES5 during compilation if you use the related arrow functions in your code. Internally, Babel determines how to transform JavaScript code by reading a .babelrc configuration file. You can create the presets you want to compile according to your needs or use all plugins to transpile all features. For example, in the following example, an arrow function is transformed into a code compatible with ES5:

// Babel input: ES6 arrow function
[1, 2, 3].map(n => n + 1);

// Babel output: ES5 anonymous function
[1, 2, 3].map(function(n) {
  return n + 1;
});

Although we often no longer need to transform the core language of JavaScript, Babel is still commonly used to support non-standard extensions to the JavaScript language. One of these extensions is Flow, which we mentioned in a previous lecture. During compilation, Babel can help remove type annotations in Flow. In addition to Flow, Babel also supports removing type annotations in the TypeScript language.

To install the Flow preset:

npm install --save-dev @babel/preset-flow

To install the TypeScript preset:

npm install --save-dev @babel/preset-typescript

By combining Babel with a code bundling tool, we can automatically run Babel on JavaScript files. This simplifies the process of generating executable code. For example, Webpack supports a “babel loader” module that you can install and configure to run Babel on each JavaScript module during bundling. Now, let’s take a look at the bundling tools in JavaScript.

JavaScript Packaging #

If you use JavaScript for modular development, you should be familiar with code bundling. Even before the introduction of import and export directives in ES6, people started using these functionalities.

Back then, in order to use these functionalities, developers would use a code bundling tool that starts from a main entry point and recursively traverses the import tree to find all the modules that the program depends on. Then, the bundling tool would merge all the individual module files into a single JavaScript code file and rewrite the import and export directives so that the code can run in transpiled form. The result of bundling is a single file that can be loaded into browsers that do not support modularity.

Today, almost all mainstream browsers support ES6 modules, but developers still use code bundlers, at least for production release. The reason for this is to load the core functionalities all at once, which results in better performance and a better user experience compared to loading modules individually.

There are many good JavaScript bundling tools available in the market. Some of the well-known ones include webpack, Rollup, and Parcel. The basic functionality of these bundling tools is quite similar, and their differences mainly lie in configuration and usability. webpack is one of the oldest tools in this group and can even support non-modular libraries. However, it is also known for being difficult to configure. On the other hand, Parcel is a zero-config alternative. Rollup, compared to webpack, is more minimalistic and suitable for developing small projects.

Apart from basic bundling, bundling tools can also offer additional features such as optimization, non-standard module plugins, hot module replacement, and debugging of source code. Let’s discuss these features one by one.

Optimization #

For example, many programs have multiple entry points. For a web application with multiple pages, each page can have a different entry point. Bundling tools usually allow us to create a bundle based on each entry point or a few entry points.

As we discussed in frontend design patterns, a program can dynamically load modules using import statements instead of statically loading resources at initialization. This approach can optimize the initialization time of the application. Bundling tools that support import can create multiple bundles: one bundle that is loaded at initialization, and one or more bundles that are dynamically loaded. Multiple bundles are suitable when there are only a few import calls in the program and the modules they load have little or no overlap. If the dynamically loaded modules have highly shared dependencies, it becomes difficult to determine how many bundles should be generated, and manual configuration may be required for proper ordering.

Sometimes, when we import a module in another module, we may only need to use a few specific functionalities. A good bundling tool can analyze the code and identify which unused code can be eliminated from the bundle. This feature is known as tree-shaking.

Non-standard module plugins #

Bundling tools usually have a plugin architecture that allows importing and bundling non-JavaScript module types. For example, if your program includes a large JSON-compatible data structure, you can configure the bundling tool to treat it as a separate JSON file and import it into your program using import statements.

import widgets from "./app-widget-list.json"

Similarly, in JavaScript, we can use bundling tool plugins to import CSS files. However, it is important to note that importing anything other than JavaScript files requires non-standard extensions and introduces a certain level of dependency on the bundling tool.

Hot module replacement #

In languages like JavaScript that do not require pre-compilation or pre-bundling before execution, running a bundling tool feels like a pre-compilation step. Every time you finish writing code, you need to bundle it before executing it in the browser. For some developers, this step may feel cumbersome.

To address this issue, bundling tools usually support file systems in an observer mode to detect changes in files in the project directory and automatically regenerate the required bundles based on the changes. With this feature, you often get immediate refresh without the need to manually bundle again after saving edited files. Some bundling tools also support a “hot module replacement” option for developers, where the browser automatically reloads the application whenever a new bundle is generated.

Source code debugging #

Similar to Babel and other compilation tools, bundling tools usually generate a source code mapping file that maps the bundled code to the original source files. This helps browser developer tools automatically locate the source file positions when errors occur.

Summary #

Through today’s study, we learned about the past and present of compilation and packaging tools in JavaScript.

Babel, as a compiler, has been able to allow people to use ES6 features ahead of time for a long time. After the “transformation,” it enables people to use Flow and TypeScript for type annotations and compiles them without changing the original JavaScript language.

Later, we learned about Webpack, Rollup, and other code bundling tools, which played a role in module importing and exporting in the early stages and gradually “transformed” into loading optimization in the later stages. These tools provide support for non-standard module plugins and also offer features such as real-time update bundling and hot reloading.

Reflection Questions #

Today’s reflection questions are also a preview for the two upcoming lessons. After the transformation, Babel can not only transpile Flow and TypeScript but also support transpiling JSX. Do you know the purpose of JSX syntax extension in JavaScript?

Another question we mentioned today is that besides transpiling with Babel, we can also solve compatibility issues through polyfills. Do you know the differences in their use cases?

Feel free to share your experiences, exchange learning insights, or ask questions in the comments section. If you find it helpful, you are also welcome to share today’s content with more friends. See you in the next lesson!