Sonja Laurila Comparison of JavaScript Bundlers

Metropolia University of Applied Sciences Bachelor of Engineering

ICT Engineer Bachelor’s Thesis 06.10.2020 Abstract

Author Sonja Laurila Title Comparison of JavaScript Bundlers

Number of Pages 36 pages + 6 appendices Date 06.10.2020

Degree Bachelor of Engineering

Degree Programme ICT Engineer

Professional Major Software Engineering

Instructors Juha Kämäri, Lecturer

The objective of this thesis was to provide a thorough overview of the ideology of bundlers and then create a comparison of four different bundlers selected by their popularity.

The study first focused on building a solid understanding of the history of modular JavaScript and how the need for a tool such as bundler arose, how the bundling process goes and what kind of optimization steps the bundling process includes. Next, four different bundlers were configured to bundle a simple React web application. After that a set of test runs was performed to get comparable results of the bundling speed and the resulting bundle size. The bundlers were evaluated from both technical and usability perspectives.

As a result, a good understanding of the bundling process and the different bundlers and their characteristics was gained to be able to make better informed decisions when selecting a bundler for a certain use case. As a summary, Parcel would be great for beginners and getting the development environment up and running quickly. Parcel also offers extremely quick rebuild times due to caching. offers the best flexibility, quick rebuild times and good chunk splitting options. Rollup will most probably result in the smallest bundle size due to its advanced tree shaking capabilities. As to Browserify, it would probably be good to keep it on the server-side JavaScript, if the user is not already familiar with it.

Keywords Bundler, bundling, Webpack, Browserify, Rollup, Parcel

Abstract

Tekijä Sonja Laurila Otsikko JavaScript bundlerien vertailu

Sivumäärä 36 sivua + 6 liitettä Aika 06.10.2020

Tutkinto Insinööri21.8.2017 (AMK)

Tutkinto-ohjelma Tieto- ja viestintätekniikka

Ammatillinen pääaine Ohjelmistotuotanto

Ohjaajat Juha Kämäri, Lehtori

Työn tavoitteena oli luoda kattava kokonaiskuva koodin koontityökalujen (bundlerien) ideologiasta ja laatia vertailu, jossa vertaillaan neljää suosituinta koontityökalua.

Opinnäytetyö keskittyy ensin rakentamaan syvällisen ymmärryksen modulaarisen JavaScriptin historiasta ja siitä, kuinka tarve koontityökalun kaltaiselle työkalulle heräsi. Mitä koontiprosessin aikana tapahtuu ja minkälaisia optimointivaiheita koontiprosessi sisältää. Konseptin ymmärtämisen jälkeen neljä eri koontityökalua konfiguroitiin niin, että niillä voitiin koota yksinkertainen React-sovellus. Konfiguroinnin jälkeen ajettiin testiajoja, jotta saatiin vertailukelpoisia tuloksia koontityökalujen nopeudesta ja tuloksena saadun kootun tiedoston koosta. Koontityökaluja arvioitiin sekä teknisestä, että käytettävyyden näkökulmasta.

Lopputuloksena saatiin hyvä käsitys koontiprosessista ja eri koontityökaluista sekä niiden eroista, jotta voidaan tulevaisuudessa tehdä parempia päätöksiä valittaessa koontityökalua tiettyyn käyttötapaukseen. Yhteenvetona Parcel olisi hyvä aloittelijoille ja kehitysympäristön käynnistämiseen nopeasti sekä sen välimuistin käytön vuoksi tarvittaessa erittäin nopeita uudelleenkoontiaikoja. Webpack on kaikista joustavin ja tarjoaa myös nopeat koontiajat. Rollup todennäköisesti saavuttaa pienimmän kootun koodin koon, koska sillä on erinomaiset ”puunravistus”-kyvyt (tree shaking). Browserify on ehkä hyvä pitää serveripuolen JavaScriptissä, ellei käyttäjä sitten ole jo entuudestaan perehtynyt siihen.

Avainsanat Bundler, bundlaus, Webpack, Browserify, Rollup, Parcel, koodin koontityökalu

Contents

List of Abbreviations

1 Introduction 1

2 JavaScript Bundling 2

2.1 Modules and Closures 2 2.1.1 Closure 3 2.1.2 Global Module Object with IIFE 3 2.1.3 CommonJS 4 2.1.4 Asynchronous Module Definition (AMD) 4 2.1.5 Universal Module Definition (UMD) 5 2.1.6 ES6 modules 5 2.2 Module Loaders 6 2.3 Bundlers 7 2.3.1 Bundling Process 8 2.3.2 Optimizing Bundles 9

3 Different Bundlers 12

3.1 Webpack 13 3.2 Browserify 14 3.3 Rollup 15 3.4 Parcel 15

4 Creating Simple Web Application for Comparison Basis 16

4.1 Creating Package. File 16 4.2 Creating Simple React Application 17

5 Configuring and Bundling Projects with Different Bundlers 19

5.1 Webpack 19 5.2 Browserify 22 5.3 Rollup 24 5.4 Parcel 27

6 Bundler Comparison 29

7 Conclusions 35

References 36

Appendices Appendix 1. Small React application’s source code Appendix 2. Package.json and webpack.config.js when bundling with Webpack Appendix 3. Package.json when bundling with Browserify Appendix 4. Package.json and rollup.config.js when bundling with Rollup Appendix 5. Package.json when bundling with Parcel Appendix 6. Explanations for bundler features in Table 2

List of Abbreviations

CLI Command Line Interface. Interface which one can use to run a software from the command line.

DOM Document Object Model. HTML or XML tree structure interface.

ES6 ECMAScript 6. Scripting-language specification standardized by in ECMA-262.

HTML Hypertext Markup Language. The standard mark-up language for files designed to be displayed in a browser.

IIFE Immediately Invoked Function Expression. JavaScript function that runs immediately after it has been defined.

JSX JavaScript XML. Syntax extension for JavaScript, used in React and resembling XML. npm Node Package Manager. Software registry and command line interface for node packages to share and borrow open-source node packages to and from other developers. 1

1 Introduction

To run JavaScript in the browser, HTML files contain script tags, which contain JavaScript. In a case of larger applications, one typically wants to split the code into multiple JavaScript files, also known as modules, which would then lead into multiple script tags in the HTML file. The struggle here is to have the files in the correct order, because otherwise, the code does not run correctly and may lead into bugs and application crashing. Also, one should make sure that in the multiple files, the global variables and function names do not overlap each other, since if they are global in the file, they would be global also on the application level, which could cause overriding and mal-functioning. Bundlers can be used to automate handling the imports and taking care of how the modules work together.

This thesis focuses on explaining the bundling need and process and comparing the features of the four most popular bundlers: Webpack, Browserify, Rollup and Parcel. During the thesis project, a small React frontend application was bundled with all four bundlers to get hands-on experience on what was it like to configure and use the different bundlers and what kind of features and characteristics they have.

Currently there is widely spread inertia for using Webpack, which might lead into making misinformed decisions on which bundling tool to use and furthermore to unoptimized production bundles. The goal of the thesis is to find and understand the key differences between different bundlers and which bundler fits best in a certain use case and to learn how to configure them. The topic was chosen due to the author’s personal interest and identification of personal lack of knowledge and understanding.

2

2 JavaScript Bundling

