ReactWheels06-02测试覆盖率和持续集成下

每次自己发代码是不是太麻烦

能不能 push 代码 ci运行test 成功之后自动发布代码

可以

修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#https://github.com/revolunet/create-react-app-circleci/blob/master/.circleci/config.yml
defaults: &defaults
docker:
# 我们使用 node8来测试
- image: circleci/node:8
# circleci的 version
version: 2
jobs:
# 准备阶段
prepare:
<<: *defaults
steps:
# 迁出代码
- checkout
# 根据当前的package.json 创建一个 md5 把它当作key 创建一个缓存,加速之后代码的运行,只要package.json里面没有变化
# 就可以使用上次的缓存,不用从头安装
- restore_cache:
keys:
- v2-dependencies-{{ checksum "package.json" }}
- run: yarn install
- save_cache:
paths:
- node_modules
key: v2-dependencies-{{ checksum "package.json" }}
- persist_to_workspace:
root: .
paths:
- node_modules
build:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: .
- run: yarn build
# 持久化,意思就是 dist目录不删除
- persist_to_workspace:
root: .
paths:
- dist
# 这三个文件必须有才能发布
- package.json
- LICENSE
- README.md
test:
<<: *defaults
steps:
- checkout
- attach_workspace:
at: .
- run: yarn ci
- store_test_results:
path: test-results
publish:
<<: *defaults
steps:
# 把 build产生的目录 放在当前目录
- attach_workspace:
at: .
- run: npm publish

workflows:
version: 2
build_accept_deploy:
jobs:
- prepare
- build:
requires:
- test
- test:
requires:
- prepare
# 新增 pubish的依赖 就是先打包
- publish:
requires:
- build

提交代码

  1. https://circleci.com/dashboard 看你的项目 publish失败了, 因为没法登录 npm啊
  2. 无论如何不要泄漏你的密码
  3. 无论如何不要泄漏你的密码
  4. 无论如何不要泄漏你的密码

npm token

  1. 登录你的npm
  2. 右上角你头像点击 tokens
  3. Create New Token 然后选择 read and publish > create token
  4. 复制你的那一串token 形如 add5b0f1-1111-4036-dd11-3118e4bfd279 我这个是假的
  5. 到你的 circle ci 点击 左侧 jobs 选中你的项目,右边小齿轮(settings)
  6. 选择 Environment Variables 环境变量
  7. 点击 Add Variable

    1
    2
    3
    4
    5
    name: NPM_TOKEN
    value: add5b0f1-1111-4036-dd11-3118e4bfd279

    点击 Add Variable
    添加之后它就作为脚本的环境变量,你可以读取到
  8. 回去继续发布我们的代码,还是失败。

  9. 因为你没告诉脚本环境变量 token 在哪里

    1
    - run: npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN

    完整版

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    #https://github.com/revolunet/create-react-app-circleci/blob/master/.circleci/config.yml
    defaults: &defaults
    docker:
    # 我们使用 node8来测试
    - image: circleci/node:8
    # circleci的 version
    version: 2
    jobs:
    # 准备阶段
    prepare:
    <<: *defaults
    steps:
    # 迁出代码
    - checkout
    # 根据当前的package.json 创建一个 md5 把它当作key 创建一个缓存,加速之后代码的运行,只要package.json里面没有变化
    # 就可以使用上次的缓存,不用从头安装
    - restore_cache:
    keys:
    - v2-dependencies-{{ checksum "package.json" }}
    - run: yarn install
    - save_cache:
    paths:
    - node_modules
    key: v2-dependencies-{{ checksum "package.json" }}
    - persist_to_workspace:
    root: .
    paths:
    - node_modules
    build:
    <<: *defaults
    steps:
    - checkout
    - attach_workspace:
    at: .
    - run: yarn build
    # 持久化,意思就是 dist目录不删除
    - persist_to_workspace:
    root: .
    paths:
    - dist
    # 这三个文件必须有才能发布
    - package.json
    - LICENSE
    - README.md
    test:
    <<: *defaults
    steps:
    - checkout
    - attach_workspace:
    at: .
    - run: yarn ci
    - store_test_results:
    path: test-results
    publish:
    <<: *defaults
    steps:
    # 把 build产生的目录 放在当前目录
    - attach_workspace:
    at: .
    - run: npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN
    - run: npm publish

    workflows:
    version: 2
    build_accept_deploy:
    jobs:
    - prepare
    - build:
    requires:
    - test
    - test:
    requires:
    - prepare
    - publish:
    requires:
    - build
  10. 记得修改在修改一下版本好 在 push 代码

