最近的一段時間一直在搞 TypeScript JavaScript
,一個巨硬出品、賦予
語言靜态類型和編譯的語言。
第一個完全使用
TypeScript
重構的純 Node.js
項目已經上線并穩定運作了。
第二個前後端的項目目前也在重構中,關于前端基于
webpack
的 TypeScript
套路之前也有提到過:TypeScript在react項目中的實踐。
但是這些做完以後也總感覺缺了點兒什麼 (沒有盡興):
是的,依然有五分之一的
JavaScript
代碼存在于項目中,作為一個 TypeScript
的示例項目,表現的很不純粹。
是以有沒有可能将這些
JavaScript
代碼也換成 TypeScript
呢?
答案肯定是有的,首先需要分析這些代碼都是什麼:
-
打包時的配置檔案Webpack
- 一些簡單的測試用例(使用的mocha和chai)
知道了是哪些地方還在使用
JavaScript
,這件事兒就變得很好解決了,從建構工具(
Webpack
)開始,逐個擊破,将這些全部替換為
TypeScript
。
Webpack 的 TypeScript 實作版本
在這
8102
年,很幸福,
Webpack
官方已經支援了
TypeScript
編寫配置檔案,文檔位址。
除了
TypeScript
以外還支援
JSX
和
CoffeeScript
的解釋器,在這就忽略它們的存在了
依賴的安裝
首先是要安裝
TypeScript
相關的一套各種依賴,包括解釋器及該語言的核心子產品:
npm install -D typescript ts-node
typescript
為這個語言的核心子產品,
ts-node
用于直接執行
.ts
檔案,而不需要像
tsc
那樣會編譯輸出
.js
檔案。
ts-node helloworld.ts
因為要在
TypeScript
環境下使用
Webpack
相關的東東,是以要安裝對應的
types
也就是
Webpack
所對應的那些
*.d.ts
,用來告訴
TypeScript
這是個什麼對象,提供什麼方法。
npm i -D @types/webpack
一些常用的
pLugin
都會有對應的
@types
檔案,可以簡單的通過
npm info @types/XXX
來檢查是否存在
如果是一些小衆的
plugin
,則可能需要自己建立對應的
d.ts
檔案,例如我們一直在用的
qiniu-webpack-plugin
,這個就沒有對應的
@types
包的,是以就自己建立一個空檔案來告訴
TypeScript
這是個啥:
declare module 'qiniu-webpack-plugin' // 就一個簡單的定義即可
// 如果還有其他的包,直接放到同一個檔案就行了
// 檔案名也沒有要求,保證是 d.ts 結尾即可
放置的位置沒有什麼限制,随便丢,一般建議放到
types
檔案夾下
最後就是
.ts
檔案在執行時的一些配置檔案設定。
用來執行
Webpack
.ts
檔案對
tsconfig.json
有一些小小的要求。
compilerOptions
下的
target
選項必須是
es5
,這個代表着輸出的格式。
以及
module
要求選擇
commonjs
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"esModuleInterop": true
}
}
但一般來講,執行
Webpack
的同級目錄都已經存在了
tsconfig.json
,用于實際的前端代碼編譯,很可能兩個配置檔案的參數并不一樣。
如果因為要使用
Webpack
去修改真正的代碼配置參數肯定是不可取的。
是以我們就會用到這麼一個包,用來改變
ts-node
執行時所依賴的配置檔案:tsconfig-paths
在
Readme
中發現了這樣的說法:
If process.env.TS_NODE_PROJECT is set it will be used to resolved tsconfig.json
Webpack
的文檔中同樣也提到了這句,是以這是一個相容的方法,在指令運作時指定一個路徑,在不影響原有配置的情況下建立一個供
Webpack
打包時使用的配置。
- 将上述的配置檔案改名為其它名稱,
文檔示例中為Webpack
,這裡就直接沿用了tsconfig-for-webpack-config.json
- 然後添加
如下npm script
{
"scripts": {
"build": "TS_NODE_PROJECT=tsconfig-for-webpack-config.json webpack --config configs.ts"
}
}
檔案的編寫
關于配置檔案,從
JavaScript
切換到
TypeScript
實際上并不會有太大的改動,因為
Webpack
的配置檔案大多都是寫死的文本/常量。
很多類型都是自動生成的,基本可以不用手動指定,一個簡單的示例:
import { Configuration } from 'webpack'
const config: Configuration = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
}
export default config
Configuration
是一個
Webpack
定義的接口(
interface
),用來規範一個對象的行為。
VS Code
下按住
Command
+ 單擊可以直接跳轉到具體的
webpack.d.ts
定義檔案那裡,可以看到詳細的定義資訊。
各種常用的規則都寫在了這裡,使用
TypeScript
的一個好處就是,當要實作一個功能時你不再需要去網站上查詢應該要配置什麼,可以直接翻看
d.ts
的定義。
如果注釋寫得足夠完善,基本可以當成文檔來用了,而且在
VS Code
編輯器中還有動态的提示,以及一些錯誤的糾正,比如上述的
NODE_ENV
的擷取,如果直接寫
process.env.NODE_ENV || 'development'
是會抛出一個異常的,因為從
d.ts
中可以看到,關于
mode
隻有三個有效值
production
、
developemnt
none
,而
process.env.NODE_ENV
顯然隻是一個字元串類型的變量。
是以我們需要使用三元運算符保證傳入的參數一定是我們想要的。
以及在編寫的過程中,如果有一些自定義的
plugin
之類的,可能在使用的過程中會抛異常提示說某個對象不是有效的
Plugin
對象,一個很簡單的方法,在對應的
plugin
後邊添加一個
as webpack.Plugin
即可。
在這裡
TypeScript
所做的隻是靜态的檢查,并不會對實際的代碼執行造成任何影響,就算類型因為強行
as
而改變,也隻是編譯期的修改,在實際執行的
JavaScript
代碼中還是弱類型的
在完成了上述的操作後,再執行
npm run XXX
就可以直接運作
TypeScript
版本的
Webpack
配置咯。
探索期間的一件趣事
因為我的項目根目錄已經安裝了
ts-node
,而前端項目是作為其中的一個檔案夾存在的,是以就沒有再次進行安裝。
這就帶來了一個令人吐血的問題。
首先全部流程走完以後,我直接在指令行中輸入
TS_NODE_PROJECT=XXX.json NODE_ENV=dev webpack --config ./webpack/dev.ts
完美運作,然後将這行指令放到了
npm scripts
中:
{
"scripts": {
"start": "TS_NODE_PROJECT=XXX.json NODE_ENV=dev webpack --config ./webpack/dev.ts"
}
}
再次運作
npm start
,發現竟然出錯了-.-,提示我說
import
文法不能被識别,這個很顯然就是沒有應用我們在
ts_NODE_PROJECT
中指定的
config
剛開始并不知道問題出在哪,因為這個在指令行中直接執行并沒有任何問題。
期間曾經懷疑是否是環境變量沒有被正确設定,還使用了
cross-env
這個插件,甚至将指令寫到了一個
sh
檔案中進行執行。
然而問題依然存在,後來在一個群中跟小夥伴們聊起了這個問題,有人提出,你是不是全局安裝了
ts-node
檢查以後發現,果然是的,在指令行執行時使用的是全局的
ts-node
,但是在
npm scripts
中使用的是本地的
ts-node
在指令行環境執行時還以為是會自動尋找父檔案夾
node_modules
下邊的依賴,其實是使用的全局包。
乖乖的在
client-src
檔案夾下也安裝了
ts-node
就解決了這個問題。
全局依賴害人。。
測試用例的改造
前邊的
Webpack
改為
TypeScript
大多數原因是因為強迫症所緻。
但是測試用例的
TypeScript
改造則是一個能極大提高效率的操作。
為什麼要在測試用例中使用 TypeScript
測試用例使用
chai
來編寫,(之前的
Postman
也是用的
chai
的文法)
chai
提供了一系列的語義化鍊式調用來實作斷言。
在之前的分享中也提到過,這麼多的指令你并不需要完全記住,隻知道一個
expect(XXX).to.equal(true)
就夠了。
但是這樣的通篇
to.equal(true)
是巨醜無比的,而如果使用那些語義化的鍊式調用,在不熟練的情況下很容易就會得到:
Error: XXX.XXX is not a function
因為這确實有一個門檻問題,必須要寫很多才能記住調用規則,各種
not
includes
的操作。
但是接入了
TypeScript
以後,這些問題都迎刃而解了。
也是前邊提到的,所有的
TypeScript
子產品都有其對應的
.d.ts
檔案,用來告訴我們這個子產品是做什麼的,提供了什麼可以使用。
也就是說在測試用例編寫時,我們可以通過動态提示來快速的書寫斷言,而不需要結合着文檔去進行“翻譯”。
使用方式
如果是之前有寫過
mocha
chai
的童鞋,基本上修改檔案字尾+安裝對應的
@types
可以直接跳到這裡來:開始編寫測試腳本
但是如果對測試用例感興趣,但是并沒有使用過的童鞋,可以看下邊的一個基本步驟。
安裝依賴
-
相關的安裝,TypeScript
npm i -D typescript ts-node
-
Mocha
chai
npm i -D mocha chai @types/mocha @types/chai
- 如果需要涉及到一些API的請求,可以額外安裝
,chai-http
npm i -D chai-http @types/chai-http
環境的依賴就已經完成了,如果額外的使用一些其他的插件,記得安裝對應的
@types
檔案即可。
如果有使用ESLint之類的插件,可能會提示
modules
必須存在于
dependencies
而非
devDependencies
這是ESLint的
import/no-extraneous-dependencies
規則導緻的,針對這個,我們目前的方案是添加一些例外:
import/no-extraneous-dependencies:
- 2
- devDependencies:
- "**/*.test.js"
- "**/*.spec.js"
- "**/webpack*"
- "**/webpack/*"
針對這些目錄下的檔案/檔案夾不進行校驗。是的,webpack的使用也會遇到這個問題
開始編寫測試腳本
如果是對原有的測試腳本進行修改,無外乎修改字尾、添加一些必要的類型聲明,不會對邏輯造成任何修改。
一個簡單的示例
// number-comma.ts
export default (num: number | string) => String(num).replace(/\B(?=(\d{3})+$)/g, ',')
// number-comma.spec.ts
import chai from 'chai'
import numberComma from './number-comma'
const { expect } = chai
// 測試項
describe('number-comma', () => {
// 子項目1
it('`1234567` should transform to `1,234,567`', done => {
expect(numberComma(1234567)).to.equal('1,234,567')
done()
})
// 子項目2
it('`123` should never transform', done => {
const num = 123
expect(numberComma(num)).to.equal(String(num))
done()
})
})
如果全局沒有安裝
mocha
,記得将指令寫到
npm script
中,或者通過下述方式執行
./node_modules/mocha/bin/mocha -r ts-node/register test/number-comma.spec.ts
# 如果直接這樣寫,會抛出異常提示 mocha 不是指令
mocha -r ts-node/register test/number-comma.spec.ts
mocha
有一點兒比較好的是提供了
-r
指令來讓你手動指定執行測試用例腳本所使用的解釋器,這裡直接設定為
ts-node
的路徑
ts-node/register
,然後就可以在後邊直接跟一個檔案名(或者是一些通配符)。
目前我們在項目中批量執行測試用例的指令如下:
{
"scripts": {
"test": "mocha -r ts-node/register test/**/*.spec.ts"
}
}
npm test
可以直接調用,而不需要添加
run
指令符,類似的還有
start
build
等等
一鍵執行以後就可以得到我們想要的結果了,再也不用擔心一些代碼的改動會影響到其他子產品的邏輯了 (前提是認真寫測試用例)
小結
做完上邊兩步的操作以後,我們的項目就實作了100%的
TypeScript
化,在任何地方享受靜态編譯文法所帶來的好處。
附上更新後的代碼含量截圖:
最近針對
TypeScript
做了很多事情,從
Node.js
React
以及這次的
Webpack
與
Mocha+Chai
TypeScript
因為其存在一個編譯的過程,極大的降低了代碼出bug的可能性,提高程式的穩定度。
全面切換到
TypeScript
更是能夠降低在兩種文法之間互相切換時所帶來的不必要的消耗,祝大家搬磚愉快。
之前關于 TypeScript 的筆記
- TypeScript在node項目中的實踐
- TypeScript在react項目中的實踐
一個完整的 TypeScript 示例
typescript-example
歡迎各位來讨論關于
TypeScript
使用上的一些問題,針對穩重的感覺不足之處也歡迎指出。
參考資料
- ts-node
- configuration-languages | webpack
- mochajs
- chaijs