To deeply understand the need behind bundling, one needs to understand the need for JavaScript modules and the expansion of web development from little inline scripts on a HTML file into the modern web applications that are developed today.

2.1 Modules and Closures

JavaScript works on a HTML file, inside a script tag. One can start by writing inline scripts between the tags, but when the application grows, the script tag expands and the code is not reusable, but the coder shall copy and paste it to be able to use it on another page of the application. It is necessary to move it into its own file, to be able to reuse it on another page. The file then expands, and it is needed to divide into multiple files. An example of a typical old-school HTML with an inline script tag is shown below. Important parts have been highlighted in green.

Application title

It is common for a coder to want to add external node packages later to make coding easier and then the situation with the script tags can already be quite complicated, because the script tags must be in the correct order in the HTML file. Also, the global namespace gets polluted by all the globally defined functions and variables. The maintainability of the code is on a poor level and the coder must do duplicate work when implementing new changes.

An example of a HTML file with external libraries can be seen below. In this case there were scripts for adding React and ReactDOM and a button component, which can be assumed to internally use React and ReactDOM. Important parts have been highlighted green.

3

Application title

As one can imagine, adding multiple components and making them function together, might get quite laborious. To reduce the global scope pollution, one can use closures and modules. When using modules, the contents of the files are encapsulated into module objects, which can then be exposed to the global scope. Typically, the building blocks of a module are imports, exports and the code in-between. However, there are several module patterns in JavaScript, which are presented later in this chapter. Benefits of modules are e.g. reusability, composability, leveraging, isolation and organization (McGinnis 2019).

2.1.1 Closure

Closures can be used to create structure for the code and reduce the number of global variables by nesting them inside functions. An example of a simple closure can be observed below. function closureFunction(){ var variable1 = 1; var variable2 = 2; return (variable1 + variable2); }

The closureFunction wraps variable1 and variable2 inside it, so that the global scope gets polluted only by the namespace closureFunction.

2.1.2 Global Module Object with IIFE

In the global module object syntax, the functions are wrapped inside an IIFE, aka Immediately Invoked Function Expression, only exposing one object into the global namespace. This object then contains all the methods and values, that would either be

4 in the global scope. In the example below, only myModule-named object will be exposed. (Choi 2016) var myModule = {};

(function(){ myModule.add = function(a, b) { return a + b; } })()

// several more IIFEs to add functionality into myModule

IIFE as per its name, runs immediately when defined, so all the methods are immediately available. The variables and functions inside the module objects can then be utilized via the module with the dot syntax, e.g. myModule.myFunction(). There are still many popular JavaScript libraries utilizing this module pattern, such as jQuery exposing the $- object.

2.1.3 CommonJS

CommonJS aka previously ServerJS was planned for server-side JavaScript to define common for web server, desktop and command line applications. In addition, CommonJS also defined APIs for modules, which consisted of exports and imports like in the example snippet below. (Choi 2016)

// moduleName.js module.exports = function functionName(variable1, variable2){ return variable1 + variable2; }

// utilizerName.js var moduleName = require(‘./moduleName.js’);

ModuleName.js is defining a function called functionName and then exporting it. UtilizerName.js is then importing that module and the function is accessible via the variable in which it was saved on the import. Require-call is synchronous and before executing the following rows, import shall be finished.

2.1.4 Asynchronous Module Definition (AMD)

CommonJS requires things to happen synchronously and to resolve that problem, asynchronous module definition (AMD) was created. The basic idea in AMD is that all

