初識TypeScript:查找指定路徑下的文件按類型生成json

若是開發過node.js的話應該對js(javascript)很是熟悉,TypeScript(如下簡稱ts)是js的超集。javascript

下面是ts的官網:java

https://www.tslang.cn/node

 

1.環境配置(若是已經進行過環境配置,能夠跳過此步)git

開發ts須要先簡單的配置開發環境,若是使用的是Visual Studio,只須要簡單裝一個node.js的組件包便可:github

 

 該組件包主要包含Node.js開發工具,js和ts語言支持;除了該工具包外,還須要額外安裝ts sdk:正則表達式

 

但若是使用的是VS Code的話,這些就須要本身手動安裝和配置了,node.js開發工具的下載地址爲:typescript

https://nodejs.org/en/download/數據庫

安裝完node就能夠運行npm指令了,npm是Node.js包管理器(node package manager),你能夠認爲它是一個巨大的雲端數據庫,其中集成了大量js或ts開發中須要的包和代碼模塊,當你在項目中須要引用這些包或模塊時,隨時能夠利用npm指令進行快速下載使用,這樣不被引用的模塊沒必要佔用過多項目空間。好比,能夠直接利用npm來安裝ts,打開cmd輸入:npm

> npm install -g typescript

其中-g表示全局安裝,在npm指令中,install也能夠簡寫爲i:編程

> npm i -g typescript

ts安裝完成後,就能夠直接建立一個空文件夾做爲工程目錄了,但這時建立的ts文件並不能編譯,由於一個新的ts工程還須要先初始化npm和ts配置文件,能夠在VS Code中直接調用新的終端:

> npm init -y
> tsc -init

執行完這兩條指令後,咱們會發現工程中生成了兩個json文件,它們分別是package.json和tsconfig.json;參數-y表示按照默認方式生成,tsc即爲type script config的縮寫。

package.json中記錄了整個工程的基本信息,簡化的命令行指令,以及當前工程的依賴模塊和庫等;開發者能夠自行在該文件的scripts塊中添加自定義的指令,例如:

    "start": "tsc main.ts && node main.js",
    "build": "tsc main.ts && pkg -t win main.js"

這樣即可以直接調用:

> npm run start
> npm run build

來代替執行自定義添加的命令行內容;在第一次build時,系統通常會告訴你它蠢蠢的沒有找到pkg,這時,你只須要執行安裝它的指令便可:

> npm i -g pkg

一樣的,以後在編譯過程當中遇到了引用的模塊或庫找不到的狀況,能夠先考慮該模塊是否安裝,若是沒有,均可以執行相似的安裝指令,但須要區分是否全局安裝。

回過頭來講下pkg是什麼東西,這是將Node.js項目打包爲可執行文件的一個工具,參數-t win 表示生成的目標(target)平臺爲windows,更多詳情瞭解可見github:

https://github.com/vercel/pkg

另外,爲了更方便的經過ts來引用一些經常使用的node.js庫,能夠考慮提早執行如下指令:

> npm i @types/node --save-dev

完成後,工程目錄的node_modules下會自動添加對應安裝的庫。參數-save意思是在package.json中保存並寫入該依賴庫,-dev指的是僅在開發階段須要依賴該庫,編譯部署後則再也不依賴。

 

2.正式編寫

在正式開始編寫以前,須要明確的是,ts並不是強封裝類型的語言,和不少面向對象的編程語言有必定的區別,也不須要程序入口同樣的main函數,而是從上到下,從左到右依次讀取程序中的每一行;

固然了,這並不表明ts不能實現封裝,你依然能夠將固定的代碼塊封裝爲函數或類,但這並不是是強制性的。

 

爲了對文件和路徑進行操做,須要提早引用一些模塊,相似於C#中的using,ts中的格式則相似於:

import * as fs from 'fs';
import * as path from 'path';

由於以前已經安裝過@types/node, 因此這裏不會出現找不到引用的報錯。固然了,還能夠用另外一種方式來引用模塊:

const fs = require('fs');
const path = require('path');

順便提一句ts中聲明的幾個關鍵字const,var,let;const和var在C#也有,分別用於聲明常量與局部變量,而let是我以前沒有見過的,在網上查閱以後,發現let和var不少地方都是相似的,但有如下幾點區別:

1.var聲明的變量會自動提高到該語句所在代碼塊的開頭(但注意初始化的賦值並不會),這種現象稱爲變量提高;而let不具有變量提高的特性

形成的影響即是,var能夠先使用後聲明,不會有任何報錯,而是會輸出未定義類型undefined,但let這麼作就會直接報錯(迷)

2.var容許重複聲明同一變量,會覆蓋以前變量的值,但let則不能重複聲明同一變量(迷)

