├── .gitignore
├── README.md
├── assets
└── example.png
├── bin
├── 1.html
├── 2.html
├── jcs.js
└── jss.js
├── lib
├── STATIC.js
├── deleteExt.js
├── getPathsSync.js
├── getRequireInfoSync.js
├── relativePathToAbsolute.js
└── returnRelations.js
├── package.json
└── test
├── deleteExt.js
├── getPathsSync.js
├── getRequireInfoSync.js
├── relativePathToAbsolute.js
└── returnRelations.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Figure out relations between your `js` files (through `require()` and `import`)
2 |
3 | [](https://www.npmjs.com/package/js-code-structure)
4 |
5 | ### Example: Appium code:
6 |
7 | 
8 |
9 | ### Install
10 |
11 | `npm install -g js-code-structure`
12 |
13 | ### Usage
14 |
15 | 1. Open a terminal
16 | 2. Go to the directory of your project
17 | 3. Input `jss`
18 |
19 | **an html file describing the relations of your the js files will be created and opened in your browser**
20 |
21 | ### Advanced Usage
22 |
23 | 1. Ignore some directory inside the directory:
24 |
25 | `jss --ignore dirname1 dirname2 dirname3 ...`
26 |
27 | > these dirs are ignored by default: `['node_modules', '.git', 'dist', 'build', 'doc', 'test', 'submodules']`
28 | 2. Show required files: hover on the node
29 | 3. Show being required files: click the node
30 |
31 |
32 | ### Tools
33 |
34 | [sigma.js](http://sigmajs.org/)
35 |
36 | ### Thanks
37 |
38 | - [command line tool](http://jslite.io/2015/06/19/Nodejs-%E5%88%B6%E4%BD%9C%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%B7%A5%E5%85%B7/)
39 | - [directory traversal](http://swordair.com/directory-traversal-in-nodejs/)
40 |
41 | ### License
42 | MIT
43 |
44 | > [Donate with bitcoin](https://getcryptoo.github.io/)
45 |
--------------------------------------------------------------------------------
/assets/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timqian/js-code-structure/2125b4625c215a2637a0e8b3ead28891269b35a5/assets/example.png
--------------------------------------------------------------------------------
/bin/1.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
22 |
94 |
--------------------------------------------------------------------------------
/bin/jcs.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | var fs = require('fs');
3 | var getPathsSync = require('../lib/getPathsSync');
4 | var getRequireInfoSync = require('../lib/getRequireInfoSync');
5 | var relativePathToAbsolute = require('../lib/relativePathToAbsolute');
6 |
7 | var currentPath = process.cwd(); //current working directory
8 | var paths = getPathsSync(currentPath);
9 | //console.log(paths);
10 |
11 | var relations = paths
12 | .map(function (path) {
13 | var reqPaths = getRequireInfoSync(path)
14 | .map(function (reqPath) {
15 | return relativePathToAbsolute(path, reqPath);
16 | });
17 | //console.log(reqPaths);
18 | var obj = {};
19 | obj[path] = {reqPaths: reqPaths,
20 | // @Todo add size
21 | size: 'todo'
22 | };
23 | return obj;
24 | });
25 |
26 |
27 | var myJsonString = JSON.stringify(relations);
28 | console.log(myJsonString);
29 | fs.writeFile('codeRelations.json', myJsonString, function (err) {
30 | if (err) throw err;
31 | console.log('It\'s saved in codeRelations.json!');
32 | });
33 |
--------------------------------------------------------------------------------
/bin/jss.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | var fs = require('fs');
3 | var path = require('path');
4 | var open = require('open');
5 | var returnRelations = require('../lib/returnRelations');
6 |
7 |
8 | var currentDir = process.cwd();
9 |
10 | var addIgnore = [];
11 | if (process.argv[2] == '--ignore') {
12 | for (var i = 3; i < process.argv.length; i++) {
13 | addIgnore.push(process.argv[i]);
14 | }
15 | }
16 | var relations = returnRelations(currentDir, addIgnore);
17 | var strRelations = JSON.stringify(relations);
18 | var html1 = fs.readFileSync( path.resolve(path.dirname(__filename), '1.html') ).toString();
19 | var html2 = fs.readFileSync( path.resolve(path.dirname(__filename), '2.html') ).toString();
20 | fs.writeFileSync('./jsCodeStructure.html', html1 + strRelations + html2);
21 | open('jsCodeStructure.html')
22 |
--------------------------------------------------------------------------------
/lib/STATIC.js:
--------------------------------------------------------------------------------
1 | module.exports.IGNORED_DIRS = ['node_modules', '.git', 'dist', 'build', 'doc', 'test', 'submodules'];
2 | module.exports.ACCEPTED_EXTS = ['.js', '.json', '.node'];
3 |
--------------------------------------------------------------------------------
/lib/deleteExt.js:
--------------------------------------------------------------------------------
1 | /**
2 | * delete extension of path with ('js', 'json' and 'node')
3 | * @param {String} filePath
4 | * @return {String} filePath without extension
5 | */
6 | var path = require('path');
7 | var ACCEPTED_EXTS = require('./STATIC').ACCEPTED_EXTS;
8 |
9 | module.exports = function (filePath) {
10 | var potentialExt = path.extname(filePath);
11 | var isExt = ACCEPTED_EXTS.indexOf(potentialExt) !== -1;
12 | if (isExt) {
13 | var dir = path.dirname(filePath);
14 | var barename = path.basename(filePath, path.extname(filePath));
15 | return path.join(dir, barename);
16 | } else {
17 | return filePath;
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/lib/getPathsSync.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Get js/json file paths in the dir(expect files inside ignored dirs);广度优先
3 | * @param {string} dir - initial dir
4 | * @param {array} addIgnore - add ignored dirs
5 | * @return fielList - paths without extension
6 | */
7 |
8 | var fs = require('fs');
9 | var path = require('path');
10 | var IGNORED_DIRS = require('./STATIC').IGNORED_DIRS;
11 | var ACCEPTED_EXTS = require('./STATIC').ACCEPTED_EXTS; //不限制后缀
12 | var fileList = [];
13 |
14 | var getPathsSync = function (dir, addIgnore){
15 | var ignored_dirs;
16 | if (Array.prototype.isPrototypeOf(addIgnore)) {
17 | ignored_dirs = addIgnore.concat(IGNORED_DIRS);
18 | } else {
19 | ignored_dirs = IGNORED_DIRS;
20 | }
21 |
22 | var dirList = fs.readdirSync(dir);
23 | // console.log(dir.resolve(dirList));
24 | dirList.forEach(function (item) {
25 | var currentPath = path.join(dir, item);
26 | var isFile = fs.statSync(currentPath).isFile();
27 | var extname = path.extname(item);
28 | //push extension满足要求的file
29 | if (isFile && ACCEPTED_EXTS.indexOf(extname) !== -1) {
30 | fileList.push(currentPath);
31 | }
32 | });
33 | dirList.forEach(function(item){
34 | var currentPath = path.join(dir, item);
35 | var isDirectory = fs.statSync(currentPath).isDirectory();
36 | //非ignored dirs: 递归
37 | if (isDirectory && ignored_dirs.indexOf(item) === -1) {
38 | getPathsSync(currentPath);
39 | }
40 | });
41 | return fileList;
42 | };
43 |
44 | module.exports = getPathsSync;
45 |
--------------------------------------------------------------------------------
/lib/getRequireInfoSync.js:
--------------------------------------------------------------------------------
1 | /**
2 | * get required files of the input filePath
3 | * @param {string} filePath
4 | * @return {array} results
5 | */
6 |
7 | var fs = require('fs');
8 | // 当使用new RegExp("pattern")方法的时候不要忘记将\它自己进行转义,因为\在字符串里面也是一个转义字符。
9 | var patt = /require\(['|"](.*?)['|"]\)/g;
10 | var es6Patt = /import\s.*?['|"](.*?)['|"]/g;
11 |
12 | // return required files as an Array
13 | var getRequireInfoSync = function (filePath) {
14 | var result,
15 | results = [],
16 | data = fs.readFileSync(filePath, 'utf-8');
17 | while ((result = patt.exec(data)) !== null) {
18 | results.push(result[1]); //or use result[1]?
19 | // console.log(result[1]);
20 | }
21 |
22 | // es6 import
23 | while ((result = es6Patt.exec(data)) !== null) {
24 | results.push(result[1]); //or use result[1]?
25 | // console.log(result[1]);
26 | }
27 | return results;
28 | };
29 |
30 | module.exports = getRequireInfoSync;
31 |
--------------------------------------------------------------------------------
/lib/relativePathToAbsolute.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file transfrom relative path (if it is)
3 | * @param {string} filePath - path of the file
4 | * @param {string[]} requiredPaths - relative path accoriding to the file
5 | * @return {string[]} path - filePaths accoriding to the entry()
6 | */
7 |
8 | var path = require('path');
9 | module.exports = function (from, to) {
10 | var absolute = '';
11 | if (to[0] === '.') {
12 | absolute = path.resolve(from, '..', to);
13 | return absolute;
14 | } else {
15 | return to;
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/lib/returnRelations.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file return relations of files based on the rule of sigma.js
3 | * @param currentPath - absolute path
4 | * @return json - describing file relations
5 | */
6 |
7 | var path = require('path');
8 | var getPathsSync = require('./getPathsSync');
9 | var getRequireInfoSync = require('./getRequireInfoSync');
10 | var relativePathToAbsolute = require('./relativePathToAbsolute');
11 | var deleteExt = require('./deleteExt');
12 |
13 | module.exports = function (currentDir, addIgnore) {
14 | var jsPaths = getPathsSync(currentDir, addIgnore);
15 | var nodes = [];
16 | var edges = [];
17 | var jsDir = '';
18 | var index = 0; // node index
19 | var dx = 0;
20 |
21 | for (var i = 0; i < jsPaths.length; i++) {
22 | var jsPath = jsPaths[i];
23 |
24 | // push to node
25 | var jsPathArray = path.relative(currentDir, jsPath).split(path.sep);
26 |
27 | // 如果这个js文件目录与上一个不同,为目录建一个node
28 | if (path.dirname(jsPath) !== jsDir) {
29 | index += 1;
30 | jsDir = path.dirname(jsPath);
31 | nodes.push({
32 | id: 'dir' + jsDir,
33 | label: path.basename(jsDir) + ': ',
34 | x: jsPathArray.length -1,
35 | y: index + 1,
36 | color: '#ccc',
37 | size: 1
38 | });
39 | // index += 1;
40 | dx = 0;
41 | } else {
42 | dx += 1;
43 | }
44 | // js文件node
45 | nodes.push({
46 | id: deleteExt(jsPath), // path without extension
47 | label: path.basename(jsPath),
48 | x: jsPathArray.length + dx,
49 | y: index + 1,
50 | color: '#c0c',
51 | size:1
52 | });
53 | // index += 1;
54 | //dx += 1;
55 | /*** push to edges ******************************/
56 | var reqPathList = getRequireInfoSync(jsPath);
57 | for (var j = 0; j < reqPathList.length; j++) {
58 | var absolutePath = deleteExt(relativePathToAbsolute(jsPath, reqPathList[j]));
59 | //TODO: 1.暂时不管内置的和npm安装的模块调用,待改善; 2.在win系统上可能有问题!
60 | if(absolutePath.slice(0,1) === '/'){
61 | edges.push({
62 | id: 'e' + i +'-' + j,
63 | source: absolutePath,
64 | target: deleteExt(jsPath),
65 | size: 1,
66 | type: 'curvedArrow',
67 | color: '#ccc',
68 | hover_color: '#000'
69 | });
70 | }
71 | }
72 | }
73 |
74 | // 为了使线条细一些,增加一条粗一些的edge
75 | edges.push({
76 | id: 'eRef',
77 | source: 'dir' + currentDir,
78 | target: 'dir' + currentDir,
79 | size: 2,
80 | type: 'curvedArrow',
81 | color: '#ccc',
82 | hover_color: '#000'
83 | });
84 |
85 | // 当有一些意外require规则没找到时,防止sourceId指向不存在的nodeId
86 | var nodeIds = nodes.map(function (node) {
87 | return node.id;
88 | });
89 |
90 | var legalEdges = edges.filter(function (edge) {
91 | var sourceId = edge.source;
92 | if(nodeIds.indexOf(sourceId) !== -1) {
93 | return edge;
94 | }
95 | });
96 | // console.log(legalEdges);
97 |
98 | return {
99 | nodes: nodes,
100 | edges: legalEdges
101 | };
102 |
103 | };
104 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-code-structure",
3 | "version": "0.1.11",
4 | "description": "Analyse the structure of your js project(relations between js files)",
5 | "bin": {
6 | "jcs": "bin/jcs.js",
7 | "jss": "bin/jss.js"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "author": "timqian",
13 | "license": "MIT",
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/timqian/jsCodeStructure"
17 | },
18 | "dependencies": {
19 | "open": "0.0.5"
20 | },
21 | "devDependencies": {
22 | "mocha": "^2.2.5",
23 | "should": "^9.0.2"
24 | },
25 | "keywords": [
26 | "file structure",
27 | "代码结构分析"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/test/deleteExt.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 | var deleteExt = require('../lib/deleteExt');
3 |
4 | describe('deleteExt', function () {
5 |
6 | it('delete .js', function () {
7 | deleteExt('/a/b/c.js').should.equal('/a/b/c')
8 | deleteExt('/a/b/c.json').should.equal('/a/b/c')
9 | deleteExt('/a/b/c.node').should.equal('/a/b/c')
10 | });
11 |
12 | it('not deleting other file format(maybe path)', function () {
13 | deleteExt('/a/b/c.java').should.equal('/a/b/c.java')
14 | });
15 |
16 | });
17 |
18 |
--------------------------------------------------------------------------------
/test/getPathsSync.js:
--------------------------------------------------------------------------------
1 | //var assert = require('assert');
2 |
3 | // describe('测试getPathsSync方法', function () {
4 | // it('should return all the dirs important', function () {
5 | //
6 | // });
7 | // });
8 |
9 | //
10 | var getPathsSync = require('../lib/getPathsSync');
11 | console.log('Paths under /Users/junhuachen/Documents/tim/jsCodeStructure/lib: ');
12 | console.log(getPathsSync('/Users/junhuachen/Documents/tim/jsCodeStructure/lib'));
13 |
--------------------------------------------------------------------------------
/test/getRequireInfoSync.js:
--------------------------------------------------------------------------------
1 | // 需要在主目录下执行
2 | var getRequireInfoSync = require('../lib/getRequireInfoSync');
3 |
4 | console.log('should return required moudles of bin/jcs');
5 | console.log(getRequireInfoSync('/Users/junhuachen/Documents/tim/jsCodeStructure/bin/jcs.js'));
6 |
--------------------------------------------------------------------------------
/test/relativePathToAbsolute.js:
--------------------------------------------------------------------------------
1 | var relativePathToAbsolute = require('../lib/relativePathToAbsolute');
2 |
3 | // ''./a/d.js'
4 | console.log('should return ./a/d.js');
5 | console.log(relativePathToAbsolute('/a/b/c.js', '../d.js'));
6 | console.log('should return fs');
7 | console.log(relativePathToAbsolute('/a/b/c.js', 'fs'));
8 | console.log('should return /fs.js');
9 | console.log(relativePathToAbsolute('/a/b/c.js', '/fs.js'));
10 |
--------------------------------------------------------------------------------
/test/returnRelations.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var returnRelations = require('../lib/returnRelations');
3 | var relationsInAppium = returnRelations('/Users/timqian/Documents/javascript/js-code-structure');
4 |
5 | var nodeIds = relationsInAppium.nodes.map(function (node) {
6 | return node.id;
7 | });
8 |
9 | var sourceIds = relationsInAppium.edges.map(function (edge) {
10 | return edge.source;
11 | });
12 |
13 | var targetIds = relationsInAppium.edges.map(function (edge) {
14 | return edge.target;
15 | });
16 |
17 | var myJsonString = JSON.stringify(relationsInAppium);
18 | //console.log(myJsonString);
19 | fs.writeFile('codeRelations.json', myJsonString, function (err) {
20 | if (err) throw err;
21 | console.log('json saved in codeRelations.json!');
22 | });
23 |
24 | sourceIds.forEach(function (sourceId) {
25 | try {
26 | if(nodeIds.indexOf(sourceId) == -1) throw sourceId;
27 | } catch (e) {
28 | console.log(e);
29 | }
30 | });
31 |
--------------------------------------------------------------------------------