This lab is intended to walk you through the basics of the Webpack module bundling system. Webpack is the tool most commonly used in the React & Javascript community to transpile and bundle components, and is what is used under the hood by create-react-app (we'll be using this next week!). While that scaffolding tool means you don't need to know how to set up Webpack, it is good to be at least somewhat familiar with the concept (and this exercise will give you further practice working with npm
modules).
This tutorial is adapted from one by Tyler McGinnis.
Webpack is a build tool. That is, it is a (command-line) application that is used to automatically take the source code you write and prepare that code to be run/executed, whether for development or deployment. There are numerous such build tools in existence: Gulp is the major competitor to Webpack, and most IDEs (like jGrasp or IntelliJ) provide them. However, Webpack is favored by React developers because of how easily and speedily it transpiles JSX.
At its core, Webpack is a module loader: it takes source files like JavaScript modules and bundles them into a few simplified files that can be part of your webpage. It takes your complicated source code structure (lots of files) and transforms it into a brand-new, "simplified" version (few files).
There are two things you need to do to get started:
-
First, we need to create the
package.json
file to store information about the app you're building, including dependencies that Webpack will use. You can do this with the following command:cd path/to/project npm init
You will be prompted for a bunch of information to provide about your app. Give it the following details (just hit <enter>
to accept the details on any other prompts)
name
should be "webpack-tutorial"author
should be your name
Once you're finished, you will be asked to confirm your choices (type yes
), and you'll have a brand new package.json
file ready to use!
💡 If you just want a blank
package.json
file, usenpm init -y
to skip the prompts!
-
Locally install the
webpack
program. This will allow you to run the program from the command-line and bundle your app. Using the--save-dev
flag saves the module to yourdevDependencies
, which will not be bundled in the bundled version of your app.npm install --save-dev webpack
While Webpack can be used to do simple bundling from the command-line (see the official tutorial for an example), it's most common to write down all of your bundling options inside a configuration file. This file is basically just a JavaScript file that defines a variable that represents all of the different options you'd want to pass to the webpack
program. If you name this file webpack.config.js
, Webpack will read in that configuration automatically. Thus "using Webpack" really involves creating this file.
-
Create a new file called
webpack.config.js
and open it up in your favorite editor (e.g., VS Code). -
Inside this file, add the following line of code:
module.exports = {}
This is the CommonJS version of the ES6
export default {}
—you are defining an object (initially empty) that will be exported and used by the Webpack program. The rest of this tutorial will involve adding properties to this object.- Note that is it possible to use ES 6 style
import
andexport
commands, but you need to name your filewebpack.config.babel.js
and will need to have some Babel libraries installed. See here for discussion. For this lab, stick to the CommonJS syntax.
- Note that is it possible to use ES 6 style
Webpack's basic job is to take your source files, changing them in some way, and producing a new version. Thus there are three things you'll need to specify:
- What files to transform (specifically: what JavaScript file makes your program start)
- What transformations to make
- Where to save the transformed files
We specify the first piece (what files to transform) by giving the exported object an entry
property:
module.exports = {
//list of entry points
entry: [
__dirname + '/src/index.js'
]
}
This indicates which file (relative to the folder you are currently in) is the "start" or "entry point" into your program—in a way, which file has "main" in it. In our case, src/index.js
(the __dirname
is a Node constant referring to the current working directory). Note that the entry
property is actually an array, since it's possible to support multiple entry points.
We'll skip step 2 for a moment and also specify where to save the transformed files. This is specified as the output
property of the config object:
module.exports = {
entry: [
__dirname + '/src/index.js'
],
output: {
filename: "bundle.js",
path: __dirname + '/dist'
},
}
The output
property is itself an object with more details about the output (rather than an array of possible outputs). The above example says that we should output into the dist
folder (in the current directory), combining the code into a file called bundle.js
.
Since we don't have webpack installed globally (which you're welcome to do if you wish npm install -g webpack
), we'll need to add to our package.json
file, to tell npm
what to do with our local files!
Adding to your package.json
file, we'll need to update the scripts
key:
...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
...
Now, if you run npm run build
, the project will build! Similar to npm install
, when we run this command, we tell npm
that, within this project, we want to run
the build
script, which will call the local version of webpack
to run. When webpack
runs, it looks for the webpack.config.js
file, and then executes using that file to build your project!
Here is roughly the output you should see:
> [email protected] build /Users/evanfrawley/Github/webpack-demo
> webpack
Hash: fc5c075a9cfa7ee938c0
Version: webpack 3.8.1
Time: 56ms
Asset Size Chunks Chunk Names
bundle.js 3 kB 0 [emitted] main
[0] multi ./src/index.js 28 bytes {0} [built]
[1] ./src/index.js 390 bytes {0} [built]
This will create a new file dist/bundle.js
. If you view this file in VS Code, you'll see it contains some extra code that organizes the different modules into functions (to support variable scoping), one of which is the content of the index.js
file!
Look at all this red! This is the support that ES6 has among today's modern browsers. What does this mean? While ES6 features are being adopted more and more, there isn't great uniform support for it. So, we have to do a little trick called transpiling our code so that all browsers know what our Javascript is doing.
To do this, we use what are called loaders. A loader is basically a plugin that is used to perform a particular transformation (e.g., transpiling JSX or even ES 6 syntax!). Webpack's strength is its set of loaders that enable it to handle pretty much any kind of file and transformation. (It's of course possible to write your own, but that's well beyond the scope of this tutorial).
The module
property is used to specify the list of loaders
:
module.exports = {
entry: [
__dirname + '/src/index.js'
],
output: {
filename: "bundle.js",
path: __dirname + '/dist'
},
module: {
loaders: [
{
test: /\.jsx?$/,
loaders: ["babel-loader"],
}
]
},
}
The module
property is an object that itself contains a loaders
property, which is an array of loaders to apply. Each loader is described as an object (see? The nesting really does occur!).
-
The
test
property indicates which file types we want the loader to transform. This may look scary and confusion, but that's because it's using a Regular Expression (similar to how you split words in the JavaScript Challenge) to specify the file extension that we want to consider. This particular expression indicates files that end in.js
or.jsx
(the later is often used for JSX React components). How to make sense of this expression and its crazy punctuation:- Regular expressions are like Strings, but surrounded by
/ /
instead of" "
- The
.
in.js
needs to be escaped, so has a\
in front of it. Like\n
for newlines. - The
x
in.jsx
is op tional (without it we have.js
, which is fine), so is written with a?
after it to indicate that it can be present or not. - The last
$
indicates the "end of the line", so means we'll only talk about files that end in.js
(e.g.,libray.js.css
wouldn't get transformed).
- Regular expressions are like Strings, but surrounded by
-
the
loaders
property is a list of which loaders we want to apply to files whose names match the "test". In our case, we use Babel to transform our JSX, so we'll be using"babel"
as our loader.
Loaders such as Babel need to be installed individually using npm
as if they were separate programs (since they are in fact separate libraries!). This we will need to install the babel-loader
package to be able to apply Babel transformations:
npm install babel-loader --save-dev
- (The
--save-dev
argument here is like--save
in that it saves the dependency into yourpackage.json
file. However,--save-dev
lists the dependency as only needed for development, not for deployment. Thus if you wanted to upload your code to a web server (like on AWS), this would let that server know that it doesn't need to install Babel because you've already transpiled the code into a production build).
But because nothing is ever simple, the babel-loader
actually requires an additional library (babel-core
, which is the Babel program itself) to do its work. Thus we also need to install:
npm install babel-core --save-dev
Babel is able to perform all kinds of transformations, such as compiling JSX and converting ES6 syntax into older, browser-compatible versions. Babel is very modular, so each transformation we want to apply can be downloaded as an individual libraries called presets.
npm install babel-preset-es2015 --save-dev
This installs babel-preset-es2015
(the transformation for ES 6).
Of course, downloading to preset transformations doesn't automatically tell Babel to use them. To do that, we actually need to create another file that will contain which presets Babel should use. This file is called .babelrc
("rc" stands for "run commands"; note the leading .
indicating a hidden file). Create this file in the same directory as webpack.config.js
.
Your .babelrc
file just contains some JSON indicating which presets to use (other options are possible as well):
{
"presets": ["es2015"]
}
Altogether, you've specified what Babel transformations to apply (in .babelrc
), and told your Webpack config file to use Babel to modify any JavaScript files.
So finally, you should be able to use webpack
to build a working version of your React program next week! Open the index.html
file in a Browser to see your lovely app.
Some things to note:
- All your code is inside the
bundle.js
file. - Whenever you change your code, you will need to "re-build" your application (run
webpack
again). There are further webpack plugins that can help automate this, such as ones that will automatically refresh the page when the source files change.
Now that we've put in the hard work making webpack bundle our files, let's actually convert our files to proper ES6 synxtax and use correct import statements, given that only Chrome supports importing natively with special exceptions.
- Be sure to export the functions from
src/sorter.js
,src/looper.js
andsrc/printer.js
! - Now that they're exported, use ES6 syntax in
src/index.js
to import the functions
Hint: 3rd syntax in the list here
- Delete the
index.html
<script>
tags for your files. - One last thing: in
src/sorter.js
, we're using an npm package calledlodash
to sort the array. Runnpm install --save lodash
(--save
because we want the module to be used in our final app) and then importlodash
in thesrc/sorter.js
file. Hint: use the second import syntax from the MDN docs
With any luck, running npm run build
one last time should give you a good output, and finally, opening index.html
and looking at the console log should have your sorted user array!
The webpage doesn't look great yet, because there is no styling (CSS) involved. To practice working with webpack loaders, add an import
for main.css
stylesheet to your index.js
, and modify webpack.config.js
so that it will bundle that file:
-
Add the import to your
index.js
file. The file path should be relative to theindex.js
file. -
Install the
style-loader
andcss-loader
webpack loaders (remember to--save-dev
). Together, these loaders are able to handle CSS files. -
Add another element to the
module.loaders
array in thewebpack.config.js
file to specify thestyle-loader
transformation.- The
test
should be a regular expression for files ending in.css
- The
loader
itself should be"style-loader!css-loader"
, which refers to the "css loader" module for the style-loader plugin.
- The
-
Re-build your application using
webpack
. If you reload the page, you should now see it has a gray background!
There are lots of further configurations and options used by Webpack. For example, you can use the webpack-dev-server to have webpack run a local server that will automatically re-bundle modules when the files change. You are encouraged to check out that example if you have time.