Webpack — why, what, and how
Giving webpack a small look
A background on modules — Simple example on why we might use webpack
JavaScript programs started off pretty small — most of its usage in the early days was to do isolated scripting tasks, providing a bit of interactivity to your web pages where needed, so large scripts were generally not needed. Fast forward a few years and we now have complete applications being run in browsers with a lot of JavaScript, as well as JavaScript being used in other contexts (Node.js, for example).
It has therefore made sense in recent years to start thinking about providing mechanisms for splitting JavaScript programs up into separate modules that can be imported when needed. Node.js has had this ability for a long time, and there are a number of JavaScript libraries and frameworks that enable module usage (for example, other CommonJS and AMD-based module systems like RequireJS, and more recently Webpack and Babel).
— source
In other simple words if you are running JavaScript in Node.js context you can split up your code into multiple files (modules) and include them in each other, for example:
// ./main.jsconst func = require('./util/funcs.js')
// ./util/funcs.jsmodule.exports = () => {
// code that dose stuff
}
In the above example, we have two files ./main.js
and ./util.funcs.js
and we exported an arrow function from the funcs.js
file and imported it with the name func
into main.js
.
But remember this will run in Node.js context and if you want to run the code from main.js
in the context of a browser, you might include it in a script tag like so:
// ./index.html
<html>
<head></head>
<body>
<script src="./main.js"></script>
</body>
</html>
But when you open your index.html
in your browser, you will see the following error message
require
function in a browserThat happened because the require
function does not exist in any browser, and to solve this we can use webpack.
The above issue is one of the reasons why we might use webpack and other reasons:
What Is Webpack
At its core, webpack is a static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph which maps every module your project needs and generates one or more bundles.
— source
To make this simpler webpack will look into your js code and see all the file that depend on each other and build a dependency graph and by building this it can bundle your js code (put the code for the files that depend on each other in the same output file)
So in our example above main.js
depends on funcs.js
so webpack will put the code from funcs.js
into main.js
( roughly speaking ).
Then after bundling the js code webpack will output a file that includes all the bundled (combined) code which you can put into your <script>
tag.
How to use Webpack
Before starting using webpack we need to have Node.js and NPM installed on our machine, and that’s because:
Let’s start by creating an NPM project, go to any directory you want then run the following command in your terminal
$ npm init -y
this will generate a pacakge.json
inside the directory and the -y
is to answer “yes” to all the questions npm will ask us.
now after we have our package.json
file we can install any npm package we want so let’s install webpack, webpack-cli and rxjs
$ npm i webpack webpack-cli -D
-D
means it’s a Develpment dependency
which is a way to say that this package help automates and develop the application not to build the actual functionality for it.
webpack will use webpack-cli that’s why we installed it as well
$ npm i rxjs
now let’s create the following empty files [main.js
— index.html
— util/funcs.js
], our project will look like this:
.
├── index.html
├── main.js
├── package-lock.json
├── package.json
└── util
└── funcs.js
└── node_modules
webpack and rxjs are inside
node_modules
now to hook webpack we need to create an npm script that runs it from the node_modules
folder, so in your package.json
change the script
object to look like this
"scripts": {
"dev": "webpack --config webpack.config.js"
}
by adding this npm script, we can run npm run dev
to tell webpack to bundle our project based on the configuration we will add inside a file called webpack.config.js
.
here is the webpack.config.js
file and in the next points I’ll explain each part of it.
const { join } = require('path')module.exports = {
entry: {
main: join(__dirname, 'main.js'),
},
output: {
filename: '[name].js',
path: join(__dirname, 'dist'),
}
}
Remember that webpack runs in a nodejs context so I can use the require
function to load the join
function from the path
built-in module in nodejs ( it’s a function that makes it easy to build file system paths without worrying about the operating system or / \
characters)
Your webpack file should export an object that contains information about how webpack should build your files ( if you think about it webpack will read this object by requiring your file )
In that object we have 2 properties:
- entry: this is where webpack will start to build the dependency graph, you can have multiple entry points and for each entry point webpack will create an output file.
- output: this is the configuration for each output file webpack will do, so each file will be named as the value from
filename
property which is[name].js
and this[name]
is a placeholder that points to the current file being processed, so in our case we will have a file calledmain.js
as an output file, and the path property is where to put the bundled files.
__dirname is always the directory in which the currently executing script resides
so let’s put the following code in util/funcs.js
export const sayHi = () => {
alert('hi')
}export const sayBye = () => {
alert('bye')
}
note that we are using ES6 module system ( feel free to use Commonjs (require) or ES6 modules )
in our main.js
import { sayBye, sayHi } from './util/funcs'sayHi()
sayBye()
in our index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>webpack</title>
</head>
<body>
<script src="./dist/main.js"></script>
</body>
</html>
now run npm run dev
you will see the following output
go to ./dist/main.js
you will notice that all the dependency code have been added to the file!
./dist/main.js
content
(()=>{"use strict";alert("hi"),alert("bye")})();
now if you opened your index.html
you will have 2 alerts popping up.
Remember that we installed RxJs so let’s import a couple of functions from it and bundle our js again
our util/funcs.js
will stay the same and I’ll create a new file util/rxjs.js
with the following content
import { delay } from 'rxjs/operators'
import { of } from 'rxjs'export const delayedHi = of('hi..').pipe(delay(2000))
and in our min.js
import { sayBye, sayHi } from './util/funcs'
import { delayedHi } from './util/rxjs'
import { repeat } from 'rxjs/operators'sayHi()
sayBye()delayedHi.pipe(repeat(3)).subscribe(console.log)
Now run npm run dev
and open the index.html
in a browser, you will see that everything works fine, and this is a good enough intro for webpack and how to bundle your project.
give your
./dist/main.js
a look, cool right?
Notice here that we created our own ES6 modules util/funcs.js
and imported functions from node_modules
(Rxjs) and run all of that after bundling on a browser which is very cool.
if you writing the code with this atricle you might notcied that writing npm run dev over and over again is annoying, to fix it just put watch: true in the webpack exported object
Since we moved the basic out of the way, let’s talk more about some intermediate subjects in webpack
Webpack Loaders
Out of the box, webpack only understands JavaScript and JSON files. Loaders allow webpack to process other types of files and convert them into valid modules that can be consumed by your application and added to the dependency graph.
If you read that carefully, it means we can import
another type of files using loaders for example
- SASS loader allows you to load your
scss
intojs
and the styles will apply to the DOM ( you might have seen this in react) - URL loader allows you to load images into your
js
and it does that by converting the images to base64 while bundling
There are a lot of loaders for webpack which allows you to customize it in a very unique way, and I’ll give you now an example of using the SASS loader.
Using SASS loader ( import our scss files to our js code )
we’ll work on the same project and create a file in the following path scss/main.scss
and add the following content to it
$color: #000;
body {
background: $color;
h1 {
font-size: 200px;
}
}
now install the SASS loader, dart-sass (which will be used by the SASS loader), style-loader and css-loader
$ npm install sass-loader sass style-loader css-loader -D
then go to your webpack.config.js
and add the code for the loader
const { join } = require('path')module.exports = {
entry: {
main: join(__dirname, 'main.js'),
},
output: {
filename: '[name].js',
path: join(__dirname, 'dist'),
},
module: {
rules: [{
test: /\.s[ac]ss$/i,
use: [
'style-loader',
'css-loader',
'sass-loader',
]
}]
}
}
Notice the only change is inside the module
property and for each loader you have you will put its rules there inside the module.rules
array
Now for each file that is being processed by webpack and matches the regex in the test
property webpack will pass it to the correct loader in the use
array
Here is the purpose of each one of these loaders
- style-loader: Inject CSS into the DOM.
- css-loader: will resolve your imported css files
- sass-loader: Loads a Sass/SCSS file and compiles it to CSS
now go to your main.js
and import the main.scss
file
import { sayBye, sayHi } from './util/funcs'
import { delayedHi } from './util/rxjs'
import { repeat } from 'rxjs/operators'
import './scss/main.scss'sayHi()
sayBye()delayedHi.pipe(repeat(3)).subscribe(console.log)
now run npm run dev
you will see that the styles from the .scss
file has been applied.
Another important loader is babel-loader which compiles your js code to old versions so it will run in almost all browsers ( I think you can add this loader by yourself now )
Other Webpack concepts
we barely scratched the surface of webpack features and I recommend that you read more about
- Plugins I’ll recommend seeing webpack-dashboard and webpack-build-notifier
- Module Resolution
- Hot Module Replacement
- Tree Shaking
- Advanced entry
- TypeScript
- dependency-management — this one is very cool
End & Final Notes
The good news is that modern browsers have started to support module functionality natively, [….]. This can only be a good thing — browsers can optimize loading of modules, making it more efficient than having to use a library and do all of that extra client-side processing and extra round trips.
— read more about this here and give the support chart a look here