|
import path from 'path' |
|
import { argv, fs } from 'zx' |
|
import type { IModule } from 'dependency-cruiser' |
|
import { |
|
keyBy, |
|
} from 'lodash-es' |
|
|
|
/** |
|
* depcruise -p -c dependency-cruiser.config.js --output-type json src > .logs/depcruise/src.json |
|
*/ |
|
import cruiserResult from './.logs/depcruise/src.json' |
|
|
|
|
|
const selfPath = path.relative(process.cwd(), process.argv[1]) |
|
|
|
if (!(argv.track || argv['find-unused'])) { |
|
console.log(` |
|
Usage: |
|
tsx ${selfPath} --track <track-file> |
|
tsx ${selfPath} --track-all <track-file> |
|
tsx ${selfPath} --find-unused |
|
`) |
|
process.exit(1) |
|
} |
|
|
|
const keeps = (await fs.readFile('.logs/keeps.log', { encoding: 'utf-8' })) |
|
.trim() |
|
.split('\n') |
|
|
|
const entries = [ |
|
'src/.umi/umi.ts', |
|
'src/app.tsx', |
|
'src/global.ts', |
|
'src/config/theme/index.js', |
|
'src/config/routes/index.ts', |
|
'src/pages/loginFail.tsx', |
|
'src/pages/404.jsx', |
|
'src/pages/loading.tsx', |
|
'src/layouts/index.tsx', |
|
// 'src/pages/xxx/xxx', |
|
] |
|
|
|
|
|
|
|
const modules: IModule[] = cruiserResult.modules as IModule[] |
|
const moduleMap: Record<string, IModule> = keyBy(modules, 'source') |
|
|
|
const track = (trackFile: string) => { |
|
console.log('track file:', trackFile) |
|
|
|
const links: string[] = [] |
|
const visited: Set<string> = new Set() |
|
|
|
const queue: string[] = [trackFile] |
|
|
|
while (queue.length) { |
|
const file = queue.shift()! |
|
if (visited.has(file)) { |
|
continue |
|
} |
|
|
|
visited.add(file) |
|
links.unshift(file) |
|
|
|
const module = moduleMap[file] |
|
|
|
if (!module) { |
|
throw new Error(`not found module of "${trackFile}"`) |
|
} |
|
const parent = module.dependents[0] |
|
if (parent) { |
|
queue.push(parent) |
|
} |
|
} |
|
|
|
console.log('links:', links) |
|
} |
|
|
|
const trackAll = (trackFile: string) => { |
|
const getToRoot = (parent: IModule, source: string, refs: Set<string>): string[][] => { |
|
const parents = parent.dependents |
|
.filter(file => !refs.has(file)) |
|
.map(file => moduleMap[file]) |
|
.map(module => getToRoot(module, module.source, new Set([...refs, module.source]))) |
|
.flat() |
|
|
|
if (!parent.dependents.length) return [[source]] |
|
|
|
parents.forEach(list => list.push(source)) |
|
|
|
return parents |
|
} |
|
|
|
const module = moduleMap[trackFile] |
|
if (!module) { |
|
throw new Error(`not found module of "${trackFile}"`) |
|
} |
|
|
|
const roots = new Set(entries) |
|
|
|
const links = getToRoot(module, trackFile, new Set([trackFile])) |
|
// filter top to link |
|
.filter(link => roots.has(link[0])) |
|
.sort((a, b) => (a.length > b.length ? 1 : -1)) |
|
|
|
const fileCounts: Record<string, number> = {} |
|
|
|
links.forEach(link => { |
|
link.forEach(file => { |
|
if (!fileCounts[file]) { |
|
fileCounts[file] = 0 |
|
} |
|
fileCounts[file] += 1 |
|
}) |
|
}) |
|
|
|
const maxCount = Math.max(...Object.values(fileCounts)) |
|
const maxCountLen = String(maxCount).length |
|
|
|
console.log(` |
|
total links count: ${links.length} |
|
|
|
preview: |
|
`) |
|
|
|
links.slice(0, 10).forEach(link => { |
|
console.log( |
|
'link:', |
|
link.map(file => { |
|
const count = String(fileCounts[file]).padStart(maxCountLen, ' ') |
|
return `${count} ${file}` |
|
}), |
|
) |
|
}) |
|
} |
|
|
|
const findUnusedFiles = () => { |
|
const stack = [...entries] |
|
const visited = new Set<string>() |
|
|
|
while (stack.length) { |
|
const file = stack.shift()! |
|
|
|
if (visited.has(file)) continue |
|
visited.add(file) |
|
|
|
const module = moduleMap[file] |
|
if (!module) { |
|
throw new Error(`not find module: ${file}`) |
|
} |
|
const dependencies: string[] = module.dependencies |
|
.map(({ resolved }) => resolved) |
|
|
|
stack.push(...dependencies) |
|
} |
|
|
|
const allFiles = Object.keys(moduleMap) |
|
|
|
const unused = allFiles |
|
.filter(file => ( |
|
!visited.has(file) |
|
&& file.startsWith('src/') |
|
&& !keeps.some(prefix => file.startsWith(prefix)) |
|
)) |
|
|
|
console.log(unused.join('\n')) |
|
} |
|
|
|
|
|
if (argv.track && argv.all) { |
|
trackAll(argv.track) |
|
|
|
} else if (argv.track) { |
|
track(argv.track) |
|
|
|
} else if (argv['find-unused']) { |
|
findUnusedFiles() |
|
} |