5 the functions are first defined and then the callbacks will enable the real functionalities. This enables the code to continue executing, before imports have been fully loaded. An example of an AMD pattern can be inspected below. define([‘foo’, ‘bar’], function(foo, bar){ var myModule = { function functionName(){ console.log('I am a function’); } } return myModule; });

In addition to solving the problem with polluting global scope, AMD also solves the issues related to dependency resolution i.e. wealth of script tags, only requiring taking care of dependencies of each module or file (Choi 2016).

2.1.5 Universal Module Definition (UMD)

When having three different patterns for writing modules, utilizing libraries, using different types of modules can get heavy. Universal Module Definition (UMD) is not really a module pattern, but a way to identify which module pattern is being used and to enable mixing different module patterns. The UMD return export from UMD’s source code (without the comments) can be seen below (UMDJS 2017).

(function (root, factory) { if (typeof define === 'function' && define.amd) { define(['b'], factory); } else if (typeof module === 'object' && module.exports) { module.exports = factory(require('b')); } else { root.returnExports = factory(root.b); } }(typeof self !== 'undefined' ? self : this, function (b) { return {}; }));

At a grassroots level, UMD is just if and else clauses to recognize the module pattern used and then to combine them into universally functioning modules. (Choi 2016)

2.1.6 ES6 modules

An even more advanced and future-proof way to create modules is however JavaScript’s own ES6 syntax (released 2015). The reason why it was previously done with global

6 module objects, CommonJS, AMD and UMD, was that JavaScript did not have a module system built into the language. (Choi 2016) ES6 looks much like CommonJS with imports and exports, as visible in the snippet below. import something from './something'; export default function functionName(variable1){ return something(variable1, 5); }

In the example one can see a module exporting a function called functionName, which is utilizing an imported method called something. This way the functionality can be nested and global namespace pollution reduced. It also takes care of the dependency resolution and hazzle of script tags. All the major browsers apart from support the use of ES6 modules. (MDN Web Docs 2020)

2.2 Module Loaders

Modules became widely popular and still most of the popular JavaScript libraries use this pattern. The second remaining problem with the orders of the scripts has been solved with module loaders. (Choi 2016).

Module loaders are used for taking care of the dependency management. In more layman’s terms this means that they take care of having the necessary modules in place when they are needed. By using a module loader, one does not need to worry about having the script tags in a correct order anymore, but they can just use a module loader to do the job for them. Two most common module loaders are SystemJS and RequireJS. Using RequireJS would change the HTML to only have one entry point like presented below.

Application title

7

And inside the main.js requirejs()-function could be used to load any other scripts one would need (RequireJS 2020). However, to get the application ready for production, task runners such as Grunt and Gulp would still be needed to transform and package the code. If comparing module loader to a bundler (cf. Figure 2), a module loader would only be capable of doing the first two steps.

2.3 Bundlers

With module loaders, there are lot of requests. Since they only take care of having the correct files in place in the order in which they are needed. Bundlers are the next, more sophisticated way, of handling the modules. Bundlers have the capabilities of module loaders, but they are also much more: they also transform and package the code.

In the early days, the first bundler was just a simple concatenation (+=) to combine the different files into one big file (Lawson 2017) and then injecting that into a script tag. However, they have advanced a lot from that today.

If inspected as black and white, the bundler takes as an input the codebase and then creates a bundle file. A simplified ideology of a bundler is presented in Figure 1 below.

Figure 1. Ideology of a bundler.

As a more sophisticated definition, a bundler processes the application code, creating a dependency graph based on the imports and exports recursively, which will then contain all the modules needed by the application. It will then bundle them into one or multiple bundle files, which will then be injected into the HTML file and the loaded and run by the browser. (Rezende 2019)

8

2.3.1 Bundling Process

To better understand how the bundler works, the bundling process can be demonstrated as presented below in Figure 2.

Dependency Collection Transformation Optimization graph

Figure 2. Bundling process phases (Rappl 2019).

The bundling process starts with collecting all the different pieces of code from the code base, then proceeds to going through each import and export and based on that, forming a dependency graph. Then transforming the code into a format that the bundler will be able to understand. Unless the bundler has a built-in support for this, additional plugins may be needed. After the transformation, some optimizations, such as minifying, tree shaking, or bundle splitting can be performed. Finally, the resulting code is in an understandable format for the browser, ready to be referred from the HTML file with a combined bundle script.

To better explain the dependency graph (Figure 3), one was drawn for the test application created later during this thesis. For the simplicity, the dependencies of the React (6 pcs) and ReactDOM (2 pcs) libraries were not presented.

Figure 3. Dependency graph of the test application.

9

The graph in Figure 3 has a tree structure, where the entry point is the top of the tree, the index.js file. From the tree one can see that to run index.js, React, App.js and ReactDOM must be defined. And to run App.js, App.css and Spinner.js must be defined and so on. Based on the graph it would be possible to create a bundle with simple copy and paste style by making sure that the dependencies are always defined before they are used.

2.3.2 Optimizing Bundles

There are multiple ways to optimize the bundle size, such as tree shaking or dead and duplicate code elimination, scope hoisting, bundle splitting and minifying. In this chapter, a closer look is taken on each of them.

Tree shaking aka dead and duplicate code elimination is in a key position when aiming for the optimum bundle size. Tree shaking becomes especially helpful when using external libraries, because the whole external library is being installed, even though the coder would only need a fraction of the codebase. Different bundlers support different types of modules in their tree shaking process, may it be ES6 or CommonJS. Also, the way the tree shaking is executed matters for the build times, and thus on the user- friendliness of the bundler, during development. Caching and parallel processing when executing tree shaking can help to keep the bundling process fast. (Sharma 2019)

When implementing tree shaking, another important performance-related thing to understand is the scope hoisting. Scope hoisting means that instead of wrapping each module in a function and then chaining the functions, all the module code can be hoisted into a single function (Hardy 2018). This removes boiler plate code remarkably. It also reduces the amount of chained function calls, which makes the code run faster (Menichelli 2017). When hoisting, the bundler however needs to take care of preserving the original behavior and preventing naming collisions (Hardy 2018). An example of scope hoisting can be seen below.

// source files

// subModule.js export default function (variable1, variable2) { return variable1 + variable2; }

// mainModule.js import functionName from './subModule';

10

const result = functionName(1, 2); console.log(result);

// resulting file (after scope hoisting)

(function () { 'use strict'; function functionName(variable1, variable2) { return variable1 + variable2; } const result = functionName(1, 2); console.log(result);

}());

In the above example, two modules are combined into one by taking their contents and wrapping them inside an IIFE. The “use strict” declaration defines that the code should be executed in “strict mode”, where e.g. undeclared variables are not allowed (W3Schools 2020).

Once the web application grows, so does the bundle size, especially due to all the third- party dependencies. Since the loading time of a web page is directly proportional to the data moved via Internet, the bundle size matters. Splitting bundles will offer a solution to lazy loading code progressively, when it is needed, which can drastically improve the performance and user experience. (Sharma 2019) A common practice is to separate the vendor code from the actual code to a separate chunk file and then caching the chunk file with a hash. Then whenever revisiting the page, the chunk file is already in the cache, leading into faster site loading times. When the vendor chunk is updated, a new hash is created and then on the next site visit, the new chunk file will be loaded. Different bundlers have their own way of handling the bundle splitting.

One more step to make bundles smaller is minifying the code. Minifying means making the code shorter and reducing the amount of characters, whitespace, comments, semicolons and other extra markup used and renaming variables and functions with shorter ones (Cloudflare 2020). To perform the minifications, there are plenty of different tools available, such as UglifyJS, Terser or in this use case different plugins of the bundlers. After minification, in some use cases the code is then also gzipped to make it even smaller. An example of the same scope-hoisted code minified can be seen below.

!function(){"use strict";const o=2+3;console.log(o)}();

11

As can be seen, the minifier noticed that the function from subModule.js could be removed totally. It also renamed the variables and removed extra spaces. If inspecting source code in browsers, this is the kind of code one most typically finds there. It is not very human-eye-friendly and thus the code people write is not minified by default.

12

3 Different Bundlers

There are plenty of different kinds of JavaScript bundlers available. Unambiguously the most downloaded one via npm is Webpack, followed by other less used bundlers as shown in Figure 4 below.

Figure 4. Download statistics via npm for different bundlers from 2015 until 5th of September 2020 (Vorback, 2020).

Following Webpack, Rollup has just recently bypassed Browserify in the number of downloads. Next there come even less used bundlers: Parcel and Google Closure Compiler. There are more bundlers available, such as Brunch, Microbundle and FuseBox, but their usage is relatively small with less than a million downloads in 2019 (Vorback, 2020). The reason why the results of 2020 are not comparable is that the statistics were checked in September. But it would seem Webpack’s popularity has risen even more, because already in September its downloads were on the same level as during 2019. Also Rollup has almost doubled it download times this year.

Roughly the bundlers can be categorized into two (Rappl, 2019):

• bundlers in which utilities can be expanded with different types of plugins

• bundlers which try to include as much as possible in their core

13

The bundlers presented in this thesis were selected by their popularity and so that both bundler types were covered. From the bundlers presented in this thesis, Webpack, Browserify and Rollup belong to the first category and Parcel to the second category.

3.1 Webpack

Webpack was started in 2012 by Tobias Kopper to solve the problems related to single- page applications: code splitting to on-demand loadable chunk files and accessing static assets. (Harris 2017)

As stated before, Webpack is currently the most utilized bundling tool. Webpack basic utilities can be extended with dozens of module-loaders and official and third party created plugins. Webpack has a high support for different types of resources. The basic ideology of Webpack is presented in Figure 5 below.

Figure 5. Webpack ideology (Webpack 2020).

So Webpack functions so that it takes in the interdependent source code files, be it JavaScript, SASS, pictures or other files, then bundles the code following the steps shown previously in Figure 2 and creates separate bundle files for different file types. To customize the bundling process, the user can configure Webpack with additional loaders and plugins.

The difference between loaders and plugins is their access to the actual build process. Loaders can only do pre-processing of files, whereas plugins can also register hooks within the Webpack’s build system and modify the compiler and compilation and how

14 they work. (Heiner 2016) Actually loaders are just algorithms which tell Webpack how to load specific type of files. (Webpack 2020)

Different loaders can be piped, from right to left, to be able to pre-process the resource (Webpack 2020). For example, from sass-loader to CSS-loader and last to style-loader suitable format, as seen below.

{ test: /\.s[ac]ss$/i, loaders: ['style-loader', 'css-loader', 'sass-loader'], }

The use of loaders and plugins is further discussed in Chapter 5.1.

Webpack provides capabilities for bundle splitting and lazy module loading with build in highly customizable plugins as well as supporting sharing modules between different applications. The configuration can be done also as JavaScript, instead of JSON, which enables conditional configuring, and for example dynamic configurations by the node environment being development or production. Webpack also has a built-in easily configurable development server, which automatically on code change and saving re- bundles the code and refreshes the browser. (Webpack 2020)

3.2 Browserify

Browserify was created soon after Node.js was first launched in 2010. It was not actually originally designed to be used for bundling, but to be able to re-use and run Node.js code in the browser, like its name states “browser-ify” (Lawson, 2017). Browserify has, however, expanded a lot from its early days, being now used for bundling also. However, its history is the reason why Browserify’s focus is on CommonJS and the enabling of require(‘example-library’) function.

Like Webpack, and due to existing since 2010, Browserify also has several plugins available to extend its capabilities. Unlike Webpack pulling features in its core, Browserify is keeping the core simple and plugins separate. Like Webpack, Browserify can also be configured with JavaScript or JSON or CLI options. Unlike Webpack, Browserify does not support bundle splitting or shared bundles. (Browserify 2020)

15

3.3 Rollup

Rollup was created in 2014 to build flat distributables of JavaScript libraries as efficiently as possible. Unlike other bundlers wrapping each module into its own function, Rollup was the forerunner of scope hoisting, thus enabling smaller and flatter bundles with less boiler plate code and faster function execution times. (Harris 2017) Rollup builds on top of ES6. The major difference to Webpack is that Rollup is much more lightweight having less dependencies and less out-of-the-box features. For almost anything, an extra plugin will be needed. (Rollup.js 2020)

Like Webpack and Browserify, Rollup works with plugins type of architecture and can be configured with separate JavaScript configuration file, JSON or command line options. Despite of its relatively young age compared to the two other bundlers, there are a lot of different plugins available. And quite as Webpack, the newer version of Rollup supports bundle splitting. (Rollup.js 2020)

3.4 Parcel

Parcel differs a lot from the other bundlers presented here, since it belongs to the second category trying to include as much as possible in their core (Parcel 2020). It is promoting itself as a zero-configuration tool, which is just as simple as the simplified bundler presentation in Figure 1.

Another great feature of Parcel is its parallelly performed multicore processing when doing the tree shaking and caching of the file system, which keep the build times extremely fast (Sharma 2019). This is important especially in the development phase of an application.

Like other bundlers, Parcel has development server, code splitting and code transformations enabled. Parcel also supports laze loading, but it requires syntaxial changes in the source code from import to import(). (Parcel 2020)

16

4 Creating Simple Web Application for Comparison Basis

To be able to compare the chosen bundlers, the comparisons required a simple base application to be bundled with each bundler. The test application created was a spinning picture. To keep the focus of the thesis in the bundlers, a choice was made to create the application with React, which was already familiar to the author. Also using a framework (external dependencies from the viewpoint of a bundler) made the configurations a little more interesting if compared to not using any frameworks.

The steps to re-produce this simple React application are presented in this chapter. The development environment used consisted of Visual Studio Code as the IDE, Git bash terminal, Node.js version 10.15.2 node package manager (npm) version 6.4.1 and React.js version 16.13.1. All of which should be installed before starting to replicate the research.

4.1 Creating Package.json File

The app was initialized from the terminal, by running the following commands: mkdir test-app cd test-app npm init

The npm initializer then run the user through a set of questions to produce a simple package.json file which became the basis of the project. The name of the application created was test-app and its starting point was index.js as shown below. Values which were needed to be typed, are marked in green color, others were default values, accepted by pressing enter.

$ npm init This utility will walk you through creating a package.json file. It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields and exactly what they do.

Use `npm install ` afterwards to install a package and save it as a dependency in the package.json file.

Press ^C at any time to quit. package name: (test-app) version: (1.0.0) description: Test app to test different bundlers

17

entry point: (index.js) test command: git repository: keywords: author: Sonja Laurila license: (ISC) About to write to C:\Users\...\test-app\package.json:

{ "name": "test-app", "version": "1.0.0", "description": "Test app to test different bundlers", "main": "index.js", "scripts": { "test": " \"Error: no test specified\" && exit 1" }, "author": "Sonja Laurila", "license": "ISC" }

Is this OK? (yes)

After initializing the node project, a small package.json file with the JSON contents as seen above was created to work as the core for the application.

4.2 Creating Simple React Application

For the set-up, the wanted result was a small React application. In order to install React, one needed to install and save as project dependencies both React and ReactDOM with the following command npm install --save react react-dom.

Then to be able to start writing code, running the command mkdir src build will created two directories named “src” and “build”. The first one was needed to create the actual source code and the second one was where the bundled code reside.

Then by running command touch src/index.js src/App.js src/Spinner.js src/App.css build/index.html all the necessary code files were created: the starting point “index.js” for the source code inside the src-directory, the “App.js” file to place the React component and the “index.html” file to contain the script tag referring to the bundled source code. Additionally, it created a few more files to make the application a little more complex, so that there is something to bundle. To have different file types, a picture in png-format was also downloaded from Internet and it was named pic.png. The file structure and contents of the files are shown in Appendix 1.

18

Some code transpiling from React to JavaScript and from newer JavaScript to older browser-supported JavaScript was also needed in the project. Thus, @babel/core, @babel/preset-env and @babel/preset-react were already installed as development dependencies with command npm install --save-dev @babel/core @babel/preset-env @babel/preset-env.

If tried to run the code in the browser in its current state, it would not work, due to the nested imports in the files. It already crashes on the first line of index.js, which is trying to import the ReactDOM from node_modules, with an error message “Uncaught SyntaxError: Cannot use import statement outside a module”. This is due to not having the imports on the HTML file with separate script tags, but inside the source code. This is the reason for the need to bundle the code. If creating a React application with the create-react-app tool, it internally uses Webpack and thus works out of the box with zero configuration, because someone has already done the configurations. In this point, the sample application was in perfect state to be started to bundle with different bundlers.

19

5 Configuring and Bundling Projects with Different Bundlers

To compare the bundlers, all four bundlers were configured for the previously created simple react application.

To have a workspace for each bundler, three clones of the test-app project were needed. First node_modules and package-lock.json files were removed, then the whole application directory was copied three times and the directories and application names in the package.json were renamed to be:

• test-app-webpack

• test-app-parcel

• test-app-browserify and

• test-app-rollup.

Then the dependencies were re-installed by running npm install inside each project’s root directory.

5.1 Webpack

To include Webpack and the Webpack-CLI tool in the project, npm install --save webpack webpack-cli was run inside the test-app-webpack directory. Then the webpack.config.js file was created with touch webpack.config.js.

For the test application, there was needed support for at least JavaScript (.js), JavaScript extension (.jsx), CSS and the image file (.png). Thus, the following loaders were also needed:

• babel-loader together with before installed @babel/core, @babel/preset-env and @babel/preset-react for transpiling JavaScript files using Webpack

• style-loader and CSS-loader for CSS and

20

• file-loader for the image.

The necessary loaders were installed with command: npm install --save-dev babel-loader style-loader css-loader file-loader

The webpack config consists of multiple parts: entry, output and module, which in turn contains a list of rules, containing loaders and presets and other more detailed configuration options. Webpack was configured as shown below: const path = require('path') const config = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'build'), filename: 'index.js' }, module: { rules: [ { test: /\.js$/, loader: 'babel-loader', query: { presets: ['@babel/preset-react', '@babel/preset-env'], }, }, { test: /\.css$/, loaders: ['style-loader', 'css-loader'], }, { test: /\.(png|jpe?g|gif)$/i, loader: 'file-loader', options: { outputPath: 'images', publicPath: 'images', } } ] } } module.exports = config

The entry point for the application was the index.js file and output was bundled inside build directory with the same file name index.js. Module object contained rules, containing babel-loader and its necessary presets for compiling react, style- and css- loaders to compile CSS and finally file-loader to compile the image. Key value named “test” uses regex syntax for defining what type of files are meant to process with the loader in question.

21

After configuring Webpack, the package.json had to be updated with the Webpack’s build script to be able to easily run the bundling command with npm run :

"scripts": { "build": "webpack --progress --colors --mode=production" }

After running the script with npm run build, one could open the index.html in the browser and notice that the picture is not showing correctly. This got fixed by changing the importing style of the picture from “require” to “import” as seen below:

// changed, working: import pic from './pic.png';

// previous, not working // const pic = require('./pic.png’);

Re-running the build fixed the issue and resulted in two files: image and index.js. The size of the JavaScript bundle was at this point 132 KiB.

However, because Parcel’s and Rollup’s CSS plugin automatically separate CSS from the JavaScript, the CSS was separated also in the Webpack configuration by adding the following lines of configurations (marked in green): const path = require('path') const MiniCssExtractPlugin = require('mini-css-extract-plugin') module.exports = { // ... other configurations as before module: { rules: [ // ... other loaders as before { test: /\.css$/, loaders: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader'], } ] }, plugins: [ new MiniCssExtractPlugin({ filename: 'bundle.css' }), ] }

MiniCssExtractPlugin is a commonly used tool for separating the CSS from JavaScript with Webpack. Because the loaders in the list of loaders are piped from right to left, the MiniCssExtractPlugin was put between the previously added two loaders. Then it runs after css-loader but before style-loader.

22

As a final step to make the application work, the stylesheet was also needed to add on the HTML’s head as follows:

Build results can be seen in Figure 6 below.

Figure 6. Webpack build results.

After separating the CSS, the JavaScript bundle size went slightly smaller and ended up in 130 KiB. However, when viewing the CSS file, it was not minified. It could be minified with optimize-css-assets-webpack-plugin, but since the focus is on JavaScript, it is not in the scope of this research. The whole package.json and webpack.config.js are presented in Appendix 2.

5.2 Browserify

To include Browserify in the project, the command npm install --save-dev browserify was run.

23

The Browserify configurations could be made in its own configuration file, but due to simplicity and lack of such examples in the documentation they were included in the package.json.

For the test application, there was needed support for JavaScript (.js), JavaScript extension (.jsx), CSS and the image file (.png). Thus, some supportive libraries were needed:

• Babelify for Browserify’s Babel transformation

• Browserify-css for CSS and

• Imgurify for the image

Installing the necessary loaders happened with the command: npm install --save-dev babelify browserify-css imgurify

To use Browserify, a build script and some configurations to transform the code were needed. The build script used was:

"scripts": { "build": "browserify -t browserify-css -t imgurify src/index.js -o build/index.js" }

The script consisted of transforming (-t) the code with the browserify-css and imagurify, the input was set to src/index.js and output (-o) was set to build/index.js.

Babelify also needed some extra configurations in package.json to be able to interpret React and newer JavaScript. The necessary Browserify configurations needed in the package.json can be seen below.

"browserify": { "transform": [ [ "babelify", { "presets": [ "@babel/preset-react", "@babel/preset-env" ]

24

} ] ] }, "browserify-css": { "minify": true, "output": "build/bundle.css" }

The whole package.json is presented in Appendix 3. No code changes were needed, and the original picture import style worked out of the box. Running the build script created the same index.js file inside build directory as before with Webpack. With the index.html file, the same trick was needed as previously to add the stylesheet tag in the HTML’s head. In this point, the size of the bundle ended up being 1230kt because the code was not minified by default and adding a package was required to make it minified. The tinyify package was installed with npm install --save-dev tinyify and the build script was modified to include the tinyify as follows:

"scripts": { "build": "browserify -t browserify-css -t imgurify -p tinyify src/index.js -o build/index.js" }

Tinyify was run with -p flag which stands for plugin. After this change, the outcome was 279kt, because the picture (114kt) was still included in the JavaScript bundle. In order to separate the picture from the bundle, also Urify-plugin was tested, but it did not work any better than Imgurify.

Also, Browserify did not print out any statistics about the build process, but the time and bundle sizes were inspected one by one for all the tools when all the configurations were ready.

5.3 Rollup

To install Rollup npm install --save rollup was run inside the test-app-webpack directory and then rollup.config.js file was created with touch rollup.config.js. Then to run the test application, multiple different plugins were needed:

• rollup-plugin-babel for transpiling JavaScript files using Rollup

25

• rollup-plugin-css-only for being able to understand and separate the CSS

• @rollup/plugin-url for the png-file

• @rollup/plugin-node-resolve to find date-fns modules in node_modules and @rollup/plugin- to convert them into ES modules

• @rollup/plugin-replace to define the environment process, otherwise React complains about "Uncaught ReferenceError: process is not defined"

The plugins were installed with command: npm install --save-dev @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-replace rollup-plugin-babel @rollup/plugin-image rollup-plugin- css-only

The initial rollup.config.js file looked like this: import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import replace from '@rollup/plugin-replace'; import babel from 'rollup-plugin-babel'; import image from '@rollup/plugin-image'; import css from 'rollup-plugin-css-only'; export default { input: 'src/index.js', output: { file: 'build/index.js', format: 'iife', sourcemap: false }, plugins: [ replace({ 'process.env.NODE_ENV': JSON.stringify( 'production' ) }), resolve(), babel({ exclude: 'node_modules/**' }), commonjs(), image(), css({ output: 'build/bundle.css' }) ] };

The syntax of Rollup is quite similar to the one of Webpack. In the configuration it has input and output and then list of plugins. The syntax of the plugins list differs from Webpack, because where Webpack resembles more JSON, Rollup resembles JavaScript, having just a list of function calls. Rollup also needed to be configured much

26 more in detail, needing to specify many modern JavaScript features that Webpack has as out-of-the-box features, e.g. commonjs or resolve.

The build script and babel configurations were added into the package.json:

"scripts": { "build": "rollup -c" }, "babel": { "presets": [ "@babel/preset-react" ] }

When bundling the code, the resulting bundle size was 288kt. A plugin to minify the code was added with npm --save-dev rollup-plugin-terser and the following lines (marked in green) were added into the rollup.config.js: import { terser } from 'rollup-plugin-terser'; export default { // ... other configs plugins: [ // ...other plugins terser() ] };

After a re-bundle the bundle size decreased into 279kt. Still quite large when compared to the result of Webpack, but it had to be remembered that the picture was still included in the bundle, like in Browserify. When taking a closer look on the documentation of @rollup/plugin-image, one can notice that it was not the right plugin for the task in hand. To separate the image from the bundle, the plugin was replaced with @rollup/plugin-url. Configurations were changed as presented below (additions marked green, deletions red): import url from '@rollup/plugin-url'; import image from '@rollup/plugin-image'; export default { // ... other configs plugins: [ // ...other plugins url(), image(), ] };

27

After this change, the new bundle became the smallest, about 127kt. When trying to run the application, the spinner was not spinning. This was again due to the index.html not being automatically generated and thus it did not have the stylesheet on it. The stylesheet was added inside the head part of the HTML with the following line:

With the styles in place, the application worked correctly. Injecting the HTML could also be done automatically with @rollup/plugin-html and some additional configurations, but it was not in the scope of the research. The final package.json and rollup.config.js can be found in Appendix 4.

5.4 Parcel

Parcel is marketing itself as a zero-configuration bundling tool, so it should work easily out of the box. The necessary babel presets and the parcel-bundler itself were installed with the command npm install --save-dev parcel-bundler. After this, the babel preset configuration was added in the package.json as seen below:

"babel": { "presets": [ "@babel/preset-react" ] }

Finally, also the following build script for bundling was added:

"scripts": { "build": "parcel build src/index.html - build/ --public-url ./" },

In comparison to the previous bundlers, this was quite an easy task. The total package.json is shown in Appendix 5. After running the npm run build command, the bundler did its job. It even splitted the code and separated it by the filetypes without any additional configuration needed. The only thing, which could have been highlighted more in the documentation was the --public-url tag, which made the index.html work when run locally. The results of the build can be seen in Figure 7 below.

28

Figure 7. Parcel build results.

The build process took about 5 seconds and the JavaScript bundle size was about 129 KB. However, with command time npm run build, the result was 8,057 seconds, which states that the time printed by Parcel was not comparable with the ones with time- command. In the following comparison chapter, the use of time-command for clocking the comparable values for each bundler is introduced.

29

6 Bundler Comparison

Configuring the different bundlers was quite similar for all of them, apart from Parcel, which was considerably easier than any of the others. Also, after configuring the first bundler, which happened to be Webpack, the next ones went quite smoothly, when it was already understood, why and what kind of plugins were needed.

If comparing Rollup and Webpack, their configurations were quite similar with slightly different syntax. Although Rollup did need a few extra plugins, which could have been coming out of the box since they are needed in almost any project, such as the CommonJS and node-resolve plugins. Also, the error message received for the process environment was quite cryptic, but luckily it did not require that many minutes of googling to solve it. Separating the image from the bundle was made difficult with Browserify, but this is probably more related to its plugins not being as actively developed as for Webpack than the actual bundler.

In the comparison, the latest version of each bundler was used. The aim was to keep the configurations as simple as possible, to be able to compare the basic setups of the bundlers. Although, separating CSS and minifying were implemented to see how small the bundle goes and because it was initially activated in some of the bundlers, when bundling a production build. The comparison of the technical attributes can be seen in Table 1 below.

Table 1. Technical comparison of the bundlers.

Webpack Rollup Browserify Parcel Version used 4.42.1 2.6.0 16.5.1 1.12.4 Downloads in 386 210 713 38 579 586 33 454 983 4 232 013 2019 Release year 2012 2014 2010 2017 Collaborators 591 219 183 229 Total packages 567 209 523 770 in the setup (158 initially)

30

Number of 5 7 4 0 extra plugins installed Node_modules 34,0 21,0 37,6 73,3 size (Mt) Bundle size 131 127 280 130 (kt) Image (114kt) Yes Yes No Yes separately CSS (<1kt) Yes Yes Yes Yes separately

Key take-aways from Table 1 are that Webpack is ruling the markets with their download times. Rollup is notably lighter than any of the other bundlers, and on the other end Parcel is the most heavyweight. Rollup needed the largest number of additional plugins installed and Parcel did not need any. Image was not separated with Browserify, and thus its bundle size ended up much bigger. The sizes of the bundle files affect directly to the loading time of the web page.

To compare the bundling time, there was a set of 4 test runs, with the following setups for each round:

1. first run after removing node_modules, package-lock.json, build and possible cache files and then re-installing all dependencies and then bundling

2. second run after removing build and possible cache files

3. third run, no removals and no changes to files

4. fourth run, no removals and no changes to files

Time was measured by using the time npm run build -command. The results are presented in Figure 8 below.

31

ms 18000 16000 14000 12000 10000 8000 6000 4000 2000 0 1 2 3 4 # of test browserify parcel rollup webpack round

Figure 8. Bundling time test results.

As one can notice, the first run was always much more tedious than the later ones, this was because it was the first launch of all the dependencies. Rollup was performing here the best with 9.4 seconds, probably because it is the most lightweight of the projects package-wise. The second round where the focus was purely on bundling, the times were already much smaller for all the bundlers. Webpack seemed to lead the game with around 3.4 seconds. For the next two rounds, Webpack had about the same results each round, Rollup speeded a little ending up in about 4.8 seconds, Browserify ended up being the slowest with its 6.8 seconds and the quickest ended up being Parcel, which was utilizing caching, getting a result of about 2.7 seconds.

In addition to a quantitative type of a performance comparison, also a qualitative type of a feature comparison was made and is presented in Table 2 below. The explanations for the terms can be found in Appendix 6.

Table 2. Feature comparison of the bundlers.

Webpack Rollup Browserify Parcel Multi-entry Yes Yes Yes Yes Dev-server Yes Yes Yes Yes Hot Module Yes Yes Yes Yes Replacement (HRM)

32

Tree shaking ES6, ES6, CommonJS ES6, CommonJS CommonJS CommonJS Scope Yes Yes Yes, but not Experimental hoisting maintained. Source maps Yes Yes Yes Yes Async require Yes Yes No Yes / module splitting Configuration Intermediate, Intermediate, Intermediate, Very simple complexity 39 29 17 and lines of code

As one can see, there was not any remarkable differences between the bundlers. To summarize some of the differences, Browserify was lacking ES6 support and scope hoisting, increasing the bundle size and not offering async requiring of modules. Parcel’s scope hoisting was on experimental stability level and might work in some cases and others not. The biggest difference was the configuration complexity of Parcel. In the table, also lines of code were being presented, but it does not measure well, how difficult it was to come up with those lines and thus a verbal expression based on the author’s personal experience was also added.

Based on the technical and feature comparisons and practical experience gained through configuring the bundlers, the strengths and weaknesses of each bundler were gathered into a summarizing table below (Table 3).

Table 3. Strengths and weaknesses of different bundlers summarized.

Strengths Weaknesses Webpack Most users and contributors → Somewhat complicated best support. configurations. Quick rebuilds. Good scope hoisting capabilities. Support for including non-JS files.

33

Good collection of official plugins. Works well with legacy integrations which use mixed modules and global objects. Rollup Lightweight and least external Somewhat complicated dependencies. configurations. Good scope hoisting capabilities. Need of plugins for basic things, Smallest bundle size. which should work out of the box. Good collection of 3rd-party Slower rebuilds than Webpack. plugins. Need for third party plugins for including non-JS files. Using some of the 3rd-party plugins may need trial and error kind of approach. Browserify Good collection of 3rd-party Somewhat complicated plugins. configurations. Works well with CommonJS Fewer plugins available than others. modules. Better for node than client-side code. Lack of support for scope hoisting. The slowest rebuilds of the four. Need for third party plugins for including non-JS files. Not supporting ES6 modules. Parcel No configurations needed. Activating code splitting needs code Caching and parallel processing. changes: import → import(). Extremely quick rebuilds. If something goes wrong, fixing it Support for including non-js files. may be harder, since the Some plugins available, also with configurations are not in your zero configuration. hands. Scope hoisting not stable.

As can be noted from the comparison tables, the differences between the similar types of bundlers are quite pedantic. All the three plugin-architecture bundlers have wide plugin libraries. Rollup has a little bit smaller bundle sizes than Webpack, but Webpack is faster

34 and offers more sophisticated bundle splitting tools. Rollup may be better for libraries where the size matters the most and Webpack for web applications where the bundle splitting capabilities are important. Parcel on the other hand is quite different from all the others. It shines especially as a development time tool and it is good for getting things quickly up and running, quick proofs of concepts and when used by unexperienced web developers.

However, in production environment with scaled applications, sometimes those minor differences may be repeated so many times to form a huge difference. It is reasonable to also consider using a different tool for development and another for production builds. Then in development phase, meaningful features would be the ease of getting started and the bundling time after updating the code. Also, the development server and hot module replacement capabilities would be important. On the other hand, for production build the most important characteristics would be the bundle size and code splitting capabilities. When considering the right bundler for the development environment or smaller projects, it is also a lot about personal preference.

Some users consider Browserify simpler than Webpack, but one can disagree with that. In this research there were definitely more problems with getting Browserify to work than Webpack due to lack of examples. Additionally, in the end, the image could not be separated from the bundle due to incompatibilities of the plugins with the setup of the research, since Urify-plugin, did not support JSX. The syntax of Webpack can be more complex but finding suitable plugins, finding working solutions from Stack Overflow and the quality of documentation is a lot better for Webpack.

35

7 Conclusions

During the research, a deep understanding of bundlers, their need and how they work under the hood was gained. This study gathers together a thorough overview of bundlers and explains in detail how they work and the background of how the need for bundling was born. It also compares the most commonly used bundlers from both qualitative and quantitative perspective and thus offers a good basis for decision making on which bundler to choose for a certain use case. Each bundler was also successfully configured and used to gain more practical experience for the comparison. However, a larger test application would have enabled deeper analysis of the code splitting capabilities of the different bundlers and this could be a good opportunity for a broader research.

For a newbie coder it would be recommendable to start with Parcel, if bundling is not familiar at all. Also, in case of a quickly created proof of concept project, Parcel would probably be the number one choice due to minimal configurations. However, in case having a complex project, with a lot of third part dependencies and need for polished and clearly defined bundle splitting, Webpack may offer the coder better means to refine the result. In the case of Parcel, there may be limitations in the customizability since the bundler is so heavily pre-defined. Parcel is also a relatively new bundler and thus may be less mature and have more open issues.

When it comes to Rollup and Browserify, they have been clearly less used than Webpack, which affects the amount of support available, more specifically the number of plugins, the maturity of the plugins and help available on different kind of coding support channels, e.g. Stack Overflow. If bundle size is extremely critical, then Rollup offers the best scope hoisting capabilities and hence the smallest bundle size. Rollup also has a slight advantage of having fewer external dependencies in case that is something the developer values. Browserify is not initially even meant for client-side code bundling, but for Node.js and then only later extended for client-side, and unfortunately that can be noticed in its capabilities. In case the developer is not already familiar with it and thus wants to use it, for client-side code it would not be the number one choice in most circumstances.

36

References

Browserify. (2020). Browserify Handbook. [Online]. Available (cited on 13.4.2020): https://github.com/browserify/browserify-handbook

Choi, S. (2016). Brief history of JavaScript Modules. [Online]. Available (cited on 15.4.2020, 17.7.2020): https://medium.com/sungthecoder/javascript-module-module- loader-module-bundler-es6-module-confused-yet-6343510e7bde

Cloudflare, Inc. (2020). What is minification in JavaScript? [Online]. Available (cited on 20.4.2020): https://www.cloudflare.com/learning/performance/why-minify-javascript- code/

Hardy, A. (2018). Optimizing JavaScript Through Scope Hoisting. [Online]. Available (cited on 21.4.2020): https://medium.com/adobetech/optimizing-javascript-through- scope-hoisting-2259ef7f5994

Harris, R. (2017). Webpack and Rollup: the same but different. [Online]. Available (cited on 17.4.2020): https://medium.com/webpack/webpack-and-rollup-the-same-but- different-a41ad427058c

Heiner, N. (2016). Webpack loaders vs plugins; what's the difference? [Online]. Available (cited on 17.4.2020): https://stackoverflow.com/questions/37452402/webpack-loaders- vs-plugins-whats-the-difference

McGinnis, T. (2019). JavaScript Modules: From IIFEs to CommonJS to ES6 Modules. [Online]. Available (cited on 15.4.2020): https://tylermcginnis.com/javascript-modules- iifes-commonjs-esmodules/

MDN Web Docs. (2020). JavaScript modules. Available (cited on 17.7.2020): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules

Menichelli, J. (2017). Brief introduction to scope hoisting in Webpack. [Online]. Available (cited on 21.4.2020): https://medium.com/webpack/brief-introduction-to-scope-hoisting- in-webpack-8435084c171f

37

Lawson, N. (2017). Brief and incomplete history of JavaScript bundlers. [Online]. Available (cited on 18.4.2020): https://nolanlawson.com/2017/05/22/a-brief-and- incomplete-history-of-javascript-bundlers/

Parcel. (2020). Parcel. Blazing fast, zero configuration web application bundler. [Online]. Available (cited on 13.4.2020): https://parceljs.org/

Rappl, F. (2019). Choosing the Right JavaScript Bundler in 2020. [Online]. Available (cited on 23.4.2020): https://blog.bitsrc.io/choosing-the-right-javascript-bundler-in-2020- f9b1eae0d12b

RequireJS. (2020). How to get started with RequireJS. [Online]. Available (cited on 24.4.2020): https://requirejs.org/docs/start.html

Rezende, I. (2019). A web history: The Origin of Bundlers, Part 3. [Online]. Available (cited on 18.4.2020): https://blog.avenuecode.com/a-web-history-the-origin-of-bundlers- part-3

Rollup.js. (2020). Rollup.js. [Online]. Available (cited on 13.4.2020): https://rollupjs.org/guide/en/

Sharma, M. (2019). Rollup vs. Parcel vs. webpack: Which Is the Best Bundler? [Online]. Available (cited on 20.4.2020): https://medium.com/better-programming/the-battle-of- bundlers-6333a4e3eda9?

UMDJS. (2017). UMD returnExports.js. [Online]. Available (cited on 17.7.2020): https://github.com/umdjs/umd/blob/master/templates/returnExports.js

Vorback, P. (2020). npm-stat. [Online]. Available (cited on 10.4.2020): https://npm- stat.com/

W3Schools. (2020). JavaScript Use Strict. [Online]. Available (cited on 17.7.2020): https://www.w3schools.com/js/js_strict.asp#:~:text=The%20%22use%20strict%22%20 directive%20was,for%20example%2C%20use%20undeclared%20variables.

38

Webpack. (2020). Webpack. [Online]. Available (cited on 10.4.2020): https://webpack.js.org/

Appendix 1 1 (3)

Small React application’s source code

File structure of the application:

├── build │ └──index.html ├── package.json ├── src │ ├── index.js │ ├── App.css │ ├── App.js │ ├── Spinner.js │ └── pic.png └── package.json index.html:

React App

package.json

{ "name": "test-app", "version": "1.0.0", "description": "Test app to test different bundlers", "main": "index.js", "scripts": {}, "author": "Sonja Laurila", "license": "ISC", "dependencies": { "react": "^16.13.1", "react-dom": "^16.13.1" }, "devDependencies": { "@babel/core": "^7.9.0", "@babel/preset-env": "^7.9.5", "@babel/preset-react": "^7.9.4", } }

index.js:

Appendix 1 2 (3) import ReactDOM from 'react-dom' import App from './App'

ReactDOM.render(, document.getElementById('root'))

App.js: import React from 'react'; import './App.css'; import Spinner from './Spinner' const App = () => { return ( ); } export default App;

Spinner.js import React from 'react'; const pic = require('./pic.png') const Spinner = () => { return (

); } export default Spinner;

App.css:

.App-logo { height: 40vmin; pointer-events: none; padding-top: 100px; }

@media (prefers-reduced-motion: no-preference) { .App-logo { animation: App-logo-spin infinite 1s linear; } }

.spinner { min-height: 100vh; display: flex; align-items: center; justify-content: center; }

@keyframes App-logo-spin { from { transform: rotate(0deg);

Appendix 1 3 (3)

} to { transform: rotate(360deg); } } pic.png: Any picture in png format.

Appendix 2 1 (2)

Package.json and webpack.config.js when bundling with Webpack

// package.json contents:

{ "name": "test-app-webpack", "version": "1.0.0", "description": "Test app to test webpack bundler", "main": "index.js", "scripts": { "build": "webpack --progress --colors --mode=production" }, "author": "Sonja Laurila", "license": "ISC", "dependencies": { "react": "^16.13.1", "react-dom": "^16.13.1", "webpack": "^4.42.1" }, "devDependencies": { "@babel/core": "^7.9.0", "@babel/preset-env": "^7.9.5", "@babel/preset-react": "^7.9.4", "babel-loader": "^8.1.0", "css-loader": "^3.5.1", "file-loader": "^6.0.0", "mini-css-extract-plugin": "^0.9.0", "style-loader": "^1.1.3", "webpack-cli": "^3.3.11" } }

// webpack.config.js contents: const path = require('path') const MiniCssExtractPlugin = require('mini-css-extract-plugin') module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'build'), filename: 'index.js' }, module: { rules: [ { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/, query: { presets: ['@babel/preset-react', '@babel/preset-env'], }, }, { test: /\.css$/, loaders: ['style-loader', MiniCssExtractPlugin.loader, 'css-loader'], }, { test: /\.(png|jpe?g|gif)$/i, loader: 'file-loader', options: { outputPath: 'images', publicPath: 'images',

Appendix 2 2 (2)

} } ] }, plugins: [ new MiniCssExtractPlugin({ filename: 'bundle.css' }), ] }

