No-nonsense webpack project
Using webpack without fully understanding it, as if it were magic? Heard about the wonders of webpack, but got flustered by its ominous documentation? Are you from outer-space (like — a totally different galaxy) and haven’t heard of webpack at all? In any of these cases (and many more), this post is for you!
Webpack: The Final Frontier. These are the voyages of the flagship app of your enterprise (or just your pet project). Its tl;dr mission: to boldly go where many developers have gone before!
What is webpack? In one word — a bundler. In a few words, webpack is a piece of code that takes your beautifully structured JavaScript (JS) project and bundles it into static files for production, all with a single command in the command-line interpreter (CLI). Magic!
What are we going to build?
An awesome app, that’s what. It’s called “Your Phrase Fireworks” (abbreviated YOPF). YOPF’s vision: You enter a phrase into an input field and the phrase is shown surrounded by crazy-cool exploding fireworks. Can’t get much more awesome than that.
Since you are a developer, I assume you are using a modern browser like Chrome or Firefox. If not, please do — we will go over browser compatibility and webpack later on.
Let’s build a webpack project
Assumptions
- You know some JS, HTML and CSS (nothing fancy —The basics will do)
- You already have Node.js installed. If not, install it and come right back
- You know how to edit files (while I prefer WebStorm IDE, even Notepad will do — although you might prefer Sublime or VSCode)
- You know how to browse folders on your machine
Setup the app
Browse to a projects folder on your machine (either via CLI or any visual file explorer). I’ll provide the CLI instructions below, but feel free to use your own methods for file/folder creation.
Every project begins the same way — by creating your project’s folder. To do so, enter the following command line prompts:
mkdir my-project
then:
cd my-project
then:
npm init -y
Amazing! We now have an npm module. Next, let’s install webpack:
npm i webpack -D
Since this is a real project, we’ll need a few additional prerequisites:
- Source control (Git)
- A spec for our app
The first is easy; if you don’t have Git installed, install it. Now, in your project’s folder, run this:
git init -q
Great! We have Git! We won’t touch it in the tutorial, but it’s good practice.
On to the second, a spec. Since this is just a blog post, we won’t put too much into this, but the basic gist of our spec would be as follows: We want to build an app in which you type something into an input, click a submit button, and then what you entered into the input appears on a screen with fireworks exploding around it. This app will be called YOPF, short for “Your Phrase Fireworks.”
Let’s create our source-files folder:
mkdir src
You’re getting good at this :). Can you guess what’s coming next? You got it:
cd src
Now, Let’s create an app.js file:
. > app.js
If you’re copying my commands above, don’t mind the error; this is just my hacky way of creating a new file on Command Prompt (CMD), Windows’ command-line interpreter. Feel free to use different CLI commands if they suit you better.
Let’s create our CSS and HTML files:
.>app.css & .>index.html
Wow — two files with the same CLI command. Can it get any better than that? Yes it can! Let’s edit the files.
To begin with, we’ll put something into our index.html file:
<div id="phrase-form-wrapper"></div>
<div id="phrase-fireworks-wrapper"></div>
Cool. We have our wrappers. Since we know that modularity is the name of the game (we do know that, right?), let’s create some modules. I really like it when my apps look the same at every level (and you should be like me… I write blog posts!), so let’s create two more folders inside the src folder:
mkdir form & mkdir fireworks
…and in each, lets create the same files:
cd form & .>form.index.js & .>form.index.html & .>form.css
& cd ../fireworks & .>fireworks.index.js & .>fireworks.index.html & .>fireworks.css
Now we have an app with two modules. One for the phrase form, one for the fireworks and a “main module” to bind them both to one app.
Configure webpack
We also need a webpack.config.js file, which will hold, well… our webpack configuration. What does that mean? It means that, in order for webpack’s “magic” to work, you need to tell it what to do. You can name this file abracadabra or legerdemain or anything that suits your fancy. Thing is — nothing is magical (sorry, Jeremy Messersmith…); all that “magical” bundling is a result of the configuration you set up.
In general there are three parts to webpack configuration:
General configuration: Tell webpack where the “main” file is (the webpack term is entry), where and how to output everything, how to handle source maps, how to act in Dev Mode, etc.
Loaders: The webpack core loads only JS files. Loaders enable webpack to handle different file types (CSS, HTML, images, etc.). You just install them with npm.
Plugins: These extend webpack’s abilities to handle things you would normally use automation tools like Gulp or Grunt for, like uglifying your code (the latest trend is to use npm directly).
Here’s a list (in no particular order) of things we wish to achieve with webpack while building our app:
- Set our main module as the entry to our app
- Load HTML and CSS files
- Uglify our code
- Create a dist folder with an
index.html
file, which will be the front-end entry point to our app
First, the low-hanging fruit — installing our HTML and CSS loaders:
npm i -D html-loader style-loader css-loader
Now we’ll install 3 plugins that will help us achieve our other goals:
npm i -D uglifyjs-webpack-plugin html-webpack-plugin clean-webpack-plugin
As their names suggest, these plugins help us with file minification, auto-generation of a working index.html, and removal of our build folders before every new build respectively.
In order to use these, let’s make our webpack.config.js file look like this:
entry
, output and plugins
properties. The module.rules
apply the loaders according to test rules.Create a new file in the project’s root:
.>webpack.config.js
…and copy the contents of the above file inside.
How does this config file work? As mentioned above — we set the entry (our main module) and the output. We also set the plugins as an array of plugins. Finally, we set the loaders (now under module.rules).
One more step before we start: Let’s set up the build command for easy usage. Open package.json (in the app’s root). Add a new property to the “scripts”
object:
“build”: “webpack”
Your package.json file should now look like this:
That’s it for infrastructure (for now ;)). Now the fun really begins!
Let’s code our app!
Let’s start with the main module. We would like it to do the following:
- Import its HTML template and add it to the DOM
- Import its CSS and add it to the DOM
- Import the two sub modules
Here’s where webpack kicks in and makes everything super simple. Let’s start coding:
Import the HTML template and add it to the DOM
import template from './index.html';
(function() {
document.body.innerHTML = template;
})();
The html-loader parses .html files , and returns an HTML string.
Import the CSS and add it to the DOM
import template from './index.html';
import {} from './app.css';(function() {
document.body.innerHTML = template;
})();
I don’t even know if this was worth its own step, but it’s my blog, so you’ll have to live with it. All I did was add a line to import the CSS file. My css-loader parses the CSS and sends it to my style-loader, which injects it into the DOM.
Let’s put some CSS inside app.css, just for fun:
Import the two sub modules
Right now you should know what to expect — just import our two classes/modules:
import template from './index.html';
import {} from './app.css';import YOPFForm from './form/form.index';
import YOPFFireworks from './fireworks/fireworks.index';(function() {
document.body.innerHTML = template;
})();
Here I added two new lines that import form and fireworks from their respective files.
Building the app
We’ve done so much, and we don’t even know if it’s working. Let’s build and see how it looks.
Run the command:
npm run build
When it’s done you will have a dist folder. Inside you should see an index.html file. Open it to see the explosive results.
Here’s what you should see when checking the page elements from dev tools:
You can see that our wrappers are in place, our style was added to the DOM by webpack. You can also see our template (which we inserted to the DOM in the app.js immediately invoked function expression (IIFE)).
Run it on your computer to see how it looks:
Recap
Wow — we did a lot! We’ve set up webpack to bundle our app — JS, CSS and HTML. We’ve set up our entry (main) module to load our template and CSS and put them in the DOM, and configured webpack to place them for us neatly inside an HTML page.
Now our mission is to create the app’s two building blocks: our form and our fireworks.
The form module
What should the form module do?
- Import our HTML template and inject it into the DOM (sound familiar?)
- Set up the form’s action to submit the phrase
- Create some kind of API so other modules can communicate with it
Yea, API. It’s a phrase that makes me look smart…
This module is not an entry. It’s a module that is consumed by another module. We can just export a class that could be instantiated each time it’s needed. So, the basic code for our module would be:
class YOPFForm{
constructor() {
}
}
export default YOPFForm;
This means, that when we import
the module, we get a class that can be instantiated with variables. Since our module/class needs to put some HTML inside some element, we would like it to import the HTML and get an element to append the HTML to:
If you run the code above, you’ll see no change. For this, you need to edit the form.index.html. Let’s put some content inside:
We also need to use the new module inside our app.js:
Now, build again and load the index.html (usually a tab refresh will do). You should see this:
How awesome is that? One more thing is needed here: the ability to connect a response to the form submit. For this we need to add a form submit listener as well as an API to enable external modules (e.g., our app.js) to hook up to it.
Setting up the form’s API
We will add a method to the class that accepts a callback and uses it every time the form is submitted. Then, we can use this method in our app.js file. This isn’t a webpack thing, so I’ll just write the full code and get on with it:
When you run this code, you’ll see that every time you submit the form, an alert with the form’s value is thrown from the main entry. What an API…
But an alert is not so impressive. We want fireworks!!!
DISCLAIMER: Full disclosure — No attempt at a fully fledged solution was made, nor is the code offered here considered best practice. When building an app, consider validating input, allowing for a safe “unlisten” event, etc.
The fireworks module
Ooooooh — this is gonna be fun!
We’re going to use npm for that. Let’s install a fireworks package:
npm i -S fireworks
Now we’re going to require fireworks (just to show off, I’ll use Node.js) and import the fireworks style. After that, it’s plain JS coding, so I won’t go into much detail. Here’s the gist of it, though: I’ll expose an API method that accepts a phrase and presents it with fireworks. Here are the relevant files to change:
Nothing webpack about what was done above —we’ve just created the module, imported it into our main file, and used it. We now have our awesome app!!!
Head scratching moment
So far, we’ve managed to almost magically build our app and create a working index.html file with webpack. But now you scratch your head — what? Do I need to build and refresh every time I make a change? It’s time consuming and confusing.
Here webpack has two solutions: watch mode and webpack-dev-server.
Watch mode is simple — you just add —-watch
to the webpack command inside of package.json and it works — webpack will build (or try to build) your app on every code change. Just refresh your browser and, there’s that magic again!
webpack-dev-server is a much more robust solution. It requires a bit of setup (e.g., npm install
and a line in the configuration file) but it does the refresh out of the box (and can do much more than that). Because we don’t want to refresh every time, we will just install and setup the webpack-dev-server:
npm i -D webpack-dev-server
And then, instead of using webpack in the CLI or inside the package.json, you can just use webpack-dev-server to start a development server:
After running this:
npm run dev
You will get something like this:
“Project is running at http://localhost:8081/”
Just browse to the address the project is running at and you should be good to go (auto reload and all).
Two small tricks to make your life a bit easier: (1) source maps (for easier debugging) and (2) make webpack-dev-server to open your site when the bundling is done.
Here’s the final configuration:
The difference is in the first two properties in the config object:
devtool: 'inline-source-map',
devServer: {
open: true,
contentBase: './dist'
}
This tells webpack to add inline source maps so you can debug your code with ease. The second property is also pretty self explanatory.
There is much more to the development server than what I’ve covered here. You can learn much more in the official docs. If you find something interesting and relevant, write about it in the comments section below, and, if there’s enough material, I’ll add a section about webpack-dev-server to this post.
Best practices— development and production
First we learned how to build for distribution. Then we learned how to use the development server. We can see there’s much in common in their configuration, and a few unique properties for each. We can now create a separate configuration for development and production with a common configuration file and use the correct file for the relevant case.
Then we can add scripts to the package.json file:
scripts: {
dev: webpack-dev-server --config webpack.dev.js,
build: webpack --config webpack.prod.js}
From now on we run npm run dev
and npm run build
.
Bonus: Adding Bootstrap to the mix
Bootstrap is by far the most widely used design toolkit/component library. Let’s add it to our project to make our form look a bit better:
npm i -S jquery bootstrap
Now, you would assume that you could just use imports like before:
import 'jquery';
import 'bootstrap/dist/js/bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
But no… This would cause an error because bootstrap.css imports font files which we didn’t tell webpack how to handle — hence, it is trying to handle them like JS. Let’s fix that with a simple loader:
npm i -D url-loader file-loader
url-loader encodes any file to base64url. If it is too long, it just serves the file’s contents (via the file-loader, hence the file-loader install above). We just add the url-loader rule to our config rules list.
// use the url-loader for font files
{
test: /\.(woff2?|ttf|eot|svg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000
}
}
]
}
Now I’m going to apply Bootstrap to the form (and make some minor CSS changes to make the app more beautiful). Nothing webpack-ish here. You can see all the changes in this commit, and check out the final app here.
Summary
By now you know enough webpack to build your own webpack-powered project.
You can see the full project we’ve just built here on Github.
Let’s recap some highlights from what we’ve done in this post:
- Webpack installation and setup in a webpack.config.js file
- Using loaders to load HTML and CSS files
- Using plugins to make our lives easier
- Splitting our app to sub modules which are imported by one entry
- Using npm modules on the client-side the way we use them in Node.js
- Setting up an easy-to-use development server
With this foundational knowledge, you can build something awesome! But webpack has so much more. To continue your educational journey, read the posts below:
- Lazy Loading (coming soon)
- Browser compatibility (coming soon)
You can also explore webpack on your own and/or ask questions in the comments section below if you are stuck.
Enjoy the fireworks!