使用 TypeScript 編寫一個完善包含測試、文檔和持續集成的庫

這篇文章主要是講述如何使用 TypeScript 編寫一個完善,包含測試、文檔、持續集成的庫,涵蓋了編寫整個庫所需要的技術和工具,主要涵蓋:

  1. 項目目錄骨架
  2. TypeScript 配置
  3. 使用 jest 單元測試
  4. 使用 vuepress 編寫文檔
  5. 使用 github pages 部署文檔
  6. 持續集成部署

原文首發於我的個人網站:聽說 - https://tasaid.com/,推薦在我的網站閱讀更多技術文章。

前端開發 QQ 羣:377786580
歡迎使用和了解滴滴金融出品的移動端組件庫 Mand-mobile

爲了迎合這篇文章,我編寫了一個可以開箱即用的庫模板:https://github.com/linkFly6/ts-lib-basic

裏面集成了這篇文章所闡述的所有內容。

初始化項目目錄

先初始化項目目錄,一般來說,src 放源碼,dist 放編譯後的代碼,tests 放單元測試,所以先初始化好基礎目錄。

.
├── .vscode           # vscode 配置
│   └── launch.json   # vscode 調試配置
├── dist              # 編譯產出目錄,編譯後纔有
├── src               # 源碼
├── tests             # 單元測試
├── .gitignore        # git 忽略文件
├── .npmrc            # npm 配置
├── .travis.yml       # github 持續集成
├── LICENSE           # 開源協議
├── README.md         # README
├── package-lock.json # npm 鎖定依賴
├── package.json      # npm
├── tsconfig.json     # typescript 配置
└── tslint.json       # tslint 校驗

先按照這個目錄文件結構,然後我們會一步步填上內容。

通過 npm init 初始化一個 npm 配置:

clipboard.png

初始化 TypeScript 相關工具

既然包是基於 TypeScript 的,那麼 TypeScript 工具必不可少。

ts-node

在開發中,可以使用 ts-node(可以理解爲可以直接執行 ts 文件的 node)來直接運行我們的 ts 代碼。

npm i --save-dev typescript
npm i --save-dev ts-node

如果是 node 應用,爲了讓 TypeScript 能夠進行 node 類型推導,則需要安裝 Node 對應的類型聲明:

npm i --save-dev @types/node

tsconfig.json

tsconfig.json 是 TypeScript 的配置文件,這裏提供一份可供參考是配置,置於項目根目錄:

{
  "compilerOptions": {
    "sourceMap": false,
    "module": "commonjs", // 模塊配置
    "noImplicitAny": true, // 是否默認禁用 any
    // "removeComments": true, // 是否移除註釋
    "types": [ // 默認引入的類型聲明
      "node", // 默認引入 node 的類型聲明
    ],
    "baseUrl": ".", // 工作根目錄
    "paths": {
      // ~/ 指向 server/types,types 目錄下都是 types 文件,所以不會編譯產出
      "~/*": [
        "./types/*"
      ]
    },
    "target": "es6", // 編譯目標
    "outDir": "dist", // 輸出目錄
    "declaration": true, // 是否自動創建類型聲明
  },
  // 此配置生效範圍
  "include": [
    "src/**/*"
  ],
}

tslint.json

tslint 類似 eslint,是 TypeScript 中的代碼風格約束工具。

關於 lint,個人方面比較傾向於非強制性的,所以只在 vscode 中安裝了擴展 tslint,這樣 vscode 會根據項目根目錄配置的 tslint.json 標出不符合規範的信息。

這裏有一份推薦配置:

