source-map-explorer 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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({