Appendix 3 1 (1)

Package.json when bundling with Browserify

{ "name": "test-app-browserify", "version": "1.0.0", "description": "Test app to test browserify bundler", "main": "index.js", "scripts": { "build": "browserify -t browserify-css -t imgurify -p tinyify src/index.js -o build/index.js" }, "author": "Sonja Laurila", "license": "ISC", "dependencies": { "react": "^16.13.1", "react-dom": "^16.13.1" }, "devDependencies": { "@babel/core": "^7.9.0", "@babel/preset-env": "^7.9.5", "@babel/preset-react": "^7.9.4", "babelify": "^10.0.0", "browserify": "^16.5.1", "browserify-css": "^0.15.0", "imgurify": "^2.0.1", "tinyify": "^2.5.2" }, "browserify": { "transform": [ [ "babelify", { "presets": [ "@babel/preset-react", "@babel/preset-env" ] } ] ] }, "browserify-css": { "autoInject": true, "minify": true, "rootDir": "." } }

Appendix 4 1 (2)

Package.json and rollup.config.js when bundling with Rollup

// package.json contents:

{ "name": "test-app-rollup", "version": "1.0.0", "description": "Test app to test rollup bundler", "main": "index.js", "scripts": { "build": "rollup -c" }, "author": "Sonja Laurila", "license": "ISC", "dependencies": { "react": "^16.13.1", "react-dom": "^16.13.1", "rollup": "^2.6.0" }, "devDependencies": { "@babel/core": "^7.9.0", "@babel/preset-env": "^7.9.5", "@babel/preset-react": "^7.9.4", "@rollup/plugin-commonjs": "^11.1.0", "@rollup/plugin-url": "^4.0.2", "@rollup/plugin-node-resolve": "^7.1.2", "@rollup/plugin-replace": "^2.3.1", "rollup-plugin-babel": "^4.4.0", "rollup-plugin-css-only": "^2.0.0", "rollup-plugin-terser": "^5.3.0" }, "babel": { "presets": [ "@babel/preset-react" ] } }

