Wednesday, June 22, 2016

NPM & Webpack - the barest of bones!

I'm still a developer in training (but, then again, aren't we all?) and NPM has been a particular challenge in my learning process. Important disclaimer: I don't pretend to have a full grasp of NPM, yet. But writing about it helps me to sort it out and, hopefully, further my understanding.

NPM is basically a build tool. What the heck is a build tool? Yeah, I'm not great at explaining that yet but generally I 'get' that it takes your code and compiles it into a folder that is optimized for production. I understand that it plays well with dependencies between multiple files, but having an example of that is a bit further down my path.

Edit: Big error above. NPM is a node package manager, that can be USED as a build tool because of scripts. It is not just a build tool, but rather, that is one of the uses of NPM. Webpack, a module bundler, is more the 'builder'.  See! I am still trying to make sense out of all of this!

For now, I know how to use Webpack to take my html, css, javascript and assets (pictures, etc.) and put them into a 'build' file which is ready for production. Additionally, I no longer have to put any <link> or <script> tags in my html. Webpack does it for me!

I understand that these tools are designed to streamline your process and eliminate tasks that you are used to doing over and over again. I've discovered, at this point, two tasks that are now automated and are making my life easier: building my file tree and publishing my code to gh-pages on GitHub.

I won't particularly explain what every line in my code does, but rather present it with confidence that it works.

Additionally, I won't explain how to install and set up NPM on your machine the first time. Helpful directions can be found here:

Here goes nothing!

Starting a Project with NPM

This is MY process every time I start a new project. We all do it differently. I won't say mine is the best, but it serves me well.

1)  In the terminal I navigate to the folder where I plan on putting my project folder. There, I build my project folder:

          md npmworkflow

2)  Navigate into the new folder:

          cd npmworkflow

3)  I always (always!) start my git flow, initialize my project and create my repository on GitHub:

       git init
     git remote add origin https://github.com/myusername/npmworkflow.git

4)  Time to start working with NPM. The first step is to initialize NPM:

          npm init

5)  A number of questions will be presented. You do not have to answer them, but I answer a few:

          name:
          version:
          description: a NPM workflow basic project
          entry point: js/index.js
          test command:
          git repository: (self populated when git init is 1st)
          keywords:
          author: Ryan S. Buchholtz
          license:

6)  Lastly, you will be asked "Is this ok?". Simply press enter.

7)  Back to the root of your project folder. Want to see what has happened with the npm int?

          ls

8)  A new file has been added to your project. The package.json file was the result of the npm init. Time to head to Sublime and have a look around!

9)  Open the package.json file. It should look a little something like this:


{
  "name": "npmworkflow",
  "version": "1.0.0",
  "description": "a NPM workflow basic project",
  "main": "js/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/myusername/npmworkflow.git"
  },
  "author": "Ryan S. Buchholtz",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/myusername/npmworkflow/issues"
  },
  "homepage": "https://github.com/myusername/npmworkflow#readme"
}
10)  Time to make some changes. I overwrite the scripts with the following. Study them and try to make sense out of what each one does. Many make perfect sense - they are just terminal commands that have been wrapped in a script so that they can be called using npm run:

"initialbuild": "npm run build:assets && npm run build:initialhtml && npm run build:initialcss && npm run build:initialjs && npm run build:initialwebpack", 
"build:assets": "mkdir -p assets", 
"build:initialhtml": "touch index.html", 
"build:initialcss": "mkdir -p css && touch css/style.scss && touch css/normalize.scss, 
"build:initialjs": "mkdir -p js && touch js/index.js", 
"build:initialwebpack": "touch webpack.config.js", 
"mkdir": "mkdir -p build", 
"build": "npm run clean && npm run mkdir && webpack", 
"watch": "npm run build && webpack --watch", 
"clean": "rm -rf build", 
"deploy": "gh-pages -d build"
11)  File save and navigate back to the terminal. Make sure you are in the project folder and then enter the following script (seen above):

          npm run initialbuild 

12)  What just happened? Well, for me, a nice short-cut. My file tree and files have been built! Go back to Sublime and take a look. You should see 3 new folders (assets, css, js) and 2 new files (index.html, webpack.config.js). COOL!! But it's time to install a bunch of dependencies for your project. In my case, these are the bare bones.

13)  Using the terminal again, run the following commands to install some NPM packages for development (not necessary in production of your product):

     npm install --save-dev webpack     
     npm install --save-dev copy-webpack-plugin 
     npm install --save-dev css-loader
     npm install --save-dev extract-text-webpack-plugin 
     npm install --save-dev gh-pages 
     npm install --save-dev html-webpack-plugin 
     npm install --save-dev style-loader
     

Shortcut: you can chain these together into one command e.g. npm install --save-dev css-loader webpack style-loader, etc.

14)  There is at least one NPM package that I install that I would need in production, and that is jQuery. Go ahead and install if necessary:

     npm install --save jquery

15)  Navigate back to Sublime and your package.json file. You will see a list of dependencies that have been installed - in two lists - those for development (devDependencies) and those for production (dependencies). Voila!

16)  With all these installed they are kinda just sitting there, but are not invoked to 'get to work'! That all happens in the webpack.config.js file. Go ahead and open that up and add the following to that file:

var path = require('path');
var packageData = require('./package.json');
var filename = [packageData.name, packageData.version, 'js'];

var HtmlWebpackPlugin = require('html-webpack-plugin');
var webpack = require('webpack');
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var CopyWebpackPlugin = require('copy-webpack-plugin');



var plugins = [
    new HtmlWebpackPlugin({
      inject: 'head',
      template: 'index.html',
      minify: {
        "collapseWhitespace": true, 
        "removeComments": true, 
        "removeRedundantAttributes": true, 
        "removeScriptTypeAttributes": true, 
        "removeStyleLinkTypeAttributes": true
      }
    }),
    new ExtractTextPlugin('style.css'),
    new CopyWebpackPlugin([
          {from: 'assets', to: 'assets'}
      ])
  ];

module.exports = {
    entry: {
      main: [
        path.resolve(__dirname, packageData.main)
      ]
    },

    output: {
        path: path.resolve(__dirname, 'build'),
        filename: filename.join('.'),
    },
    devtool: 'source-map',
    plugins: plugins,
    module: {
      loaders: [
        { 
            test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader")
          }
      ]
    }

};

17)  Save that file! Study it (could take hours, it did for me!) and try to make sense out of each line and how it interacts with your project. Each line comes from the ReadMe page for each of the dependencies that were installed and listed in the package.json file. They have been linked in steps 13 and 14. 

18)  Before going any further, let's populate a few files with some code so that we can actually see what happens when we run webpack:

index.html


<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <title>Document</title></head><body><div class="first-div">  <h1>My Name is Ryan</h1></div><div>  <p>I'm a Monkey!</p></div>
</body></html>


style.css

@import url(normalize.css);
* {  background-color: orange;}
body {  width: 60%;  margin: 0 auto;  border: 1px solid green;  height: 1000px;  background-color: blue;}
.first-div {  background-color: green;  width: 50%;  margin: 0 auto;  border: 5px solid red;}
.first-div h1 {  font-size: 5em;  text-align: right;  background-color: white;}

normalize.css

copy and paste from https://necolas.github.io/normalize.css/4.1.1/normalize.css

index.js
var $ = require('jquery'); 
require('../css/style.css'); 
$(document).ready(function(){  alert("I'm from index.js");});


19)  Make sure all those files are saved and let's run our first build script. In the terminal:

     npm run build

20)  Back to Sublime and an exploration of our file tree.  See anything new? Yup! A folder called build. Dive into there and have a look around. You will see all the files necessary to launch this little project. Open them one at a time, starting with index.html:

This file now has two things that the original index.html didnt - a <script> and a <link>, referencing your javascript and css files.  Cool, eh? View this file in the browser and the style from the css and the alert from the js work. I find that awesome.

One thing you may notice is that the asset folder did not copy over into the build folder. Why? Near as I can tell this is because there is nothing in the folder. If you add a single file and re-run the npm run build command you will see it copy over.

In that last paragraph you will see that I mentioned a change in code, and then told you to re-run npm run build. That suggests that, every time you make the smallest change to your html, or css, or javascript - well, anywhere - you would need to re-run npm run build. That doesn't really jive with the idea that this tool eliminates repetitive actions, does it? Well, there is a solution:

     npm run watch

What did you notice when you ran that command in the terminal? Did it throw up some messages, and then never return you to the prompt that you are used to? Hopefully it did, because that now means that a 'watch' event is happening. To test it, go into your css file and make a change. I changed the background color of * to lightorange and saved the file.

You can now do one of two things - if index.html is still open in your browser, refresh it. Did the background color change? Yup, it did! You can validate this by going to the build folder and opening the style.css. Scroll to the bottom and you will see that the code in the build folder has been changed to reflect the changes you made in the original style.css. LOVE THAT!

The same would be true for all of your files that are used to create the build folder.

Also - because this watch command kind of locked up your terminal, you will need to open another terminal window to continue to use it. When you are done with the watch, click ctrl-c and it will cancel the watch.

One more thing that I find useful for this stage in development that I want to share. Head back to the terminal and install this package:

     npm install webpack-dev-server -g

Something a little different here - the -g. This has been installed globally and is not a dependency of this project. You can use this any time going forward, wherever you might be and not need to re-install it.

With the install complete, go ahead and run it in the terminal (make sure you are in the parent folder of your project).

     webpack-dev-server

The first line it returned, upon being run, was an http:// address. Copy this and open it in your browser. Change something in your css - I changed the .first-div background color to yellow. Save the file and navigate back to your browser. The change has been reflected and you didn't have to refresh or re-run build! WOWZERS! I love this.

Alrighty. This is what I feel comfortable writing about for now. I have been able to incorporate some different packages like font-awesome and css-animations and will share when I feel more confident. What I do know for now: this NPM/Webpack code works. But it doesn't really do THAT much. It doesn't minify my code, it doesn't make dependencies, and it doesn't do THAT much more than I'm capable of doing on my own. But I feel that this foundation is necessary to continue to dive deeper and figure out all the ways that NPM will help to improve my skill set and make me that much better of a programmer.

Happy coding! Look forward to hearing from any and all!