Browse files

An attempt at showing cycles.

  • Loading branch information...
1 parent 4839785 commit 90a3aae72c80edbe47e1e07145b769d601e19cfe @jrburke jrburke committed Jun 28, 2012
Showing with 234 additions and 15 deletions.
  1. +21 −0 README.md
  2. +1 −0 package.json
  3. +12 −2 parts/build.js
  4. +37 −0 parts/templates/cycle.html
  5. +1 −0 parts/templates/cycleChainEntry.html
  6. +6 −0 parts/templates/cycleEntry.html
  7. +156 −13 xrayquire.js
View
21 README.md
@@ -27,3 +27,24 @@ Place it as a script tag right after require.js:
Checks for module IDs that differ only by case, which indicates a probable
typing error, or something that will lead to problems on case insensitive
operating systems.
+
+## Information views
+
+There are some information views about the modules that were loaded. These
+views are shown by popping a new window to a data: URL that has the display.
+
+These displays are still very new, need lots of work.
+
+### Dependency tree
+
+To see the dependency tree for all the modules loaded, enter the following in
+the browser developer tool's console:
+
+ xrayquire.showTree()
+
+### Show cycles
+
+To show cycles (circular dependencies) in the modules loaded, enter the
+following in the browser developer tool's console:
+
+ xrayquire.showCycles()
View
1 package.json
@@ -1,4 +1,5 @@
{
+ "name": "xrayquire",
"volo": {
"url": "https://raw.github.com/requirejs/xrayquire/{version}/xrayquire.js"
}
View
14 parts/build.js
@@ -25,15 +25,25 @@ function jsRead(path) {
var fs = require('fs'),
dir = __dirname + '/',
contents = read(dir + '../xrayquire.js'),
+
tree = jsRead(dir + 'templates/tree.html'),
treeDepItem = jsRead(dir + 'templates/treeDepItem.html'),
treeDepItemNoLink = jsRead(dir + 'templates/treeDepItemNoLink.html'),
- treeItem = jsRead(dir + 'templates/treeItem.html');
+ treeItem = jsRead(dir + 'templates/treeItem.html'),
+
+ cycle = jsRead(dir + 'templates/cycle.html'),
+ cycleEntry = jsRead(dir + 'templates/cycleEntry.html'),
+ cycleChainEntry = jsRead(dir + 'templates/cycleChainEntry.html');
contents = contents
.replace(/treeHtml: '.*?',[\r\n]/, "treeHtml: '" + tree + "',\n")
.replace(/treeDepItemHtml: '.*?',[\r\n]/, "treeDepItemHtml: '" + treeDepItem + "',\n")
.replace(/treeDepItemNoLinkHtml: '.*?',[\r\n]/, "treeDepItemNoLinkHtml: '" + treeDepItemNoLink + "',\n")
- .replace(/treeItemHtml: '.*?',[\r\n]/, "treeItemHtml: '" + treeItem + "',\n");
+ .replace(/treeItemHtml: '.*?',[\r\n]/, "treeItemHtml: '" + treeItem + "',\n")
+
+ .replace(/cycleHtml: '.*?',[\r\n]/, "cycleHtml: '" + cycle + "',\n")
+ .replace(/cycleEntryHtml: '.*?',[\r\n]/, "cycleEntryHtml: '" + cycleEntry + "',\n")
+ .replace(/cycleChainEntryHtml: '.*?',[\r\n]/, "cycleChainEntryHtml: '" + cycleChainEntry + "',\n");
+
fs.writeFileSync(dir + '../xrayquire.js', contents, 'utf8');
View
37 parts/templates/cycle.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Module Cycles</title>
+<style>
+body {
+ font-family: "Inconsolata",Andale Mono,Monaco,Monospace;
+ color: green;
+}
+
+a {
+ color: #2E87DD;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+.mod {
+ background-color: #FAFAFA;
+ border: 1px solid #E6E6E6;
+ border-radius: 5px 5px 5px 5px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+ font-size: 13px;
+ line-height: 18px;
+ margin: 7px 0 21px;
+ overflow: auto;
+ padding: 5px 10px;
+}
+
+</style>
+</head>
+<body>
+{content}
+</body>
+</html>
View
1 parts/templates/cycleChainEntry.html
@@ -0,0 +1 @@
+<li>{id}</li>
View
6 parts/templates/cycleEntry.html
@@ -0,0 +1,6 @@
+<div class="mod">
+ <span class="id">{id}</span>
+ <ul class="chain">
+ {chain}
+ </ul>
+</div>
View
169 xrayquire.js
@@ -54,6 +54,35 @@ var xrayquire;
}
}
+ function hasProp(obj, prop) {
+ return obj.hasOwnProperty(prop);
+ }
+
+ /**
+ * Simple function to mix in properties from source into target,
+ * but only if target does not already have a property of the same name.
+ * This is not robust in IE for transferring methods that match
+ * Object.prototype names, but the uses of mixin here seem unlikely to
+ * trigger a problem related to that.
+ */
+ function mixin(target, source, force, deepStringMixin) {
+ if (source) {
+ eachProp(source, function (value, prop) {
+ if (force || !hasProp(target, prop)) {
+ if (deepStringMixin && typeof value !== 'string') {
+ if (!target[prop]) {
+ target[prop] = {};
+ }
+ mixin(target[prop], value, force, deepStringMixin);
+ } else {
+ target[prop] = value;
+ }
+ }
+ });
+ }
+ return target;
+ }
+
function isRequire(id) {
return id.indexOf('_@r') !== -1;
}
@@ -178,6 +207,14 @@ var xrayquire;
};
+ function sortTraceOrder(traceOrder) {
+ //Sort the traceOrder, but do it by lowercase comparisons,
+ //to keep 'something' and 'Something' next to each other.
+ traceOrder.sort(function (a, b) {
+ return a.toLowerCase() > b.toLowerCase() ? 1 : -1;
+ });
+ }
+
function htmlEscape(id) {
return (id || '')
.replace('<', '&lt;')
@@ -199,6 +236,58 @@ var xrayquire;
});
}
+
+ function findCycle(mod, traced, masterVisited, visited) {
+ var id = mod.map.id,
+ depArray = mod.deps,
+ foundModule;
+
+ //Do not bother with require calls or standard deps,
+ //or things that are already listed in a cycle
+ if (isRequire(id) || masterVisited[id] || standardDeps[id]) {
+ return;
+ }
+
+ //Found the cycle.
+ if (visited[id]) {
+ return {
+ mod: mod,
+ visited: visited
+ };
+ }
+
+ visited[id] = true;
+
+ //Trace through the dependencies.
+ each(depArray, function (depMap) {
+ var depId = depMap.id,
+ depMod = traced[depId];
+
+ if (!depMod) {
+ return;
+ }
+
+ //mixin visited to a new object for each dependency, so that
+ //sibling dependencies in this object to not generate a
+ //false positive match on a cycle. Ideally an Object.create
+ //type of prototype delegation would be used here, but
+ //optimizing for file size vs. execution speed since hopefully
+ //the trees are small for circular dependency scans relative
+ //to the full app perf.
+ return (foundModule = findCycle(depMod, traced, masterVisited, mixin({}, visited)));
+ });
+
+ return foundModule;
+ }
+
+ function showHtml(html) {
+ //Convert to URL encoded data
+ html = encodeURIComponent(html);
+
+ //Display the HTML
+ window.open('data:text/html;charset=utf-8,' + html, '_blank');
+ }
+
/**
* Public API
*/
@@ -220,18 +309,13 @@ var xrayquire;
};
},
- showTree: function (context) {
- context = context || requirejs.s.contexts._;
-
- var xray = getX(context),
+ showTree: function (contextName) {
+ var context = requirejs.s.contexts[contextName || '_'],
+ xray = getX(context),
traced = xray.traced,
html = '';
- //Sort the traceOrder, but do it by lowercase comparisons,
- //to keep 'something' and 'Something' next to each other.
- xray.traceOrder.sort(function (a, b) {
- return a.toLowerCase() > b.toLowerCase() ? 1 : -1;
- });
+ sortTraceOrder(xray.traceOrder);
//Generate the HTML
each(xray.traceOrder, function (id) {
@@ -263,11 +347,70 @@ var xrayquire;
content: html
});
- //Convert to URL encoded data
- html = encodeURIComponent(html);
+ showHtml(html);
+ },
+
+ getCycles: function (contextName) {
+ var context = requirejs.s.contexts[contextName || '_'],
+ cycles = {},
+ xray = getX(context),
+ traced = xray.traced,
+ masterVisited = {},
+ foundCycle = false;
+
+ sortTraceOrder(xray.traceOrder);
+
+ each(xray.traceOrder, function (id) {
+ var mod = traced[id],
+ cycleInfo = findCycle(mod, traced, masterVisited, {});
+
+ if (cycleInfo) {
+ foundCycle = true;
+ mod = cycleInfo.mod;
+ mixin(masterVisited, cycleInfo.visited);
+
+ cycles[mod.map.id] = {
+ visited: cycleInfo.visited
+ };
+ }
+ });
+
+ return foundCycle ? cycles : null;
+ },
+
+ cycleHtml: '<!DOCTYPE html>\n<html>\n<head>\n<title>Module Cycles</title>\n<style>\nbody {\n font-family: \"Inconsolata\",Andale Mono,Monaco,Monospace;\n color: green;\n}\n\na {\n color: #2E87DD;\n text-decoration: none;\n}\n\na:hover {\n text-decoration: underline;\n}\n\n.mod {\n background-color: #FAFAFA;\n border: 1px solid #E6E6E6;\n border-radius: 5px 5px 5px 5px;\n box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);\n font-size: 13px;\n line-height: 18px;\n margin: 7px 0 21px;\n overflow: auto;\n padding: 5px 10px;\n}\n\n</style>\n</head>\n<body>\n{content}\n</body>\n</html>\n',
+ cycleEntryHtml: '<div class=\"mod\">\n <span class=\"id\">{id}</span>\n <ul class=\"chain\">\n {chain}\n </ul>\n</div>\n',
+ cycleChainEntryHtml: '<li>{id}</li>',
+
+ showCycles: function (contextName) {
+ var cycles = xrayquire.getCycles(contextName),
+ html = '';
+
+ if (cycles) {
+ eachProp(cycles, function (cycle, id) {
+ var chainHtml = '';
+ eachProp(cycle.visited, function (value, cycleId) {
+ if (cycleId !== id) {
+ chainHtml += template(xrayquire.cycleChainEntryHtml, {
+ id: cycleId
+ });
+ }
+ });
+
+ html += template(xrayquire.cycleEntryHtml, {
+ id: id,
+ chain: chainHtml
+ });
+ });
+ } else {
+ html = 'No cycles found';
+ }
+
+ html = template(xrayquire.cycleHtml, {
+ content: html
+ });
- //Display the HTML
- window.open('data:text/html;charset=utf-8,' + html, '_blank');
+ showHtml(html);
}
};
}());

0 comments on commit 90a3aae

Please sign in to comment.