此时我们做到了 push之后,自动测试 自动发布

但是你每次改还要更改版本号,能不能自动改版本号?

  • 可以
1
2
3
4
5
6
7
8
# 自动改你版本的最后一位
npm version patch

# 自动改你版本的倒数第二位
npm version minor

# 自动改你版本的第一位
npm version major

任何一个包都有这样一个版本

1
2
3
4
5
6
7
8
A.B.C

分别对应
major.minor.patch

patch 代表补丁,没有API的变化
minor 代表 API有变化,但不影响现有代码
major 代表 API变化很大,影响现有代码

我们的项目根目录 新建 deploy.sh

1
2
3
#!/bin/env bash
npm version patch
git push

分配执行权限

1
chmod 777 ./deploy.sh

本地运行

1
2
3
4
./deploy.sh

# 如果运行不了你就
sh ./deploy.sh

我们想动态指定发布的版本而不是写死 patch

1
2
3
#!/bin/env bash
npm version $1
git push

这样你就可以传参数来指定发布的版本了

1
./deploy.sh patch

但是有时候你改了代码 然后在./deploy.sh 就会失败,因为你必须先提交代码才能执行它

所以我们应该修改下 deploy.sh

1
2
#!/bin/env bash
npm version $1 && git push

再次运行

1
./deploy.sh patch

假如我正常提交代码后 git push 然后CI通过后自动帮我们 publish,因为只要 build 之后就会 publish 这样会出错的

根据 tag 来发布

搜索 circle tag filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
workflows:
version: 2
build_accept_deploy:
jobs:
- prepare
- build:
requires:
- test
- test:
requires:
- prepare
- publish:
requires:
- build
filters:
tags:
only: /v[0-9]+(\.[0-9]+)*/
# 忽略所有分支,我只看 tag
branches:
ignore: /.*/

如何给我们的项目加上小标志

  • circleci 小图标circleci badge
    • 在你的 circleci 选择 Status Badges
    • 选择默认分支
    • 他会给你个markdown 的代码 复制到你项目的 readme.md
  • npm 小图标npm badge
    • 搜索你的 UI库 如我的 package.json 里的name 是fui_t888 得到一个 markdown内容复制到你的 readme.md

js jsx ts tsx的区别

  • jsx 是 js 的扩展,支持 用 js 的方式写 标签 是xml标准

    1
    2
    3
    return (<div></div>)
    就不用这样
    React.createElement('div')
  • js 和 ts 的区别就是 ts = Type + JavasSript 也是对js的扩展

    1
    2
    3
    4
    5
    // js
    const a = 1

    // ts
    const a:number = 1
  • tsx 是对 ts 的扩展,支持 写标签 支持 Type

所以 tsx 是最厉害的

收尾工作

  • 比如我们 yarn test 的时候,它帮我们生成了 测试覆盖,但是这个过程会很慢,我们一般来说不需要知道这个测试覆盖率。只有 CI 生成了才去看

新建 jest.config.ci.js

1
2
3
4
5
6
7
8
9
10
11
const base = require('./jest.config')
module.exports = Object.assign({},base,{
reporters: ["jest-junit"],
collectCoverage: true,
// "lib/**/*.{ts,tsx}" lib里的文件都要测,除了__tests__里的,jest的默认规则是排除有__tests__目录的文件
// "!**/node_modules/**" 任意包含node_modules的目录不测
collectCoverageFrom: ["lib/**/*.{ts,tsx}", "!**/node_modules/**"],
// 生成的报告放在那里
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov'],
})

