天天看點

使用 TypeScript 改造建構工具及測試用例

最近的一段時間一直在搞

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

打包時使用的配置。

  1. 将上述的配置檔案改名為其它名稱,

    Webpack

    文檔示例中為

    tsconfig-for-webpack-config.json

    ,這裡就直接沿用了
  2. 然後添加

    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

可以直接跳到這裡來:開始編寫測試腳本

但是如果對測試用例感興趣,但是并沒有使用過的童鞋,可以看下邊的一個基本步驟。

安裝依賴

  1. TypeScript

    相關的安裝,

    npm i -D typescript ts-node

  2. Mocha

    chai

    npm i -D mocha chai @types/mocha @types/chai

  3. 如果需要涉及到一些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