├── .gitignore ├── index.js ├── bin └── bundle-analyzer.js ├── package.json ├── LICENSE ├── server.js ├── CONTRIBUTING.md ├── sizes.js ├── README.md ├── client.html └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./server.js'); 2 | -------------------------------------------------------------------------------- /bin/bundle-analyzer.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const fs = require('fs'); 3 | const server = require('../server.js'); 4 | 5 | const dir = process.argv[2]; 6 | const analyzer = server.start({dir}); 7 | 8 | fs.watch(dir, analyzer.update); 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bundle-analyzer", 3 | "version": "0.0.6", 4 | "repository": "lhorie/bundle-analyzer", 5 | "bin": { 6 | "bundle-analyzer": "bin/bundle-analyzer.js" 7 | }, 8 | "dependencies": { 9 | "express": "^4.16.2", 10 | "opn": "^5.2.0", 11 | "source-map": "^0.7.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018 Leo Horie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const express = require('express'); 3 | const getSizes = require('./sizes'); 4 | const open = require('opn'); 5 | 6 | function start({dir, port = 9000}) { 7 | const connections = new Set(); 8 | 9 | const app = express(); 10 | app.get('/', (req, res) => { 11 | const html = __dirname + '/client.html'; 12 | fs.createReadStream(html).pipe(res); 13 | }); 14 | 15 | app.get('/_sse', async (req, res) => { 16 | connections.add(res); 17 | req.connection.addListener('close', () => connections.delete(res)); 18 | res.writeHead(200, { 19 | 'Content-Type': 'text/event-stream', 20 | 'Cache-Control': 'no-cache', 21 | Connection: 'keep-alive', 22 | }); 23 | const data = await getSizes(dir); 24 | res.write(`data: ${JSON.stringify(data)}\n\n`); 25 | }); 26 | 27 | const server = app.listen(port, () => { 28 | console.log(`Running on http://localhost:${port}`); 29 | }); 30 | open(`http://localhost:${port}`); 31 | 32 | async function update() { 33 | const data = await getSizes(dir); 34 | for (const res of connections) { 35 | res.write('data: ' + JSON.stringify(data) + '\n\n'); 36 | } 37 | } 38 | 39 | function close() { 40 | server.close(); 41 | } 42 | 43 | return {update, close}; 44 | } 45 | 46 | module.exports = {start, getSizes}; 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ### Code of conduct 4 | 5 | Be nice. 6 | 7 | ### Running in development 8 | 9 | ```sh 10 | bin/bundle-analyzer.js ../some-project/dist-dir 11 | ``` 12 | 13 | ### Project structure 14 | 15 | This project uses [yarn](https://yarnpkg.com/en/) for lock file enforcement. 16 | 17 | The `analyzer` interface can be found in `server.js`. This file sets up the express server and routes and exports the programmatic interface for this package. The express server pushes size data via server-side events upon client connection or if `update` is called on the server. 18 | 19 | The code to calculate sizes from source maps is in `sizes.js`. The `getSizesByFile` function asynchronously returns a map of form `{'some/path': size}`. The `groupByHierarchy` function converts that map to a tree of form `{some: {path: size}}`. The `normalize` function converts that tree into a normalized tree of form `{name, children, size}`. The exported function asynchronously returns an array of normalized trees. 20 | 21 | The CLI code can be found in `bin/bundle-analyzer.js`. If you run into permission issues, run `chmod +x bin/bundle-analyzer`. The CLI watches the filesystem via `fs.watch`. 22 | 23 | The client-side code is in `client.html`. It sets up an server-side-event listener, code to handle URL state via `history.pushState`, a D3 zoomable sunburst visualization, and some DOM glue code. 24 | 25 | ### Credits 26 | 27 | The code to calculate sizes is adapted from [https://github.com/danvk/source-map-explorer](https://github.com/danvk/source-map-explorer) 28 | 29 | The code for the zoomable sunburst is adapted from [https://bl.ocks.org/vasturiano/12da9071095fbd4df434e60d52d2d58d](https://bl.ocks.org/vasturiano/12da9071095fbd4df434e60d52d2d58d) 30 | -------------------------------------------------------------------------------- /sizes.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const {SourceMapConsumer} = require('source-map'); 5 | 6 | const readDir = util.promisify(fs.readdir); 7 | const readFile = util.promisify(fs.readFile); 8 | 9 | module.exports = async function(dir) { 10 | const paths = await readDir(dir).catch(() => []); 11 | return (await Promise.all([ 12 | ...paths.map(async p => { 13 | if (p.match(/\.js$/)) { 14 | return { 15 | name: p, 16 | children: normalize( 17 | groupByHierarchy(await getSizesByFile(dir, p).catch(() => [])) 18 | ), 19 | }; 20 | } 21 | }), 22 | ])).filter(Boolean); 23 | }; 24 | 25 | async function getSizesByFile(dir, p) { 26 | const js = path.resolve(dir, p); 27 | const map = path.resolve(dir, p + '.map'); 28 | const jsData = await readFile(js, 'utf-8'); 29 | const sourceMapData = await readFile(map, 'utf-8'); 30 | mapConsumer = await new SourceMapConsumer(sourceMapData); 31 | 32 | const lines = jsData.split('\n'); 33 | const sourceExtrema = {}; 34 | let numChars = 0; 35 | let lastSource = null; 36 | for (let line = 1; line <= lines.length; line++) { 37 | const lineText = lines[line - 1]; 38 | const numCols = lineText.length; 39 | for (let column = 0; column < numCols; column++, numChars++) { 40 | const pos = mapConsumer.originalPositionFor({ 41 | line: line, 42 | column: column, 43 | }); 44 | const source = pos.source; 45 | if (source == null) { 46 | continue; 47 | } 48 | 49 | if (source != lastSource) { 50 | if (!(source in sourceExtrema)) { 51 | sourceExtrema[source] = {min: numChars}; 52 | lastSource = source; 53 | } 54 | } else { 55 | sourceExtrema[source].max = numChars; 56 | } 57 | } 58 | } 59 | return Object.keys(sourceExtrema).reduce((memo, key) => { 60 | const v = sourceExtrema[key]; 61 | memo[key] = v.max - v.min + 1; 62 | return memo; 63 | }, {}); 64 | } 65 | function groupByHierarchy(map) { 66 | let groups = {}; 67 | for (key in map) { 68 | const levels = key.split('/').slice(1); 69 | let level = groups; 70 | levels.forEach((l, i) => { 71 | if (!level[l]) level[l] = {}; 72 | if (i === levels.length - 1) level[l] = map[key]; 73 | level = level[l]; 74 | }); 75 | } 76 | while (true) { 77 | const keys = Object.keys(groups); 78 | if (keys.length === 1 && typeof groups[keys[0]] !== 'number') { 79 | groups = groups[keys[0]]; 80 | } else break; 81 | } 82 | return groups; 83 | } 84 | function normalize(object) { 85 | const list = []; 86 | for (const key in object) { 87 | if (typeof object[key] === 'number') { 88 | list.push({name: key, size: object[key]}); 89 | } else { 90 | list.push({name: key, children: normalize(object[key])}); 91 | } 92 | } 93 | return list; 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bundle analyzer 2 | 3 | Displays a visualization of code sizes by file based on source map information. 4 | 5 | ![Bundle analyzer in action](https://raw.githubusercontent.com/wiki/lhorie/bundle-analyzer/images/bundle-analyzer.gif) 6 | 7 | File sizes displayed by this tool reflect size of transpiled minified bundled code, as calculated from source maps. Note that this tool does not take compression (i.e. gzip/brotli) into account. Compression profiles vary depending on the compression algorithm and aggressiveness setting, thus cannot be easily calculated with accuracy. For the purposes of optimization work, making an assumption that compression is applied evenly across the bundle is usually good enough. 8 | 9 | --- 10 | 11 | ### Installation 12 | 13 | ```sh 14 | # NPM 15 | npm install bundle-analyzer -g 16 | 17 | # Yarn 18 | yarn add bundle-analyzer --global 19 | ``` 20 | 21 | --- 22 | 23 | ### Usage 24 | 25 | #### CLI 26 | 27 | ```sh 28 | bundle-analyzer dist-directory 29 | ``` 30 | 31 | * `dist-directory` - Required. A directory path that contains minified Javascript files and their source maps. It's expected that source map files have the same name as their respective js files, plus a `.map` extension. 32 | 33 | --- 34 | 35 | ### API 36 | 37 | ```js 38 | import analyzer from 'bundle-analyzer'; 39 | ``` 40 | 41 | #### analyzer.start 42 | 43 | * `const server = analyzer.start({dir: string, port: number})` 44 | 45 | * `dir: string` - Required. A directory path that contains minified Javascript files and their source maps. It's expected that source map files have the same name as their respective js files, plus a `.map` extension. 46 | * `port: number` - Optional. Defaults to `9000`. The port at which the analyzer server runs 47 | * returns `server: {update : () => void, close: () => void}` 48 | * `server.update()` - Hot-reloads the visualization 49 | * `server.close()` - Shuts down the analyzer server 50 | 51 | #### analyzer.getSizes 52 | 53 | * `const sizes = await analyzer.getSizes(dir)` 54 | 55 | * `dir: string` - Required. A directory path that contains minified Javascript files and their source maps. It's expected that source map files have the same name as their respective js files, plus a `.map` extension. 56 | * returns `sizes: Node {name: string, children: ?[Node], size: ?number}` 57 | * `name: string` - a directory or file name 58 | * `children: [Node]` - a list of subdirectories or files 59 | * `size: number` - a size calculation based on source maps. It represents the sum of sizes of all the code originating from a single file, after transpilation, minification and bundling. 60 | 61 | --- 62 | 63 | ### Ways to decrease file size 64 | 65 | #### Bundle splitting 66 | 67 | Typically, the simplest way to reduce the amount of code being downloaded on page load is to [employ code splitting](https://webpack.js.org/guides/code-splitting/) 68 | 69 | #### Reduce dependencies 70 | 71 | For example, prefer native Array methods over utility libraries such as Lodash and Ramda, and prefer tree-shaking friendly libraries over monolythic ones (e.g. date-fns vs moment.js) 72 | 73 | #### Alias libraries 74 | 75 | For example, React can often be replaced by similar libraries such as Nerv.js or Preact. 76 | 77 | #### Remove polyfills 78 | 79 | Ensure you're not [polyfilling/mocking node globals](https://webpack.js.org/configuration/node/) 80 | 81 | --- 82 | 83 | ### Differences from webpack-bundle-analyzer 84 | 85 | Bundle analyzer CLI hot-reloads, and its programmatic API is designed to integrate with server push events. 86 | 87 | Bundle analyzer uses a D3-based zoomable sunburst visualization, which provides a more accurate visual representation of size breakdowns. In addition, bundle analyzer aggregates the analysis of all bundles into a single interface, and it hot-reloads them when it detects changes in disk. 88 | 89 | Bundle analyzer looks at source maps rather than webpack's `stats.json` file, so it should work with other non-Webpack tools such as Rollup or Parcel. 90 | 91 | Bundle analyzer does not report compression estimates, since different compression algorithms and different settings give different results and the results of gzipping each section individually doesn't provide an accurate number anyways. 92 | 93 | ### Differences from source-map-explorer 94 | 95 | Both bundle analyzer and source map explorer display roughly the same information, but slightly differently. Bundle analyzer uses a D3-based zoomable sunburst visualization, which provides a more accurate visual representation of size breakdowns. In addition, bundle analyzer aggregates the analysis of all bundles into a single interface, and it hot-reloads them when it detects changes in disk. 96 | 97 | --- 98 | 99 | License: MIT 100 | -------------------------------------------------------------------------------- /client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Package sizes 7 | 45 | 46 | 47 | 48 | 58 | 59 | 263 | 264 | 265 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | accepts@~1.3.4: 6 | version "1.3.5" 7 | resolved "https://unpm.uberinternal.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" 8 | dependencies: 9 | mime-types "~2.1.18" 10 | negotiator "0.6.1" 11 | 12 | array-flatten@1.1.1: 13 | version "1.1.1" 14 | resolved "https://unpm.uberinternal.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 15 | 16 | body-parser@1.18.2: 17 | version "1.18.2" 18 | resolved "https://unpm.uberinternal.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" 19 | dependencies: 20 | bytes "3.0.0" 21 | content-type "~1.0.4" 22 | debug "2.6.9" 23 | depd "~1.1.1" 24 | http-errors "~1.6.2" 25 | iconv-lite "0.4.19" 26 | on-finished "~2.3.0" 27 | qs "6.5.1" 28 | raw-body "2.3.2" 29 | type-is "~1.6.15" 30 | 31 | bytes@3.0.0: 32 | version "3.0.0" 33 | resolved "https://unpm.uberinternal.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" 34 | 35 | commander@2: 36 | version "2.15.0" 37 | resolved "https://unpm.uberinternal.com/commander/-/commander-2.15.0.tgz#ad2a23a1c3b036e392469b8012cec6b33b4c1322" 38 | 39 | content-disposition@0.5.2: 40 | version "0.5.2" 41 | resolved "https://unpm.uberinternal.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 42 | 43 | content-type@~1.0.4: 44 | version "1.0.4" 45 | resolved "https://unpm.uberinternal.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 46 | 47 | cookie-signature@1.0.6: 48 | version "1.0.6" 49 | resolved "https://unpm.uberinternal.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 50 | 51 | cookie@0.3.1: 52 | version "0.3.1" 53 | resolved "https://unpm.uberinternal.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 54 | 55 | d3-array@1, d3-array@1.2.1, d3-array@^1.2.0: 56 | version "1.2.1" 57 | resolved "https://unpm.uberinternal.com/d3-array/-/d3-array-1.2.1.tgz#d1ca33de2f6ac31efadb8e050a021d7e2396d5dc" 58 | 59 | d3-axis@1.0.8: 60 | version "1.0.8" 61 | resolved "https://unpm.uberinternal.com/d3-axis/-/d3-axis-1.0.8.tgz#31a705a0b535e65759de14173a31933137f18efa" 62 | 63 | d3-brush@1.0.4: 64 | version "1.0.4" 65 | resolved "https://unpm.uberinternal.com/d3-brush/-/d3-brush-1.0.4.tgz#00c2f238019f24f6c0a194a26d41a1530ffe7bc4" 66 | dependencies: 67 | d3-dispatch "1" 68 | d3-drag "1" 69 | d3-interpolate "1" 70 | d3-selection "1" 71 | d3-transition "1" 72 | 73 | d3-chord@1.0.4: 74 | version "1.0.4" 75 | resolved "https://unpm.uberinternal.com/d3-chord/-/d3-chord-1.0.4.tgz#7dec4f0ba886f713fe111c45f763414f6f74ca2c" 76 | dependencies: 77 | d3-array "1" 78 | d3-path "1" 79 | 80 | d3-collection@1, d3-collection@1.0.4: 81 | version "1.0.4" 82 | resolved "https://unpm.uberinternal.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2" 83 | 84 | d3-color@1, d3-color@1.0.3: 85 | version "1.0.3" 86 | resolved "https://unpm.uberinternal.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b" 87 | 88 | d3-dispatch@1, d3-dispatch@1.0.3: 89 | version "1.0.3" 90 | resolved "https://unpm.uberinternal.com/d3-dispatch/-/d3-dispatch-1.0.3.tgz#46e1491eaa9b58c358fce5be4e8bed626e7871f8" 91 | 92 | d3-drag@1, d3-drag@1.2.1: 93 | version "1.2.1" 94 | resolved "https://unpm.uberinternal.com/d3-drag/-/d3-drag-1.2.1.tgz#df8dd4c502fb490fc7462046a8ad98a5c479282d" 95 | dependencies: 96 | d3-dispatch "1" 97 | d3-selection "1" 98 | 99 | d3-dsv@1, d3-dsv@1.0.8: 100 | version "1.0.8" 101 | resolved "https://unpm.uberinternal.com/d3-dsv/-/d3-dsv-1.0.8.tgz#907e240d57b386618dc56468bacfe76bf19764ae" 102 | dependencies: 103 | commander "2" 104 | iconv-lite "0.4" 105 | rw "1" 106 | 107 | d3-ease@1, d3-ease@1.0.3: 108 | version "1.0.3" 109 | resolved "https://unpm.uberinternal.com/d3-ease/-/d3-ease-1.0.3.tgz#68bfbc349338a380c44d8acc4fbc3304aa2d8c0e" 110 | 111 | d3-force@1.1.0: 112 | version "1.1.0" 113 | resolved "https://unpm.uberinternal.com/d3-force/-/d3-force-1.1.0.tgz#cebf3c694f1078fcc3d4daf8e567b2fbd70d4ea3" 114 | dependencies: 115 | d3-collection "1" 116 | d3-dispatch "1" 117 | d3-quadtree "1" 118 | d3-timer "1" 119 | 120 | d3-format@1, d3-format@1.2.2: 121 | version "1.2.2" 122 | resolved "https://unpm.uberinternal.com/d3-format/-/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a" 123 | 124 | d3-geo@1.9.1: 125 | version "1.9.1" 126 | resolved "https://unpm.uberinternal.com/d3-geo/-/d3-geo-1.9.1.tgz#157e3b0f917379d0f73bebfff3be537f49fa7356" 127 | dependencies: 128 | d3-array "1" 129 | 130 | d3-hierarchy@1.1.5: 131 | version "1.1.5" 132 | resolved "https://unpm.uberinternal.com/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz#a1c845c42f84a206bcf1c01c01098ea4ddaa7a26" 133 | 134 | d3-interpolate@1, d3-interpolate@1.1.6: 135 | version "1.1.6" 136 | resolved "https://unpm.uberinternal.com/d3-interpolate/-/d3-interpolate-1.1.6.tgz#2cf395ae2381804df08aa1bf766b7f97b5f68fb6" 137 | dependencies: 138 | d3-color "1" 139 | 140 | d3-path@1, d3-path@1.0.5: 141 | version "1.0.5" 142 | resolved "https://unpm.uberinternal.com/d3-path/-/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764" 143 | 144 | d3-polygon@1.0.3: 145 | version "1.0.3" 146 | resolved "https://unpm.uberinternal.com/d3-polygon/-/d3-polygon-1.0.3.tgz#16888e9026460933f2b179652ad378224d382c62" 147 | 148 | d3-quadtree@1, d3-quadtree@1.0.3: 149 | version "1.0.3" 150 | resolved "https://unpm.uberinternal.com/d3-quadtree/-/d3-quadtree-1.0.3.tgz#ac7987e3e23fe805a990f28e1b50d38fcb822438" 151 | 152 | d3-queue@3.0.7: 153 | version "3.0.7" 154 | resolved "https://unpm.uberinternal.com/d3-queue/-/d3-queue-3.0.7.tgz#c93a2e54b417c0959129d7d73f6cf7d4292e7618" 155 | 156 | d3-random@1.1.0: 157 | version "1.1.0" 158 | resolved "https://unpm.uberinternal.com/d3-random/-/d3-random-1.1.0.tgz#6642e506c6fa3a648595d2b2469788a8d12529d3" 159 | 160 | d3-request@1.0.6: 161 | version "1.0.6" 162 | resolved "https://unpm.uberinternal.com/d3-request/-/d3-request-1.0.6.tgz#a1044a9ef4ec28c824171c9379fae6d79474b19f" 163 | dependencies: 164 | d3-collection "1" 165 | d3-dispatch "1" 166 | d3-dsv "1" 167 | xmlhttprequest "1" 168 | 169 | d3-scale@1.0.7: 170 | version "1.0.7" 171 | resolved "https://unpm.uberinternal.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d" 172 | dependencies: 173 | d3-array "^1.2.0" 174 | d3-collection "1" 175 | d3-color "1" 176 | d3-format "1" 177 | d3-interpolate "1" 178 | d3-time "1" 179 | d3-time-format "2" 180 | 181 | d3-selection@1, d3-selection@1.3.0, d3-selection@^1.1.0: 182 | version "1.3.0" 183 | resolved "https://unpm.uberinternal.com/d3-selection/-/d3-selection-1.3.0.tgz#d53772382d3dc4f7507bfb28bcd2d6aed2a0ad6d" 184 | 185 | d3-shape@1.2.0: 186 | version "1.2.0" 187 | resolved "https://unpm.uberinternal.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777" 188 | dependencies: 189 | d3-path "1" 190 | 191 | d3-time-format@2, d3-time-format@2.1.1: 192 | version "2.1.1" 193 | resolved "https://unpm.uberinternal.com/d3-time-format/-/d3-time-format-2.1.1.tgz#85b7cdfbc9ffca187f14d3c456ffda268081bb31" 194 | dependencies: 195 | d3-time "1" 196 | 197 | d3-time@1, d3-time@1.0.8: 198 | version "1.0.8" 199 | resolved "https://unpm.uberinternal.com/d3-time/-/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84" 200 | 201 | d3-timer@1, d3-timer@1.0.7: 202 | version "1.0.7" 203 | resolved "https://unpm.uberinternal.com/d3-timer/-/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531" 204 | 205 | d3-transition@1, d3-transition@1.1.1: 206 | version "1.1.1" 207 | resolved "https://unpm.uberinternal.com/d3-transition/-/d3-transition-1.1.1.tgz#d8ef89c3b848735b060e54a39b32aaebaa421039" 208 | dependencies: 209 | d3-color "1" 210 | d3-dispatch "1" 211 | d3-ease "1" 212 | d3-interpolate "1" 213 | d3-selection "^1.1.0" 214 | d3-timer "1" 215 | 216 | d3-voronoi@1.1.2: 217 | version "1.1.2" 218 | resolved "https://unpm.uberinternal.com/d3-voronoi/-/d3-voronoi-1.1.2.tgz#1687667e8f13a2d158c80c1480c5a29cb0d8973c" 219 | 220 | d3-zoom@1.7.1: 221 | version "1.7.1" 222 | resolved "https://unpm.uberinternal.com/d3-zoom/-/d3-zoom-1.7.1.tgz#02f43b3c3e2db54f364582d7e4a236ccc5506b63" 223 | dependencies: 224 | d3-dispatch "1" 225 | d3-drag "1" 226 | d3-interpolate "1" 227 | d3-selection "1" 228 | d3-transition "1" 229 | 230 | d3@4.13.0: 231 | version "4.13.0" 232 | resolved "https://unpm.uberinternal.com/d3/-/d3-4.13.0.tgz#ab236ff8cf0cfc27a81e69bf2fb7518bc9b4f33d" 233 | dependencies: 234 | d3-array "1.2.1" 235 | d3-axis "1.0.8" 236 | d3-brush "1.0.4" 237 | d3-chord "1.0.4" 238 | d3-collection "1.0.4" 239 | d3-color "1.0.3" 240 | d3-dispatch "1.0.3" 241 | d3-drag "1.2.1" 242 | d3-dsv "1.0.8" 243 | d3-ease "1.0.3" 244 | d3-force "1.1.0" 245 | d3-format "1.2.2" 246 | d3-geo "1.9.1" 247 | d3-hierarchy "1.1.5" 248 | d3-interpolate "1.1.6" 249 | d3-path "1.0.5" 250 | d3-polygon "1.0.3" 251 | d3-quadtree "1.0.3" 252 | d3-queue "3.0.7" 253 | d3-random "1.1.0" 254 | d3-request "1.0.6" 255 | d3-scale "1.0.7" 256 | d3-selection "1.3.0" 257 | d3-shape "1.2.0" 258 | d3-time "1.0.8" 259 | d3-time-format "2.1.1" 260 | d3-timer "1.0.7" 261 | d3-transition "1.1.1" 262 | d3-voronoi "1.1.2" 263 | d3-zoom "1.7.1" 264 | 265 | debug@2.6.9: 266 | version "2.6.9" 267 | resolved "https://unpm.uberinternal.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 268 | dependencies: 269 | ms "2.0.0" 270 | 271 | depd@1.1.1: 272 | version "1.1.1" 273 | resolved "https://unpm.uberinternal.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" 274 | 275 | depd@~1.1.1: 276 | version "1.1.2" 277 | resolved "https://unpm.uberinternal.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 278 | 279 | destroy@~1.0.4: 280 | version "1.0.4" 281 | resolved "https://unpm.uberinternal.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 282 | 283 | ee-first@1.1.1: 284 | version "1.1.1" 285 | resolved "https://unpm.uberinternal.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 286 | 287 | encodeurl@~1.0.1: 288 | version "1.0.2" 289 | resolved "https://unpm.uberinternal.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 290 | 291 | escape-html@~1.0.3: 292 | version "1.0.3" 293 | resolved "https://unpm.uberinternal.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 294 | 295 | etag@~1.8.1: 296 | version "1.8.1" 297 | resolved "https://unpm.uberinternal.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 298 | 299 | express@4.16.2: 300 | version "4.16.2" 301 | resolved "https://unpm.uberinternal.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" 302 | dependencies: 303 | accepts "~1.3.4" 304 | array-flatten "1.1.1" 305 | body-parser "1.18.2" 306 | content-disposition "0.5.2" 307 | content-type "~1.0.4" 308 | cookie "0.3.1" 309 | cookie-signature "1.0.6" 310 | debug "2.6.9" 311 | depd "~1.1.1" 312 | encodeurl "~1.0.1" 313 | escape-html "~1.0.3" 314 | etag "~1.8.1" 315 | finalhandler "1.1.0" 316 | fresh "0.5.2" 317 | merge-descriptors "1.0.1" 318 | methods "~1.1.2" 319 | on-finished "~2.3.0" 320 | parseurl "~1.3.2" 321 | path-to-regexp "0.1.7" 322 | proxy-addr "~2.0.2" 323 | qs "6.5.1" 324 | range-parser "~1.2.0" 325 | safe-buffer "5.1.1" 326 | send "0.16.1" 327 | serve-static "1.13.1" 328 | setprototypeof "1.1.0" 329 | statuses "~1.3.1" 330 | type-is "~1.6.15" 331 | utils-merge "1.0.1" 332 | vary "~1.1.2" 333 | 334 | finalhandler@1.1.0: 335 | version "1.1.0" 336 | resolved "https://unpm.uberinternal.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" 337 | dependencies: 338 | debug "2.6.9" 339 | encodeurl "~1.0.1" 340 | escape-html "~1.0.3" 341 | on-finished "~2.3.0" 342 | parseurl "~1.3.2" 343 | statuses "~1.3.1" 344 | unpipe "~1.0.0" 345 | 346 | forwarded@~0.1.2: 347 | version "0.1.2" 348 | resolved "https://unpm.uberinternal.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 349 | 350 | fresh@0.5.2: 351 | version "0.5.2" 352 | resolved "https://unpm.uberinternal.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 353 | 354 | http-errors@1.6.2, http-errors@~1.6.2: 355 | version "1.6.2" 356 | resolved "https://unpm.uberinternal.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" 357 | dependencies: 358 | depd "1.1.1" 359 | inherits "2.0.3" 360 | setprototypeof "1.0.3" 361 | statuses ">= 1.3.1 < 2" 362 | 363 | iconv-lite@0.4, iconv-lite@0.4.19: 364 | version "0.4.19" 365 | resolved "https://unpm.uberinternal.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" 366 | 367 | inherits@2.0.3: 368 | version "2.0.3" 369 | resolved "https://unpm.uberinternal.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 370 | 371 | ipaddr.js@1.6.0: 372 | version "1.6.0" 373 | resolved "https://unpm.uberinternal.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b" 374 | 375 | is-wsl@^1.1.0: 376 | version "1.1.0" 377 | resolved "https://unpm.uberinternal.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" 378 | 379 | media-typer@0.3.0: 380 | version "0.3.0" 381 | resolved "https://unpm.uberinternal.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 382 | 383 | merge-descriptors@1.0.1: 384 | version "1.0.1" 385 | resolved "https://unpm.uberinternal.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 386 | 387 | methods@~1.1.2: 388 | version "1.1.2" 389 | resolved "https://unpm.uberinternal.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 390 | 391 | mime-db@~1.33.0: 392 | version "1.33.0" 393 | resolved "https://unpm.uberinternal.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" 394 | 395 | mime-types@~2.1.18: 396 | version "2.1.18" 397 | resolved "https://unpm.uberinternal.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" 398 | dependencies: 399 | mime-db "~1.33.0" 400 | 401 | mime@1.4.1: 402 | version "1.4.1" 403 | resolved "https://unpm.uberinternal.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" 404 | 405 | ms@2.0.0: 406 | version "2.0.0" 407 | resolved "https://unpm.uberinternal.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 408 | 409 | negotiator@0.6.1: 410 | version "0.6.1" 411 | resolved "https://unpm.uberinternal.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 412 | 413 | on-finished@~2.3.0: 414 | version "2.3.0" 415 | resolved "https://unpm.uberinternal.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 416 | dependencies: 417 | ee-first "1.1.1" 418 | 419 | opn@^5.2.0: 420 | version "5.2.0" 421 | resolved "https://unpm.uberinternal.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225" 422 | dependencies: 423 | is-wsl "^1.1.0" 424 | 425 | parseurl@~1.3.2: 426 | version "1.3.2" 427 | resolved "https://unpm.uberinternal.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" 428 | 429 | path-to-regexp@0.1.7: 430 | version "0.1.7" 431 | resolved "https://unpm.uberinternal.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 432 | 433 | proxy-addr@~2.0.2: 434 | version "2.0.3" 435 | resolved "https://unpm.uberinternal.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" 436 | dependencies: 437 | forwarded "~0.1.2" 438 | ipaddr.js "1.6.0" 439 | 440 | qs@6.5.1: 441 | version "6.5.1" 442 | resolved "https://unpm.uberinternal.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" 443 | 444 | range-parser@~1.2.0: 445 | version "1.2.0" 446 | resolved "https://unpm.uberinternal.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" 447 | 448 | raw-body@2.3.2: 449 | version "2.3.2" 450 | resolved "https://unpm.uberinternal.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" 451 | dependencies: 452 | bytes "3.0.0" 453 | http-errors "1.6.2" 454 | iconv-lite "0.4.19" 455 | unpipe "1.0.0" 456 | 457 | rw@1: 458 | version "1.3.3" 459 | resolved "https://unpm.uberinternal.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" 460 | 461 | safe-buffer@5.1.1: 462 | version "5.1.1" 463 | resolved "https://unpm.uberinternal.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 464 | 465 | send@0.16.1: 466 | version "0.16.1" 467 | resolved "https://unpm.uberinternal.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3" 468 | dependencies: 469 | debug "2.6.9" 470 | depd "~1.1.1" 471 | destroy "~1.0.4" 472 | encodeurl "~1.0.1" 473 | escape-html "~1.0.3" 474 | etag "~1.8.1" 475 | fresh "0.5.2" 476 | http-errors "~1.6.2" 477 | mime "1.4.1" 478 | ms "2.0.0" 479 | on-finished "~2.3.0" 480 | range-parser "~1.2.0" 481 | statuses "~1.3.1" 482 | 483 | serve-static@1.13.1: 484 | version "1.13.1" 485 | resolved "https://unpm.uberinternal.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719" 486 | dependencies: 487 | encodeurl "~1.0.1" 488 | escape-html "~1.0.3" 489 | parseurl "~1.3.2" 490 | send "0.16.1" 491 | 492 | setprototypeof@1.0.3: 493 | version "1.0.3" 494 | resolved "https://unpm.uberinternal.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" 495 | 496 | setprototypeof@1.1.0: 497 | version "1.1.0" 498 | resolved "https://unpm.uberinternal.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" 499 | 500 | source-map@0.7.2: 501 | version "0.7.2" 502 | resolved "https://unpm.uberinternal.com/source-map/-/source-map-0.7.2.tgz#115c3e891aaa9a484869fd2b89391a225feba344" 503 | 504 | "statuses@>= 1.3.1 < 2": 505 | version "1.4.0" 506 | resolved "https://unpm.uberinternal.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" 507 | 508 | statuses@~1.3.1: 509 | version "1.3.1" 510 | resolved "https://unpm.uberinternal.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" 511 | 512 | type-is@~1.6.15: 513 | version "1.6.16" 514 | resolved "https://unpm.uberinternal.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" 515 | dependencies: 516 | media-typer "0.3.0" 517 | mime-types "~2.1.18" 518 | 519 | unpipe@1.0.0, unpipe@~1.0.0: 520 | version "1.0.0" 521 | resolved "https://unpm.uberinternal.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 522 | 523 | utils-merge@1.0.1: 524 | version "1.0.1" 525 | resolved "https://unpm.uberinternal.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 526 | 527 | vary@~1.1.2: 528 | version "1.1.2" 529 | resolved "https://unpm.uberinternal.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 530 | 531 | xmlhttprequest@1: 532 | version "1.8.0" 533 | resolved "https://unpm.uberinternal.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" 534 | --------------------------------------------------------------------------------