{
  "defaultSeverity": "error",
  "extends": [
    "tslint:recommended"
  ],
  "jsRules": {},
  "rules": {
    "max-line-length": [
      true,
      140
    ],
    // 禁止內置原始類型
    "ban-types": false,
    // 禁止給參數賦值
    "no-parameter-reassignment": false,
    // 禁止空接口
    "no-empty-interface": true,
    // 顯示類型代碼就不需要再加類型聲明瞭
    "no-inferrable-types": true,
    // 不允許使用內部模塊
    "no-internal-module": true,
    // 不允許在變量賦值之外使用常量數值。如果未指定允許值的列表, 則默認情況下允許-1、0和1 => 亂七八糟的數字會讓人混淆
    // "no-magic-numbers": [true],
    // 不允許使用內部 'modules' 和 'namespace'
    "no-namespace": true,
    // 非空斷言,強制使用 == null 之類的斷言
    // "no-non-null-assertion": true
    // 禁止 /// <reference path=>,直接用 import 即可
    "no-reference": true,
    // 禁止使用 require,應該使用 import foo = require('foo')
    "no-var-requires": false,
    // import 的順序按照字母表
    "ordered-imports": false,
    // 對象屬性聲明按照字母表
    "object-literal-sort-keys": false,
    // // 結束語句後的分號
    "semicolon": [
      false,
      "always"
    ],
    // 字符串強制單引號
    "quotemark": [
      true,
      "single",
      "jsx-double"
    ],
    // 禁止 arguments.callee
    "no-arg": true,
    // if 語句的單行不用括號,多行用括號
    "curly": false,
    // 是否強制使用箭頭函數,禁止匿名函數
    "only-arrow-functions": false,
    // 是否禁止多個空行
    "no-consecutive-blank-lines": false,
    // 在函數括號前要求或不允許空格
    "space-before-function-paren": false,
    // 箭頭函數的參數使用括號
    "arrow-parens": [
      true,
      "ban-single-arg-parens"
    ],
    // 不固定變量類型
    "no-shadowed-variable": false,
    // 行尾多餘的空格
    "no-trailing-whitespace": false,
    // == 和 ===
    "triple-equals": false,
    // 禁止一些位運算符
    "no-bitwise": false,
    // 禁止 console
    "no-console": false,
    // 檢查變量名
    "variable-name": [
      true,
      "ban-keywords"
      // "check-format",
      // "allow-leading-underscore"
    ],
    // 一行聲明變量表達式
    "one-variable-per-declaration": false,
    // 允許在一個文件裏定義多個 class
    "max-classes-per-file": [
      true,
      5
    ],
    // 判斷表達式 fn && fn()
    "no-unused-expression": [
      true,
      "allow-fast-null-checks"
    ],
    // 空函數
    "no-empty": false,
    // forin 是否必須包含 hasOwnProperty 判斷
    "forin": false,
    "no-debugger": false,
    // 強制要求必須要聲明類型
    "typedef": [
      true
    ]
  },
  "rulesDirectory": [
    "./src"
  ]
}

package-lock.json

package-lock.json 是 npm 5 之後引入的,爲了解決 npm 過去使用的 package.json 版本依賴太寬鬆的問題。

比如說 package.json 中依賴了包 mand-mobile,使用了最常用的插入依賴(^):

"mand-mobile": "^4.16.4",

假設自己項目在上線階段, mand-mobile 更新到了 [email protected],而剛好 [email protected] 又不小心出現了一個新 bug 會導致頁面腳本錯誤。這時候上線安裝依賴的時候,由於 package.json^ 約束太寬鬆,就會導致 [email protected] 被安裝,從而導致上線出問題。

package-lock.json 就是爲了解決這個問題,通過 npm 安裝包的時候,會檢測本地是否有 package-lock.json

  • 如果沒有 package-lock.json,就在安裝包的時候將當前包依賴的詳細信息(包括子級依賴)都寫入生成 package-lock.json
  • 如果有 package-lock.json,則根據 package.json,參考 pacakge-lock.json 來安裝包依賴。來保證依賴穩定。

本質上 ppackage-lock.json 的作用類似於 node_modules 包依賴的快照。

單元測試

一個合格的庫應該包含完整的單元測試。這裏我們使用 jest 對應的 TypeScript 版本:ts-jest

ts-jest

ts-jestjest 的 TypeScript 支持版,API 和 jest 是一樣的,它能夠直接運行 .ts 爲後綴的單元測試文件。

安裝 ts-jest 和對應的類型聲明文件:

npm i --save-dev jest  #ts-jest 依賴 jest
npm i --save-dev ts-jest
npm i --save-dev @types/jest

package.json 中加入 jest 配置和 npm run test 的腳本:

{
  "name": "my-app",
  "main": "dist/index.js",
  "scripts": {
    "test": "jest --verbose"
  },
  "jest": {
    "rootDir": "tests",
    "transform": {
      "^.+\\.tsx?$": "ts-jest"
    },
    "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js",
      "jsx",
      "json",
      "node"
    ]
  }
}

這時候就可以基於 jest 編寫單元測試了。在 tests/ 目錄下加入 example.test.ts

import { isArrayLike } from '../src'

describe('my-app:isArrayLike', () => {
  test('isArrayLike(): true', () => {
    expect(
      isArrayLike([]),
    ).toBe(true)
  })

  test('isArrayLike(): false', () => {
    expect(
      isArrayLike({}),
    ).toBe(false)
  })
})

然後執行 npm run test 即可看到單元測試結果。

clipboard.png

express 測試

如果要測試 express/koa 之類的 web 應用框架程序,則可以使用 tj 大神的 supertest

安裝對應的包:

npm i --save-dev supertest
npm i --save-dev @types/supertest
import * as express from 'express'
/**
 * 用於測試 express、koa 等 web 應用框架的工具
 */
import * as request from 'supertest'
import middleware from '../src'

describe('my-app:basic', () => {
  test('locals', done => {
    const app = express()
    app.use(middleware)
    app.get('/example', (req, res) => {
      res.send({ code: 0 })
    })
    // 使用 supertest 進行測試
    request(app).get('/example').expect(200, { code: 0 }, done)
  })
})

debug

debug 也是 tj 大神編寫的一個庫,用於在應用程序中輸出 debug 信息,用於調試工具庫,著名的庫大部分都採用該庫進行 debug 支持。

npm i --save debug
npm i --save-dev @types/debug
import * as d from 'debug'

const debug = d(`my-app:basic`)