// rollup.config.js contents: import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import replace from '@rollup/plugin-replace'; import { terser } from 'rollup-plugin-terser'; import babel from 'rollup-plugin-babel'; import url from '@rollup/plugin-url'; import css from 'rollup-plugin-css-only'; export default { input: 'src/index.js', output: { file: 'build/index.js', format: 'iife', sourcemap: false }, plugins: [ replace({ 'process.env.NODE_ENV': JSON.stringify( 'production' ) }), resolve(), babel({

Appendix 4 2 (2)

exclude: 'node_modules/**' }), commonjs(), url(), css({ output: 'build/bundle.css' }), terser() ] };

Appendix 5 1 (1)

Package.json when bundling with Parcel

{ "name": "test-app-parcel", "version": "1.0.0", "description": "Test app to test parcel bundler", "main": "index.js", "scripts": { "build": "parcel build src/index.html -d build/ --public-url ./" }, "author": "Sonja Laurila", "license": "ISC", "dependencies": { "react": "^16.13.1", "react-dom": "^16.13.1" }, "devDependencies": { "@babel/core": "^7.9.0", "@babel/preset-env": "^7.9.5", "@babel/preset-react": "^7.9.4", "parcel-bundler": "^1.12.4" }, "babel": { "presets": [ "@babel/preset-react" ] } }

