source-map-explorer 1.3.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
package/.eslintrc.js ADDED
@@ -0,0 +1,27 @@
1
+ module.exports = {
2
+ "env": {
3
+ "commonjs": true,
4
+ "mocha": true,
5
+ "node": true
6
+ },
7
+ "extends": "eslint:recommended",
8
+ "rules": {
9
+ "indent": [
10
+ "error",
11
+ 2
12
+ ],
13
+ "linebreak-style": [
14
+ "error",
15
+ "unix"
16
+ ],
17
+ "quotes": [
18
+ "error",
19
+ "single"
20
+ ],
21
+ "semi": [
22
+ "error",
23
+ "always"
24
+ ],
25
+ "no-console": 0
26
+ }
27
+ };
package/.travis.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  sudo: false # Use container-based infrastructure
2
2
  language: node_js
3
3
  node_js:
4
- - "0.12"
4
+ - "4"
package/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  [![Build Status](https://travis-ci.org/danvk/source-map-explorer.svg?branch=v1.1.0)](https://travis-ci.org/danvk/source-map-explorer) [![NPM version](http://img.shields.io/npm/v/source-map-explorer.svg)](https://www.npmjs.org/package/source-map-explorer)
2
- # source-map-explorer
3
- Analyze and debug JavaScript code bloat through source maps.
2
+ # source-map-explorer
3
+ Analyze and debug JavaScript (or Sass or LESS) code bloat through source maps.
4
4
 
5
- The source map explorer determines which file each byte in your minified JS came from. It shows you a [treemap][] visualization to help you debug where all the code is coming from.
5
+ The source map explorer determines which file each byte in your minified code came from. It shows you a [treemap][] visualization to help you debug where all the code is coming from. Check out this [Chrome Developer video][video] (3:25) for a demo of the tool in action.
6
6
 
7
7
  Install:
8
8
 
9
- npm install source-map-explorer
9
+ npm install -g source-map-explorer
10
10
 
11
11
  Use:
12
12
 
13
13
  source-map-explorer bundle.min.js
14
- source-map-explorer bundle.min.js bundle.min.js.map
14
+ source-map-explorer bundle.min.js bundle.min.js.map
15
15
 
16
16
  This will open up a visualization of how the space is used in your minified bundle:
17
17
 
@@ -34,7 +34,7 @@ in the bundle (perhaps because of out-of-date dependencies).
34
34
  "foo.js": 137
35
35
  }
36
36
  ```
37
-
37
+
38
38
  * `--tsv`: output tab-delimited values instead of displaying a visualization:
39
39
 
40
40
  ```
@@ -43,7 +43,7 @@ in the bundle (perhaps because of out-of-date dependencies).
43
43
  dist/bar.js 62
44
44
  dist/foo.js 137
45
45
  ```
46
-
46
+
47
47
  If you just want a list of files, you can do `source-map-explorer --tsv foo.min.js | sed 1d | cut -f1`.
48
48
 
49
49
  * `--html`: output HTML to stdout. By default, source-map-explorer writes HTML to a temporary file and opens it in your default browser. If you want to save the output (e.g. to share), pipe it to a file:
@@ -51,15 +51,17 @@ in the bundle (perhaps because of out-of-date dependencies).
51
51
  ```
52
52
  source-map-explorer --html foo.min.js > tree.html
53
53
  ```
54
-
54
+
55
55
  * `--replace`, `--with`: The paths in source maps sometimes have artifacts that are difficult to get rid of. These flags let you do simple find & replaces on the paths. For example:
56
56
 
57
57
  ```
58
58
  source-map-explorer foo.min.js --replace 'dist/' --with ''
59
59
  ```
60
-
60
+
61
61
  You can specify these flags multiple times. Be aware that the find/replace is done _after_ eliminating shared prefixes between paths.
62
62
 
63
+ These are regular expressions.
64
+
63
65
  * `--noroot`: By default, source-map-explorer finds common prefixes between all source files and eliminates them, since they add complexity to the visualization with no real benefit. But if you want to disable this behavior, set the `--noroot` flag.
64
66
 
65
67
  ## Generating source maps
@@ -130,3 +132,4 @@ source-map-explorer path/to/foo.min.js{,.map}
130
132
  [exorcist]: https://github.com/thlorenz/exorcist
131
133
  [inline]: /README.md#types-of-source-maps
132
134
  [treemap]: https://github.com/martine/webtreemap
135
+ [video]: https://www.youtube.com/watch?v=7aY9BoMEpG8
package/index.js CHANGED
@@ -1,84 +1,121 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  var doc = [
4
- 'Analyze and debug space usage through source maps.',
5
- '',
6
- 'Usage:',
7
- ' source-map-explorer <script.js> [<script.js.map>]',
8
- ' source-map-explorer [--json | --html | --tsv] <script.js> [<script.js.map>] [--replace=BEFORE --with=AFTER]... [--noroot]',
9
- ' source-map-explorer -h | --help | --version',
10
- '',
11
- 'If the script file has an inline source map, you may omit the map parameter.',
12
- '',
13
- 'Options:',
14
- ' -h --help Show this screen.',
15
- ' --version Show version.',
16
- '',
17
- ' --json Output JSON (on stdout) instead of generating HTML',
18
- ' and opening the browser.',
19
- ' --tsv Output TSV (on stdout) instead of generating HTML',
20
- ' and opening the browser.',
21
- ' --html Output HTML (on stdout) rather than opening a browser.',
22
- '',
23
- ' --noroot To simplify the visualization, source-map-explorer',
24
- ' will remove any prefix shared by all sources. If you',
25
- ' wish to disable this behavior, set --noroot.',
26
- '',
27
- ' --replace=BEFORE Apply a simple find/replace on source file',
28
- ' names. This can be used to fix some oddities',
29
- ' with paths which appear in the source map',
30
- ' generation process.',
31
- ' --with=AFTER See --replace.',
4
+ 'Analyze and debug space usage through source maps.',
5
+ '',
6
+ 'Usage:',
7
+ ' source-map-explorer <script.js> [<script.js.map>]',
8
+ ' source-map-explorer [--json | --html | --tsv] [-m | --only-mapped] <script.js> [<script.js.map>] [--replace=BEFORE --with=AFTER]... [--noroot]',
9
+ ' source-map-explorer -h | --help | --version',
10
+ '',
11
+ 'If the script file has an inline source map, you may omit the map parameter.',
12
+ '',
13
+ 'Options:',
14
+ ' -h --help Show this screen.',
15
+ ' --version Show version.',
16
+ '',
17
+ ' --json Output JSON (on stdout) instead of generating HTML',
18
+ ' and opening the browser.',
19
+ ' --tsv Output TSV (on stdout) instead of generating HTML',
20
+ ' and opening the browser.',
21
+ ' --html Output HTML (on stdout) rather than opening a browser.',
22
+ '',
23
+ ' -m --onlymapped Exclude "unmapped" bytes from the output.',
24
+ ' This will result in total counts less than the file size',
25
+ '',
26
+ '',
27
+ ' --noroot To simplify the visualization, source-map-explorer',
28
+ ' will remove any prefix shared by all sources. If you',
29
+ ' wish to disable this behavior, set --noroot.',
30
+ '',
31
+ ' --replace=BEFORE Apply a simple find/replace on source file',
32
+ ' names. This can be used to fix some oddities',
33
+ ' with paths which appear in the source map',
34
+ ' generation process. Accepts regular expressions.',
35
+ ' --with=AFTER See --replace.'
32
36
  ].join('\n');
33
37
 
34
38
  var fs = require('fs'),
35
- path = require('path'),
36
- sourcemap = require('source-map'),
37
- convert = require('convert-source-map'),
38
- temp = require('temp'),
39
- open = require('open'),
40
- _ = require('underscore'),
41
- docopt = require('docopt').docopt,
42
- fileURL = require('file-url'),
43
- btoa = require('btoa');
39
+ glob = require('glob'),
40
+ path = require('path'),
41
+ sourcemap = require('source-map'),
42
+ convert = require('convert-source-map'),
43
+ temp = require('temp'),
44
+ open = require('open'),
45
+ _ = require('underscore'),
46
+ docopt = require('docopt').docopt,
47
+ btoa = require('btoa');
44
48
 
45
- function computeGeneratedFileSizes(mapConsumer, generatedJs) {
49
+ function computeSpans(mapConsumer, generatedJs) {
46
50
  var lines = generatedJs.split('\n');
47
- var sourceExtrema = {}; // source -> {min: num, max: num}
51
+ var spans = [];
48
52
  var numChars = 0;
49
- var lastSource = null;
53
+ var lastSource = false; // not a string, not null.
50
54
  for (var line = 1; line <= lines.length; line++) {
51
55
  var lineText = lines[line - 1];
52
56
  var numCols = lineText.length;
53
57
  for (var column = 0; column < numCols; column++, numChars++) {
54
58
  var pos = mapConsumer.originalPositionFor({line:line, column:column});
55
59
  var source = pos.source;
56
- if (source == null) {
57
- // Often this is from the '// #sourceMap' comment itself.
58
- continue;
59
- }
60
60
 
61
- if (source != lastSource) {
62
- if (!(source in sourceExtrema)) {
63
- sourceExtrema[source] = {min: numChars};
64
- lastSource = source;
65
- } else {
66
- // source-map reports odd positions for bits between files.
67
- }
61
+ if (source !== lastSource) {
62
+ lastSource = source;
63
+ spans.push({source: source, numChars: 1});
68
64
  } else {
69
- sourceExtrema[source].max = numChars;
65
+ spans[spans.length - 1].numChars += 1;
70
66
  }
71
67
  }
72
68
  }
73
- return _.mapObject(sourceExtrema, function(v) {
74
- return v.max - v.min + 1
75
- });
69
+ return spans;
70
+ }
71
+
72
+ var UNMAPPED = '<unmapped>';
73
+
74
+ /**
75
+ * Calculate the number of bytes contributed by each source file.
76
+ * @returns {
77
+ * counts: {[sourceFile: string]: number},
78
+ * numUnmapped: number,
79
+ * totalBytes: number
80
+ * }
81
+ */
82
+ function computeGeneratedFileSizes(mapConsumer, generatedJs) {
83
+ var spans = computeSpans(mapConsumer, generatedJs);
84
+
85
+ var numUnmapped = 0;
86
+ var counts = {};
87
+ var totalBytes = 0;
88
+ for (var i = 0; i < spans.length; i++) {
89
+ var span = spans[i];
90
+ var numChars = span.numChars;
91
+ totalBytes += numChars;
92
+ if (span.source === null) {
93
+ numUnmapped += numChars;
94
+ } else {
95
+ counts[span.source] = (counts[span.source] || 0) + span.numChars;
96
+ }
97
+ }
98
+ return {
99
+ counts: counts,
100
+ numUnmapped: numUnmapped,
101
+ totalBytes: totalBytes
102
+ };
76
103
  }
77
104
 
78
105
  var SOURCE_MAP_INFO_URL = 'https://github.com/danvk/source-map-explorer/blob/master/README.md#generating-source-maps';
79
106
 
80
107
  function loadSourceMap(jsFile, mapFile) {
81
- var jsData = fs.readFileSync(jsFile).toString();
108
+ var jsData;
109
+ try {
110
+ jsData = fs.readFileSync(jsFile).toString();
111
+ } catch(err) {
112
+ if (err.code === 'ENOENT' ) {
113
+ console.error('File not found! -- ', err.message);
114
+ return null;
115
+ } else {
116
+ throw err;
117
+ }
118
+ }
82
119
 
83
120
  var mapConsumer;
84
121
  if (mapFile) {
@@ -114,7 +151,7 @@ function loadSourceMap(jsFile, mapFile) {
114
151
  function commonPathPrefix(array){
115
152
  if (array.length == 0) return '';
116
153
  var A= array.concat().sort(),
117
- a1= A[0].split(/(\/)/), a2= A[A.length-1].split(/(\/)/), L= a1.length, i= 0;
154
+ a1= A[0].split(/(\/)/), a2= A[A.length-1].split(/(\/)/), L= a1.length, i= 0;
118
155
  while(i<L && a1[i] === a2[i]) i++;
119
156
  return a1.slice(0, i).join('');
120
157
  }
@@ -129,13 +166,13 @@ function adjustSourcePaths(sizes, findRoot, finds, replaces) {
129
166
  var prefix = commonPathPrefix(_.keys(sizes));
130
167
  var len = prefix.length;
131
168
  if (len) {
132
- sizes = mapKeys(sizes, function(source) { return source.slice(len); })
169
+ sizes = mapKeys(sizes, function(source) { return source.slice(len); });
133
170
  }
134
171
  }
135
172
 
136
173
  for (var i = 0; i < finds.length; i++) {
137
- var before = finds[i],
138
- after = replaces[i];
174
+ var before = new RegExp(finds[i]),
175
+ after = replaces[i];
139
176
  sizes = mapKeys(sizes, function(source) {
140
177
  return source.replace(before, after);
141
178
  });
@@ -151,75 +188,117 @@ function validateArgs(args) {
151
188
  }
152
189
  }
153
190
 
191
+ // On Windows, it's helpful if source-map-explorer can expand globs itself.
192
+ // See https://github.com/danvk/source-map-explorer/issues/52
193
+ function expandGlob(args) {
194
+ var arg1 = args['<script.js>'];
195
+ var arg2 = args['<script.js.map>'];
196
+ if (arg1 && !arg2) {
197
+ var files = glob.sync(arg1);
198
+ if (files.length > 2) {
199
+ throw new Error(
200
+ 'Glob should match exactly 2 files but matched ' + files.length + ' ' + arg1);
201
+ } else if (files.length === 2) {
202
+ // allow the JS and source map file to match in either order.
203
+ if (files[0].indexOf('.map') >= 0) {
204
+ var tmp = files[0];
205
+ files[0] = files[1];
206
+ files[1] = tmp;
207
+ }
208
+ args['<script.js>'] = files[0];
209
+ args['<script.js.map>'] = files[1];
210
+ }
211
+ }
212
+ return args;
213
+ }
214
+
154
215
 
155
216
  if (require.main === module) {
156
217
 
157
- var args = docopt(doc, {version: '1.1.1'});
158
- validateArgs(args);
159
- var data = loadSourceMap(args['<script.js>'], args['<script.js.map>']);
160
- if (!data) {
161
- process.exit(1);
162
- }
163
- var mapConsumer = data.mapConsumer,
218
+ var args = docopt(doc, {version: '1.5.0'});
219
+ expandGlob(args);
220
+ validateArgs(args);
221
+ var data = loadSourceMap(args['<script.js>'], args['<script.js.map>']);
222
+ if (!data) {
223
+ process.exit(1);
224
+ }
225
+ var mapConsumer = data.mapConsumer,
164
226
  jsData = data.jsData;
165
227
 
166
- var sizes = computeGeneratedFileSizes(mapConsumer, jsData);
228
+ var sizes = computeGeneratedFileSizes(mapConsumer, jsData);
229
+ var counts = sizes.counts;
167
230
 
168
- if (_.size(sizes) == 1) {
169
- console.error('Your source map only contains one source (',
170
- _.keys(sizes)[0], ')');
171
- console.error("This typically means that your source map doesn't map all the way back to the original sources.");
172
- console.error("This can happen if you use browserify+uglifyjs, for example, and don't set the --in-source-map flag to uglify.");
173
- console.error('See ', SOURCE_MAP_INFO_URL);
174
- process.exit(1);
175
- }
231
+ if (_.size(counts) == 1) {
232
+ console.error('Your source map only contains one source (',
233
+ _.keys(counts)[0], ')');
234
+ console.error('This typically means that your source map doesn\'t map all the way back to the original sources.');
235
+ console.error('This can happen if you use browserify+uglifyjs, for example, and don\'t set the --in-source-map flag to uglify.');
236
+ console.error('See ', SOURCE_MAP_INFO_URL);
237
+ process.exit(1);
238
+ }
176
239
 
177
- sizes = adjustSourcePaths(sizes, !args['--noroot'], args['--replace'], args['--with']);
240
+ counts = adjustSourcePaths(counts, !args['--noroot'], args['--replace'], args['--with']);
178
241
 
179
- if (args['--json']) {
180
- console.log(JSON.stringify(sizes, null, ' '));
181
- process.exit(0);
182
- }
242
+ var onlyMapped = args['--only-mapped'] || args['-m'];
243
+ var numUnmapped = sizes.numUnmapped;
244
+ if (!onlyMapped) {
245
+ counts[UNMAPPED] = numUnmapped;
246
+ }
247
+ if (numUnmapped) {
248
+ var totalBytes = sizes.totalBytes;
249
+ var pct = 100 * numUnmapped / totalBytes;
250
+ console.warn(
251
+ 'Unable to map', numUnmapped, '/', totalBytes,
252
+ 'bytes (' + pct.toFixed(2) + '%)');
253
+ }
183
254
 
184
- if (args['--tsv']) {
185
- console.log('Source\tSize');
186
- _.each(sizes, function(source, size) { console.log(size + '\t' + source); })
187
- process.exit(0);
188
- }
255
+ if (args['--json']) {
256
+ console.log(JSON.stringify(counts, null, ' '));
257
+ process.exit(0);
258
+ }
189
259
 
190
- var assets = {
191
- underscoreJs: btoa(fs.readFileSync(require.resolve('underscore'))),
192
- webtreemapJs: btoa(fs.readFileSync(require.resolve('./vendor/webtreemap.js'))),
193
- webtreemapCss: btoa(fs.readFileSync(require.resolve('./vendor/webtreemap.css'))),
194
- };
260
+ if (args['--tsv']) {
261
+ console.log('Source\tSize');
262
+ _.each(counts, function(source, size) { console.log(size + '\t' + source); });
263
+ process.exit(0);
264
+ }
195
265
 
196
- var html = fs.readFileSync(path.join(__dirname, 'tree-viz.html')).toString();
266
+ var assets = {
267
+ underscoreJs: btoa(fs.readFileSync(require.resolve('underscore'))),
268
+ webtreemapJs: btoa(fs.readFileSync(require.resolve('./vendor/webtreemap.js'))),
269
+ webtreemapCss: btoa(fs.readFileSync(require.resolve('./vendor/webtreemap.css')))
270
+ };
197
271
 
198
- html = html.replace('INSERT TREE HERE', JSON.stringify(sizes, null, ' '))
199
- .replace('INSERT TITLE HERE', args['<script.js>'])
200
- .replace('INSERT underscore.js HERE', 'data:application/javascript;base64,' + assets.underscoreJs)
201
- .replace('INSERT webtreemap.js HERE', 'data:application/javascript;base64,' + assets.webtreemapJs)
202
- .replace('INSERT webtreemap.css HERE', 'data:text/css;base64,' + assets.webtreemapCss);
272
+ var html = fs.readFileSync(path.join(__dirname, 'tree-viz.html')).toString();
203
273
 
204
- if (args['--html']) {
205
- console.log(html);
206
- process.exit(0);
207
- }
274
+ html = html.replace('INSERT TREE HERE', JSON.stringify(counts, null, ' '))
275
+ .replace('INSERT TITLE HERE', args['<script.js>'])
276
+ .replace('INSERT underscore.js HERE', 'data:application/javascript;base64,' + assets.underscoreJs)
277
+ .replace('INSERT webtreemap.js HERE', 'data:application/javascript;base64,' + assets.webtreemapJs)
278
+ .replace('INSERT webtreemap.css HERE', 'data:text/css;base64,' + assets.webtreemapCss);
279
+
280
+ if (args['--html']) {
281
+ console.log(html);
282
+ process.exit(0);
283
+ }
208
284
 
209
- var tempName = temp.path({suffix: '.html'});
210
- fs.writeFileSync(tempName, html);
211
- open(tempName, function(error) {
212
- if (!error) return;
213
- console.error('Unable to open web browser.');
214
- console.error('Either run with --html, --json or --tsv, or view HTML for the visualization at:');
215
- console.error(tempName);
216
- });
285
+ var tempName = temp.path({suffix: '.html'});
286
+ fs.writeFileSync(tempName, html);
287
+ open(tempName, function(error) {
288
+ if (!error) return;
289
+ console.error('Unable to open web browser.');
290
+ console.error('Either run with --html, --json or --tsv, or view HTML for the visualization at:');
291
+ console.error(tempName);
292
+ });
217
293
 
218
294
  }
219
295
 
220
296
  // Exports are here mostly for testing.
221
297
  module.exports = {
298
+ loadSourceMap: loadSourceMap,
299
+ computeGeneratedFileSizes: computeGeneratedFileSizes,
222
300
  adjustSourcePaths: adjustSourcePaths,
223
301
  mapKeys: mapKeys,
224
- commonPathPrefix: commonPathPrefix
302
+ commonPathPrefix: commonPathPrefix,
303
+ expandGlob: expandGlob
225
304
  };
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "source-map-explorer",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "description": "Analyze and debug space usage through source maps",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "source-map-explorer": "index.js"
8
8
  },
9
9
  "scripts": {
10
- "test": "mocha"
10
+ "test": "mocha && eslint *.js"
11
11
  },
12
12
  "repository": {
13
13
  "type": "git",
@@ -28,7 +28,7 @@
28
28
  "btoa": "^1.1.2",
29
29
  "convert-source-map": "^1.1.1",
30
30
  "docopt": "^0.6.2",
31
- "file-url": "^1.0.1",
31
+ "glob": "^7.1.2",
32
32
  "open": "0.0.5",
33
33
  "source-map": "^0.5.1",
34
34
  "temp": "^0.8.3",
@@ -36,6 +36,7 @@
36
36
  },
37
37
  "devDependencies": {
38
38
  "chai": "^3.3.0",
39
+ "eslint": "^4.1.1",
39
40
  "mocha": "^2.3.3"
40
41
  }
41
42
  }
package/test.js CHANGED
@@ -3,7 +3,8 @@ var expect = require('chai').expect;
3
3
  var sourceMapExplorer = require('./index'),
4
4
  adjustSourcePaths = sourceMapExplorer.adjustSourcePaths,
5
5
  mapKeys = sourceMapExplorer.mapKeys,
6
- commonPathPrefix = sourceMapExplorer.commonPathPrefix;
6
+ commonPathPrefix = sourceMapExplorer.commonPathPrefix,
7
+ expandGlob = sourceMapExplorer.expandGlob;
7
8
 
8
9
  describe('source-map-explorer', function() {
9
10
  describe('commonPathPrefix', function() {
@@ -28,14 +29,39 @@ describe('source-map-explorer', function() {
28
29
  describe('adjustSourcePaths', function() {
29
30
  it('should factor out a common prefix', function() {
30
31
  expect(adjustSourcePaths({'/src/foo.js': 10, '/src/bar.js': 20}, true, [], []))
31
- .to.deep.equal({'foo.js': 10, 'bar.js': 20});
32
+ .to.deep.equal({'foo.js': 10, 'bar.js': 20});
32
33
  expect(adjustSourcePaths({'/src/foo.js': 10, '/src/foodle.js': 20}, true, [], []))
33
- .to.deep.equal({'foo.js': 10, 'foodle.js': 20});
34
+ .to.deep.equal({'foo.js': 10, 'foodle.js': 20});
34
35
  });
35
36
 
36
37
  it('should find/replace', function() {
37
38
  expect(adjustSourcePaths({'/src/foo.js': 10, '/src/foodle.js': 20}, false, ['src'], ['dist']))
38
- .to.deep.equal({'/dist/foo.js': 10, '/dist/foodle.js': 20});
39
+ .to.deep.equal({'/dist/foo.js': 10, '/dist/foodle.js': 20});
40
+ });
41
+
42
+ it('should find/replace with regexp', function() {
43
+ expect(adjustSourcePaths({'/src/foo.js': 10, '/src/foodle.js': 20}, false, ['foo.'], ['bar.']))
44
+ .to.deep.equal({'/src/bar.js': 10, '/src/bar.le.js': 20});
45
+ });
46
+
47
+ it('should find/replace with regexp, can be used to add root', function() {
48
+ expect(adjustSourcePaths({'/foo/foo.js': 10, '/foo/foodle.js': 20}, false, ['^/foo'], ['/bar']))
49
+ .to.deep.equal({'/bar/foo.js': 10, '/bar/foodle.js': 20});
50
+ });
51
+ });
52
+
53
+ describe('command line parsing', function() {
54
+ expect(expandGlob({'<script.js>': 'testdata/foo.min.js*'})).to.deep.equal({
55
+ '<script.js>': 'testdata/foo.min.js',
56
+ '<script.js.map>': 'testdata/foo.min.js.map'
57
+ });
58
+
59
+ expect(expandGlob({
60
+ '<script.js>': 'foo.min.js',
61
+ '<script.js.map>': 'foo.min.js.map'
62
+ })).to.deep.equal({
63
+ '<script.js>': 'foo.min.js',
64
+ '<script.js.map>': 'foo.min.js.map'
39
65
  });
40
66
  });
41
67
  });
package/tree-viz.html CHANGED
@@ -28,6 +28,18 @@ body {
28
28
  <div id='map'></div>
29
29
  </body>
30
30
 
31
+ <script>
32
+ // https://stackoverflow.com/a/18650828/388951
33
+ function formatBytes(bytes, decimals) {
34
+ if (bytes == 0) return '0 B';
35
+ var k = 1000,
36
+ dm = decimals || 2,
37
+ sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
38
+ i = Math.floor(Math.log(bytes) / Math.log(k));
39
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
40
+ }
41
+ </script>
42
+
31
43
 
32
44
  <script>
33
45
  var tree = INSERT TREE HERE;
@@ -70,7 +82,7 @@ function addNode(path, size) {
70
82
  function addSizeToTitle(node, total) {
71
83
  var size = node.data['$area'],
72
84
  pct = 100.0 * size / total;
73
- node.name += ' • ' + size.toLocaleString() + ' • ' + pct.toFixed(2) + '%';
85
+ node.name += ' • ' + formatBytes(size) + ' • ' + pct.toFixed(1) + '%';
74
86
  node.children.forEach(function(x) { addSizeToTitle(x, total) });
75
87
  }
76
88