diff --git a/.gitignore b/.gitignore
index 28f1ba7..f9c8c62 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
node_modules
-.DS_Store
\ No newline at end of file
+.DS_Store
+lasso-analyze.html
+.vscode/
\ No newline at end of file
diff --git a/bin/index.js b/bin/index.js
index 1461636..6420374 100644
--- a/bin/index.js
+++ b/bin/index.js
@@ -10,11 +10,13 @@ const borderX = `${Array(30).join('-')}\n`;
const input = argv._ || [];
// extract output file.
let outputFile = parseArgs(process.argv).output;
+let colors = parseArgs(process.argv).c;
+colors = colors ? true : false;
if (input.length > 0) {
input.map((fileName) => {
createBundle({ path: fileName, outputPath: 'lasso-analyze.js' }).createBundle;
- lassoAnalyzer('lasso-analyze.js', outputFile);
+ lassoAnalyzer('lasso-analyze.js', { outputFile, colors });
});
outputFile = outputFile || 'lasso-analyze';
const startLog = `${borderX}` + `${outputFile}.html is created \n` +
diff --git a/example/lasso-analyze.html b/example/lasso-analyze.html
index ff6d1eb..5838a3b 100644
--- a/example/lasso-analyze.html
+++ b/example/lasso-analyze.html
@@ -2,381 +2,397 @@
-
- Lasso Bundle Analyzer
+
+ Lasso Bundle Analyzer
-
-
+
+
-
-
-
+ window.appendTreemap = appendTreemap;
+ })(window);
+
+
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index b20ddeb..9950116 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "lasso-analyzer",
- "version": "1.1.10",
+ "version": "1.4.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -25,11 +25,6 @@
"concat-map": "0.0.1"
}
},
- "bundle-me": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/bundle-me/-/bundle-me-1.1.0.tgz",
- "integrity": "sha512-qtOOuqqqlaI93pKomLmuZx1F1Mn8UMCbyp1CGWAFa8CcX3CRsRdDRiCY6QneGdyEPu0pVWUd6ofyFgxxgsjFFw=="
- },
"cli": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz",
@@ -185,6 +180,8 @@
},
"jshint": {
"version": "2.10.2",
+ "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.10.2.tgz",
+ "integrity": "sha512-e7KZgCSXMJxznE/4WULzybCMNXNAd/bf5TSrvVEq78Q/K8ZwFpmBqQeDtNiHc3l49nV4E/+YeHU/JZjSUIrLAA==",
"dev": true,
"requires": {
"cli": "~1.0.0",
@@ -199,6 +196,8 @@
},
"lasso-unpack": {
"version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/lasso-unpack/-/lasso-unpack-1.0.2.tgz",
+ "integrity": "sha512-pL6lvOKXJhF8PhrLF4JAUzFvp20rFNizh8ZuyusmM7jwczUlClVOhpcjei8IxbokitfTiFqpLBahnJZXkaxyTw==",
"requires": {
"acorn": "^5.5.3",
"minimist": "1.2.0"
@@ -233,12 +232,22 @@
"wrappy": "1"
}
},
+ "opener": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz",
+ "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA=="
+ },
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
+ "randomcolor": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/randomcolor/-/randomcolor-0.5.4.tgz",
+ "integrity": "sha512-nYd4nmTuuwMFzHL6W+UWR5fNERGZeVauho8mrJDUSXdNDbao4rbrUwhuLgKC/j8VCS5+34Ria8CsTDuBjrIrQA=="
+ },
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
diff --git a/package.json b/package.json
index 555a953..f4bb5db 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,8 @@
"dependencies": {
"bundle-me": "^1.1.4",
"lasso-unpack": "1.0.2",
- "opener": "^1.5.1"
+ "opener": "^1.5.1",
+ "randomcolor": "^0.5.4"
},
"repository": {
"type": "git",
@@ -35,4 +36,4 @@
"devDependencies": {
"jshint": "^2.9.6"
}
-}
\ No newline at end of file
+}
diff --git a/src/index.js b/src/index.js
index ae694ed..c64aefd 100644
--- a/src/index.js
+++ b/src/index.js
@@ -4,22 +4,23 @@ const lassoUnpack = require('lasso-unpack');
const opener = require('opener');
const Tree = require('./tree');
-function bundleAnalyzer(fileName, bundleName) {
+function bundleAnalyzer(fileName, options) {
// load lasso-unpack and create lasso-stats.json
// unpack the bundle using lasso-unpack.
lassoUnpack(fileName);
const readFile = fs.readFileSync(path.resolve("lasso-stats.json"), 'utf8');
const readJSON = JSON.parse(readFile);
+ const bundleName = (options && options.bundleName) || 'lasso-analyze'
if (readJSON.length > 0) {
readJSON.shift()
};
const tree = new Tree('/');
+ tree.setEnableColors(options && options.colors || false);
readJSON.forEach((source) => {
- tree.createNode(source, tree);
+ tree.createNode(source, tree, tree.isColorEnabled());
});
tree.createTile(tree, tree.data['$area']);
const html = generateHTML(tree);
- if (!bundleName) bundleName = "lasso-analyze";
fs.writeFileSync(getOutputHTML(bundleName), html);
// open the browser with generated html
opener(getOutputHTML(bundleName));
diff --git a/src/tree.js b/src/tree.js
index 04f9665..5d98189 100644
--- a/src/tree.js
+++ b/src/tree.js
@@ -1,5 +1,6 @@
// Inspired from https://github.com/evmar/webtreemap/blob/master/tree.ts
-
+'use strict';
+const randomColor = require('randomcolor');
class Tree {
constructor(fileName) {
this.name = fileName || '';
@@ -8,6 +9,8 @@ class Tree {
'$area': 0
};
this.children = [];
+ this.className = '';
+ this.enableColors = false;
}
setFileName(name) {
@@ -16,6 +19,12 @@ class Tree {
getFileName() {
return this.name;
}
+ setEnableColors(enableColors) {
+ this.enableColors = enableColors;
+ }
+ isColorEnabled() {
+ return this.enableColors;
+ }
setSize(size) {
this.data.$area = size;
}
@@ -28,23 +37,28 @@ class Tree {
getSize() {
return this.data.$area;
}
+ setClassName(className) {
+ this.className = className;
+ }
// add new nodes
- createNode(source, tree) {
+ createNode(source, tree, enableColors) {
const parts = source.path.split('/');
- var node = tree;
+ let node = tree;
node.data['$area'] += source.size;
- parts.forEach(function (part) {
+ let color = randomColor({ hue: 'green' });
+ parts.forEach((part) => {
let child = node.children.find(function (child) {
return child.name == part;
});
if (!child) {
child = new Tree(part);
+ applyColor(child, color, enableColors);
node.children.push(child);
}
node = child;
node.data['$area'] += source.size;
});
- };
+ }
createTile(node, totalSize) {
const size = node.data['$area'];
@@ -53,7 +67,7 @@ class Tree {
node.children.forEach((eachNode) => {
this.createTile(eachNode, totalSize)
});
- };
+ }
};
// convert bytes in to KB, MB, etc.
// https://stackoverflow.com/a/18650828/388951
@@ -66,4 +80,8 @@ function bytesToSize(bytes, decimals) {
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
+function applyColor(child, color, enableColors) {
+ if (enableColors) child.setClassName(color);
+}
+
module.exports = Tree;
\ No newline at end of file
diff --git a/static/webtreemap.js b/static/webtreemap.js
index 6e731ee..6257b5f 100644
--- a/static/webtreemap.js
+++ b/static/webtreemap.js
@@ -18,232 +18,233 @@
// We could support arbitrary borders using getComputedStyle(), but I am
// skeptical the extra complexity (and performance hit) is worth it.
-;(function() {
-var kBorderWidth = 1;
-
-// Padding around contents.
-// TODO: do this with a nested div to allow it to be CSS-styleable.
-var kPadding = 4;
-
-// x/y ratio to aim for -- wider rectangles are better for text display
-var kAspectRatio = 1.2;
-
-var focused = null;
-
-function focus(tree) {
- focused = tree;
-
- // Hide all visible siblings of all our ancestors by lowering them.
- var level = 0;
- var root = tree;
- while (root.parent) {
- root = root.parent;
- level += 1;
- for (var i = 0, sibling; sibling = root.children[i]; ++i) {
- if (sibling.dom)
- sibling.dom.style.zIndex = 0;
+; (function () {
+ var kBorderWidth = 1;
+
+ // Padding around contents.
+ // TODO: do this with a nested div to allow it to be CSS-styleable.
+ var kPadding = 4;
+
+ // x/y ratio to aim for -- wider rectangles are better for text display
+ var kAspectRatio = 1.2;
+
+ var focused = null;
+
+ function focus(tree) {
+ focused = tree;
+
+ // Hide all visible siblings of all our ancestors by lowering them.
+ var level = 0;
+ var root = tree;
+ while (root.parent) {
+ root = root.parent;
+ level += 1;
+ for (var i = 0, sibling; sibling = root.children[i]; ++i) {
+ if (sibling.dom)
+ sibling.dom.style.zIndex = 0;
+ }
}
- }
- var width = root.dom.offsetWidth;
- var height = root.dom.offsetHeight;
- // Unhide (raise) and maximize us and our ancestors.
- for (var t = tree; t.parent; t = t.parent) {
- // Shift off by border so we don't get nested borders.
- // TODO: actually make nested borders work (need to adjust width/height).
- position(t.dom, -kBorderWidth, -kBorderWidth, width, height);
- t.dom.style.zIndex = 1;
- }
- // And layout into the topmost box.
- layout(tree, level, width, height);
-}
-
-function makeDom(tree, level) {
- var dom = document.createElement('div');
- dom.style.zIndex = 1;
- dom.className = 'webtreemap-node webtreemap-level' + Math.min(level, 4);
- if (tree.data['$symbol']) {
- dom.className += (' webtreemap-symbol-' +
- tree.data['$symbol'].replace(' ', '_'));
- }
- if (tree.data['$dominant_symbol']) {
- dom.className += (' webtreemap-symbol-' +
- tree.data['$dominant_symbol'].replace(' ', '_'));
- dom.className += (' webtreemap-aggregate');
+ var width = root.dom.offsetWidth;
+ var height = root.dom.offsetHeight;
+ // Unhide (raise) and maximize us and our ancestors.
+ for (var t = tree; t.parent; t = t.parent) {
+ // Shift off by border so we don't get nested borders.
+ // TODO: actually make nested borders work (need to adjust width/height).
+ position(t.dom, -kBorderWidth, -kBorderWidth, width, height);
+ t.dom.style.zIndex = 1;
+ }
+ // And layout into the topmost box.
+ layout(tree, level, width, height);
}
- dom.onmousedown = function(e) {
- if (e.button == 0) {
- if (focused && tree == focused && focused.parent) {
- focus(focused.parent);
- } else {
- focus(tree);
- }
+ function makeDom(tree, level) {
+ var dom = document.createElement('div');
+ dom.style.zIndex = 1;
+ dom.style.backgroundColor = tree.className ? tree.className : "";
+ dom.className = 'webtreemap-node webtreemap-level' + Math.min(level, 4);
+ if (tree.data['$symbol']) {
+ dom.className += (' webtreemap-symbol-' +
+ tree.data['$symbol'].replace(' ', '_'));
}
- e.stopPropagation();
- return true;
- };
-
- var caption = document.createElement('div');
- caption.className = 'webtreemap-caption';
- caption.innerHTML = tree.name;
- dom.appendChild(caption);
- dom.title = tree.name;
-
- tree.dom = dom;
- return dom;
-}
-
-function position(dom, x, y, width, height) {
- // CSS width/height does not include border.
- width -= kBorderWidth*2;
- height -= kBorderWidth*2;
-
- dom.style.left = x + 'px';
- dom.style.top = y + 'px';
- dom.style.width = Math.max(width, 0) + 'px';
- dom.style.height = Math.max(height, 0) + 'px';
-}
-
-// Given a list of rectangles |nodes|, the 1-d space available
-// |space|, and a starting rectangle index |start|, compute an span of
-// rectangles that optimizes a pleasant aspect ratio.
-//
-// Returns [end, sum], where end is one past the last rectangle and sum is the
-// 2-d sum of the rectangles' areas.
-function selectSpan(nodes, space, start) {
- // Add rectangle one by one, stopping when aspect ratios begin to go
- // bad. Result is [start,end) covering the best run for this span.
- // http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.36.6685
- var node = nodes[start];
- var rmin = node.data['$area']; // Smallest seen child so far.
- var rmax = rmin; // Largest child.
- var rsum = 0; // Sum of children in this span.
- var last_score = 0; // Best score yet found.
- for (var end = start; node = nodes[end]; ++end) {
- var size = node.data['$area'];
- if (size < rmin)
- rmin = size;
- if (size > rmax)
- rmax = size;
- rsum += size;
-
- // This formula is from the paper, but you can easily prove to
- // yourself it's taking the larger of the x/y aspect ratio or the
- // y/x aspect ratio. The additional magic fudge constant of kAspectRatio
- // lets us prefer wider rectangles to taller ones.
- var score = Math.max(space*space*rmax / (rsum*rsum),
- kAspectRatio*rsum*rsum / (space*space*rmin));
- if (last_score && score > last_score) {
- rsum -= size; // Undo size addition from just above.
- break;
+ if (tree.data['$dominant_symbol']) {
+ dom.className += (' webtreemap-symbol-' +
+ tree.data['$dominant_symbol'].replace(' ', '_'));
+ dom.className += (' webtreemap-aggregate');
}
- last_score = score;
+
+ dom.onmousedown = function (e) {
+ if (e.button == 0) {
+ if (focused && tree == focused && focused.parent) {
+ focus(focused.parent);
+ } else {
+ focus(tree);
+ }
+ }
+ e.stopPropagation();
+ return true;
+ };
+
+ var caption = document.createElement('div');
+ caption.className = 'webtreemap-caption';
+ caption.innerHTML = tree.name;
+ dom.appendChild(caption);
+ dom.title = tree.name;
+
+ tree.dom = dom;
+ return dom;
}
- return [end, rsum];
-}
-
-function layout(tree, level, width, height) {
- if (!('children' in tree))
- return;
-
- var total = tree.data['$area'];
-
- // XXX why do I need an extra -1/-2 here for width/height to look right?
- var x1 = 0, y1 = 0, x2 = width - 1, y2 = height - 2;
- x1 += kPadding; y1 += kPadding;
- x2 -= kPadding; y2 -= kPadding;
- y1 += 14; // XXX get first child height for caption spacing
-
- var pixels_to_units = Math.sqrt(total / ((x2 - x1) * (y2 - y1)));
-
- // The algorithm does best at laying out items from largest to smallest.
- // Sort them to ensure this.
- if (!tree.children.sorted) {
- tree.children.sort(function (a, b) {
- return b.data['$area'] - a.data['$area'];
- });
- tree.children.sorted = true;
+
+ function position(dom, x, y, width, height) {
+ // CSS width/height does not include border.
+ width -= kBorderWidth * 2;
+ height -= kBorderWidth * 2;
+
+ dom.style.left = x + 'px';
+ dom.style.top = y + 'px';
+ dom.style.width = Math.max(width, 0) + 'px';
+ dom.style.height = Math.max(height, 0) + 'px';
}
- for (var start = 0, child; child = tree.children[start]; ++start) {
- if (x2 - x1 < 60 || y2 - y1 < 40) {
- if (child.dom) {
- child.dom.style.zIndex = 0;
- position(child.dom, -2, -2, 0, 0);
+ // Given a list of rectangles |nodes|, the 1-d space available
+ // |space|, and a starting rectangle index |start|, compute an span of
+ // rectangles that optimizes a pleasant aspect ratio.
+ //
+ // Returns [end, sum], where end is one past the last rectangle and sum is the
+ // 2-d sum of the rectangles' areas.
+ function selectSpan(nodes, space, start) {
+ // Add rectangle one by one, stopping when aspect ratios begin to go
+ // bad. Result is [start,end) covering the best run for this span.
+ // http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.36.6685
+ var node = nodes[start];
+ var rmin = node.data['$area']; // Smallest seen child so far.
+ var rmax = rmin; // Largest child.
+ var rsum = 0; // Sum of children in this span.
+ var last_score = 0; // Best score yet found.
+ for (var end = start; node = nodes[end]; ++end) {
+ var size = node.data['$area'];
+ if (size < rmin)
+ rmin = size;
+ if (size > rmax)
+ rmax = size;
+ rsum += size;
+
+ // This formula is from the paper, but you can easily prove to
+ // yourself it's taking the larger of the x/y aspect ratio or the
+ // y/x aspect ratio. The additional magic fudge constant of kAspectRatio
+ // lets us prefer wider rectangles to taller ones.
+ var score = Math.max(space * space * rmax / (rsum * rsum),
+ kAspectRatio * rsum * rsum / (space * space * rmin));
+ if (last_score && score > last_score) {
+ rsum -= size; // Undo size addition from just above.
+ break;
}
- continue;
+ last_score = score;
}
+ return [end, rsum];
+ }
- // Dynamically decide whether to split in x or y based on aspect ratio.
- var ysplit = ((y2 - y1) / (x2 - x1)) > kAspectRatio;
-
- var space; // Space available along layout axis.
- if (ysplit)
- space = (y2 - y1) * pixels_to_units;
- else
- space = (x2 - x1) * pixels_to_units;
-
- var span = selectSpan(tree.children, space, start);
- var end = span[0], rsum = span[1];
-
- // Now that we've selected a span, lay out rectangles [start,end) in our
- // available space.
- var x = x1, y = y1;
- for (var i = start; i < end; ++i) {
- child = tree.children[i];
- if (!child.dom) {
- child.parent = tree;
- child.dom = makeDom(child, level + 1);
- tree.dom.appendChild(child.dom);
- } else {
- child.dom.style.zIndex = 1;
- }
- var size = child.data['$area'];
- var frac = size / rsum;
- if (ysplit) {
- width = rsum / space;
- height = size / width;
- } else {
- height = rsum / space;
- width = size / height;
+ function layout(tree, level, width, height) {
+ if (!('children' in tree))
+ return;
+
+ var total = tree.data['$area'];
+
+ // XXX why do I need an extra -1/-2 here for width/height to look right?
+ var x1 = 0, y1 = 0, x2 = width - 1, y2 = height - 2;
+ x1 += kPadding; y1 += kPadding;
+ x2 -= kPadding; y2 -= kPadding;
+ y1 += 14; // XXX get first child height for caption spacing
+
+ var pixels_to_units = Math.sqrt(total / ((x2 - x1) * (y2 - y1)));
+
+ // The algorithm does best at laying out items from largest to smallest.
+ // Sort them to ensure this.
+ if (!tree.children.sorted) {
+ tree.children.sort(function (a, b) {
+ return b.data['$area'] - a.data['$area'];
+ });
+ tree.children.sorted = true;
+ }
+
+ for (var start = 0, child; child = tree.children[start]; ++start) {
+ if (x2 - x1 < 60 || y2 - y1 < 40) {
+ if (child.dom) {
+ child.dom.style.zIndex = 0;
+ position(child.dom, -2, -2, 0, 0);
+ }
+ continue;
}
- width /= pixels_to_units;
- height /= pixels_to_units;
- width = Math.round(width);
- height = Math.round(height);
- position(child.dom, x, y, width, height);
- if ('children' in child) {
- layout(child, level + 1, width, height);
+
+ // Dynamically decide whether to split in x or y based on aspect ratio.
+ var ysplit = ((y2 - y1) / (x2 - x1)) > kAspectRatio;
+
+ var space; // Space available along layout axis.
+ if (ysplit)
+ space = (y2 - y1) * pixels_to_units;
+ else
+ space = (x2 - x1) * pixels_to_units;
+
+ var span = selectSpan(tree.children, space, start);
+ var end = span[0], rsum = span[1];
+
+ // Now that we've selected a span, lay out rectangles [start,end) in our
+ // available space.
+ var x = x1, y = y1;
+ for (var i = start; i < end; ++i) {
+ child = tree.children[i];
+ if (!child.dom) {
+ child.parent = tree;
+ child.dom = makeDom(child, level + 1);
+ tree.dom.appendChild(child.dom);
+ } else {
+ child.dom.style.zIndex = 1;
+ }
+ var size = child.data['$area'];
+ var frac = size / rsum;
+ if (ysplit) {
+ width = rsum / space;
+ height = size / width;
+ } else {
+ height = rsum / space;
+ width = size / height;
+ }
+ width /= pixels_to_units;
+ height /= pixels_to_units;
+ width = Math.round(width);
+ height = Math.round(height);
+ position(child.dom, x, y, width, height);
+ if ('children' in child) {
+ layout(child, level + 1, width, height);
+ }
+ if (ysplit)
+ y += height;
+ else
+ x += width;
}
+
+ // Shrink our available space based on the amount we used.
if (ysplit)
- y += height;
+ x1 += Math.round((rsum / space) / pixels_to_units);
else
- x += width;
- }
+ y1 += Math.round((rsum / space) / pixels_to_units);
- // Shrink our available space based on the amount we used.
- if (ysplit)
- x1 += Math.round((rsum / space) / pixels_to_units);
- else
- y1 += Math.round((rsum / space) / pixels_to_units);
+ // end points one past where we ended, which is where we want to
+ // begin the next iteration, but subtract one to balance the ++ in
+ // the loop.
+ start = end - 1;
+ }
+ }
- // end points one past where we ended, which is where we want to
- // begin the next iteration, but subtract one to balance the ++ in
- // the loop.
- start = end - 1;
+ function appendTreemap(dom, data) {
+ var style = getComputedStyle(dom, null);
+ var width = parseInt(style.width);
+ var height = parseInt(style.height);
+ if (!data.dom)
+ makeDom(data, 0);
+ dom.appendChild(data.dom);
+ position(data.dom, 0, 0, width, height);
+ layout(data, 0, width, height);
}
-}
-
-function appendTreemap(dom, data) {
- var style = getComputedStyle(dom, null);
- var width = parseInt(style.width);
- var height = parseInt(style.height);
- if (!data.dom)
- makeDom(data, 0);
- dom.appendChild(data.dom);
- position(data.dom, 0, 0, width, height);
- layout(data, 0, width, height);
-}
-
-window.appendTreemap = appendTreemap;
+
+ window.appendTreemap = appendTreemap;
})(window);
diff --git a/yarn.lock b/yarn.lock
index 4403085..eabbf45 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -205,6 +205,11 @@ path-is-absolute@^1.0.0:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+randomcolor@^0.5.4:
+ version "0.5.4"
+ resolved "https://registry.yarnpkg.com/randomcolor/-/randomcolor-0.5.4.tgz#df615b13f25b89ea58c5f8f72647f0a6f07adcc3"
+ integrity sha512-nYd4nmTuuwMFzHL6W+UWR5fNERGZeVauho8mrJDUSXdNDbao4rbrUwhuLgKC/j8VCS5+34Ria8CsTDuBjrIrQA==
+
readable-stream@1.1:
version "1.1.13"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e"