It is an exciting time for Web application developers. The amount of innovation and the sheer number of technology options is staggering. However, this rapid pace of innovation comes at a cost. For a developer trying to "catch up" and make good choices, it is becoming increasingly difficult to get a firm grasp on one development stack before the next big thing comes along. Here are a few comments from Stack Overflow that I saw the other day:

I'm very new to this and confused by the seemingly incredibly complicated new world of Web Development.

I'm ready to abandon front-end altogether and just work on APIs.

I don't like this ephemeral new world.

This sort of feeling is expressed in many different forums about npm, webpack, Bower, Gulp, Grunt, Angular, React, TypeScript, Babel, Redux, and so on.

Overall objective

In a series of blog posts, I will document my personal journey of discovery while assembling a few different variations of a modern Web development stack.

I will assume that you, like me, have already done a fair amount of Web development. Personally, I've spent more time on the server (mostly ASP.NET) than on the client but I'm reasonably proficient with HTML, JavaScript, CSS, and jQuery. I will also assume that, like me, you're relatively inexperienced when it comes to some (or all) of the technologies I have listed above. Therefore, I will do my best to provide a clear introduction to each technology before I use it. I will also aim to provide the reasons why I chose one technology over another and link to additional resources that I found useful during my journey.

The code for this series of blog posts is available on GitHub.

Objective for this post

In this first post, I will create a "Hello, World" application that includes an HTML page that uses jQuery to print out a message. I'm sure you could build that kind of application in your sleep but this one will involve the use of npm, TypeScript, and webpack. It might be a bit of overkill for "Hello, World" but this will set things up for later when I start to bring in things like ASP.NET Core and React.

Toolchain

I really just need a text editor and a system command prompt to get started. For me, that will mean VS Code and the built-in Terminal app on macOS but many of the steps are identical for other platform/editor combinations. I'll grab all the other bits and pieces as I need them.

npm

The tool I will use to grab many of those other bits and pieces will be npm. So, I'll need to make sure I have that installed and working.

I think the odds are pretty good that you've at least heard of Node.js and npm. However, I have found that when I ask a question like "What is Node.js?", I get a surprisingly wide variety of responses. First and foremost, Node.js is an engine for running JavaScript code on your machine (outside of a Web browser). Tools like Yeoman, Gulp, and Grunt are just packages of JavaScript set up to be executed by Node.js. Is it possible to use Node.js to run the server-side portion of a Web application that was written in JavaScript? Sure. But, the possibilities are really endless.

Recent statistics show that there are over 350,000 packages for Node.js with an average of 400 more added every day.

So, where does npm come in? Well, with all of those packages of JavaScript available, there needs to be a way to manage all of it. npm is the package manager for Node.js and npm makes it easy for you to obtain any packages you would like to use. Very often, one package needs one or more other packages to function. Fortunately, npm also handles the process of automatically fetching these other packages (dependencies).

There are other package managers out there. For example, many developers use Bower to manage their Web application's resources and .NET developers use NuGet. Of course, it's also possible to use all of these (and more) in a single application. For the stack I will be building here, I will use npm for most things and NuGet for the ASP.NET Core pieces.

For a good read, I recommend checking out this Stack Overflow question about the differences between Bower and npm.

Okay, npm is good, I need to get some of that. Well, it's easy to find out if I already have it. To the command line! This is what I get on my system when I ask about the version of node and npm that I have installed.

$ node -v
v6.9.2  
$ npm -v
3.10.9  

Of course, if I received an error message instead, I would probably need to go install node. The best way to do that is by using the installation package available directly from nodejs.org.

Once installed, npm can be used to install packages globally or only for the current application. Global packages are good for tools like Yeoman and Gulp while local packages are good for libraries like jQuery and React. If you would like to see what global packages you have installed and where they are located...

$ npm list -g

On my machine, the global packages are in /usr/local/lib and I have quite a few of them.

One quick note about installing global packages on macOS - you may discover that you need to use sudo to do it. There is an easy fix for that described here. I went with option #1 myself.

With node and npm ready to go, it's time to get down to business and start building an application.

Create a new project

I'll start by creating a new folder (I called mine "WebStack") and open the folder in VS Code. If you want to look like a pro, you can modify your $PATH variable and open the current folder in VS Code simply by typing:

$ code .

The first thing I'll add to my project is a file named package.json. This file will contain a list of all of the packages I will use with this project. This will allow me to easily see the version of each package but also make it easy for me (or someone else) to restore all of the packages later with a simple $ npm install. This is great since it means I don't have to put all of the packages in my source control repository. Here is what I will put into package.json to start off with:

