├── .gitignore ├── bin ├── webpack.js └── webpack-graph.js ├── package.json ├── lib ├── webpack-graph.js ├── interactive.js ├── Node.js └── Graph.js ├── README.md └── example └── webpackBrowsertest.svg /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /js -------------------------------------------------------------------------------- /bin/webpack.js: -------------------------------------------------------------------------------- 1 | require("webpack/bin/webpack"); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-graph", 3 | "version": "0.1.4", 4 | "author": "Tobias Koppers @sokra", 5 | "description": "Converts JSON stats from webpack to a nice SVG-Image.", 6 | "dependencies": { 7 | "optimist": "0.2.x", 8 | "webpack": ">=0.7.2" 9 | }, 10 | "licenses": [ 11 | { 12 | "type": "MIT", 13 | "url": "http://www.opensource.org/licenses/mit-license.php" 14 | } 15 | ], 16 | "devDependencies": { 17 | "mocha": "*", 18 | "should": "*" 19 | }, 20 | "engines": { 21 | "node": ">=0.1.30" 22 | }, 23 | "homepage": "http://github.com/webpack/graph", 24 | "main": "lib/webpack-graph.js", 25 | "bin": "./bin/webpack-graph.js", 26 | "scripts": { 27 | "postinstall": "node bin/webpack lib/interactive.js js/interactive.js --colors --min --libary wpg", 28 | "test": "node node_modules/mocha/bin/_mocha --reporter spec" 29 | }, 30 | "license": "MIT" 31 | } -------------------------------------------------------------------------------- /lib/webpack-graph.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | var Graph = require("./Graph"); 7 | var interactiveSource = require("fs").readFileSync(require("path").join(__dirname, "..", "js", "interactive.js"), "utf-8"); 8 | 9 | module.exports = function webpackGraph(stats, options) { 10 | options = options || {}; 11 | var svg = []; 12 | 13 | var MAX_STEPS = options.maxSteps || (options.interactive ? 0 : 5000); 14 | 15 | var graph = new Graph(options, stats); 16 | 17 | var movement, i = 0; 18 | if(MAX_STEPS > 0) 19 | do { 20 | i++; 21 | movement = graph.simulateStep(); 22 | var progress = Math.max(0, 1 - movement / 1000); 23 | progress = Math.floor(progress * progress * 100); 24 | var progress2 = Math.floor(i * 100 / MAX_STEPS); 25 | process.stderr.write("\b \b\b\b\b" + Math.max(progress, progress2) + "%"); 26 | } while((i < 10 || movement > 10) && i < MAX_STEPS); 27 | 28 | graph.normalizePositions(); 29 | 30 | graph.writeSVG(svg); 31 | 32 | if(options.interactive) { 33 | var end = svg.pop(); 34 | svg.push(''); 38 | svg.push(end); 39 | } 40 | 41 | if(!options.outputStream) return svg.join(""); 42 | } 43 | 44 | function xmlEscape(str) { 45 | return str 46 | .replace(/&/g, "&") 47 | .replace(//g, ">"); 49 | } -------------------------------------------------------------------------------- /lib/interactive.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | var Graph = require("./Graph"); 6 | 7 | exports.use = function(json) { 8 | var graph = new Graph({ 9 | width: window.innerWidth, 10 | height: window.innerHeight 11 | }).load(json); 12 | 13 | exports.paused = false; 14 | exports.STEPS_PER_TICK = 1; 15 | 16 | graph.normalizePositions(); 17 | graph.applyTo(document); 18 | 19 | var draggingNode = null; 20 | var draggingPos = null; 21 | 22 | graph.modulesList.forEach(function(node, idx) { 23 | var el = document.getElementById("module"+idx); 24 | el.addEventListener("mousedown", mouseDownHandler.bind(null, node)); 25 | }); 26 | document.addEventListener("mousemove", mouseMoveHandler); 27 | document.addEventListener("mouseup", mouseUpHandler); 28 | 29 | function mouseDownHandler(node, event) { 30 | draggingNode = node; 31 | mouseMoveHandler(event); 32 | } 33 | function mouseMoveHandler(event) { 34 | if(!draggingNode) return; 35 | var x = event.clientX, y = event.clientY; 36 | x -= graph.MODULE_MAX_R; 37 | y -= graph.MODULE_MAX_R; 38 | x /= graph.scale; 39 | y /= graph.scale; 40 | draggingPos = [x, y]; 41 | draggingNode.pos[0] = draggingPos[0]; 42 | draggingNode.pos[1] = draggingPos[1]; 43 | } 44 | function mouseUpHandler() { 45 | draggingNode = null; 46 | } 47 | 48 | setTimeout(function tick() { 49 | if(!exports.paused) { 50 | for(var i = 0; i < exports.STEPS_PER_TICK; i++) { 51 | graph.simulateStep(); 52 | if(draggingNode) { 53 | draggingNode.pos[0] = draggingPos[0]; 54 | draggingNode.pos[1] = draggingPos[1]; 55 | } 56 | } 57 | } else if(exports.single) { 58 | exports.single = false; 59 | graph.simulateStep(); 60 | } 61 | if(!draggingNode) 62 | graph.normalizePositions(); 63 | graph.applyTo(document); 64 | setTimeout(tick, 1); 65 | }, 100); 66 | return graph; 67 | } 68 | 69 | exports.Graph = Graph; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webpack-graph 2 | 3 | It visualize your dependency tree as svg image. 4 | 5 | Provide it with webpack stats (as JSON) for version > 0.7. 6 | 7 | You can generate them by calling webpack with `--json`. 8 | 9 | ## Command Line 10 | 11 | `webpack-graph [ []]` 12 | 13 | If you don't provide the files as parameters `webpack-graph` will read them from `stdin` or write it to `stdout`. 14 | 15 | `--context ` - Shorten filenames according to this context 16 | 17 | `--width ` - The max width of the output svg 18 | 19 | `--height ` - The max height of the output svg 20 | 21 | `--steps ` - Limit the simulation steps 22 | 23 | `--interactive` - Emit simulation code to browser 24 | 25 | `--color-by-loaders` - Choose colors by loaders 26 | 27 | `--color-by-module` - Choose colors by loaders 28 | 29 | `--color-switch` - Chosse colors by hovering 30 | 31 | ## Resulting Image 32 | 33 | * Circles are modules/contexts 34 | * The size visualize the file size. 35 | * The color visualize the chunks in which the module is emitted. 36 | * Connections are dependencies 37 | * webpack-graph try to guess libaries and connect them with thin lines 38 | * Dashed lines visualize async requires. 39 | * Hover modules/contexts to display more info 40 | * Tooltip display module name and loaders 41 | * Tooltip display chunks 42 | * Green lines display requires *from* other modules/contexts 43 | * Red lines display requires *to* other modules/contexts 44 | * Brown lines display requires *to* and *from* other modules/contexts 45 | * In interactive mode 46 | * You can drag modules/contexts with your mouse 47 | * Layouting happens live 48 | * Only tested on latest Chrome and Firefox 49 | * Older browsers are not supported 50 | 51 | ### Example 52 | 53 | ![webpack-graph](http://webpack.github.com/graph/example/webpackBrowsertest.svg) 54 | 55 | [Interactive version](http://webpack.github.com/graph/example/webpackBrowsertestInteractive.svg) 56 | 57 | See more examples in webpack examples -------------------------------------------------------------------------------- /bin/webpack-graph.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | MIT License http://www.opensource.org/licenses/mit-license.php 5 | Author Tobias Koppers @sokra 6 | */ 7 | var path = require("path"); 8 | var fs = require("fs"); 9 | var util = require("util"); 10 | 11 | var argv = require("optimist") 12 | .usage("webpack-graph " + require("../package.json").version + "\n" + 13 | "Usage: $0 [ []]") 14 | 15 | .string("context") 16 | .describe("context", "Shorten filenames according to this context") 17 | 18 | .boolean("interactive") 19 | .describe("interactive", "Emit simulation code to browser") 20 | 21 | .boolean("color-by-loaders") 22 | .describe("color-by-loaders", "Choose colors by loaders") 23 | 24 | .boolean("color-by-module") 25 | .describe("color-by-module", "Choose colors by module") 26 | 27 | .boolean("color-switch") 28 | .describe("color-switch", "Chosse colors by hovering") 29 | 30 | .describe("steps", "Limit the simulation steps") 31 | 32 | .describe("width", "The max width of the output svg") 33 | .describe("height", "The max height of the output svg") 34 | 35 | .demand(0) 36 | .argv; 37 | 38 | var input = argv._[0] && path.resolve(argv._[0]); 39 | var output = argv._[1] && path.resolve(argv._[1]); 40 | 41 | var inputStream, outputStream; 42 | if(input) { 43 | inputStream = fs.createReadStream(input, { encoding: "utf-8" }); 44 | } else { 45 | process.stdin.resume(); 46 | inputStream = process.stdin; 47 | } 48 | 49 | if(output) { 50 | outputStream = fs.createWriteStream(output) 51 | } else { 52 | outputStream = process.stdout; 53 | } 54 | 55 | var options = {}; 56 | 57 | if(argv.context) { 58 | options.nameShortener = require("webpack/lib/createFilenameShortener")(path.resolve(argv.context)); 59 | } 60 | 61 | if(argv.interactive) { 62 | options.interactive = true; 63 | } 64 | 65 | if(argv.steps !== undefined) { 66 | options.maxSteps = argv.steps; 67 | } 68 | 69 | if(argv.height) { 70 | options.height = argv.height; 71 | } 72 | 73 | if(argv.width) { 74 | options.width = argv.width; 75 | } 76 | 77 | if(argv["color-by-loaders"]) { 78 | options.colorByLoaders = true; 79 | } 80 | 81 | if(argv["color-by-module"]) { 82 | options.colorByModule = true; 83 | } 84 | 85 | if(argv["color-switch"]) { 86 | options.colorSwitch = true; 87 | } 88 | 89 | var data = []; 90 | inputStream.on("data", data.push.bind(data)); 91 | inputStream.on("end", function() { 92 | data = data.join(""); 93 | 94 | var webpackGraph = require("../lib/webpack-graph.js"); 95 | var svg = webpackGraph(JSON.parse(data), options); 96 | 97 | outputStream.write(svg, "utf-8"); 98 | if(outputStream != process.stdout) outputStream.end(); 99 | }); 100 | 101 | -------------------------------------------------------------------------------- /lib/Node.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | function Node(id) { 6 | this.speed = [0, 0]; 7 | this.force = [0, 0]; 8 | this.id = id; 9 | this.files = []; 10 | this.linksWeight = {}; 11 | this.linksAsync = {}; 12 | } 13 | 14 | Node.WEIGHT_LIBARY_INDEX = 5; 15 | Node.EXTRA_DETRACTION_FILES = 5; 16 | 17 | Node.createFromStats = function(id, stats) { 18 | var node = new Node(id); 19 | var found = false; 20 | var reasons; 21 | Object.keys(stats.fileModules).forEach(function(file) { 22 | var modules = stats.fileModules[file]; 23 | modules.forEach(function(module) { 24 | if(module.id == id) { 25 | found = true; 26 | if(this.files[this.files.length-1] != file) 27 | this.files.push(file); 28 | this.size = module.size; 29 | if(module.dirname) this.name = module.dirname; 30 | else if(module.request) this.name = module.request; 31 | else if(module.filename) this.name = module.filename; 32 | reasons = module.reasons; 33 | this.loaders = module.loaders; 34 | } 35 | }, this); 36 | }, node); 37 | if(!found) return null; 38 | if(reasons) reasons.forEach(function(reason) { 39 | var name; 40 | switch(reason.type) { 41 | case "main": 42 | this.main = true; 43 | return; 44 | case "context": 45 | name = reason.dirname || reason.request || reason.filename; 46 | if(reason.async && !reason.dirname) this.linksAsync[name] = true; 47 | break; 48 | case "require": 49 | name = reason.request || reason.filename; 50 | if(reason.async) this.linksAsync[name] = true; 51 | break; 52 | default: 53 | return; 54 | } 55 | if(!name) return; 56 | this.linksWeight[name] = (this.linksWeight[name] || 0) + 1; 57 | }, node); 58 | return node; 59 | } 60 | 61 | Node.createFromJSON = function(json) { 62 | var node = new Node(json.id); 63 | Object.keys(json).forEach(function(key) { 64 | this[key] = json[key]; 65 | }, node); 66 | return node; 67 | } 68 | 69 | Node.prototype.toJSON = function() { 70 | return { 71 | id: this.id, 72 | pos: this.pos, 73 | speed: this.speed, 74 | files: this.files, 75 | size: this.size, 76 | linksAsyncResolved: this.linksAsyncResolved, 77 | linksWeightResolved: this.linksWeightResolved 78 | }; 79 | } 80 | 81 | Node.prototype.resolveLinks = function(mapNameToNode) { 82 | if(!this.linksWeightResolved) { 83 | this.linksWeightResolved = {}; 84 | this.links = Object.keys(this.linksWeight).map(function(name) { 85 | var node = mapNameToNode[name]; 86 | this.linksWeightResolved[node.id] = this.linksWeight[name]; 87 | if(node === this) return; 88 | return node; 89 | }, this).filter(function(i) { return !!i }); 90 | } else { 91 | this.links = Object.keys(this.linksWeightResolved).map(function(id) { 92 | var node = mapNameToNode[id]; 93 | if(node === this) return; 94 | return node; 95 | }, this).filter(function(i) { return !!i }); 96 | } 97 | if(!this.linksAsyncResolved) { 98 | this.linksAsyncResolved = {}; 99 | Object.keys(this.linksAsync).forEach(function(name) { 100 | var node = mapNameToNode[name]; 101 | this.linksAsyncResolved[node.id] = this.linksAsync[name]; 102 | }, this); 103 | } 104 | } 105 | 106 | Node.prototype.propageLinks = function() { 107 | this.links.forEach(function(node2) { 108 | if(node2.links.indexOf(this) < 0) 109 | node2.links.push(this); 110 | }, this); 111 | } 112 | 113 | Node.prototype.prepare = function() { 114 | this.libaryIndex = Object.keys(this.linksWeightResolved).length / this.links.length; 115 | } 116 | 117 | Node.prototype.applyForce = function(force, weight) { 118 | if(force) { 119 | this.force[0] += force[0] * weight; 120 | this.force[1] += force[1] * weight; 121 | } else { 122 | this.speed[0] *= 0.8; 123 | this.speed[1] *= 0.8; 124 | this.speed[0] += this.force[0]; 125 | this.speed[1] += this.force[1]; 126 | this.pos[0] += this.speed[0]; 127 | this.pos[1] += this.speed[1]; 128 | var d = this.speed[0]*this.speed[0] + this.speed[1]*this.speed[1]; 129 | this.force[0] = 0; 130 | this.force[1] = 0; 131 | return d; 132 | } 133 | } 134 | 135 | Node.prototype.dist2 = function(node) { 136 | var dx = this.pos[0] - node.pos[0]; 137 | var dy = this.pos[1] - node.pos[1]; 138 | return dx*dx + dy*dy; 139 | } 140 | 141 | Node.prototype.dist = function(node) { 142 | var d = this.dist2(node); 143 | return Math.sqrt(d); 144 | } 145 | 146 | Node.prototype.vector = function(node, weight) { 147 | return [ 148 | (node.pos[0] - this.pos[0]) * weight, 149 | (node.pos[1] - this.pos[1]) * weight 150 | ]; 151 | } 152 | 153 | Node.prototype.getStrongness = function(node) { 154 | var direction = this.dependOn(node); 155 | var connectionIndex = 2 / ( 156 | direction ? ( 157 | 1/this.libaryIndex + node.libaryIndex 158 | ) : ( 159 | this.libaryIndex + 1/node.libaryIndex) 160 | ); 161 | var libaryIndex = 1 / Math.max(this.libaryIndex * Math.sqrt(this.links.length), node.libaryIndex * Math.sqrt(node.links.length)); 162 | var value = (connectionIndex + libaryIndex * Node.WEIGHT_LIBARY_INDEX) / (1 + Node.WEIGHT_LIBARY_INDEX); 163 | return value * value; 164 | } 165 | 166 | Node.prototype.getDetraction = function(node) { 167 | var connections = Math.max(1/(this.libaryIndex+1), this.libaryIndex) + Math.max(1/(node.libaryIndex+1), node.libaryIndex) + 1; 168 | var extraDetraction = 0; 169 | if(this.files.join() != node.files.join()) 170 | extraDetraction = Node.EXTRA_DETRACTION_FILES; 171 | return Math.sqrt(connections) + extraDetraction; 172 | } 173 | 174 | Node.prototype.dependOn = function(node) { 175 | return !!this.linksWeightResolved[node.id]; 176 | } 177 | 178 | Node.prototype.asyncLink = function(node) { 179 | return !!this.linksAsyncResolved[node.id]; 180 | } 181 | 182 | module.exports = Node; -------------------------------------------------------------------------------- /lib/Graph.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | var VERY_SMALL = 0.00001; 6 | 7 | var COLORS = []; 8 | 9 | for(var r = 0; r < 255; r+= 80) { 10 | for(var g = 0; g < 255; g+= 80) { 11 | for(var b = 0; b < 255; b+= 80) { 12 | if(r+g+b >= 100 && r+g+b <= 550) 13 | COLORS.push([r, g, b]); 14 | } 15 | } 16 | } 17 | 18 | var Node = require("./Node"); 19 | 20 | function Graph(options, stats) { 21 | 22 | if(!options) options = {}; 23 | this.options = options; 24 | 25 | this.FORCE_INTER = 2; 26 | this.FORCE_HIT = 0.1; 27 | this.FORCE_LINK = 0.02; 28 | this.LINK_DISTANCE_EXTRA = 300; 29 | this.LINK_DISTANCE_BASE = 300; 30 | this.LINK_DISTANCE_ASYNC = 500; 31 | this.MODULE_SCALE = 2; 32 | this.MODULE_MIN_R = 5; 33 | this.MODULE_MAX_R = 20; 34 | this.INITIAL_POS_X = options.initalX || 600; 35 | this.INITIAL_POS_Y = options.initalY || 7000; 36 | this.MIN_DISTANCE = 400; 37 | this.colors = options.colors || COLORS; 38 | this.nameShortener = options.nameShortener || function(a) { return a }; 39 | 40 | 41 | if(stats) this.init(stats); 42 | } 43 | Graph.Node = Node; 44 | 45 | Graph.prototype.getColor = function(chunks, from, map) { 46 | var colors = from; 47 | var colorsMap = map; 48 | var name = chunks.join(" "); 49 | if(colorsMap[name]) return colorsMap[name]; 50 | if(colors.length == 0) colors.push.apply(colors, options.colors || COLORS); 51 | return colorsMap[name] = colors.pop(); 52 | } 53 | 54 | Graph.prototype._colorName = function(color) { 55 | return "rgb(" + color[0] + "," + color[1] + "," + color[2] + ")"; 56 | } 57 | 58 | Graph.prototype.init = function(stats) { 59 | 60 | var modulesList = this.modulesList = []; 61 | 62 | for(var i = 0; i < stats.modulesCount; i++) { 63 | var node = Node.createFromStats(i, stats); 64 | if(!node) { 65 | continue; 66 | } 67 | modulesList.push(node); 68 | } 69 | 70 | this._prepareNodes(); 71 | this._prepareMaps(); 72 | 73 | this.resetPositions(); 74 | } 75 | 76 | Graph.prototype._prepareNodes = function() { 77 | 78 | var modulesList = this.modulesList; 79 | var modules = {}; 80 | modulesList.forEach(function(node) { 81 | modules[node.name || node.id] = node; 82 | }); 83 | 84 | modulesList.forEach(function(node) { 85 | node.resolveLinks(modules); 86 | }); 87 | modulesList.forEach(function(node) { 88 | node.propageLinks(); 89 | }); 90 | modulesList.forEach(function(node) { 91 | node.prepare(); 92 | }); 93 | 94 | } 95 | 96 | Graph.prototype.resetPositions = function() { 97 | 98 | var modulesList = this.modulesList; 99 | 100 | modulesList.forEach(function(node) { 101 | delete node.pos; 102 | node.speed = [0, 0]; 103 | }); 104 | 105 | var layer = 1, position = 0; 106 | var queue = [modulesList[0], null]; 107 | queue[0].pos = [0, 0]; 108 | while(queue.length > 1) { 109 | var node = queue.shift(); 110 | 111 | if(!node) { 112 | queue.push(null); 113 | layer++; position = 0; 114 | continue; 115 | } 116 | 117 | node.links.forEach(function(node2) { 118 | if(!node2.pos) { 119 | node2.pos = [(position++) * this.INITIAL_POS_X, layer * this.INITIAL_POS_Y]; 120 | queue.push(node2); 121 | } 122 | }, this); 123 | } 124 | 125 | } 126 | 127 | Graph.prototype._prepareMaps = function() { 128 | 129 | var detractionMap = this.detractionMap = {}; 130 | var strongnessMap = this.strongnessMap = {}; 131 | 132 | var modulesList = this.modulesList; 133 | modulesList.forEach(function(node) { 134 | var currentDetraction = detractionMap[node.id] = {}; 135 | var currentStrongness = strongnessMap[node.id] = {}; 136 | modulesList.forEach(function(node2) { 137 | if(node === node2) return; 138 | currentDetraction[node2.id] = node.getDetraction(node2); 139 | }); 140 | node.links.forEach(function(node2) { 141 | currentStrongness[node2.id] = node.getStrongness(node2); 142 | }); 143 | }); 144 | 145 | } 146 | 147 | Graph.prototype.load = function(json) { 148 | 149 | this.colorsMap = json.colorsMap; 150 | this.scale = json.scale; 151 | this.modulesList = json.modulesList.map(Node.createFromJSON); 152 | 153 | this._prepareNodes(); 154 | this._prepareMaps(); 155 | 156 | return this; 157 | } 158 | 159 | Graph.prototype.toJSON = function() { 160 | return { 161 | scale: this.scale, 162 | modulesList: this.modulesList 163 | } 164 | } 165 | 166 | Graph.prototype.simulateStep = function() { 167 | var movement = 0; 168 | var modulesList = this.modulesList; 169 | modulesList.forEach(function(node) { 170 | modulesList.forEach(function(node2) { 171 | if(node === node2) return; 172 | var distance = node.dist(node2); 173 | var detraction = this.detractionMap[node.id][node2.id]; 174 | if(distance < VERY_SMALL) { 175 | node.applyForce([0.5-Math.random(), 0.5-Math.random()], FORCE_HIT * MIN_DISTANCE); 176 | } else { 177 | var vector = node2.vector(node, 1/distance); 178 | var overMin = (this.MIN_DISTANCE - distance); 179 | if(distance < this.MIN_DISTANCE) { 180 | node.applyForce(vector, this.FORCE_HIT * overMin); 181 | } 182 | node.applyForce(vector, this.FORCE_INTER*this.MIN_DISTANCE*this.MIN_DISTANCE/distance/distance * detraction); 183 | } 184 | }, this); 185 | node.links.forEach(function(node2) { 186 | if(node === node2) return; 187 | var distance = node.dist(node2); 188 | var async = node.asyncLink(node2) || node2.asyncLink(node); 189 | var linkDistance = async ? this.LINK_DISTANCE_ASYNC : this.LINK_DISTANCE_BASE; 190 | var strongness = this.strongnessMap[node.id][node2.id]; 191 | linkDistance += this.LINK_DISTANCE_EXTRA / strongness; 192 | if(distance < VERY_SMALL) { 193 | node.applyForce([0.5-Math.random(), 0.5-Math.random()], FORCE_LINK * linkDistance * strongness); 194 | } else { 195 | node.applyForce(node.vector(node2, 1/distance), this.FORCE_LINK * (distance - linkDistance) * strongness); 196 | } 197 | }, this); 198 | }, this); 199 | modulesList.forEach(function(node) { 200 | movement += node.applyForce(); 201 | }); 202 | return movement; 203 | } 204 | 205 | Graph.prototype.normalizePositions = function() { 206 | var modulesList = this.modulesList; 207 | var min = [modulesList[0].pos[0], modulesList[0].pos[1]]; 208 | var max = [modulesList[0].pos[0], modulesList[0].pos[1]]; 209 | modulesList.forEach(function(node) { 210 | if(node.pos[0] < min[0]) min[0] = node.pos[0]; 211 | if(node.pos[1] < min[1]) min[1] = node.pos[1]; 212 | if(node.pos[0] > max[0]) max[0] = node.pos[0]; 213 | if(node.pos[1] > max[1]) max[1] = node.pos[1]; 214 | }); 215 | var scale = this.scale = Math.min( 216 | ((this.options.width || 1920) - this.MODULE_MAX_R*2) / (max[0] - min[0]), 217 | ((this.options.height || 1080) - this.MODULE_MAX_R*2) / (max[1] - min[1]) 218 | ); 219 | modulesList.forEach(function(node) { 220 | node.pos[0] -= min[0]; 221 | node.pos[1] -= min[1]; 222 | }); 223 | this.width = max[0] - min[0]; 224 | this.height = max[1] - min[1]; 225 | } 226 | 227 | Graph.prototype.writeSVG = function(svg) { 228 | var svgModules = [], svgDependencies = []; 229 | var modulesList = this.modulesList; 230 | 231 | var scale = this.scale; 232 | 233 | svg.push(''); 234 | svg.push(''); 235 | svg.push(''); 246 | var colors = [this.colors.slice(0), this.colors.slice(0), this.colors.slice(0)]; 247 | var colorsMaps = [{}, {}, {}]; 248 | modulesList.forEach(function(node, nodeidx) { 249 | var color = []; 250 | color.push(this.getColor(node.files, colors[0], colorsMaps[0]));; 251 | color.push(this.getColor(node.loaders || [], colors[1], colorsMaps[1])); 252 | var match = /(?:web|node)_modules[\\\/]([^\\\/]+)(?:[\\\/])[^!]*?$/.exec(node.name); 253 | match = match ? [match[1]] : []; 254 | color.push(this.getColor(match, colors[2], colorsMaps[2])); 255 | var screenPos = [ 256 | node.pos[0]*scale + this.MODULE_MAX_R, 257 | node.pos[1]*scale + this.MODULE_MAX_R 258 | ] 259 | svgModules.push(''); 265 | svgModules.push('',this.nameShortener(node.name).split("!").join("\n\n")+'\n\nChunks:\n' + node.files.join("\n")); 266 | svgModules.push(''); 267 | node.links.forEach(function(node2, linkidx) { 268 | var startNode = node, endNode = node2; 269 | if(node2.id < node.id) { 270 | startNode = node2; 271 | endNode = node; 272 | } 273 | startNode = [ 274 | startNode.pos[0]*scale + this.MODULE_MAX_R, 275 | startNode.pos[1]*scale + this.MODULE_MAX_R 276 | ]; 277 | endNode = [ 278 | endNode.pos[0]*scale + this.MODULE_MAX_R, 279 | endNode.pos[1]*scale + this.MODULE_MAX_R 280 | ]; 281 | var strongness = this.strongnessMap[node.id][node2.id]; 282 | var async = node.asyncLink(node2) || node2.asyncLink(node); 283 | var className = async ? "async " : "" 284 | if(node.dependOn(node2)) 285 | svg.push('') 286 | if(node.dependOn(node2) && node2.dependOn(node)) 287 | className += "reqdep"; 288 | else if(node.dependOn(node2)) 289 | className += "dep"; 290 | else if(node2.dependOn(node)) { 291 | className += "req"; 292 | } 293 | svgDependencies.push('') 294 | }, this); 295 | }, this); 296 | svgDependencies.forEach(function(i) { 297 | svg.push(i); 298 | }); 299 | svgModules.forEach(function(i) { 300 | svg.push(i); 301 | }); 302 | 303 | if(this.options.colorSwitch) { 304 | svg.push(''); 305 | svg.push(''); 306 | svg.push(''); 307 | } 308 | 309 | svg.push(''); 341 | svg.push(''); 342 | } 343 | 344 | Graph.prototype.applyTo = function(document) { 345 | var modulesList = this.modulesList; 346 | var scale = this.scale; 347 | var wpgraph = document.getElementById("wpgraph"); 348 | wpgraph.setAttribute("width", "" + (this.width*scale + this.MODULE_MAX_R*2)); 349 | wpgraph.setAttribute("height", "" + (this.height*scale + this.MODULE_MAX_R*2)); 350 | modulesList.forEach(function(node, nodeidx) { 351 | var screenPos = [ 352 | node.pos[0]*scale + this.MODULE_MAX_R, 353 | node.pos[1]*scale + this.MODULE_MAX_R 354 | ] 355 | var el = document.getElementById("module" + nodeidx); 356 | el.setAttribute("r", "" + Math.max(this.MODULE_MIN_R, Math.min(this.MODULE_MAX_R, Math.sqrt(node.size)*this.MODULE_SCALE*scale))); 357 | el.setAttribute("cx", "" + screenPos[0]); 358 | el.setAttribute("cy", "" + screenPos[1]); 359 | node.links.forEach(function(node2, linkidx) { 360 | var elements = [ 361 | document.getElementById("module" + nodeidx + "line" + linkidx), 362 | document.getElementById("module" + nodeidx + "link" + linkidx)]; 363 | elements.forEach(function(el) { 364 | if(!el) return; 365 | el.setAttribute("x1", "" + screenPos[0]); 366 | el.setAttribute("y1", "" + screenPos[1]); 367 | el.setAttribute("x2", "" + (node2.pos[0]*scale + this.MODULE_MAX_R)); 368 | el.setAttribute("y2", "" + (node2.pos[1]*scale + this.MODULE_MAX_R)); 369 | }, this); 370 | }, this); 371 | }, this); 372 | } 373 | 374 | module.exports = Graph; -------------------------------------------------------------------------------- /example/webpackBrowsertest.svg: -------------------------------------------------------------------------------- 1 | .\test\browsertest\lib\index.web.js 2 | 3 | Chunks: 4 | web.js.\test\browsertest\lib\circular.js 5 | 6 | Chunks: 7 | web.js.\test\browsertest\templates 8 | 9 | Chunks: 10 | web.js.\buildin\__webpack_amd_define.js 11 | 12 | Chunks: 13 | web.js.\test\browsertest\lib\singluar.js 14 | 15 | Chunks: 16 | web.js.\buildin\__webpack_amd_require.js 17 | 18 | Chunks: 19 | web.js.\buildin\__webpack_module.js 20 | 21 | Chunks: 22 | web.js.\test\browsertest\lib\singluar2.js 23 | 24 | Chunks: 25 | web.js.\~\css-loader 26 | 27 | .\test\browsertest\css\stylesheet.css 28 | 29 | Chunks: 30 | web.js.\~\coffee-loader 31 | 32 | .\test\browsertest\resources\script.coffee 33 | 34 | Chunks: 35 | web.js.\~\jade-loader 36 | 37 | .\test\browsertest\resources\template.jade 38 | 39 | Chunks: 40 | web.js.\~\less-loader 41 | 42 | .\test\browsertest\less\stylesheet.less 43 | 44 | Chunks: 45 | web.js.\test\browsertest\lib\constructor.js 46 | 47 | Chunks: 48 | web.js.\~\css-loader 49 | 50 | .\~\val-loader 51 | 52 | .\test\browsertest\css\generateCss.js 53 | 54 | Chunks: 55 | web.js.\~\css-loader 56 | 57 | .\test\browsertest\css\folder\stylesheet-import1.css 58 | 59 | Chunks: 60 | web.js.\~\file-loader\png.js 61 | 62 | .\test\browsertest\img\image.png 63 | 64 | Chunks: 65 | web.js.\~\json-loader 66 | 67 | .\package.json 68 | 69 | Chunks: 70 | web.js.\~\raw-loader 71 | 72 | .\test\browsertest\resources\abc.txt 73 | 74 | Chunks: 75 | web.js.\~\style-loader\addStyle.web.js 76 | 77 | Chunks: 78 | web.js.\test\browsertest\~\testloader\lib\loader.webpack-loader.js 79 | 80 | .\test\browsertest\resources\abc.txt 81 | 82 | Chunks: 83 | web.js.\test\browsertest\templates\templateLoader.js 84 | 85 | Chunks: 86 | web.js.\test\browsertest\templates\templateLoaderIndirect.js 87 | 88 | Chunks: 89 | web.js.\~\raw-loader 90 | 91 | .\test\browsertest\resources 92 | 93 | Chunks: 94 | web.js.\test\browsertest\resources 95 | 96 | Chunks: 97 | web.js.\~\bundle-loader 98 | 99 | .\test\browsertest\nodetests\index.js 100 | 101 | Chunks: 102 | web.js.\~\css-loader 103 | 104 | .\~\val-loader\seperable\cacheable.js 105 | 106 | .\~\less-loader 107 | 108 | .\test\browsertest\less\stylesheet.less 109 | 110 | Chunks: 111 | web.js.\~\css-loader 112 | 113 | .\test\browsertest\css\folder\stylesheet-import3.css 114 | 115 | Chunks: 116 | web.js.\~\css-loader 117 | 118 | .\test\browsertest\~\resources-module\stylesheet-import2.css 119 | 120 | Chunks: 121 | web.js.\~\enhanced-require\lib\require.webpack.js 122 | 123 | Chunks: 124 | web.js.\~\jade-loader\~\jade\lib\runtime.js 125 | 126 | Chunks: 127 | web.js.\~\jade-loader\web_modules\fs.js 128 | 129 | Chunks: 130 | web.js.\~\raw-loader 131 | 132 | .\~\val-loader 133 | 134 | .\test\browsertest\css\generateCss.js 135 | 136 | Chunks: 137 | web.js.\~\raw-loader 138 | 139 | .\test\browsertest\js\libary1.js 140 | 141 | Chunks: 142 | web.js.\~\raw-loader 143 | 144 | .\test\browsertest\resources\script.coffee 145 | 146 | Chunks: 147 | web.js.\~\raw-loader 148 | 149 | .\test\browsertest\resources\template.jade 150 | 151 | Chunks: 152 | web.js.\~\script-loader\addScript.web.js 153 | 154 | Chunks: 155 | web.js.\~\script-loader 156 | 157 | .\test\browsertest\js\libary1.js 158 | 159 | Chunks: 160 | web.js.\~\style-loader 161 | 162 | .\~\css-loader 163 | 164 | .\~\val-loader\seperable\cacheable.js 165 | 166 | .\~\less-loader 167 | 168 | .\test\browsertest\less\stylesheet.less 169 | 170 | Chunks: 171 | web.js.\~\style-loader 172 | 173 | .\~\css-loader 174 | 175 | .\test\browsertest\css\stylesheet.css 176 | 177 | Chunks: 178 | web.js.\test\browsertest\folder\file1.js 179 | 180 | Chunks: 181 | web.js.\test\browsertest\folder\file2.js 182 | 183 | Chunks: 184 | web.js.\test\browsertest\folder\file3.js 185 | 186 | Chunks: 187 | web.js.\test\browsertest\folder\typeof.js 188 | 189 | Chunks: 190 | web.js.\test\browsertest\~\subfilemodule.js 191 | 192 | Chunks: 193 | web.js.\test\browsertest\~\testloader\lib\loader-indirect.js 194 | 195 | .\test\browsertest\resources\abc.txt 196 | 197 | Chunks: 198 | web.js.\test\browsertest\~\testloader\lib\loader.loader.js 199 | 200 | .\test\browsertest\resources\abc.txt 201 | 202 | Chunks: 203 | web.js.\test\browsertest\~\testloader\lib\loader.web-loader.js 204 | 205 | .\test\browsertest\resources\abc.txt 206 | 207 | Chunks: 208 | web.js.\test\browsertest\~\testloader\lib\loader.webpack-loader.js 209 | 210 | .\test\browsertest\loaders\reverseloader.js 211 | 212 | .\test\browsertest\resources\abc.txt 213 | 214 | Chunks: 215 | web.js.\test\browsertest\resources\abc.txt 216 | 217 | Chunks: 218 | web.js.\test\browsertest\templates\subdir\tmpl.js 219 | 220 | Chunks: 221 | web.js.\test\browsertest\templates\tmpl.js 222 | 223 | Chunks: 224 | web.js.\buildin\__webpack_process.js 225 | 226 | Chunks: 227 | 6.web.js 228 | 7.web.js.\buildin\web_modules\assert.js 229 | 230 | Chunks: 231 | 7.web.js.\test\browsertest\nodetests\common.js 232 | 233 | Chunks: 234 | 7.web.js.\buildin\__webpack_console.js 235 | 236 | Chunks: 237 | 6.web.js 238 | 7.web.js.\buildin\__webpack_global.js 239 | 240 | Chunks: 241 | 6.web.js 242 | 7.web.js.\buildin\web_modules\events.js 243 | 244 | Chunks: 245 | 6.web.js 246 | 7.web.js.\buildin\web_modules\util.js 247 | 248 | Chunks: 249 | 7.web.js.\buildin\web_modules\buffer.js 250 | 251 | Chunks: 252 | 7.web.js.\test\browsertest\lib\duplicate.js 253 | 254 | Chunks: 255 | 1.web.js 256 | 3.web.js 257 | 4.web.js 258 | 6.web.js.\test\browsertest\lib\duplicate2.js 259 | 260 | Chunks: 261 | 1.web.js 262 | 3.web.js 263 | 4.web.js 264 | 6.web.js.\test\browsertest\lib\a.js 265 | 266 | Chunks: 267 | 6.web.js 268 | 10.web.js 269 | 11.web.js.\test\browsertest\lib\acircular.js 270 | 271 | Chunks: 272 | 3.web.js 273 | 6.web.js 274 | 8.web.js.\test\browsertest\lib\acircular2.js 275 | 276 | Chunks: 277 | 4.web.js 278 | 6.web.js 279 | 9.web.js.\test\browsertest\lib\b.js 280 | 281 | Chunks: 282 | 6.web.js 283 | 11.web.js.\test\browsertest\~\vm-browserify\index.js 284 | 285 | Chunks: 286 | 7.web.js.\test\browsertest\lib 287 | 288 | Chunks: 289 | 6.web.js.\buildin\web_modules\child_process.js 290 | 291 | Chunks: 292 | 7.web.js.\buildin\web_modules\path.js 293 | 294 | Chunks: 295 | 7.web.js.\buildin\web_modules\punycode.js 296 | 297 | Chunks: 298 | 7.web.js.\buildin\web_modules\querystring.js 299 | 300 | Chunks: 301 | 7.web.js.\test\browsertest\lib\c.js 302 | 303 | Chunks: 304 | 2.web.js 305 | 6.web.js.\test\browsertest\lib\circular2.js 306 | 307 | Chunks: 308 | 6.web.js.\test\browsertest\lib\d.js 309 | 310 | Chunks: 311 | 2.web.js 312 | 6.web.js.\test\browsertest\web_modules\subcontent\index.js 313 | 314 | Chunks: 315 | 5.web.js.\test\browsertest\nodetests\simple 316 | 317 | Chunks: 318 | 7.web.js.\buildin\__webpack_filename.js 319 | 320 | Chunks: 321 | 7.web.js.\buildin\web_modules\url.js 322 | 323 | Chunks: 324 | 7.web.js.\test\browsertest\jam\subcontent-jam\index.js 325 | 326 | Chunks: 327 | 5.web.js.\test\browsertest\lib\index.js 328 | 329 | Chunks: 330 | 6.web.js.\test\browsertest\~\subcontent2\file.js 331 | 332 | Chunks: 333 | 5.web.js.\test\browsertest\nodetests\fixtures\global\plain.js 334 | 335 | Chunks: 336 | 7.web.js.\test\browsertest\nodetests\index.js 337 | 338 | Chunks: 339 | 7.web.js.\test\browsertest\nodetests\simple\test-assert.js 340 | 341 | Chunks: 342 | 7.web.js.\test\browsertest\nodetests\simple\test-event-emitter-add-listeners.js 343 | 344 | Chunks: 345 | 7.web.js.\test\browsertest\nodetests\simple\test-event-emitter-check-listener-leaks.js 346 | 347 | Chunks: 348 | 7.web.js.\test\browsertest\nodetests\simple\test-event-emitter-max-listeners.js 349 | 350 | Chunks: 351 | 7.web.js.\test\browsertest\nodetests\simple\test-event-emitter-modify-in-emit.js 352 | 353 | Chunks: 354 | 7.web.js.\test\browsertest\nodetests\simple\test-event-emitter-num-args.js 355 | 356 | Chunks: 357 | 7.web.js.\test\browsertest\nodetests\simple\test-event-emitter-once.js 358 | 359 | Chunks: 360 | 7.web.js.\test\browsertest\nodetests\simple\test-event-emitter-remove-all-listeners.js 361 | 362 | Chunks: 363 | 7.web.js.\test\browsertest\nodetests\simple\test-event-emitter-remove-listeners.js 364 | 365 | Chunks: 366 | 7.web.js.\test\browsertest\nodetests\simple\test-global.js 367 | 368 | Chunks: 369 | 7.web.js.\test\browsertest\nodetests\simple\test-next-tick-doesnt-hang.js 370 | 371 | Chunks: 372 | 7.web.js.\test\browsertest\nodetests\simple\test-next-tick-ordering2.js 373 | 374 | Chunks: 375 | 7.web.js.\test\browsertest\nodetests\simple\test-path.js 376 | 377 | Chunks: 378 | 7.web.js.\test\browsertest\nodetests\simple\test-punycode.js 379 | 380 | Chunks: 381 | 7.web.js.\test\browsertest\nodetests\simple\test-querystring.js 382 | 383 | Chunks: 384 | 7.web.js.\test\browsertest\nodetests\simple\test-sys.js 385 | 386 | Chunks: 387 | 7.web.js.\test\browsertest\nodetests\simple\test-timers-zero-timeout.js 388 | 389 | Chunks: 390 | 7.web.js.\test\browsertest\nodetests\simple\test-timers.js 391 | 392 | Chunks: 393 | 7.web.js.\test\browsertest\nodetests\simple\test-url.js 394 | 395 | Chunks: 396 | 7.web.js.\test\browsertest\nodetests\simple\test-util-format.js 397 | 398 | Chunks: 399 | 7.web.js.\test\browsertest\nodetests\simple\test-util-inspect.js 400 | 401 | Chunks: 402 | 7.web.js.\test\browsertest\nodetests\simple\test-util.js 403 | 404 | Chunks: 405 | 7.web.js --------------------------------------------------------------------------------