從0到1開發一個開源項目(TS + ESlint + Jest + TravisCI)

最近想嘗試一下如何開源一個項目,正好手邊作異常監控的時候遇到一個功能。就是解析errorstack中的源碼位置。在npm中找了很久都沒有合適的庫。正好本身造一個輪子發佈出來。也趁這個機會把開源過程整理一下。

搭建項目框架

建立文件夾

# 建立項目文件件
mkdir sourcemap-stacktrack-parser
# 建立README.md文件
echo "# sourcemap-stacktrack-parser" >> README.md
複製代碼

初始化git倉庫

git init
# 添加README文件
git add README.md
# 提交代碼
git commit -m "first commit"
# 設置遠程倉庫地址
git remote add origin git@github.com:su37josephxia/sourcemap-stacktrack-parser.git
# 推送代碼
git push -u origin master

複製代碼

初始化npm

npm init -y
複製代碼

初始化tsc

安裝typescript包

npm i typescript ts-node-dev @types/node -d
複製代碼

建立tsconfig.json文件

{
    "compilerOptions": {
        "outDir": "./lib",
        "target": "es2017",
        "module": "commonjs",//組織代碼方式
        "sourceMap": true,
        "moduleResolution": "node", // 模塊解決策略
        "experimentalDecorators": true, // 開啓裝飾器定義
        "allowSyntheticDefaultImports": true, // 容許es6方式import
        "lib": ["es2015"],
        "typeRoots": ["./node_modules/@types"],
    },
    "include": ["src/**/*"]
}
複製代碼

建立index.js文件

mkdir src
echo 'console.log("helloworld")' >> src/index.ts
複製代碼

添加npm腳本

在package.json文件中添加node

"scripts": {
    "start": "ts-node-dev ./src/index.ts -P tsconfig.json --no-cache",
    "build": "tsc -P tsconfig.json",
}
複製代碼

修改程序入口

修改package.json中webpack

{
  ...
  "main": "lib/index.js",
  ...
}
複製代碼

驗證

npm start
複製代碼

image-20200210163007258

初始化Jest測試

安裝jest庫

npm install jest ts-jest @types/jest -d
複製代碼

建立jestconfig.json文件

{
  "transform": {
    "^.+\\.(t|j)sx?$": "ts-jest"
  },
  "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
  "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
}
複製代碼

package.json裏的 scripts 下的 test

{
  "scripts": {
    "test": "jest --config jestconfig.json --coverage",
  }
}
複製代碼

源碼中導出一個函數

export const add = (a: number, b: number) => a + b
複製代碼

建立測試用例

在src/___tests___文件夾中建立index.spec.tsgit

import { add } from "../index";
test("Index add fun", () => {
    const ret = add(1, 2)
    console.log(ret)
    expect(ret).toBe(3);
});
複製代碼

啓動測試用例

npm run test
複製代碼

初始化Eslint

安裝eslint包es6

npm install prettier tslint tslint-config-prettier -d
複製代碼

配置tslint.jsongithub

{
  "extends": ["tslint:recommended", "tslint-config-prettier"],
  "rules": {
    "no-console": false, // 忽略console.log
    "object-literal-sort-keys": false,
    "member-access": false,
    "ordered-imports": false
  },
  "linterOptions": {
    "exclude": ["**/*.json", "node_modules"]
  }
}
複製代碼

配置 .prettierrc

Prettier 是格式化代碼工具。用來保持團隊的項目風格統一。web

{
  "trailingComma": "all",
  "tabWidth": 4,
  "semi": false,
  "singleQuote": true,
  "endOfLine": "lf",
  "printWidth": 120,
  "overrides": [
    {
      "files": ["*.md", "*.json", "*.yml", "*.yaml"],
      "options": {
        "tabWidth": 2
      }
    }
  ]
}
複製代碼

配置.editorconfig

「EditorConfig幫助開發人員在不一樣的編輯器和IDE之間定義和維護一致的編碼樣式。EditorConfig項目由用於定義編碼樣式的文件格式和一組文本編輯器插件組成,這些插件使編輯器可以讀取文件格式並遵循定義的樣式。EditorConfig文件易於閱讀,而且與版本控制系統配合使用。typescript

對於VS Core,對應的插件名是EditorConfig for VS Codenpm

# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4

[{*.json,*.md,*.yml,*.*rc}]
indent_style = space
indent_size = 2
複製代碼

添加script腳本json

{
  "scripts": {
    "format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
    "lint": "tslint -p tsconfig.json"
  }
}
複製代碼

設置 git 提交的校驗鉤子

安裝husky庫

npm install husky -d
複製代碼

新建.huskyrc

{
    "hooks": {
        "pre-commit": "npm run format && npm run lint && npm test"
    }
}
複製代碼

驗證結果

image-20200210174945767

設置Travis CI

Travis CI 提供的是持續集成服務,它僅支持 Github,不支持其餘代碼託管。它須要綁定 Github 上面的項目,還須要該項目含有構建或者測試腳本。只要有新的代碼,就會自動抓取。而後,提供一個虛擬機環境,執行測試,完成構建,還能部署到服務器。只要代碼有變動,就自動運行構建和測試,反饋運行結果。確保符合預期之後,再將新代碼集成到主幹。緩存