package.json

My project itself will be viewed by npm as a package. So, my project has a version number and a name but it's private because I don't plan to publish it to a package registry. My project doesn't have any dependencies yet but it will soon.

Add the jQuery package

I think I'll end up using jQuery in my application so I'll add that package first. From within the project's directory...

$ npm install jquery --save

This says I would like to add the jQuery package to my project's folder (-g option would say to install it globally). The --save option says to add this package to the list of dependencies in package.json.

If you don't know the name of the package you want, the creator of the package typically highlights this information on their website. For example, you can see this on the jQuery download page. You can also search for packages on the npm website.

After installing jQuery, the dependencies section of my package.json file now looks like this:

"dependencies": {
  "jquery": "^3.1.1"
} 

The ^ symbol here says that I want this specific version or any newer version that is deemed to be compatible according to semantic versioning.

This operation also added a node_modules folder to my project folder that contains the files for jQuery.

jQuery files

Nice! I feel the urge to start writing code. However, I don't want to just start pumping out some JavaScript. I'd like to embrace some more of this new world and write TypeScript instead.

TypeScript

JavaScript is a great language and many developers absolutely love it. However, it has been around for quite a while now and it's being pushed well outside the box it was intended for. So, a team at Microsoft led by Andres Hejlsberg (creator of Delphi and C#) started the TypeScript project. A good description of TypeScript comes right from the TypeScript homepage.

  • JavaScript that scales.
  • Typed superset of JavaScript that compiles to plain JavaScript.
  • Any browser. Any host. Any OS. Open source.

That second point it a big one. Even though I have more features available to me as I write my code (and improved tool support), what I send to the client is still good old pure JavaScript. I can even choose which version of JavaScript the TypeScript compiler should generate. Personally, I have very quickly become a fan.

The team at Google that developed Angular 2 worked in partnership with the TypeScript team and built the framework itself with TypeScript.

Okay, TypeScript is good, I need to get some of that. I'll install TypeScript the same way I installed the jQuery package.

$ npm install typescript --save

Well, hold on, should this one be installed globally? Perhaps. However, TypeScript is still evolving rapidly and I might want different projects to use different versions of TypeScript.

I should be able to write some code now. I'll add a new file to the project folder named greeter.ts.

In VS Code, when you add your first TypeScript file, you may receive a message that says that the workspace folder contains a different version of the TypeScript compiler than the one included with VS Code's language service. When this dialog appears, I will click the option to have VS Code use the workspace version.

In greeter.ts, I will add a simple function and export this function.

function greet(name: string) {  
  return "Hello, " + name;
}
export = greet;  

I am using the CommonJS module format for my JavaScript. This is easy to use in TypeScript and supported by webpack. If you're not familiar with modules in JavaScript, the webpack site has a good introduction here and the TypeScript documentation goes a bit deeper. Really, it just comes down to keeping our JavaScript files more cleanly separated from each other to avoid things like name collisions and unwanted side effects. I'll need to export anything I want to be usable outside of the file and import that thing to use it in another file.

I'll go ahead and add another TypeScript file to my project named app.ts. I would like to update a page with a greeting using jQuery. So, at the top of the new file, I will import my greeter module as well as jQuery.

import greeter = require("./greeter");  
import $ = require("jquery");  

At this point, I can add a line to the file and start with the trusty $ shorthand for the jQuery function. Sadly, I am not presented with any code completion options. This is one of the main reasons I'm using TypeScript (better tool support)! I can fix this by providing a definition file for jQuery. A definition file tells the TypeScript compiler what functions and types a library exposes. The site DefinitelyTyped contains a massive registry of type definitions and you can easily install then using npm. For my project, I'll use npm to install the type definition file for jQuery.

npm install @types/jquery --save  

This added a @types folder in node_modules and added a dependency to package.json. Back in app.ts, I now have code completion!

Code Completion

With that in place, I'll add code to update some HTML content based on a call to the greeter module.

$(() => {
  $(document.body).html(greeter("World"));
});      

Before I look to compile my new TypeScript code, I will add a file named tsconfig.json that will specify how I would like the TypeScript compiler to work.

tsconfig.json

There are many options you can configure in tsconfig.json and details are available here. The two important ones for me here are target and outDir. These allow me to specify that I want my output to be valid ECMAScript 5 and placed into a directory named js.

