In the Node environment, we have a "CommonJS" module system that uses module.exports/require to isolate parts of each file (or "module"). Up until ES6, there were no built-in "modules" in browser code.* By default, each script in an HTML document is executed in order and shares one scope.
From the Webpack 5 docs:
Webpack is a bundler for modules. The main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset.
What does this mean? Let's see Webpack in action by building a small JavaScript program in Node.
Setup
Make a new project with npm and install webpack
and webpack-cli
.
mkdir hello-webpack && cd hello-webpack
npm init -y
npm install --save-dev webpack webpack-cli
Now, within your root folder, make the directories src
and public
. The src
folder will hold our unprocessed source code, and we'll direct Webpack to output our transpiled code in the public
folder. You'll also need to create a file called webpack.config.js
- more on that later. Your project should look like this:
hello-webpack/
├── src/
├── public/
├── webpack.config.js
└── package.json
package.json
{
"name": "hello-webpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11"
}
}
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="../src/game.js" defer></script>
<script src="../src/main.js" defer></script>
<link rel="stylesheet" href="style.css" />
<title>Click Me</title>
</head>
<body>
<button id="button">Click Me!</button>
</body>
</html>
public/style.css
button {
height: 300px;
width: 300px;
font-size: 40px;
background-color: goldenrod;
color: white;
border-radius: 50%;
cursor: pointer;
}
src/game.js
let numTimesClicked = 0;
function win() {
alert('You win!');
reset();
}
function reset() {
numTimesClicked = 0;
}
function click() {
numTimesClicked++;
console.log(`You've been clicked!`);
if (numTimesClicked === 10) win();
}
src/main.js
const button = document.getElementById('button');
button.addEventListener('click', function() {
click();
});
Why do you need Webpack?
From your command line, run open public/index.html
. You should see a yellow button. When clicked, the button should log a message to your console. If you click the button 10 times, an alert should pop up letting you know - you've won! Great! We're done!
Just kidding. Take a look at your index.html
file. What happens if you don't include the defer keyword in lines 7 and 8? What about if you re-order your JavaScript files?
<!-- remove 'defer' from lines 7 and 8 -->
<!-- re-order 'game.js' and 'main.js' -->
<script src="../src/main.js"></script>
<script src="../src/game.js"></script>
Did you see something like this in your console?
Uh-oh.** Remember that thing I said in the beginning about scripts executing in order? The defer
attribute tells your browser not to run a specific JavaScript file until after the HTML file is done loading. Without defer
, your JavaScript executes as soon as the HTML loads. And if the code in your 'main.js' file runs before the code in 'game.js', your program will try to run your 'click()' function before it's been defined.
Which is why you now have an error in your console.
Bundling modules with Webpack
Now that we know why we need Webpack, let's see it in action.
Webpack is a module bundler. Its purpose is to process your application by tracking down its dependencies, then bundle them all up into one or more files that can be run in the browser. Just like Node apps are universally configured by a package.json
, you'll configure Webpack in your webpack.config.js
file.
webpack.config.js
Webpack is based around several key components: an entry point, an output location, loaders, and plugins. I'll only focus on entry and output, but you'll definitely use the other two when you configure Webpack for larger projects.
Entry: The JavaScript file where Webpack begins building.
module.exports = {
entry: './path/to/my/entry/file.js'
};
Output: Name and path for the bundled JavaScript.
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js', // the starting point for our program
output: {
path: path.resolve(__dirname, 'directory_name'), // the absolute path for the directory where we want the output to be placed
filename: 'my-first-webpack.bundle.js' // the name of the file that will contain our output - we could name this whatever we want, but bundle.js is typical
}
};
Your webpack.config.js
file may look something like this:
const path = require('path');
module.exports = {
mode: "development", // could be "production" as well
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'public'),
filename: 'bundle.js'
}
};
NPM scripts
Now that we have our Webpack configuration, we need to add an npm script to our package.json. We can pick any word we want, but "build" is conventional. We can simply use "webpack." If we want Webpack to watch for changes and hot reload files, we can add a "--w" flag at the end. (If we didn't do this step, we would have to run a local copy of Webpack from the command line every time we wanted to run it.)
Your NPM scripts should look like this:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --w"
},
Now... go ahead and fire her up!
Huh? What's this in my console?
That's your first bundle. The metadata in your console tells you how big your bundle is. Wow! Now that you've done this, you can use ES Modules. This means that as your program gets bigger, you can import and export functions between JavaScript files. Cool!
Bring it to the web
We're almost done. We've configured Webpack to bundle our 'main.js' file and output a 'bundle.js' file in our /public directory.
Now, we can make use of ES Modules in our JavaScript. Remember how the click
function was being invoked before it existed to the browser? Now, we can use export
and import
syntax to export it from game.js
and call it within main.js
, avoiding this problem altogether. Like this:
game.js
// below the click() function
export default click;
main.js
// at the top of main.js
import click from './game'
Lastly, we need to make a small change to our HTML file. Before we knew about Webpack, index.html
loaded two separate JavaScript files. Now, all of the code in those files has been packaged into bundle.js
- so we can simply point our script tag to bundle.js
.
Go ahead and replace your script tags with a reference to bundle.js
:
<!-- <script src="../src/game.js" defer></script>
<script src="../src/main.js" defer></script> -->
<script src="bundle.js" defer></script>
Now, run open public/index.html
.
Does your program look and function exactly the same as before? Great! You've done everything right.
Take a peek in your DevTools, and navigate over to the 'Sources' tab. You should be able to click on bundle.js
and observe your beautifully bundled JavaScript. Neat!
What did we learn?
Webpack is a bundling tool which packages up all of your JavaScript files into one neat file. We learned:
- Webpack bundles your JS code & helps support ES Modules
- Two main concepts are entry and output
- How to set up webpack.config.js
Great job! You've learned so much, and yet, there is still so much more to learn. From here, you may want to read about a compiler called Babel. Webpack is commonly used with Babel to transpile the latest JavaScript syntax across older browsers. You can also read about how Webpack handles CSS files, code splitting, and other fun things. It's also not the only tool of its kind - you could take a look at grunt, gulp, or browserify.
Top comments (9)
It's funny - since I use create-react-app I never mess with Webpack and honestly just thought it was a mess to deal with. Your explanation is so clean and straight forward!
I'm actually looking to go the module route with three.js and need a bundler that will allow me to import .obj models. Webpack will be perfect for this, so thank you!
The Error is because the button is undefined right? not because of the order of loading the .js files.
Amazing explanation!!!
Try hq hqjs.org it is literally one command to start and the other to build nothing to configure at all. The plugin to VSCode is available as well.
It looks messing game.js into webpack.config.js, please advise it.
I recommend Snowpack instead - snowpack.dev/
Thanks for the rec
Amazing tutorial!
:) Thanks for reading!