source-map-explorer 1.4.0 → 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/index.js CHANGED
@@ -1,79 +1,105 @@
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. Accepts regular expressions.',
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
- glob = require('glob'),
36
- path = require('path'),
37
- sourcemap = require('source-map'),
38
- convert = require('convert-source-map'),
39
- temp = require('temp'),
40
- open = require('open'),
41
- _ = require('underscore'),
42
- docopt = require('docopt').docopt,
43
- fileURL = require('file-url'),
44
- 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');
45
48
 
46
- function computeGeneratedFileSizes(mapConsumer, generatedJs) {
49
+ function computeSpans(mapConsumer, generatedJs) {
47
50
  var lines = generatedJs.split('\n');
48
- var sourceExtrema = {}; // source -> {min: num, max: num}
51
+ var spans = [];
49
52
  var numChars = 0;
50
- var lastSource = null;
53
+ var lastSource = false; // not a string, not null.
51
54
  for (var line = 1; line <= lines.length; line++) {
52
55
  var lineText = lines[line - 1];
53
56
  var numCols = lineText.length;
54
57
  for (var column = 0; column < numCols; column++, numChars++) {
55
58
  var pos = mapConsumer.originalPositionFor({line:line, column:column});
56
59
  var source = pos.source;
57
- if (source == null) {
58
- // Often this is from the '// #sourceMap' comment itself.
59
- continue;
60
- }
61
60
 
62
- if (source != lastSource) {
63
- if (!(source in sourceExtrema)) {
64
- sourceExtrema[source] = {min: numChars};
65
- lastSource = source;
66
- } else {
67
- // source-map reports odd positions for bits between files.
68
- }
61
+ if (source !== lastSource) {
62
+ lastSource = source;
63
+ spans.push({source: source, numChars: 1});
69
64
  } else {
70
- sourceExtrema[source].max = numChars;
65
+ spans[spans.length - 1].numChars += 1;
71
66
  }
72
67
  }
73
68
  }
74
- return _.mapObject(sourceExtrema, function(v) {
75
- return v.max - v.min + 1
76
- });
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
+ };
77
103
  }
78
104
 
79
105
  var SOURCE_MAP_INFO_URL = 'https://github.com/danvk/source-map-explorer/blob/master/README.md#generating-source-maps';
@@ -83,8 +109,8 @@ function loadSourceMap(jsFile, mapFile) {
83
109
  try {
84
110
  jsData = fs.readFileSync(jsFile).toString();
85
111
  } catch(err) {
86
- if (err.code === "ENOENT" ) {
87
- console.error("File not found! -- ", err.message);
112
+ if (err.code === 'ENOENT' ) {
113
+ console.error('File not found! -- ', err.message);
88
114
  return null;
89
115
  } else {
90
116
  throw err;
@@ -125,7 +151,7 @@ function loadSourceMap(jsFile, mapFile) {
125
151
  function commonPathPrefix(array){
126
152
  if (array.length == 0) return '';
127
153
  var A= array.concat().sort(),
128
- 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;
129
155
  while(i<L && a1[i] === a2[i]) i++;
130
156
  return a1.slice(0, i).join('');
131
157
  }
@@ -140,13 +166,13 @@ function adjustSourcePaths(sizes, findRoot, finds, replaces) {
140
166
  var prefix = commonPathPrefix(_.keys(sizes));
141
167
  var len = prefix.length;
142
168
  if (len) {
143
- sizes = mapKeys(sizes, function(source) { return source.slice(len); })
169
+ sizes = mapKeys(sizes, function(source) { return source.slice(len); });
144
170
  }
145
171
  }
146
172
 
147
173
  for (var i = 0; i < finds.length; i++) {
148
174
  var before = new RegExp(finds[i]),
149
- after = replaces[i];
175
+ after = replaces[i];
150
176
  sizes = mapKeys(sizes, function(source) {
151
177
  return source.replace(before, after);
152
178
  });
@@ -171,7 +197,7 @@ function expandGlob(args) {
171
197
  var files = glob.sync(arg1);
172
198
  if (files.length > 2) {
173
199
  throw new Error(
174
- 'Glob should match exactly 2 files but matched ' + files.length + ' ' + arg1);
200
+ 'Glob should match exactly 2 files but matched ' + files.length + ' ' + arg1);
175
201
  } else if (files.length === 2) {
176
202
  // allow the JS and source map file to match in either order.
177
203
  if (files[0].indexOf('.map') >= 0) {
@@ -189,67 +215,81 @@ function expandGlob(args) {
189
215
 
190
216
  if (require.main === module) {
191
217
 
192
- var args = docopt(doc, {version: '1.4.0'});
193
- expandGlob(args);
194
- validateArgs(args);
195
- var data = loadSourceMap(args['<script.js>'], args['<script.js.map>']);
196
- if (!data) {
197
- process.exit(1);
198
- }
199
- 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,
200
226
  jsData = data.jsData;
201
227
 
202
- var sizes = computeGeneratedFileSizes(mapConsumer, jsData);
228
+ var sizes = computeGeneratedFileSizes(mapConsumer, jsData);
229
+ var counts = sizes.counts;
203
230
 
204
- if (_.size(sizes) == 1) {
205
- console.error('Your source map only contains one source (',
206
- _.keys(sizes)[0], ')');
207
- console.error("This typically means that your source map doesn't map all the way back to the original sources.");
208
- console.error("This can happen if you use browserify+uglifyjs, for example, and don't set the --in-source-map flag to uglify.");
209
- console.error('See ', SOURCE_MAP_INFO_URL);
210
- process.exit(1);
211
- }
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
+ }
212
239
 
213
- sizes = adjustSourcePaths(sizes, !args['--noroot'], args['--replace'], args['--with']);
240
+ counts = adjustSourcePaths(counts, !args['--noroot'], args['--replace'], args['--with']);
214
241
 
215
- if (args['--json']) {
216
- console.log(JSON.stringify(sizes, null, ' '));
217
- process.exit(0);
218
- }
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
+ }
219
254
 
220
- if (args['--tsv']) {
221
- console.log('Source\tSize');
222
- _.each(sizes, function(source, size) { console.log(size + '\t' + source); })
223
- process.exit(0);
224
- }
255
+ if (args['--json']) {
256
+ console.log(JSON.stringify(counts, null, ' '));
257
+ process.exit(0);
258
+ }
225
259
 
226
- var assets = {
227
- underscoreJs: btoa(fs.readFileSync(require.resolve('underscore'))),
228
- webtreemapJs: btoa(fs.readFileSync(require.resolve('./vendor/webtreemap.js'))),
229
- webtreemapCss: btoa(fs.readFileSync(require.resolve('./vendor/webtreemap.css'))),
230
- };
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
+ }
265
+
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
+ };
231
271
 
232
- var html = fs.readFileSync(path.join(__dirname, 'tree-viz.html')).toString();
272
+ var html = fs.readFileSync(path.join(__dirname, 'tree-viz.html')).toString();
233
273
 
234
- html = html.replace('INSERT TREE HERE', JSON.stringify(sizes, null, ' '))
235
- .replace('INSERT TITLE HERE', args['<script.js>'])
236
- .replace('INSERT underscore.js HERE', 'data:application/javascript;base64,' + assets.underscoreJs)
237
- .replace('INSERT webtreemap.js HERE', 'data:application/javascript;base64,' + assets.webtreemapJs)
238
- .replace('INSERT webtreemap.css HERE', 'data:text/css;base64,' + assets.webtreemapCss);
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);
239
279
 
240
- if (args['--html']) {
241
- console.log(html);
242
- process.exit(0);
243
- }
280
+ if (args['--html']) {
281
+ console.log(html);
282
+ process.exit(0);
283
+ }
244
284
 
245
- var tempName = temp.path({suffix: '.html'});
246
- fs.writeFileSync(tempName, html);
247
- open(tempName, function(error) {
248
- if (!error) return;
249
- console.error('Unable to open web browser.');
250
- console.error('Either run with --html, --json or --tsv, or view HTML for the visualization at:');
251
- console.error(tempName);
252
- });
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
+ });
253
293
 
254
294
  }
255
295
 
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "source-map-explorer",
3
- "version": "1.4.0",
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,6 @@
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",
32
31
  "glob": "^7.1.2",
33
32
  "open": "0.0.5",
34
33
  "source-map": "^0.5.1",
@@ -37,6 +36,7 @@
37
36
  },
38
37
  "devDependencies": {
39
38
  "chai": "^3.3.0",
39
+ "eslint": "^4.1.1",
40
40
  "mocha": "^2.3.3"
41
41
  }
42
42
  }
package/test.js CHANGED
@@ -29,31 +29,31 @@ describe('source-map-explorer', function() {
29
29
  describe('adjustSourcePaths', function() {
30
30
  it('should factor out a common prefix', function() {
31
31
  expect(adjustSourcePaths({'/src/foo.js': 10, '/src/bar.js': 20}, true, [], []))
32
- .to.deep.equal({'foo.js': 10, 'bar.js': 20});
32
+ .to.deep.equal({'foo.js': 10, 'bar.js': 20});
33
33
  expect(adjustSourcePaths({'/src/foo.js': 10, '/src/foodle.js': 20}, true, [], []))
34
- .to.deep.equal({'foo.js': 10, 'foodle.js': 20});
34
+ .to.deep.equal({'foo.js': 10, 'foodle.js': 20});
35
35
  });
36
36
 
37
37
  it('should find/replace', function() {
38
38
  expect(adjustSourcePaths({'/src/foo.js': 10, '/src/foodle.js': 20}, false, ['src'], ['dist']))
39
- .to.deep.equal({'/dist/foo.js': 10, '/dist/foodle.js': 20});
39
+ .to.deep.equal({'/dist/foo.js': 10, '/dist/foodle.js': 20});
40
40
  });
41
41
 
42
42
  it('should find/replace with regexp', function() {
43
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});
44
+ .to.deep.equal({'/src/bar.js': 10, '/src/bar.le.js': 20});
45
45
  });
46
46
 
47
47
  it('should find/replace with regexp, can be used to add root', function() {
48
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});
49
+ .to.deep.equal({'/bar/foo.js': 10, '/bar/foodle.js': 20});
50
50
  });
51
51
  });
52
52
 
53
53
  describe('command line parsing', function() {
54
54
  expect(expandGlob({'<script.js>': 'testdata/foo.min.js*'})).to.deep.equal({
55
55
  '<script.js>': 'testdata/foo.min.js',
56
- '<script.js.map>': 'testdata/foo.min.js.map',
56
+ '<script.js.map>': 'testdata/foo.min.js.map'
57
57
  });
58
58
 
59
59
  expect(expandGlob({