从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的开发走了一遍。

由于是第一遍完整的走感受仍是挺麻烦的。后续我也找找有没有相应的脚手架和工具。若是没有特别合适的考虑本身造一个这样的轮子。

相关文章
相关标签/搜索