Appendix 6 1 (2)

Explanations for bundler features in Table 2

Multi-entry: If bundler supports multiple entry-points, meaning that one JavaScript application can be run from several different starting points. As a concrete example, for example, if there would be several SPA applications, having multiple index.html / index.js as starting points, but they would share some components, it would not be reasonable to have duplicate code, but to be able to start the application from separate starting points and be bundled to separate bundles.

Dev-server: Used in the development phase of the application to easily run the code after making changes. Reloading the code.

Hot Module Replacement (HRM): HRM is a feature related to the capabilities of the dev-server. With HRM enabled, codebase changes, while the application is running, do not cause the application to fully reload. It will just update the necessary parts, which were affected by the change, still remaining application state.

Tree shaking: Tree shaking means dead and duplicate code elimination. It works so that while going through the modules, it excludes from the bundle all code, which is not really used. In the table, it is also noted, which module pattern types the tree-shaking supports.

Scope hoisting: Instead of wrapping each module in a function and then chaining the functions, all the module code can be hoisted into a single function. This makes the code run faster and can reduce boiler plate significantly.

Source maps: Used in debugging of code between e.g. JS and CSS. A source map provides a way of mapping code within a compressed file back to its original position in a source file.

Async require / module splitting: Enables splitting code into logical entities and then loading it once the user has done something that will require that new block of code. This can significantly reduce the size of the initially loaded bundle file and thus makes the pages load faster by reducing the unnecessary network traffic.

Appendix 6 2 (2)

Configuration complexity and lines of code: This was a subjective opinion of the complexity of setting up the bundler to bundle the code. Lines of code cannot be directly interpreted as a measurement of complexity, since the lines can contain whatever, but it was seen as an interesting metrics anyway.