3.var重複聲明變量時內部代碼塊的值能夠覆蓋外部值(什麼還有這種操做?!),但let則表現爲不一樣的兩個變量,

主要由於var與let聲明的變量做用範圍不一樣,var的做用範圍包含子塊以及它所在的函數的任何位置(迷),而let只在當前塊(不包含子塊)中有效

emm...感受和C#裏的var徹底不同啊,做爲新手若是爲了保險起見,能夠均使用let來聲明局部變量。

 

下面的方法爲查找指定路徑下的文件,並將全部文件的絕對路徑存儲到一個臨時的數組中:

 1 let temp: string[] = new Array();
 2 function fileDisplay(filePath: string) {
 3     // 根據文件路徑讀取文件,返回一個文件列表
 4     const files = fs.readdirSync(filePath);
 5     // 遍歷讀取到的文件列表
 6     for (let filename of files) {
 7         // path.join獲得當前文件的絕對路徑
 8         const filepath = path.join(filePath, filename);
 9         // 根據文件路徑獲取文件信息
10         const stats = fs.statSync(filepath);
11         const isFile = stats.isFile(); // 是否爲文件
12         const isDir = stats.isDirectory(); // 是否爲文件夾
13         if (isFile) {
14             temp.push(filepath);
15         }
16         if (isDir) {
17             fileDisplay(filepath); // 遞歸,若是是文件夾,就繼續遍歷該文件夾裏面的文件
18         }
19     };
20 }

注意在上述的方法中須要須要同步讀取文件(Sync),而不該該採起默認的異步讀取,這樣以後的代碼中取到temp數組時纔會獲得正確的值,若是非要異步讀取,則須要用回調的方式來寫json。

 

爲了得到命令行中輸入的參數,可使用下面的語句:

let argument = process.argv.splice(2);

process.argv()爲node.js中返回當前命令行參數的方法,其中2表明的是實際輸入的參數數組,若是輸入0的話則表明獲取node,1的話返回執行的js的完整路徑

以後直接將命令行輸入的第一個參數,也就是用戶鍵入的文件夾路徑做爲參數傳遞給fileDisplay方法便可:

fileDisplay(argument[0]);

 

獲得全部的文件路徑後,接下來就是按照文件的類型寫入json中了

首先咱們須要先遍歷全部的文件路徑,經過路徑字符串能夠獲得文件的一些基本信息,例如文件的拓展名,文件的基本名稱等,經過文件的擴展名能夠對文件資源的類型重定義和分類:

1 for (let item of temp)
2 {
3     let extname = path.extname(item);//獲取文件的擴展名,帶.
4     let basename = path.basename(item, extname);//獲取文件的基本名稱,第二個參數爲須要剔除的擴展名
5     //...
6 }

固然了,若是你不想用path模塊的方法,也能夠直接用字符串的方式來截取:

    let fileExtension = item.substring(item.lastIndexOf('.'));
//
    let fileName = item.substring(item.lastIndexOf('\\') + 1, item.lastIndexOf('.'));