這個項目須要Travis在提交後自動進行測試而且向codecov提供測試報告。

  • 測試
  • 報告分析

登陸TravicCI網站

登陸https://www.travis-ci.org/網站

使用github帳號登陸系統

配置.travis.yml

運行自動化測試框架

language: node_js               # 項目語言,node 項目就按照這種寫法就OK了
node_js:
- 13.2.0 			# 項目環境
cache:				# 緩存 node_js 依賴,提高第二次構建的效率
 directories:
 - node_modules
test:
 - npm run test # 運行自動測試框架
複製代碼

參考教程:Travis CI Tutorial

上傳配置到github

啓動持續集成

經過github帳號登陸travis

image-20200211114132012

image-20200210xxsdfds

獲取持續集成經過徽標

將上面 URL 中的 {GitHub 用戶名} 和 {項目名稱} 替換爲本身項目的便可,最後能夠將集成完成後的 markdown 代碼貼在本身的項目上

image-20200211180202113

http://img.shields.io/travis/{GitHub 用戶名}/{項目名稱}.svg
複製代碼

image-20200211115443738

設置Codecov

Codecov是一個開源的測試結果展現平臺,將測試結果可視化。Github上許多開源項目都使用了Codecov來展現單測結果。Codecov跟Travis CI同樣都支持Github帳號登陸,一樣會同步Github中的項目。

codecov.io/

npm install codecov -d
複製代碼

在package.json添加codecov

{
    ...,
    "scripts": {
        ...,
        "codecov": "codecov"
    }
}
複製代碼

在travis.yaml中添加

after_success:			# 構建成功後的自定義操做
- npm run codecov		# 生成 Github 首頁的 codecov 圖標
複製代碼

image-20200211165610650

將圖標嵌入到README.md之中

