Getting started with Webpack: Source Maps

With the rise of Service Workers and performance improvements like cache-control: immutable becoming critical for modern applications. Tooling is increasingly essential for deploying and updating production code in safe and consistent ways.

As tooling becomes better and more advanced there is a growing divide between what we write as developers and what ships to the users. Source Maps are a neat method of getting access to the original source code when debugging compiled applications.

For this guide we'll be using Node v8.5, NPM v5.5, TypeScript v2.5, and Webpack v3.8. The following commands will tell you which versions you have installed. If they are not exact they may still work. TypeScript and Webpack will be installed a little further on.

$ node --version
v8.5.0

$ npm --version
5.5.1

We'll use the existing code from the Getting started with Webpack: Dev Server blog post as a starting point. Right now if you execute npm run build you'll get output similar to this.

$ npm run build

> pickle@1.0.0 build /Users/abraham/dev/pickle
> webpack

ts-loader: Using typescript@2.5.3 and /Users/abraham/dev/pickle/tsconfig.json
Hash: 4d4e0a8faad0bad39da8
Version: webpack 3.8.1
Time: 1295ms
     Asset       Size  Chunks             Chunk Names
 bundle.js    28.6 kB       0  [emitted]  main
index.html  320 bytes          [emitted]  
[./index.ts] ./index.ts 775 bytes {0} [built]
Child html-webpack-plugin for "index.html":
     1 asset
    [./node_modules/html-webpack-plugin/lib/loader.js!./index.html] ./node_modules/html-webpack-plugin/lib/loader.js!./index.html 633 bytes {0} [built]
    [./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 488 bytes {0} [built]
    [./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 495 bytes {0} [built]
        + 1 hidden module

Note the bundle.js 28.6 kB 0 [emitted] main line. This means the index.ts module is being compiled to a single bundle.js file. What we would like is for the output file name to be based on its contents so that it's easier to deploy. Update webpack.config.js output to look like the following.

module.exports = {
  ...
  output: {
    filename: '[name].[hash].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
  ...
};

The two main changes to the Webpack Output config are the addition of [name] and [hash]. [hash] is a hash based on the resulting module bundle and will not change between builds unless there are code changes. [name] is the name of the bundle being generated. You can configure it but it will default to main.

Now if you execute npm run build you'll see something like the following with a filname that looks like main.f072afdaf56b42be9129.bundle.js.

$ npm run build

> pickle@1.0.0 build /Users/abraham/dev/pickle
> webpack

ts-loader: Using typescript@2.5.3 and /Users/abraham/dev/pickle/tsconfig.json
Hash: f072afdaf56b42be9129
Version: webpack 3.8.1
Time: 1315ms
                              Asset       Size  Chunks             Chunk Names
main.f072afdaf56b42be9129.bundle.js    28.6 kB       0  [emitted]  main
                         index.html  346 bytes          [emitted]  
[./index.ts] ./index.ts 775 bytes {0} [built]
Child html-webpack-plugin for "index.html":
     1 asset
    [./node_modules/html-webpack-plugin/lib/loader.js!./index.html] ./node_modules/html-webpack-plugin/lib/loader.js!./index.html 633 bytes {0} [built]
    [./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 488 bytes {0} [built]
    [./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 495 bytes {0} [built]
        + 1 hidden module

If you execute npm run build a second time the [hash] should remain the same. But if you make a code change to index.ts the [hash] should be different after npm run build. And since we set up the Dev Server last blog post the JavaScript file names can change as much as they want and the output HTML will automatically include them.

Update the index.ts colorChange method to include a debugger. debugger will tell the browser devtools to pause when it hits that line of code so you can perform more advanced debugging.

class Pickle {
  ...
  colorChange(event: Event): void {
    // `<HTMLInputElement>` tells TypeScript what type `target` is so that it knows there is a `value` property available.
    let input = <HTMLInputElement>event.target;
    this.background.style.backgroundColor = input.value;
    debugger;
  }
  ...
}

If you open the development page at http://localhost:8080, enable your browser's devtools, and select a different background color you should see something like this.

Browser devtools paused mid execute showing bundled code

You'll find that the code doesn't look quite like you wrote it. For example colorChange(event: Event): void {} from index.ts now looks like Pickle.prototype.colorChange = function (event) {}. This makes it more difficult to debug problems as there are a lot of code changes and optimizations made during the compilation stages. But this is also the exact problem that Source Maps solve!

To enable source maps, we will have to make two configuration changes. The first change is to tsconfig.json so that TypeScript knows to build Source maps from the original *.ts code to the first level of processed JavaScript. For that simply uncomment (or add) the following line.

{
  ...
  "sourceMap": true,
  ...
}

The second is to webpack.config.js where we tell Webpack to take the Source Maps from the initial compiled JavaScript and include that in the bundled output. For devtool there are actually a number of options to choose. Some are better for development and some are better for production. We are going to stick with eval-source-map for now which gives a good mix of performance and accuracy. We'll cover production friendly options in a later post.

module.exports = {
  ...
  devtool: 'eval-source-map'
};

Make sure you shut down any npm run serve you have active so that the config changes will be picked up. Open the page and browser devtools back up to select a background color and it should pause and show you the original TypeScript code instead of the bundled JavaScript.

Browser devtools paused mid execute showing original code

(It's fine to get rid of the debugger line; it was just to pause code execution within code we wrote.)

You can view the source for the progress so far on GitHub.


Category: Development
Tags: Webpack