I've been avoiding learning Webpack for a while now as I never thought I needed to learn another build tool, Gulp does everything I'd ever need from a build tool now or in the future. However, ever since we've moved from AngularJS to Angular (or Angular 2+) as well as introducing standards such as; TypeScript instead of Javascript and a Jasmine/Karma combo for UI testing, but also Webpack as an initial build tool. I've avoided it for long enough and now, in September 2017, I thought it's time to finally move on from my old friend Gulp.
If you've never heard of Gulp before, this isn't the post to learn, there are plenty of good tutorials out there a Google search away. Then again, you don't really need to know Gulp to understand what's going on so feel free to continue reading nevertheless.
Here's what my old Gulpfile looks like. I've decided against putting it all into this post as it's quite long. It essentially does 5 thing;
- Starts a webserver
- Preprocesses
scss
tocss
- Merges and compresses js files
- Moves and minifies html files
- And compresses images
It's a pretty standard set of tasks for creating a basic static site. Although it's pretty unlikely that someone will go through the effort of applying webpack to a simple site, I will go through how to do all of this in said build tool as a kind of beginners guide to webpack, or a designers guide, however you see fit. I wouldn't advise running this, a lot of the plugins are very old and not only will you get a bunch of messages asking you to upgrade them, the file probably wouldn't work π
Let's start from a blank slate here. Create six new folders in the hierarchy specified below.
gulp-to-webpack
βββ src
β βββ app
β βββ assets
| β βββ img
| β βββ styles
I found out after wiriting this that the app
folder is pretty pointless but it's not that big of a deal.
Create a file called index.html
in the src
folder with the following code;
<!-- gulp-to-webpack/src/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test Document</title>
</head>
<body>
<h1>Test Doc</h1>
</body>
</html>
Create a file called app.js
in the app
file and add some simple code like;
// gulp-to-webpack/src/app/app.js
document.write("It works.");
I remember listening to ShopTalk show a while ago and one of the hosts(Dave Rupert) joked it took around a day to set webpack up. Luckily nowadays it doesn't take that long and you should have it up and running in just a few minutes.
Open up a terminal/command prompt, cd into the gulp-to-webpack
directory and run;
$ npm init -y
The -y
answers yes to all the questions npm asks during setup. This creates a package.json
file in the root directory.
Now install webpack
and webpack-dev-server
;
$ npm i --save-dev webpack webpack-dev-server
webpack-dev-server
is similar to gulp-webserver
in the sense that it creates a server to test on. It has a bunch of other advantages.
Open the package.json file and if it's not there already, add a new line in the scripts section to run the webpack-dev-server
;
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack-dev-server",
},
This will allow us to start the server by typing npm start
in the terminal, or yarn start
if you have Yarn installed.
Create a file in the root directory called webpack.config.js
and open it up. There are four main things a webpack config file should have; entry, output, module and plugins.
- Entry is the main JS file that webpack will use.
- Output is where what it will output all the content to.
- Module all the rules that will be applied to your files.
- Plugins where all the plugins get loaded.
In our case we'll just need Entry and Output from the four.
const path = require('path');
module.exports = {
entry: './src/app/app.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
};
Okay I'll go through things line-by-line.
const path = require('path');
We add a constant variable called path which comes default with Node.js. This is used to specify the the root folder in line 6.- blank
module.exports = {
Is the way to export modules in Node.js so it can be used by other files. I'm not sure exaclty why webpack needs an export but I guess that's what makes it so magical.entry: './src/app/app.js',
Our main js file. Most files in webpack go through this file so as you'll see later on, our css and any other js will need to be imported here.output: {
Creates the output object.path: path.resolve(__dirname, 'dist'),
The path we want our used assets and code to be go to. We use the path import from line 1 forpath.resolve
the we refer to the root directory with__dirname
and we export our processed code to a file calleddist
.filename: 'bundle.js'
The filename to export all the bundled JS to.
Okay, one more thing before we're good to go. Let's tell the dev server were our directory containing the index.html
file is. Add this code to the bottom of the file above the last closing bracket };
.
// gulp-to-webpack/webpack.config.js
devServer: {
contentBase: './src'
}
Okay not run;
$ npm start
If everything went well, navigating to http://localhost:8080/ in your browser should show you this.
Note: Gone through the code and realised the html-loader is required before the js is loaded in automatically, so please ignore the image below until that is done.
First let's create a CSS file. Make a new file in your assets > styles
folder called styles.css
and add the following code;
/* gulp-to-webpack/src/assets/styles/styles.css */
:root {
--grey: #ccc;
}
body {
background-color: var(--grey);
font-family: sans-serif;
font-size: 14px;
}
h1 {
font-size: 10em;
}
Now let's import the css to our app.js
file, add this line right to the top.
// gulp-to-webpack/src/app/app.js
import '../assets/styles/style.css';
So there are several ways this can be done;
- Inlining css from your .css file to the head of your document using style-loader
- Or doing it the old fashioned way, using a tag in the head using extract-text-webpack-plugin
I will run through both, but the latter takes a bit longer than the former.
Install both style-loader and css-loader
$ npm i --save-dev style-loader css-loader
Open up your webpack.config.js and create a module object with a rules array just below your output objct.
// gulp-to-webpack/webpack.config.js
entry: './src/app/app.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
]
}
Luckily the github pages on most of the webpack plugins and loaders have good examples in their documentation so it's simple to add a bunch in. I'll simplify their example a bit.
// gulp-to-webpack/webpack.config.js
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
And that's it. It's important to make suer you're using arrays [] where they should be instead of objects {} I've made that mistake a few times before.
Webpack is esentially finding all the files in your code that end with .css
, then executing actions from right to left on the next line. So in our case ['style-loader', 'css-loader']
css-loader gets the css first, then style loader places it in the head of our code.
Run npm start
to see it in action.
So if you've followed the steps in part a please comment out or delete that code in the webpack.config.js
file. We'll keep the 'css-loader' as we'll need that for this method as well, go ahead and install extract-text-webpack-plugin
.
$ npm i --save-dev extract-text-webpack-plugin
Now in the webpack.config.js
add the following code below your const path
line;
// gulp-to-webpack/webpack.config.js
const ExtractTextPlugin = require('extract-text-webpack-plugin');
Now create a new rule, again for css but written like so;
// gulp-to-webpack/webpack.config.js
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: ['css-loader']
})
}
]
}
Here it's extracting all the code from files that end in .css using the css-loader. Now we need to tell it where to put all the code. Add a new plugins array above the devServer object, and below the module object.
plugins: [
new ExtractTextPlugin('style.css'),
],
Here we're exporting all our code to a file named 'style.css'. Once again, run npm start
to see the changes.
It's pretty simple from this point to export sass. Let's change our style.css
to style.scss
, remove all the code in the file and replace it with this;
// gulp-to-webpack/src/assets/styles/style.scss
$grey: #ccc;
body {
background-color: $grey;
}
h1 {
font-size: 5em;
}
Now update the style import in app.js
and install sass-loader
.
$ npm i --save-dev sass-loader
Let's add a new rule to our webpack.config.js
file;
// gulp-to-webpack/webpack.config.js
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: ['css-loader']
})
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
use: ['css-loader', 'sass-loader']
})
}
]
Pretty simple right? Now let's ammend that rule to minimise our css.
// gulp-to-webpack/webpack.config.js
test: /\.scss$/,
use: ExtractTextPlugin.extract({
use: [
{
loader: 'css-loader',
options: {
minimize: true
}
},
'sass-loader'
]
})
As you can see, we've applied the minimisation option to the css-loader
, to it is possible to minimise the code from section 3.a and b in this exact same way.
This is possibly the simplest thing we can do, all it requires is the addition of a plugin, and webpack does the rest.
Let's install the uglifyjs-webpack-plugin.
$ npm i --save-dev uglifyjs-webpack-plugin
Add it to the top of our webpack.config.js
file, below the ExtractTextPlugin.
// gulp-to-webpack/webpack.config.js
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
And let's scroll down to our plugins array and stick it in there too.
// gulp-to-webpack/webpack.config.js
plugins: [
new UglifyJSPlugin(),
new ExtractTextPlugin('style.css')
],
And that's it. As any js you use will have to be imported through the app.js
file it'll compress right at the end.
I'll have to admit, I haven't yet tried this wth more than one Html file, but it shouldn't be too hard to figure out if that's a road you want to go down.
Again, it's another plugin install, just in case you hadn't figured it out. Let's install the html-webpack-plugin
$ npm i --save-dev html-webpack-plugin
Then the usual process for a plugin so a const at the top of the webpack.config.js
file.
// gulp-to-webpack/webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
Now let's add some options for it. Below all the constants let's add a new object variable.
// gulp-to-webpack/webpack.config.js
let htmlOptions = {
template: 'src/index.html',
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true
}
};
The options should be pretty self explanitory so I won't run through them, but there are a full list of minification options available on the HTMLMinifier github page which you can look at if you would like more information.
As for the moving of Html files or files in general that happens in memory each time you run the webpack server so you probably haven't noticed a physical file yet. That can be easily rectified. Let's add a new script command to the package.json
file just below 'start', add;
"build": "webpack",
So now running npm run build
will build a dist
folder in your root directory with all your compiled code.
This is optional but let's say it provides, piece of mind. Whenver the build
command is run you'll notice the dist
folder stays there, there's no way of knowing if the files in it have been updated or not without manually checking it. One way to solve this is to delete the folder at the start of a build and replace it with the newly created file. That can be done with the clean-webpack-plugin.
The usually steps of installing the plugin and placing it at the opt of the webpack.config.js
file apply here. However in the plugins array we'll need to place the file we want to remove on build like so.
// gulp-to-webpack/webpack.config.js
new CleanWebpackPlugin(['dist'])
And that's it, another simple plugin added. Now run a build
and observe.
Phew, we've got to the final point of this post, if you've gone through everything up to this point you've done incredibly well.
Compressing images will involve the installation of two loaders, file-loader and image-webpack-loader.
$ npm i --save-dev file-loader image-webpack-loader
Now let's add a new rule to the webpack.config.js
file;
// gulp-to-webpack/webpack.config.js
{
test: /\.(jpg|png|gif|svg)$/,
use: [ 'file-loader', 'image-webpack-loader']
}
And the final step is to include an image tag in our Html. Open up the index.html
file and add the following code below the h1 tag.
<!-- gulp-to-webpack/src/index.html -->
<h2>Extra Text</h2>
<img src=<%=require("./assets/img/webpack.jpg")%> />
As you can see instead of requiring the image file at the top of the app.js
file like the css we've required it in a specific location in the html so webpack know where to place the image in markup once it's done with it's compression. Now let's downlaod an image and save it in the assets > img
folder. Say this one save it as webpack.jpg
and place it in the relevant folder.
Now if you run the dev server and view the source, you'll notice the image isn't called 'webpack' but instead has a random alphanumeric string. That's because 'file-loader' by default will hash assets. To change that we'll have to add some options;
// gulp-to-webpack/webpack.config.js
{
test: /\.(jpeg|png|gif|svg)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
}, 'image-webpack-loader'
]
}
So now well use the actual name of the file, then it's extension. Run the dev server and you'll see that the naming is as it should be.
And that's it. Everyhting my gulp file could do, re-written in webpack. I hope this has been a helpful introduciton to those who have been wanting to move away from Gulp/Grunt and try webpack. The syntax is very different but once you get used to it, it makes sense. From this point onwards you can include a bunch of other loaders and plugins all with the same techniques listed above, and even go as far as having seperate webpack.config.js
files for production, development, and tesitng. The possibilities are endless.