├── .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 | [![npm](https://nodei.co/npm/js-code-structure.png?downloadRank=true)](https://www.npmjs.com/package/js-code-structure) 4 | 5 | ### Example: Appium code: 6 | 7 | ![appium code](./assets/example.png) 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 |
11 | 20 |
21 |
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 | --------------------------------------------------------------------------------