debug('debug info')

在啓動應用程序的時候,只需要在環境變量中注入 DEBUG 即可:

DEBUG=my-app* node app.js

DEBUG=my-app* ts-node app.ts

clipboard.png

vscode 基於 ts-node 調試

.vscode/launch.json 中可以配置基於 ts-node 的調試:

{
  // 使用 IntelliSense 瞭解相關屬性。 
  // 懸停以查看現有屬性的描述。
  // 欲瞭解更多信息,請訪問: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "啓動程序",
      // 基於 ts-node 調試
      "program": "${workspaceFolder}/node_modules/ts-node/dist/bin.js",
      "args": [
        "-P",
        "${workspaceRoot}/tests/tsconfig.json",
        "${workspaceRoot}/tests/app.ts", // 入口文件
      ]
    }
  ]
}

clipboard.png

文檔

文檔方面,簡陋一點的,可以直接使用 README,也可以用 gitbook。不過我個人方便比較推薦 vuepress

遠程託管文檔方面,要麼自建服務器,要麼直接託管到 Github 的 Pages。

使用 vuepress 編寫文檔

個人比較傾向於使用 vuepress 編寫文檔,是因爲裏面擴展 Markdown 擴展了許多豐富實用的語法,以及菜單結構的強大可配置。

這裏我們討論的是在項目中集成文檔。

  1. 在項目根目錄新建目錄 /docs
  2. npm i --save-dev vuepress
  3. 在項目的 package.json 中加入腳本
  "scripts": {
    "docs": "vuepress dev docs",
    "docs:build": "vuepress build docs"
  }

/docs 新增文件 README.md,寫入以下內容:

---
home: true
actionText: 開始使用 →
actionLink: /readme
footer: MIT Licensed | Copyright © 2018-present linkFly
features:
- title: 快速
  details: 快速創建庫
- title: 集成
  details: 集成單元測試和自動化 doc 部署
- title: TypeScript
  details: TypeScript 支持
---

集成了基礎工具的,使用 TypeScript 快速編寫一個應用庫

然後執行結合我們剛纔配置的命令,執行 npm run docs,終端 shell 會輸出 vuepress 啓動的服務地址:

clipboard.png

訪問地址,即可看到文檔頁面:

clipboard.png

使用 github pages 託管文檔

github pages 是 Github 提供的一個免費的頁面託管服務,我們可以將 vuexpress 編譯出來的文檔託管到上面。

Github Pages 服務和 Github 已經打通,可以從項目的 /docs 目錄自動部署,這也就是我們爲什麼要在項目裏新建 /docs 目錄的原因。

首先,我們將項目中 pageage.json 的腳本進行更新:

  "scripts": {
    "docs:build": "vuepress build docs && cp -rf ./docs/.vuepress/dist/* ./docs && rm -r ./docs/.vuepress/dist"
  }

這段腳本的大體意思就是先使用 vuepress 構建產出文檔的 HTML 文件(在 /docs/.vuepress/dist 目錄下),然後將 dist 目錄移動到 docs/ 目錄下,因爲 Github Pages 在識別 docs/ 的時候只能識別 docs/index.html

執行 npm run docs:build

將本地的項目 push 到 Github 以後,打開該項目的 Setting

clipboard.png

在 Github Pages 配置項選擇 docs/ 文件夾:

clipboard.png

然後訪問 https://<USERNAME or GROUP>.gitlab.io/<REPO>/ 即可看到自動部署的文檔。例如:https://linkfly6.github.io/ts-lib-basic/

使用持續集成服務 travis-ci

travis-ci 是一個持續集成服務,它可以用來自動部署和構建 Github 上的項目。

我們可以集成我們的單元測試。

在項目根目錄加入 .travis.yml,在 master 分支進行提交的時候自動運行 npm run test 命令(npm run test 命令配置參見 ts-jest 章節):

sudo: false
language: node_js
node_js:
  - "8"

cache:
  directories:
  - node_modules

branches:
  only:
  - master

script:
  npm run test

打開 https://travis-ci.org/ 進行註冊或登錄。新增接入的項目:

clipboard.png

選擇要打開持續集成的項目:

clipboard.png

然後我們更新文檔或代碼,提交代碼到 Github。

稍等大概幾十秒,就可以在 travis-ci 裏面看到自己的單元測試任務:

clipboard.png

最後,在測試完畢的情況下,在 https://www.npmjs.com/ 進行註冊。

在 npm 的源是官方的(npm config set registry https://registry.npmjs.org/)情況下,執行 npm login 登錄 npm 以後,npm publish 發佈包即可。

最後,爲了迎合這篇文章,我編寫了一個可以開箱即用的庫模板:https://github.com/linkFly6/ts-lib-basic

裏面集成了這篇文章所闡述的所有內容。

前端開發 QQ 羣:377786580
歡迎使用和了解金融出品的移動端組件庫 Mand-mobile

原文首發於我的個人網站:聽說 - https://tasaid.com/,推薦在我的網站閱讀更多技術文章。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章