We have updated the content of our program. To access the current Software Engineering curriculum visit curriculum.turing.edu.
NPM & Webpack
Configuring Your Projects
Adding dependencies, bundling your JavaScript files with Webpack, adding style loaders writing npm scripts, linting your code, preventing files from being added to github … This lesson will begin to draw back the veil on these mysteries!
In this lesson, we will hand-roll a boilerplate repo that can be used for a life-changing app - a Wombat Greeter.
Vocab
Dependency
Something your program needs in order to do it’s job that is provided by an external sourceNPM
Node Package Manager is a repository of JavaScript librariesWebpack
Broadly, a thing that takes your many files and combines them into just a few files a web browser can understand
NPM
What is NPM?
Node Package Manager
A package is a bit of reusable code!
- usually solves a single problem
- SOMEONE wrote it and shared it
- makes your life easier
You can compare these to
gems
from your Rails experience.
You can write your own packages, bundle them up, and publish them (either on the NPM library or on GitHub) for other devs to use!
NPM is a manager; it includes a set of tools that let you use and control node packages. Using it, you can:
- install packages into your project as dependencies (aka, code that your project depends on)
- create custom scripts to run code
- specify specific packages to use when deploying your app to production
You are so far pretty familiar with cloning down a repo, changing into the directory, and running npm install
. Let’s unpack what’s happening when you do that!
package.json
The package.json
file is created when we initialize npm in a repo (the terminal command is npm init
). It keeps track of our dependencies, lets us write npm scripts, and is the instruction manual NPM follows when we run npm install
.
You can compare to you Gemfile and Rakefile combined in one.
Let’s take a look at an example package.json
file:
{
"name": "Shih-Tzus",
"version": "1.0.0",
"description": "It has very important shih-tzu information.",
"main": "index.js",
"dependencies": {
"jquery": "^3.3.1",
"node": "6.11.1"
},
"devDependencies": {
"chai": "^4.0.2",
"mocha": "^3.4.2"
},
"scripts": {
"test": "./node_modules/mocha/bin/mocha --timeout 5000"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ameseee/SuperRadApp.git"
},
"keywords": ["furry", "fun", "soft"],
"author": "Sodie Holt",
"license": "ISC"
}
Take a minute to look over this. What’s familiar? What’s confusing?
Let’s break this down. The package.json is just meta data about our installed packages. It can be helpful to think of the package.json
as the recipe for our app.
dependencies (production):
We’re listing what ingredients we need for the app to run: we need jquery, and we need version 3.3.1 or any version compatible with 3.3.1. (You can read up on the what the symbols by the package version numbers mean here, and learn a bit about what the version numbers themselves even mean here.)
To install a package as a production dependency:
npm install [PACKAGE NAME OR GITHUB URL]
When you install a package this way, a new key-value pair with that package’s information will be added to the "dependencies"
object for you.
devDependencies (development):
The devDependencies are the tools that our chefs will need in order to work on our app - like our testing libraries, mocha and chai. The end user of an app will not need to have mocha and chai on their computers, since they will never be running the testing suite of the app. We designate those libraries as devDependencies
because they are packages that only developers working on the app will use.
To install a package as a development dependency:
npm install [PACKAGE NAME OR GITHUB URL] --save-dev
Note: you must use the --save-dev
flag. Otherwise, the dependency will be installed as a production dependency.
When you install a package this way, a new key-value pair with that package’s information will be added to the "devDependencies"
object for you.
scripts:
Our package.json
recipe also has instructions. The scripts
object has a key of "test"
whose value is "./node_modules/mocha/bin/mocha --timeout 5000"
.
We’re creating a shortcut; we can now run npm test
in our terminal, and it is equivalent to running npm run ./node_modules/mocha/bin/mocha --timeout 5000
in the terminal, but obviously much shorter and easier for us.
We’re able to create any scripts. One thing to be aware of: scripts named start
or test
are so ubiquitous that they can be run in the terminal by simply typing in npm start
and npm test
. For any other script, however, you must add the keyword run
into the terminal command.
For example, if you create a script called deploy
, to run that script in the terminal, you must type npm run deploy
.
Note: you can always edit the package.json
file manually! Just go in there and create key value pairs to your heart’s content!
Another note: Additional reading about the package-lock.json
file here. It is a log of snapshots of your dependency tree. It ensures that a team or production build is using the exact same dependencies, and is also a log that allows you to “time travel” into earlier versions of the dependency tree.
Common NPM Commands
- npm init
and optionally npm init --yes
Initializes npm and creates the package.json
file. If run without the --yes
flag, it will run you through a dialogue designed to help customize the package.json
file. The file can always be edited and updated at any point.
- npm install
Install package dependencies and devDependencies listed in package.json
- npm install [package-name]
Install package locally in folder location node_modules
- npm install -g [package-name]
Install package globally, usually to /usr/local/lib/node_modules
- npm install --save [package-name]
Install package locally in folder location node_modules
and update package.json
dependencies
- npm install --save-dev [package-name]
Install package locally in folder location node_modules
and update package.json
developer dependencies. Shortcut: npm install -D
.
- npm start
Run start script located in package.json
- npm test
Run test script located in package.json
- npm run [custom script]
Run custom script located in package.json
- NOTE: Notice that the –save-dev flags can be typed before or after the package name.
In your wombat repo:
-
Run
npm init
to create thepackage.json
file. You will need to answer a series of questions before you file is created. Shortcut:npm init --yes
, but you might want to go through the process for your first time. -
Edit the
scripts
portion of thepackage.json
file:"test": "./node_modules/mocha/bin/mocha --require babel-core/register test", "start": "webpack"
- Install the following dev dependencies:
- mocha
- chai
npm install mocha -D npm install chai -D
- Install the following dependencies:
- webpack
- webpack-dev-server
- webpack-cli
- babel-loader
- babel-cli
- babel-preset-env
- babel-preset-es2015
- babel-core@7
- style-loader
- css-loader
npm install webpack webpack-cli babel-loader babel-cli babel-preset-env style-loader css-loader
Webpack Basics
Webpack ‘Getting Started’ documentation
What is Webpack?
Webpack is a build tool that takes multiple JavaScript modules and bundles them up into a single, unified file.
You can compare webpack to the Asset Pipeline.
Why Do We Like It?
While webpack can be difficult to understand at first, it makes our lives much easier.
-
We can organize our code into separate files. This makes it easier to find specific pieces of code and improves maintainability.
-
Webpack creates a unique scope for each our files, helping to prevent adding things to the global and naming collisions.
-
By bundling all of our JS into a single file, the browser only needs to request, wait for, and process through one file.
-
Otherwise, it would have to request every JS file and individual dependency!
-
Network requests are expensive (they take a long time), which slows down the app and makes for a poor user experience.
-
-
We can even bundle up our HTML and CSS files by using loaders, further reducing the number of files the browser has to request and process.
Configuring Webpack (and Babel)
Here is the webpack.config.js
file from the gametime repo:
const path = require('path');
module.exports = {
devtool: 'inline-source-map',
entry: {
main: "./lib/index.js",
test: "mocha-loader!./test/index.js"
},
mode: 'development',
output: {
path: __dirname,
filename: "[name].bundle.js"
},
module: {
rules: [
{ test: /\.css$/, exclude: /node_modules/, use: [ 'style-loader', 'css-loader' ]},
{ test: /\.js$/, exclude: /node_modules/, use: ['babel-loader'] }
]
},
resolve: {
extensions: ['.js', '.json', '.css']
}
};
devtool
This provides a map of our bundled code, so that when we run into errors it will tell us in the console where the error is in our pre-bundled code.
entry
This is where webpack will start bundling up our app. It should be the entry point to your application.
output
This defines where where webpack will create your bundled code and what it will name the file.
loaders
Let’s focus on the rules
key-value pair inside the module
object.
Loaders transform different non-JS code into valid JS modules so they can be included when webpack bundles everything up into a single file for the browser.
Loaders have two parts: a npm module, and a configuration object which is added to the webpack.config.js
file.
Let’s walk through how the css-loader
was added to the react starter kit repo.
-
Install the style and css loaders
npm install style-loader css-loader --save-dev
-
In the
webpack.config.js
file, we add our configuration object
{
test: /\.css$/,
exclude: /node_modules/,
use: 'style-loader!css-loader'
}
That’s it! But what is that object doing?
The regular expression inside the object looks for all files whose extension is .css
and identifies them as code to be transformed into a JS, using the style-loader and css-loader npm packages.
When we run webpack, it transforms the css files into JS modules, which are then bundled together with the rest of our JS into a single file, bundle.js
(as specified in the output
section of the webpack.config.js
file!).
You can read more about loaders here.
Using Our Bundled File
In the HTML of the project, we point our <script>
tag to "bundle.js"
, so it references the all-neatly-bundled-up JS file that webpack made for us!
In your wombat repo:
- Create a new file in the root of your repo:
webpack.config.js
-
Create a new file in the root of your repo:
.babelrc
touch webpack.config.js .babelrc
-
Paste the following code into the
.babelrc
file.preset-env
is a smart preset that allows us to use the latest JavaScript without needing to micromanage syntax and browser polyfills.{ "presets": ["env"] }
-
Paste the following code to the
webpack.config.js
file:const path = require('path'); module.exports = { devtool: 'inline-source-map', entry: { main: "./lib/index.js" }, output: { path: __dirname, filename: "dist/[name].bundle.js" }, mode: 'development', module: { rules: [ { test: /\.css$/, exclude: /node_modules/, loader: "style-loader!css-loader" }, { test: /\.js$/, loader: 'babel-loader', query: { presets: ['es2015'] } } ] }, resolve: { extensions: ['.js', '.json', '.css'] } };
-
In the
test/index-test.js
file, let’s require chai and write one test to see that mocha and chai are properly hooked up:import { assert } from 'chai'; describe('test', function() { it('should return true', function() { assert.equal(true, true); }); });
-
In your terminal, run
npm test
-
Before this will work, we need to change the file we are providing to
index.html
. The script tag should point to./dist/main.bundle.js
. - In your terminal, run
npm start
. Visit localhost:8080 to view in your browser.
.gitignore Basics
github documentation on ignoring files
.gitignore is a file we can add to any git repo. This file contains filepaths and types of files we do not want to add to our git repo.
Common things to add to your .gitignore
file are:
- dependency directories
- log files
- files generated by compilers
- API keys, other sensitive data (note: there are better ways to obscure and protect data that will be covered in Mod 4)
In your wombat repo:
- Create a new file in the root of your repo:
.gitignore
- In that file, add the filepaths of the directories or files we don’t want added to github.
That’s it!
An example .gitignore
file might look like:
# dependencies
/node_modules
# production
/build
# misc
.DS_Store
.env
APIkey.js
# logs
*.log
The lines beginning with #
are simply comments that make it easier to navigate and maintain the .gitignore
file. Notice that the contents of the file are filepaths. Any file with /node_modules
in its filepath will not be added, committed, or pushed to github. If we want to ignore an entire type of file we can use the *
to indicate all, and then follow it with the file extension we want to ignore.