TypeScriptで作成したコンポーネントの依存関係をさっと見るためのツール
$ cd <project_folder>
$ yarn build
$ cd -
$ node parser.js referenced <project_folder>/tsconfig.tsbuildinfo --parent=organisms/component.vue
const fs = require('fs') | |
const usage = "node parser.js referenced|exported path --reverse(-r) --depth(-d)=0 --last(-l) --include(-i)=path --exclude(-e)=path --parent(-p)=path --json(-j)" | |
let options = { | |
target: "referencedMap", | |
isReverse: false, | |
isPrintLastOnly: false, | |
isPrintJson: false, | |
printDepth: null, | |
parent: "", | |
includeConditions: ["/src/"], | |
excludeConditions: ["/node_modules/"] | |
} | |
let path = '' | |
let arguments = process.argv.slice(2) | |
if (arguments.length === 0) { | |
console.log(usage) | |
return | |
} | |
if (arguments[0] === 'exported') { | |
options.target = 'exportedModulesMap' | |
} | |
let isUpdateIncludeConditions = false | |
let isUpdateExcludeConditions = false | |
arguments.slice(1).forEach(argument => { | |
if (argument[0] === '-') { | |
let splitted = argument.split("=") | |
switch (splitted[0]) { | |
case '--reverse': | |
case '-r': | |
options.isReverse = true | |
break | |
case '--last': | |
case '-l': | |
options.isPrintLastOnly = true | |
break | |
case '--json': | |
case '-j': | |
options.isPrintJson = true | |
break | |
case '--depth': | |
case '-d': | |
options.printDepth = parseInt(splitted[1]) | |
break | |
case '--parent': | |
case '-p': | |
options.parent = splitted[1] | |
break | |
case '--include': | |
case '-i': | |
if (!isUpdateIncludeConditions) options.includeConditions = [] | |
if (splitted.length > 1) options.includeConditions.push(splitted[1]) | |
isUpdateIncludeConditions = true | |
break | |
case '--exclude': | |
case '-e': | |
if (!isUpdateExcludeConditions) options.excludeConditions = [] | |
if (splitted.length > 1) options.excludeConditions.push(splitted[1]) | |
isUpdateExcludeConditions = true | |
break | |
} | |
} else { | |
path = argument | |
} | |
}) | |
if (path.length === 0) { | |
console.log(usage) | |
return | |
} | |
// よみこみ | |
function readFile(path) { | |
let buff = null | |
try { | |
buff = fs.readFileSync(path, "utf8"); | |
} | |
catch(e) { | |
console.log('ERROR: readFile', e.message); | |
} | |
return buff | |
} | |
// 絞込み | |
function isInclude(filePath) { | |
const includes = options.includeConditions.length === 0 || options.includeConditions.some(w => filePath.indexOf(w) > -1) | |
const excludes = options.excludeConditions.some(w => filePath.indexOf(w) > -1) | |
return includes && !excludes | |
} | |
function narrowing(map) { | |
for (let k in map) { | |
if (isInclude(k)) { | |
map[k] = map[k].filter(path => isInclude(path)) | |
} else { | |
delete map[k] | |
} | |
} | |
} | |
// 反転 | |
function reverse(map) { | |
let rmap = {} | |
for (let k in map) { | |
let parent = k | |
map[k].forEach(child => { | |
if (child in rmap) { | |
rmap[child].push(parent) | |
} else { | |
rmap[child] = [parent] | |
} | |
}); | |
} | |
return rmap | |
} | |
// ツリー | |
function generateTree(map) { | |
let ret = {} | |
// {parent: { child: {}}} | |
for (let k in map) { | |
if (k.indexOf(options.parent) > -1) { | |
ret[k] = search(map, k, 0) | |
} | |
} | |
return ret | |
} | |
let memo = {} | |
let count = 0 | |
function search(map, key, level) { | |
let ret = {} | |
count++ | |
// if (level >= options.printDepth) return ret | |
let childMap = map[key] | |
for (let i in childMap) { | |
let child = childMap[i] | |
// 同じやつがいる | |
if (child === key) continue | |
if (child in memo) { | |
ret[child] = memo[child] | |
} else { | |
memo[child] = {} | |
memo[child] = search(map, child, level + 1) | |
ret[child] = memo[child] | |
} | |
} | |
return ret | |
} | |
// プリント | |
function printMap(map, level) { | |
let blank = '' | |
const noPrintNext = level === options.printDepth | |
if (level > 0) { | |
blank = '|'.repeat(level-1) + '-' | |
} | |
for (let key in map) { | |
let childMap = map[key] | |
let currentBlank = blank | |
if(noPrintNext) { | |
if (Object.keys(childMap).length) { | |
currentBlank = currentBlank.replace('-', '+') | |
} else { | |
currentBlank = currentBlank.replace('-', ' ') | |
} | |
} else if (!Object.keys(childMap).length) { | |
currentBlank = currentBlank.replace('-', ' ') | |
} | |
console.log(`${currentBlank}${key}`) | |
if (!noPrintNext) { | |
printMap(childMap, level + 1) | |
} | |
} | |
} | |
// 最後だけ、プリント | |
let children = {} | |
function searchChild(map) { | |
for (let key in map) { | |
let childMap = map[key] | |
if (Object.keys(map[key]).length) { | |
searchChild(childMap) | |
} else { | |
children[key] = key | |
} | |
} | |
} | |
const buildinfo = JSON.parse(readFile(path)) | |
let targetMap = buildinfo.program[options.target] | |
narrowing(targetMap) | |
if (options.isReverse) { | |
targetMap = reverse(targetMap) | |
} | |
let treeMap = generateTree(targetMap) | |
// console.log(count) | |
if (options.isPrintLastOnly) { | |
searchChild(treeMap) | |
sortedLastComponents = Object.keys(children).sort() | |
if (options.isPrintJson) { | |
console.log(JSON.stringify(sortedLastComponents)) | |
} else { | |
for (let c of sortedLastComponents) { | |
console.log(c) | |
} | |
} | |
} else if (options.isPrintJson) { | |
console.log(JSON.stringify(treeMap)) | |
} else { | |
printMap(treeMap, 0) | |
} |