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 +27 -0
- package/.travis.yml +1 -1
- package/index.js +152 -112
- package/package.json +3 -3
- package/test.js +6 -6
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
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
|
-
'
|
24
|
-
'
|
25
|
-
'
|
26
|
-
'',
|
27
|
-
'
|
28
|
-
'
|
29
|
-
'
|
30
|
-
|
31
|
-
'
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
49
|
+
function computeSpans(mapConsumer, generatedJs) {
|
47
50
|
var lines = generatedJs.split('\n');
|
48
|
-
var
|
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
|
63
|
-
|
64
|
-
|
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
|
-
|
65
|
+
spans[spans.length - 1].numChars += 1;
|
71
66
|
}
|
72
67
|
}
|
73
68
|
}
|
74
|
-
return
|
75
|
-
|
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 ===
|
87
|
-
console.error(
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
193
|
-
expandGlob(args);
|
194
|
-
validateArgs(args);
|
195
|
-
var data = loadSourceMap(args['<script.js>'], args['<script.js.map>']);
|
196
|
-
if (!data) {
|
197
|
-
|
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(
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
240
|
+
counts = adjustSourcePaths(counts, !args['--noroot'], args['--replace'], args['--with']);
|
214
241
|
|
215
|
-
|
216
|
-
|
217
|
-
|
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['--
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
}
|
255
|
+
if (args['--json']) {
|
256
|
+
console.log(JSON.stringify(counts, null, ' '));
|
257
|
+
process.exit(0);
|
258
|
+
}
|
225
259
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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(
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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
|
-
|
242
|
-
|
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
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
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.
|
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
|
-
|
32
|
+
.to.deep.equal({'foo.js': 10, 'bar.js': 20});
|
33
33
|
expect(adjustSourcePaths({'/src/foo.js': 10, '/src/foodle.js': 20}, true, [], []))
|
34
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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({
|