我在 github上有个维护时间比较长的 repository,开始时只有几个文件,后来文件数目逐渐增多,期间整理了好几回,如今已经整理成了好几个文件夹了,有时候想找某个文件的时候,可是不肯定到底在哪一个文件夹里面,因而就凭感受一个一个文件夹试过去,层级少点还好,可是层级一多,就算是明确知道在哪一个文件夹里,一层层点进去也要点好几回html
因而心中一动,就想着把当前仓库的目录结构列出来,直接写在 README.md
文件上,想看哪一个文件直接点,一次点击便可,手写目录确定是不太友好的,由于我可能频繁增删文件,甚至是再次整理文件结构,并且也不具有通用性,万一哪天又想把另一个仓库也列出目录结构,那么又要手写一遍,因此最好写个代码程序来帮我完成这种工做node
先看效果图:git
目标是输出目录的层级结构,那么首先要把当前仓库根目录下全部文件的路径获取到,思路很清晰的,先用 fs.readdirSync
读取目录,而且递归循环子目录,直到最后一层es6
function getDirStruct(basePath = __dirname) {
const files = fs.readdirSync(basePath)
files.forEach(file => {
// 处理先不要显示的文件
if (excludeFile.indexOf(file) !== -1 || excludePrefix.some(pre => file.indexOf(pre) === 0)) return
const fullPath = path.resolve(basePath, file)
const fileStats = fs.statSync(fullPath)
// 若是是文件夹,则继续遍历其子文件
return fileStats.isDirectory(file) ? getDirStruct(fullPath) : absolutePath.push(fullPath)
})
}
复制代码
这里获取到的是全部文件在本地目录的绝对全路径,可是后面是须要把这个东西上传到 github
的,因此须要把这个绝对路径改成相对路径,用于拼接文件的 url
地址github
// 绝对路径转相对路径
const rPath = path.relative(__dirname, apath)
复制代码
这里有几个小点须要注意下编程
程序不可能直接运行在 github
页面上的,因此你须要把仓库下载下来,再本地目录中运行程序,那么由于使用了 git
的缘故,因此根目录中确定存在一个 .git
文件夹,这个文件夹里的东西不少,并且你也不太可能但愿展现这个东西,因此最好排除掉小程序
相似的还有一些 img
文件夹,里面存了不少图片,你可能也不想展现出来,由于太占篇幅了并且也没什么用,因此也要排除掉windows
不一样平台上的文件路径分隔符是不同的,在 windows
上 路径分隔符是 \
,而在 POSIX
(即类 UNIX
系统,包括 Mac
、Linux
)上则是 /
,因此须要区别处理数组
nodejs
中可经过 path.sep
来获取当前操做系统的文件路径分隔符性能优化
文件的逐级读取涉及到递归操做,若是目录层级不是深到使人智熄的地步,那么除了程序运行时间比较长之外,不会有什么问题,可是若是是读取相似于 node_modules
这种文件夹,并且嵌套很深,那么就可能致使 栈溢出,程序直接 boom
那么这里就不得不提到 尾递归
了,尾递归
就能很好地避免 栈溢出
问题,关于 尾递归
,参见 知乎:什么是尾递归?
获取到了全部文件路径以后,须要对这些文件路径按照进行整理,获得一棵 Dir Tree
,就是一个用于描述这些文件路径的层级结构的数据
这里构建的 Dir Tree
相似下述结构:
{
_children: ['README.md', 'LICENSE'],
'CSS': {
_children: ['CSS-Note-1.md', 'CSS-Note-2.md', '性能优化.md'],
},
'Vue': {
_children: ['性能优化.md', '新特性.md'],
'无渲染Vue组件': {
...
}
}
}
复制代码
每一个目录层级下,对于单文件,直接存入这个层级下的一个名为 _children
的数组属性中,对于文件夹,则将文件夹的名字做为这个层级下一个属性名,而后这个属性的值,再按照上述规则进行递归,直到最后一层
固然,这个 Dir Tree
的结构仁者见仁智者见智,只要你以为顺眼怎么样均可以,这里只是举了一个栗子◎
数据结构肯定了,下面就须要将文件路径数组整理成上面的结构,例如,对于 /project/demo/src/index.js
这个路径,须要整理成:
const tree = {
project: {
demo: {
src: {
_children: ['index.js']
}
}
}
}
复制代码
那么这里就有个问题了,在创建这个 tree
对象以前,tree
这个数据多是没有 project
这个属性的,又或者有 project
属性,可是这个属性下没有 demo
这个属性
解决的方法,很明显的一个就是逐级判断,没有这个属性的,就加上去,而后当构造出 tree.project.demo.src
结构的时候,再在这个结构上,加上 ._children = ['index.js']
结构,这个过程其实能够简化一下,好比借助 元编程
例如,你要是以为不要每次都要判断到底有没有这个属性,那么就可使用 es6 Proxy
,实现自动添加属性的能力:
function autoAddProperty() {
return new Proxy({}, {
get(target, key, receiver) {
if (!(key in target)) {
target[key] = autoAddProperty()
}
return Reflect.get(target, key, receiver)
}
})
}
// 用法
const obj = autoAddProperty()
obj.a.b.c.d = 1
console.log(obj.a.b.c.d) // => 1
复制代码
固然,除了 proxy
以外,也能够借助 eval
,本示例使用的就是 eval
,由于代码更简洁,eval
这个方法可能不少 JavaScript
书籍上都会提到不要随便用,对于新手来讲,这个特性坑比较多,因此在不明确其反作用的状况下,仍是最好不要用,但并非说不能用,若是用这个特性多写一行代码就能另外少写十行,那为何不用一用?
更多关于元编程的内容,可见 知乎: 怎么理解元编程?、 【资源集合】 ES6 元编程(Proxy & Reflect & Symbol)
数据结构搞定了,那么最后的输出就很简单了,就是按照层级进行解构,一样是须要用到尾递归,值得稍微提一下的就是,想让输出的目录结构呈现出一种次序关系,那么就须要在递归中记住层级关系,可经过指定一个参数 level
来实现,根据这个参数的值来决定制表符 \t
的数量,从而控制缩进来表现层级
function formatLink(obj = structs, basePath = '', level = 1) {
// ...
}
复制代码
另外,给你们说个小 tip
,github
页面上是可使用快捷键的,例如在一个 github
仓库页面上按下 t
键,就会激活查找文件模式,不一样的页面,例如帐户我的主页和某个仓库页面可用的快捷键可能全部差异,并且这些快捷键不少,想要凭记忆记下来可能有些困难,能够按下 shift + /
键,便可在当前 github
页面弹出一个 modal
弹窗,上面就显示当前页面全部可用的快捷键
实际上我作完这件事情后,最大的收获并非写出了一个小程序,解决了我想使用代码解放双手自动生成文件目录树的问题,而是在解决这个问题的过程当中,延伸开来学到的其余的东西,例如尾递归、元编程乃至是 github
的快捷键,这才是价值更大的收货。
这些东西都是我之前不知道的东西,而且由于这些知识可能对于小白不太友好因此很难在其余的技术文章中看到,因此若是我不主动探寻,可能要再过很长一段时间甚至是永远都不知道。
不懂不可怕,由于只要你想懂你总会懂的,但不知道就很可怕了,不知道就是不知道,哪怕那东西再简单,但就由于你不知道因此你就是不知道,这就无解了
本文示例代码已经放到 github上了,嗯,这个 github仓库下 README.md上展现的目录层级结构,就是根据这份文件生成的