如果開發過node.js的話應該對js(javascript)非常熟悉,TypeScript(以下簡稱ts)是js的超集。
下面是ts的官網:
1.環境配置(如果已經進行過環境配置,可以跳過此步)
開發ts需要先簡單的配置開發環境,如果使用的是Visual Studio,只需要簡單裝一個node.js的組件包即可:
該組件包主要包含Node.js開發工具,js和ts語言支持;除了該工具包外,還需要額外安裝ts sdk:
但如果使用的是VS Code的話,這些就需要自己手動安裝和配置了,node.js開發工具的下載地址為:
https://nodejs.org/en/download/
安裝完node就可以運行npm指令了,npm是Node.js包管理器(node package manager),你可以認為它是一個巨大的雲端數據庫,其中集成了大量js或ts開發中需要的包和代碼模塊,當你在項目中需要引用這些包或模塊時,隨時可以利用npm指令進行快速下載使用,這樣不被引用的模塊不必占用過多項目空間。比如,可以直接利用npm來安裝ts,打開cmd輸入:
> 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:
另外,為了更方便的通過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是否讓人滿意:(只截取了一部分)
我的博客即將同步至 OSCHINA 社區,這是我的 OSCHINA ID:osc_25590913,邀請大家一同入駐:https://www.oschina.net/sharing-plan/apply