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 +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({
|