How JavaScript works: A deep dive into Webpack
This is post # 58 of the series, dedicated to exploring JavaScript and its building components. In the process of identifying and describing the core elements, we also share some rules of thumb we use when building SessionStack, a JavaScript tool for developers to identify, visualize, and reproduce web app bugs through pixel-perfect session replay.
Introduction
Webpack is a module bundler that packages all our app assets into a production-ready bundle. Webpack does this by bundling all our app’s scripts into one file. And as a result, it minimizes HTTP requests and speeds up the site’s performance.
Also, Webpack helps us to load our app modules and their dependencies and handles different definitions and browser compatibility out of the box.
With the use of loaders, Webpack can process files such as SASS and LESS to CSS and convert JSX and modern ECMAScript into browser-friendly JavaScript. Also, Webpack uses plugins to enable us to hook into its compilation process for advanced operations.
In this article, we will take a deep dive into Webpack. And learn about its feature by elaborating on the points mentioned above using code examples.
We will start by scaffolding a simple app by running the code below:
Next, create a public
directory with an index.html
file containing the code below:
Also, create an src
directory with an index.js
file containing the code below:
Now, open the package.json
file and edit the scripts section as follows:
Finally, we can run our app by running npm start
.
In our small contrived app above, the index.js
script uses the Lodash camelCase
function to log our greeting to the console. However, when we check the console we get the following error:
Uncaught SyntaxError: Cannot use import statement outside a module
as seen below:
This error is thrown because the browser does not know how to resolve the Lodash code, and this is where module bundlers like Webpack can help us out.
To use Webpack, we will install it as a dev dependency by running the following code:
npm i -D webpack webpack-cli
Next, we update the package.json
scripts as seen below:
Now, when we run npm run build
, Webpack will analyze the code in our index.js
file and compile it into a dist/main.js
file. And this dist/main.js
is our production code. And as a result, we have to update the script src
in the index.html
file to dist/main.js
as seen below:
<script src=”../dist/main.js”></script>
Now, when we run our app everything works correctly and we can see the greeting in the console as seen below:
Now, when we run our app, everything works correctly, and we can see the greeting in the console as seen below:
Modern web development involves multiple third-party modules and utility libraries like Lodash, React, Moment, etc. And following our example above, we can see why we need module bundlers like Webpack.
Also, it is important to note that we used the default settings of Webpack in our example above. But in more complex applications, we will need to customize Webpack’s default behavior. And we can do this via the webpack.config.js
file.
Let’s learn about this in the next section.
Customizing Webpack
The webpack.config.js
file is a JavaScript module that exports an object that customizes the behavior of Webpack.
The webpack.config.js
file has the following properties:
entry
: defines the entry point to our app. In our example above, the entry point is./src/index.js
which corresponds to the default Webpack setting.- In some cases,
entry
can be an object. And this is particularly useful for code splitting. output
: refers to the name of the file that contains our compiled code. By default,output
ismain.js
but we use our custom filename.- Note: when determining the file location for
output
, we can use the Node.jspath
module to resolve a consistent path name as seen below:
mode
: this can either be set to “development” which will enable useful tools for development, “production” which enables built-in optimization for production builds, or “none” which simply means no default.
The mode
option tells Webpack how to use its built-in optimization settings.
module
: the module property is used to handle the configuration of our application modules. Themodule
property has arules
property that is an array of objects. And these objects contain the configurations for different modules such asloaders
,parser
options, etc.
We will learn more about this option in the next section.
Loaders
By default Webpack only understands JavaScript and JSON. Loaders tell Webpack how to handle other file types. There are loaders for nearly everything in Webpack.
For example, to use CSS, we need to use both the style-loader
and the css-loader
together.
The css-loader
loads all the CSS codes into the JS file, and the style-loader
adds these styles into the DOM.
To use both loaders, install them as dependencies by running:
npm i style-loader css-loader
Then configure the webpack.config.js
file as seen below:
Note: in our above Webpack configuration, we used modern JavaScript syntax. And for this to work correctly, you must add the type: module
property to your package.json
file.
Now, we can import our CSS file wherever it is required to apply the needed styles.
The benefit of this pattern is seen in cases where we have many different JS components, with each JS component injecting a different component or element into our index.html
.
So with this pattern, every time we use one of these components, we can import the CSS we need for only that component instead of loading all of the CSS into the index.html
file. And this enables us to modularize our CSS, and import only what we need.
Webpack loaders also enable us to perform transformations. For example, we can use babel-loader
to transform our JSX code or latest JavaScript into browser-friendly vanilla JavaScript.
To perform transformation using the babel-loader
, we need to install the required dependencies as seen below:
npm i -D babel-loader @babel/core @babel/preset-env
Next, we need to install the presets to use for transformation. E.g, to transform the latest JavaScript, we need to install the @babel/preset-env
preset.
Lastly, we would configure our webpack.config.js
file as seen below:
Although Webpack loaders can do a lot of the heavy lifting for us, we can tap into the Webpack compiler by using plugins.
We will learn about Webpack plugins in the next section.
Plugins
Webpack plugins enable us to hook into the Webpack compilation lifecycle.
The webpack.config.js
file has a plugins
property that is an array of different plugins.
To elaborate on this we would use the Webpack HTML Webpack Plugin.
When Webpack bundles our application into a dist
folder we still need an index.html
file that correctly points to the JS bundle in the dist
folder. We can either do this manually or use the plugin mentioned above. Thus this plugin simplifies the generation of HTML files when Webpack creates our bundles. Consequently removing the need for us to supply our own template.
To use this plugin we need to install it as seen below:
`npm i — save-dev html-webpack-plugin`
Now we would configure our webpack.config.js
file as seen below:
Note: in our above Webpack configuration, we used modern JavaScript syntax. And for this to work correctly, you must add the type: module
property to your package.json
file.
Now when our code is bundled, Webpack would generate the following index.html
file in the dist
folder that points to the path of the output file as seen below:
You can see this file in the dist
folder after running npm run build
from your CLI.
Conclusion
We have covered a lot in this article yet Webpack is packed with so many features that there are still many things to cover.
Webpack can process images and with the webpack-dev server
, we can do things like hot-reloading and more. Webpack is regarded as the swiss army knife of module bundlers.
There are other module bundlers like Parcel and Rollup that still follow the concept of Webpack but try to reduce configurations. An exception, however, is Snowpack which is regarded as the feature because of its innovative unbundled development pattern.
To learn more about these bundlers you can read our previous article on this series.
We all like to apply new technologies & upgrade our code. Even if we feel we’ve tested everything before release it’s always necessary to verify that our users have a great experience with our product.
A solution like SessionStack allows us to replay customer journeys as videos, showing us how our customers actually experience our product. We can quickly determine whether our product is performing according to their expectations or not. In case we see that something is wrong, we can explore all of the technical details from the user’s browser such as the network, debug information, and everything about their environment so that we can easily understand the problem and resolve it. We can co-browse with users, segment them based on their behavior, analyze user journeys, and unlock new growth opportunities for our applications.
There is a free trial if you’d like to give SessionStack a try.
If you missed the previous chapters of the series, you can find them here:
- An overview of the engine, the runtime, and the call stack
- Inside Google’s V8 engine + 5 tips on how to write optimized code
- Memory management + how to handle 4 common memory leaks
- The event loop and the rise of Async programming + 5 ways to better coding with async/await
- Deep dive into WebSockets and HTTP/2 with SSE + how to pick the right path
- A comparison with WebAssembly + why in certain cases it’s better to use it over JavaScript
- The building blocks of Web Workers + 5 cases when you should use them
- Service Workers, their life-cycle, and use case
- The mechanics of Web Push Notifications
- Tracking changes in the DOM using MutationObserver
- The rendering engine and tips to optimize its performance
- Inside the Networking Layer + How to Optimize Its Performance and Security
- Under the hood of CSS and JS animations + how to optimize their performance
- Parsing, Abstract Syntax Trees (ASTs) + 5 tips on how to minimize parse time
- The internals of classes and inheritance + transpiling in Babel and TypeScript
- Storage engines + how to choose the proper storage API
- The internals of Shadow DOM + how to build self-contained components
- WebRTC and the mechanics of peer to peer connectivity
- Under the hood of custom elements + Best practices on building reusable components
- Exceptions + best practices for synchronous and asynchronous code
- 5 types of XSS attacks + tips on preventing them
- CSRF attacks + 7 mitigation strategies
- Iterators + tips on gaining advanced control over generators
- Cryptography + how to deal with man-in-the-middle (MITM) attacks
- Functional style and how it compares to other approaches
- Three types of polymorphism
- Regular expressions (RegExp)
- Introduction to Deno
- Creational, Structural, and Behavioural design patterns + 4 best practice
- Modularity and reusability with MVC
- Cross-browser testing + tips for prerelease browsers
- The “this” variable and the execution context
- High-performing code + 8 optimization tips
- Debugging overview + 4 tips for async code
- Deep dive into call, apply, and bind
- The evolution of graphics
- Dockerizing a Node.js application
- A deep dive into decorators
- Best practices for data compliance
- Proxy and Reflect
- SVG and its use cases (part 1)
- Class static blocks + 6 proposed semantics
- Introduction to Graphs and Trees
- Introduction to PM2, Strongloop, and Forever + 4 tips for Production Process Managers
- Аdvanced SVG capabilities (part 2)
- Тhe publisher-subscriber pattern
- Stacks and Queues + tips for efficient implementation
- Lists vs Blockchain + implementation practices
- The module pattern + comparing CommonJS, AMD, UMD, and ES6 Modules
- The different types of conditional statements + 3 best practices
- The different ways of declaring a function + 5 best practices
- The factory design pattern + 4 use cases
- A guide to build tools + exploring Webpack, Parcel, Rollup, ES Build, and Snowpack
- Building a child process in Node.js
- Streams and their use cases
- Understanding Maps and their use cases + 4 advantages compared to Objects