Now, I can use the TypeScript compiler to generate some output. Eventually, we want webpack to do this work for us. However, I want to see something happen now. Since we installed TypeScript locally, I want to run the TypeScript compiler that's in my node_modules directory. There are a few ways to do that but I will use a feature of npm to make it super-easy. In package.json, I will add a scripts section include the TypeScript compiler. Shown below is my entire package.json file after making that addition.

{
  "version": "1.0.0",
  "name": "web-stack",
  "private": true,
  "dependencies": {
    "@types/jquery": "^2.0.34",
    "jquery": "^3.1.1",
    "typescript": "^2.1.4"
  },
  "scripts": {
    "tsc": "tsc"
  }
}

From the command line, I can check the version of the TypeScript compiler being used:

npm run tsc -- -v  

Then, I can use it to actually compile my TypeScript.

npm run tsc  

The result of this should be a new js folder in the project with two JavaScript files. Nice!

Technically, since we are transforming code in one source language (TypeScript) into code in another source language (JavaScript), this is commonly referred to as transpiling instead of compiling.

This is great but I would like to do a little more before I try to include my code in an HTML page. I would really like to merge all of my JavaScript into one file (bundle) and compress (minify) it. To accomplish those tasks, I will use another component (webpack).

webpack

webpack is a "module bundler" for JavaScript applications. A straightforward description of what webpack does is provided via an image on the webpack GitHub page:

webpack

At this stage, I am interested having webpack take all of my TypeScript and produce a single JavaScript bundle. Later on, I will have webpack do more for me.

The first step is use npm again to install webpack:

$ npm install webpack --save

I also need the TypeScript loader for webpack to understand TypeScript.

$ npm install ts-loader  --save

You might guess at this point that another configuration file will be necessary to control how webpack does its thing. Yep. This file should be named webpack.config.js.

var webpack = require('webpack');  
module.exports = {  
  entry: './app.ts',
  output: {
    filename: './js/bundle.js'
  },
  resolve: {
    extensions: ['', '.webpack.js', '.web.js', '.ts', '.js']
  },
  module: {
    loaders: [
      { test: /\.ts$/, loader: 'ts-loader' }
    ]
  }
}

By specifying entry, webpack will be able to assemble everything by working through all of the dependencies (starting with app.ts). The output setting is the single JavaScript bundle file that will be created. Let's do it!

We'll use the same technique we used for running the local version of the TypeScript compiler and add an entry to the scripts section in package.json:

"scripts": {
  "tsc": "tsc",
  "webpack": "webpack"
}

Now, we can run webpack:

$ npm run webpack

In the js folder, there should now be a bundle.json file and I can delete the other two JavaScript files that were generated earlier.

I should finally be able to use this code in an HTML page. I'll add index.html to my project with the following markup:

<html>  
<body>  
  <script src="js/bundle.js"></script>
</body>  
</html>  

If I open this file in a browser, voila, "Hello, World"! We can't stop there though. That was a lot of effort and we need to see some cooler stuff before we wrap up.

Minification and sourcemaps

I would like webpack to minify the JavaScript bundle that it creates and have a sourcemap so I can see the actual TypeScript when debugging in Chrome.

For the minification, I just need configure the right plugin in webpack.config.json.

plugins: [  
  new webpack.optimize.UglifyJsPlugin({
    compress: { warnings: false }
  })
],

I added this between the resolve section and the module section. The warnings item just says that I am not interested in hearing about JavaScript that webpack thinks is unreachable. This would be nice for my own code but it also triggers for libraries like jQuery.

On to sourcemaps. I will need an entry in tsconfig.json as well as webpack.config.js. In tsconfig.json, I just need to add this to the compilerOptions section (don't forget to add a comma at the end of the line if needed):

"sourceMap": true

In webpack.config.js, I'll add this line (I put it immediately above the resolve: { line):

devtool: 'source-map',  

Time to webpack and test:

$ npm run webpack

This time in Chrome, I am able to set and hit breakpoints in my TypeScript!

TypeScript Breakpoint

Watchmode

For the last item for this post, I would like to be able to make changes to my TypeScript and see the changes immediately. To accomplish this, we will launch webpack in watch mode.

$ npm run webpack -- --watch

Now I can open index.html in Chrome, change the greet function in VS Code:

function greet(name: string) {  
  return "Yo, " + name;
}

Refresh the page in Chrome and see the change. It's crazy, I know.

Conclusion

This might seem like a lot of work for "Hello, World" but hopefully you see the potential of where we can go with this. We have great tooling support and a solid debugging experience for our client-side code. At the same time, we're bundling, minifying, and supporting older clients. In a future post, I'll bring ASP.NET Core into the picture.