修改 jest.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// https://jestjs.io/docs/en/configuration.html

module.exports = {
verbose: true,
clearMocks: false,
reporters: ["default"],

collectCoverage: false,
// // "lib/**/*.{ts,tsx}" lib里的文件都要测,除了__tests__里的,jest的默认规则是排除有__tests__目录的文件
// // "!**/node_modules/**" 任意包含node_modules的目录不测
// collectCoverageFrom: ["lib/**/*.{ts,tsx}", "!**/node_modules/**"],
// // 生成的报告放在那里
// coverageDirectory: 'coverage',
// coverageReporters: ['text', 'lcov'],

moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
moduleDirectories: ['node_modules'],
globals: {
'ts-jest': {
tsConfig: 'tsconfig.test.json',
},
},
moduleNameMapper: {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/__mocks__/file-mock.js",
"\\.(css|less|sass|scss)$": "<rootDir>/test/__mocks__/object-mock.js",
},
testMatch: ['<rootDir>/**/__tests__/**/*.unit.(js|jsx|ts|tsx)'],
transform: {
"^.+unit\\.(js|jsx)$": "babel-jest",
'^.+\\.(ts|tsx)$': 'ts-jest',
},
setupFilesAfterEnv: ["<rootDir>test/setupTests.js"]
}

修改 package.json

1
2
3
4
针对 ci 才使用 覆盖率测试 ,我们的测试 使用默认的 jest.config.js
"scripts": {
"ci": "cross-env NODE_ENV=test JEST_JUNIT_OUTPUT=./test-results/jest/results.xml jest --config=jest.config.ci.js"
},

制作我们的简陋官网,来鼠标点点点

修改 webpack.config.dev.js 里的入口文件,我们用example.tsx覆盖默认的入口。因为这是 dev 环境

1
2
3
4
5
6
7
8
9
10
11
12
13
const base = require('./webpack.config')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = Object.assign({},base,{
mode:'development',
entry:{
example:'./example.tsx'
},
plugins:[
new HtmlWebpackPlugin({
template:'example.html'
})
]
})

修改 index.html 为 example.html

跟路径新建 example.tsx

1
2


启动我们项目

1
yarn start

运行成功。 我们需要一个左侧菜单右侧显示组件的面板

此时我们需要 router

1
2
3
yarn add react-router-dom@5.0.0

yarn add --dev @types/react-router-dom@4.3.1

修改我们的 example.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React from 'react';
import ReactDOM from 'react-dom';
import {HashRouter as Router, Route, NavLink} from 'react-router-dom';

import IconExample from './lib/icon/icon.example';
import ButtonExample from './lib/button/button.example';

ReactDOM.render(
<Router>
<div>
<header>
<div className="logo">
FUI
</div>
</header>
<div>
<aside>
<h2>组件</h2>
<ul>
<li>
<NavLink to="/icon">Icon</NavLink>
</li>
<li>
<NavLink to="/button">Button</NavLink>
</li>
</ul>
</aside>
<main>
<Route path="/icon" component={IconExample}/>
<Route path="/button" component={ButtonExample}/>
</main>
</div>
</div>
</Router>
,document.querySelector('#root'));

新建 lib/icon/icon.example.tsx

1
2
3
4
5
6
7
8
9
10
11
import React from 'react';
import Icon from './icon'
const IconExample:React.FunctionComponent = ()=>{
return (
<div>
<Icon name="alipay"/>
</div>
)
}

export default IconExample;

新建 lib/button/button.example.tsx

1
2
3
4
5
6
import React from 'react';
const ButtonExample:React.FunctionComponent = ()=>{
return (<div>hi!button</div>)
}

export default ButtonExample;

此时 我们的项目虽然没样式,但是点击对应的组件,就显示对应的页面了

代码仓库