我在 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上展現的目錄層級結構,就是根據這份文件生成的