[![Codecov Coverage](https://img.shields.io/codecov/c/github/<Github Username>/<Repository Name>/&lt;Branch Name>.svg?style=flat-square)](https://codecov.io/gh/<Github Username>/<Repository Name>/)
複製代碼

最後獲取測試經過圖標

Codecov Coverage

TDD方式編寫功能

編寫測試用例

這個庫的功能須要將js錯誤的調用棧中的壓縮代碼位置轉換爲源碼位置。固然要藉助sourcemap的幫忙。因此輸入數據分別是errorstack和sourcemap。

image-20200212144102845

首先把測試用的sourcemap放入src/__test__目錄中

而後編寫測試用例index.spec.ts

import StackParser from "../index"
const { resolve } = require('path')
const error = {
  stack: 'ReferenceError: xxx is not defined\n' +
    ' at http://localhost:7001/public/bundle.e7877aa7bc4f04f5c33b.js:1:1392\n' +
    ' at http://localhost:7001/public/bundle.e7877aa7bc4f04f5c33b.js:1:1392',
  message: 'Uncaught ReferenceError: xxx is not defined',
  filename: 'http://localhost:7001/public/bundle.e7877aa7bc4f04f5c33b.js'
}
describe('parseStackTrack Method:', () => {
  it("測試Stack轉換爲StackFrame對象", () => {
    expect(StackParser.parseStackTrack(error.stack, error.message))
      .toContainEqual(
        {
          columnNumber: 1392,
          lineNumber: 1,
          fileName: 'http://localhost:7001/public/bundle.e7877aa7bc4f04f5c33b.js',
          source: ' at http://localhost:7001/public/bundle.e7877aa7bc4f04f5c33b.js:1:1392'
        })
  })
})

describe('parseOriginStackTrack Method:', () => {
  it("正常測試", async () => {
    const parser = new StackParser(resolve(__dirname, './data'))
    const originStack = await parser.parseOriginStackTrack(error.stack, error.message)
    // 斷言 
    expect(originStack[0]).toMatchObject(
      {
        source: 'webpack:///src/index.js',
        line: 24,
        column: 4,
        name: 'xxx'
      }
    )
  })

  it("sourcemap文件不存在", async () => {
    const parser = new StackParser(resolve(__dirname, './xxx'))
    const originStack = await parser.parseOriginStackTrack(error.stack, error.message)
    // 斷言 
    expect(originStack[0]).toMatchObject(
      {
        columnNumber: 1392,
        lineNumber: 1,
        fileName: 'http://localhost:7001/public/bundle.e7877aa7bc4f04f5c33b.js',
        source: ' at http://localhost:7001/public/bundle.e7877aa7bc4f04f5c33b.js:1:1392'
      }
    )
  })
})

複製代碼

實現功能

const ErrorStackParser = require('error-stack-parser')
const { SourceMapConsumer } = require('source-map')
const path = require('path')
const fs = require('fs')
export default class StackParser {
    private sourceMapDir: string
    private consumers: Object
    constructor(sourceMapDir) {
        this.sourceMapDir = sourceMapDir
        this.consumers = {}
    }
    /** * 轉換錯誤對象 * @param stack 堆棧字符串 * @param message 錯誤信息 */
    static parseStackTrack(stack: string, message?: string) {
        const error = new Error(message)
        error.stack = stack
        const stackFrame = ErrorStackParser.parse(error)
        return stackFrame
    }

    /** * 轉換錯誤對象 * @param stack 堆棧字符串 * @param message 錯誤信息 */
    parseOriginStackTrack(stack: string, message?: string) {
        const frame = StackParser.parseStackTrack(stack,message)
        return this.getOriginalErrorStack(frame)
    }



    /** * 轉換源代碼運行棧 * @param stackFrame 堆棧片斷 */
    async getOriginalErrorStack(stackFrame: Array<Object>) {
        const origin = []
        for (let v of stackFrame) {
            origin.push(await this.getOriginPosition(v))
        }
        return origin
    }

    /** * 轉換源代碼運行棧 * @param stackFrame 堆棧片斷 */
    async getOriginPosition(stackFrame) {
        let { columnNumber, lineNumber, fileName } = stackFrame
        fileName = path.basename(fileName)
        // 判斷consumer是否存在
        let consumer = this.consumers[fileName]
        if (consumer === undefined) {
            // 讀取sourcemap
            const sourceMapPath = path.resolve(this.sourceMapDir, fileName + '.map')
            // 判斷文件是否存在
            if (!fs.existsSync(sourceMapPath)) {
                return stackFrame
            }
            const content = fs.readFileSync(sourceMapPath, 'utf-8')
            // console.log('content',content)
            consumer = await new SourceMapConsumer(content, null)
            this.consumers[fileName] = consumer
        }
        const parseData = consumer.originalPositionFor({line:lineNumber,column:columnNumber})

        return parseData
    }

}
複製代碼

啓動jest進行調試

npm run dev
複製代碼

image-20200212145126731

添加開源許可證

每一個開源項目都須要配置一份合適的開源許可證來告知全部瀏覽過咱們的項目的用戶他們擁有哪些權限,具體許可證的選取能夠參照阮一峯前輩繪製的這張圖表:

image-20200212152048790

那咱們又該怎樣爲咱們的項目添加許可證了?其實 Github 已經爲咱們提供了很是簡便的可視化操做: 咱們平時在逛 github 網站的時候,發現很多項目都在 README.md 中添加徽標,對項目進行標記和說明,這些小圖標給項目增色很多,不只簡單美觀,並且還包含清晰易懂的信息。

  1. 打開咱們的開源項目並切換至 Insights 面板
  2. 點擊 Community 標籤
  3. 若是您的項目沒有添加 License,在 Checklist 裏會提示您添加許可證,點擊 Add 按鈕就進入可視化操做流程了

image-20200212153554131

image-20200212154111197

編寫文檔

編寫README.md

編寫內容

能夠參考README最佳實踐

參考https://github.com/jehna/readme-best-practices/blob/master/README-default.md

添加修飾圖標

以前已經添加了travisci的build圖標和codecov的覆蓋率圖表。

若是想繼續豐富圖標給本身的項目增光登陸

shields.io/ 這個網站

好比以添加github的下載量爲例

image-20200212151934578

image-20200212151848746

編寫package.json描述信息

JSdoc

添加開源許可證

每一個開源項目都須要配置一份合適的開源許可證來告知全部瀏覽過咱們的項目的用戶他們擁有哪些權限,具體許可證的選取能夠參照阮一峯前輩繪製的這張圖表:

image-20200212155122396

那咱們又該怎樣爲咱們的項目添加許可證了?其實 Github 已經爲咱們提供了很是簡便的可視化操做: 咱們平時在逛 github 網站的時候,發現很多項目都在 README.md 中添加徽標,對項目進行標記和說明,這些小圖標給項目增色很多,不只簡單美觀,並且還包含清晰易懂的信息。

  1. 打開咱們的開源項目並切換至 Insights 面板
  2. 點擊 Community 標籤
  3. 若是您的項目沒有添加 License,在 Checklist 裏會提示您添加許可證,點擊 Add 按鈕就進入可視化操做流程了

發佈到NPM倉庫

建立發佈腳本

publish.sh

#!/usr/bin/env bash
npm config get registry # 檢查倉庫鏡像庫
npm config set registry=http://registry.npmjs.org
echo '請進行登陸相關操做:'
npm login # 登錄
echo "-------publishing-------"
npm publish # 發佈
npm config set registry=https://registry.npm.taobao.org # 設置爲淘寶鏡像
echo "發佈完成"
exit
複製代碼

執行發佈

./publish.sh
複製代碼

填入github用戶名密碼後

image-20200212155812424

登陸https://www.npmjs.com/~josephxia

image-20200212155918645

就能夠看到本身的第一個開源做品誕生啦。

總結

基本上把開源過程和TDD的開發走了一遍。

由於是第一遍完整的走感受仍是挺麻煩的。後續我也找找有沒有相應的腳手架和工具。若是沒有特別合適的考慮本身造一個這樣的輪子。

相關文章
相關標籤/搜索