須要注意的是,在ts中遍歷元素內容的方式爲of而非in(習慣C#了這裏被坑了一把),in只能遍歷出索引...

另外,匹配[\]時須要用兩個[\\]才能夠,由於一個[\]表明的大多爲轉義字符。 

 

根據文件的擴展名返回自定義的文件類型:

 1 function GetType(extension: string): string {
 2     switch (extension) {
 3         case ".png":
 4         case ".jpg":
 5             return "image";
 6         case ".fnt":
 7             return "bitmapFont";
 8         case ".TTF":
 9         case ".ttf":
10             return "font";
11         case ".spine":
12         case ".particle":
13             return "particle";
14         case ".mp3":
15             return "audio";
16         default:
17             return "null";
18     }
19 }

 篩選過濾文件:

1     let type = GetType(extname);
2     //過濾非指定類型文件
3     if (type == "null")
4         continue;
5     //過濾重名文件
6     if (resources[basename]) {
7         console.log(`錯誤!!!該文件名已存在【${basename}】`);
8         continue;
9     }

定義json基礎數據結構:

1 let outjson: any = {}
2 let resources: any = {};
3 let d: any = {};
4 
5 d.tye = type;
6 d.url = item;
7 
8 resources[basename] = d;
9 outjson.resources = resources;

上面是爲了更方便讀者理解而將這三個變量放在一塊兒,實際上變量d是在循環體內部聲明的局部變量,any類型是ts中的一種特殊類型,它能夠被定義爲任何一種其餘類型,這裏將它定義爲了一種大括號類型的數據結構,表明它的內部還有一些其餘的任意成員變量。

若是是在C#中書寫json的數據結構,將是一件很是麻煩的事,須要嚴格的定義爲一個新的類或結構體,但ts中彷佛至關自由,只須要用一個變量來代替便可,甚至直接在賦值初始化的時候來肯定鍵值。

但網上關於大括號類型的any講解並很少,因此作了一點額外的測試:

 1 let a: any = {};
 2 let b: any = {};
 3 let c: any = {};
 4 a.b = "c";
 5 a.c = 5.6;
 6 a.a = a.b;
 7 a["b"] = a.b;
 8 b["c"] = a.c;
 9 b["a"] = a;
10 c.a = b;
11 c[a.b] = a;
12 console.log(c);

你們能夠推導下會打印出什麼結果;好,接下來公佈答案:

 1 {
 2     a: {
 3         c: 5.6,
 4         a: {
 5             b: 'c',
 6             c: 5.6,
 7             a: 'c'
 8         }
 9     },
10     c: {
11         b: 'c',
12         c: 5.6,
13         a: 'c'
14     }
15 }

下面來進行一個簡單的梳理:

測試第四行 表明a中有一個鍵(變量名)爲b的成員,它的值爲字符串c

測試第五行 表明a中有一個鍵(變量名)爲c的成員,它的值爲數字類型5.6(ts中全部的數字類型均爲浮點型,省去了不少其餘編程語言中值類型數據的繁瑣分類)

測試第六行 表明a中有一個鍵(變量名)爲a的成員,它的值初始化爲a中鍵爲b的那個成員的值,也便是一樣的字符串c

測試第七行 實際意義與第四行相同,但這裏是爲了測試[key]這種書寫形式所存在的意義,實際上結合第十一行就能得出結論,那就是——當咱們須要一個字符串變量而很是量來做爲鍵時就不能直接用「.成員名」的方式了,由於這樣的方式只能生成固定的字符串名,

能夠再比較如下例子:

 1 let x1: any;
 2 let x2: any = {};
 3 x1 = "x2";
 4 x2.x1 = x1;
 5 x2[x1] = x1;
 6 console.log(x2);
 7 x1 = "6";
 8 x2.x1 = x1;
 9 x2[x1] = x1;
10 console.log(x2);

你們能夠再推導一下會打印出什麼結果;好,如今公佈答案:

1 {
2     x1: 'x2',
3     x2: 'x2'
4 } {
5     '6': '6',
6     x1: '6',
7     x2: 'x2'
8 }

是否是讓人有些驚訝,實話說,第二次的打印結果筆者也沒有作對,我沒有想到它居然能打印出3個值...緣由就在於在第一次中x2[x1]中x1對應的字符串x2這一鍵並無被修改或刪除,而x2.x1中鍵x1是一個固定的變量名,因此它的值理所固然的被改變爲了後面的字符串6,又由於x1的值已經發生了改變,因此x2[x1]已經再也不是原來的任何一個鍵,從而又生成了一個新的鍵值對。

 

通過上面的對比測試,應該已經能夠很好的區分何時用".成員名",何時用[變量]了,返回前面的json的數據結構;由於文件名這一鍵是根據文件的不一樣隨時都會變化的值,因此採用中括號的形式,而typ,url等爲固定的字符串,每個文件都具有這類成員名,因此直接用點的形式便可。

 

接下來只須要將json寫入到指定的路徑便可:

1 //寫入json文件選項
2 function writeJson(data: any, jsonFilePath: string) {
3     fs.writeFileSync(jsonFilePath, JSON.stringify(data, null, 4).replace(/\\\\/g, "/"), 'utf-8');
4 }
5 writeJson(outjson, "./default.res.json");

我在寫入json時遇到了一個問題,就是路徑的\老是在寫入時實際文件時變爲\\,但在控制檯打印字符串時又是正常的(迷),因此沒辦法就用正則表達式全局匹配\\替換爲\,至於出現這個問題的緣由到如今尚未弄清楚,若是有大佬發現是什麼緣由歡迎告知筆者。

 

3.生成可執行文件和批處理文件

在環境配置時已經說了pkg安裝與運行指令,這裏直接在命令行中調用:npm run build便可,由於已經設置了平臺爲win,build後文件夾中就會出現exe文件。

此時直接點擊這個應用程序沒有任何效果,由於程序中設置的是須要獲得用戶輸入的命令行參數——搜索的文件夾路徑才行,固然了,你能夠直接打開cmd來執行該exe並設置參數,但每次都要設置參數未免有些難受,這是就能夠寫一個批處理來執行當前exe所在路徑下的文件查找和生成json,這樣即便是程序白癡也能用了。

main.exe .\
pause

 

打開看一下生成的json是否讓人滿意:(只截取了一部分)

相關文章
相關標籤/搜索