npm

一、发布npm包

  1. 注册npm用户

  2. 在本地开发内容

  3. 在该文件内执行npm login 登陆用户

  4. npm publish 发布自己开发完成的包

注册没什么好说的,直接略过,主要说如何开发以及发布中可能遇到哪些问题。

一、基础

1.1 基础配置

建议优先建立github线上库,同时写个README.md 然后直接克隆下来。如果在本地文件内直接npm init 后面package.json的内容还要自己填写。

  • github 创建仓库,生成readme.md

  • 克隆完库之后npm init 因为有.git的存在,homepage ,bugs, repository的地址都会默认填充好。

  • 设计项目的目录结构,这个看自己设计了。常见的src 源文件,lib打包后输出的文件。

1.2 package.json字段解释,需要了解。

  • scripts: 指定了运行脚本命令的npm命令行缩写,比如start指定了运行npm run start时,所要执行的命令。

  • version: npm 每次从新发包时,必须要改版本号,不然报错。

1
2
3
4
5
升级补丁版本号:npm version patch

升级小版本号:npm version minor

升级大版本号:npm version major
  • dependencies: 字段指定了项目运行所依赖的模块。

  • devDependencies: 指定项目开发所需要的模块。

  • dependencies 和devDependencies中,~1.2.2,表示安装1.2.x的最新版本(不低于1.2.2),但是不安装1.3.x。

  • dependencies 和devDependencies中,^1.2.2,表示安装1.x.x的最新版本(不低于1.2.2),但是不安装2.x.x。

  • dependencies 和devDependencies中,latest:安装最新版本。

  • peerDependencies字段,就是用来供插件指定其所需要的主工具的版本

1
2
3
4
5
6
7
8
{
"name": "chai-as-promised",
"peerDependencies": {
"chai": "1.x"
}
}

安装chai-as-promised模块时,主程序chai必须一起安装,而且chai的版本必须是1.x。如果你的项目指定的依赖是chai的2.0版本,就会报错。
  • bin字段 bin项用来指定各个内部命令对应的可执行文件的位置。
1
2


  • main字段指定了加载的入口文件,require(‘moduleName’)或import就会加载这个文件。一般会指向打包完的文件里。

  • config 字段

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name" : "foo",
"config" : { "port" : "8080" },
"scripts" : { "start" : "node server.js" }
}

// server.js
http
.createServer(...)
.listen(process.env.npm_package_config_port)


用户执行npm run start命令时,这个脚本就可以得到值。
  • browser字段 browser指定该模板供浏览器使用的版本。Browserify这样的浏览器打包工具,通过它就知道该打包那个文件。

  • engines 字段字段指明了该模块运行的平台,比如 Node 的某个版本或者浏览器。

1
{ "engines" : { "node" : ">=0.10.3 <0.12" } }

二、开发规范

2.1 代码规范eslint

使用eslint:

1
2
npm install eslint -g
eslint init

根据 eslint 提供的选项结合自己的需求,一路选择后,生成.eslintrc.js文件,里面是一系列的规则配置。有eslintrc配置文件,编辑器才会生效提示。

Sublime Text3: 需要安装插件 Sublime​Linter 和 SublimeLinter-contrib-eslint。才可以自动提示eslint语法错误。

vscode: 安装eslint 插件即可。

脚本命令:

1
2
3
4
5
"scripts": {
"lint": "eslint --fix src"
}

npm run lint // 执行代码eslint检测,对于一些简单错误,例如多了空行,他会自动删除,复杂错误会抛出来。

如果某些目录不想使用校验,可以创建一个 .eslintignore,把不需要校验的目录放进入。语法和gitignore类似。

1
2
3
4
// .eslintignore

/node_modules/
/lib/

Tips:

  • window 下创建隐藏文件:.eslintignore.

  • mac 下直接 touch .eslintignore

2.2 git hook 搭配eslint校验

2.2.1 使用 husky插件,规范提交

安装:

1
npm install husky --save-dev

配置:在package.json的script

1
2
3
"precommit": "./node_modules/.bin/eslint --ext .jsx,.js ./",
"prepush": "./node_modules/.bin/eslint --ext .jsx,.js ./",
"eslint_fix": "./node_modules/.bin/eslint --fix --ext .jsx,.js ./",

注意:

  • eslint是否全局安装,对脚本执行的写法是有影响的。上面的代码执行的是局部安装的eslint

  • 在commit之前,如果有不符合eslint规范的代码时无法提交的。也无法推到远程库。

2.2.2 husky + lint-staged 对提交内容进行规范

对于一些以前从来没有用过eslint的项目来说.突然引入这种工具。你可能面临的是把所有文件都按照eslint都格式化一遍。

安装:

1
npm install lint-staged --save-dev

package.json 配置

1
2
3
4
5
6
7
8
9
10
11
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack-dev-server --inline",
"api": "node api/server.js",
"precommit": "lint-staged"
},
"lint-staged": {
// 文件目录
"src/**/*.js": "eslint"
},

2.2.2 husky + lint-staged + prettier 自动格式化代码

在上面的基础上加入自动格式化代码。格式化不了的在抛出错误。该功能可能在格式化的时候还需要额外配置,或者格式化的代码并不是完全符合eslint。反而鸡肋了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack-dev-server --inline",
"api": "node api/server.js",
"precommit": "lint-staged"
},
"lint-staged": {
"src/**/*.js": [
"eslint --fix --ext .js",
"prettier --write",
"git add"
]
},
  • 为了和eslint配合使用需要引入两个插件eslint-plugin-prettier和eslint-config-prettier。

  • 因为 ESLint 和 Prettier 都可以格式化代码,如果他们对格式化代码执行不同规则,那就可能发生冲突,可以通过配置解决大部分冲突,如下配置。

  • eslint-plugin-prettier,它会对比格式化前和用 Prettier 格式化后的代码,有不一致的地方就会报错提示。

  • 两者混合使用时候,需要修改规则,以防止重复或冲突;eslint-config-prettier 即为解决此问题的存在,可以使用它关闭所有可能引起冲突的规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
// .prettierrc.js 文件

module.exports = {
printWidth: 80,
semi: true,
singleQuote: true,
trailingComma: 'none',
bracketSpacing: true,
jsxBracketSameLine: false,
arrowParens: 'avoid',
requirePragma: false,
proseWrap: 'preserve'
};

但仍有一些是无法解决的,比如,Prettier 在 function 关键字后不允许有空格且不能自定义,那如果想避免 ESLint 不报错,只能配置 ESLint 允许 function 关键字后不带空格(主要针对匿名函数)。如下。

1
2
3
4
5
6
7
8
9
function aa () {
console.log(3333);
}

会被格式化下面这种,但是函数名与括弧间没有空格是不符合eslint。

function aa() {
console.log(3333);
}

其实通过前两种方式,已经算是解决规范问题了,第三种不过是扩充,而且存在不稳定性和非预期错误的产生,以及对规则的配置。本想提高产出,但是却额外增加了学习成本和时间成本,有点鸡肋。

2.3 git的分支策略

线上分支:

  1. master 主分支,用来管理当前部署在线上的应用版本。提供回退备份机制。

  2. dev 分支,用来管理接下来要开发的内容版本。

  3. fixbug 分支,用来处理紧急bug的分支。

  • 用dev分支发布最新开发完成的内容,master作为回退备份。可以保证当发布线上出现重大问题后,退到上一个线上稳定版本。

  • 用fixbug分支作为bug处理,可以保证bug和当前开放内容分开处理,并快速开发上线。

  • 不论dev还是fixbug,只要新的迭代内容发布到线上通过测试后,都要及时合并到主分支。

本地分支:

本地分支个人开发习惯是,temp分支用来开发,backup分支用来备份开发前的内容,然后定期merge分支。

单独出来一份temp分支的好处:

  1. 当我想pull代码时,必然要切换分支,这时候必须进行两种操作之一,commit代码或git stash 暂存修改内容。无论哪种都是比较好的操作,都能避免误操作checkout丢失内容,无法找回。

  2. 冲突不会发生在pull阶段,而是在merge阶段。在pull代码时冲突,处理不好的话,也会产生数据错误或丢失,另外temp分支可以备份内容,然后废弃。

2.4 钉钉机器人和git

将git的提交动作和钉钉群里机器人关联,并通知到群里。可以为协同开发提供一个反馈机制。

进入 GitHub 代码库,依次点击「Settings」、「Webhooks & Services」、「Add Webhook」

钉钉文档

三、设计包内容

3.1 目录结构设计

包可简可复杂,目录设计可以根据需求敲定,package.json 中main指定输出,保证功能正常提供即可。

常见的:

1
2
3
4
5
6
7
|-- bin/  命令脚本文件
|-- lib/ 打包后的输出文件
|-- test/ 测试
|-- src/ 源文件
|-- package.json
|-- README.md
|-- ··· more

3.2 开发基本思路

  1. 高内聚,低耦合 功能性组件开发
  2. 可拓展性 不是单个方法类的包都需要考虑拓展性
  3. 兼容性 es6语法的处理,或者其他兼容性问题
  4. 按需加载 功能复杂,庞大的组件按需加载能提供更好的体验
  5. 说明文档 没说明,没人知道包是干嘛的

3.3 单元测试

定义:

单元测试是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

作用:

消除程序单元的不可靠,采用自底向上的测试路径。通过先测试程序部件再测试部件组装,使集成测试变得更加简单。

模式:

BDD: 行为驱动开发,它鼓励软件项目中的开发者、 QA和非技术人员或商业参与者之间的协作。主要是从用户的需求出发,强调系统行为。

TDD: 测试驱动开发,TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD的基本思路就是通过测试来推动整个开发的进行,但测试驱动开发并不只是单纯的测试工作,而是把需求分析,设计,质量控制量化的过程。

karma + mocha + power-assert + karma-coverage-istanbul-reporter 参考

karma: 一个测试运行器,搭配插件用于启动浏览器,运行测试案例并将结果报告给我们。该工具的主要作用是将项目运行在各种主流Web浏览器进行测试。

Mocha:一个测试框架。可结合chai断言库使用。

power-assert:一个测试断言库,提供了更好的断言语法。

karma-coverage-istanbul-reporter : 测试覆盖报告。

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
安装:
npm install karma-cli -g

其他库和依赖:
npm install karma mocha power-assert karma-coverage-istanbul-reporter istanbul-instrumenter-loader

初始化:
karma init karma.conf.js

// karma.conf.js

const path = require('path');
module.exports = function(config) {
config.set({
// 基础路径,用在files,exclude属性上
basePath: '',
// 表示可以在测试文件中不需引入即可使用两个库的全局方法
frameworks: ['mocha', 'power-assert'],
files: [
'./test/index.js'
],
exclude: [
],
preprocessors: {
'./test/index.js': ['webpack', 'sourcemap']
},

reporters: ['spec', 'coverage-istanbul'],
port: 9876,
colors: true,
/**
* 日志等级
* 可能的值:
* config.LOG_DISABLE //不输出信息
* config.LOG_ERROR //只输出错误信息
* config.LOG_WARN //只输出警告信息
* config.LOG_INFO //输出全部信息
* config.LOG_DEBUG //输出调试信息
*/
logLevel: config.LOG_INFO,
// 启用或禁用自动检测文件变化进行测试, 如果为false。测试运行需要两步,karma start darma.conf.js 然后在启动terminal运行karma run
autoWatch: true,
browsers: ['Chrome'],

coverageReporter: {
dir: './coverage', // 覆盖率结果文件放./coverage 文件夹中
type: 'html'
},
webpack: {
output: {
path: __dirname + '/dev',
filename: '[name].js',
libraryTarget: 'umd'
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'istanbul-instrumenter-loader',
options: { esModules: true }
},
enforce: 'post',
include: path.resolve('./src')
}
]
}
},
webpackMiddleware: {
noInfo: false
},
singleRun: false,
concurrency: Infinity
})
}


// package.json全部依赖

"abbrev": "^1.1.1",
"array-find-index": "^1.0.2",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-eslint": "^10.0.1",
"babel-loader": "^7.1.5",
"babel-plugin-istanbul": "^5.1.4",
"babel-preset-es2015": "^6.24.1",
"babel-preset-power-assert": "^3.0.0",
"babel-register": "^6.26.0",
// 生成 coveralls
"coveralls": "^3.0.3",
"cross-env": "^5.2.0",
"eslint": "^5.16.0",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-standard": "^4.0.0",
// git commit push hook。 搭配eslint做代码检测
"husky": "^1.3.1",
"istanbul-instrumenter-loader": "^3.0.1",
"karma": "^4.1.0",
// 启动浏览器
"karma-chrome-launcher": "^2.2.0",
// 生成测试覆盖文件html。lcov.info
"karma-coverage-istanbul-reporter": "^2.0.5",
"karma-espower-preprocessor": "^1.2.0",
"karma-mocha": "^1.3.0",
// 搭配mocha生成测试报告
"karma-mocha-reporter": "^2.2.5",
"karma-power-assert": "^1.0.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "^0.0.32",
"karma-webpack": "^3.0.5",
"mocha": "^6.1.4",
"nopt": "^4.0.1",
// 生成测试覆盖率工具
"nyc": "^14.1.1",
// 断言库
"power-assert": "^1.6.1",
"webpack": "^3.0.0"

问题:

希望测试文件也类似src源文件一样,一个入口index.js,其他的采用es6模块引入,切支持es6语法。(node 是 CommonJS规范,没办法直接用)。

  • test目录下建立index.js, karma.conf.js 的file指到index.js
1
2
3
4
5
6
7
8
9
// test/index.js 配置  istanbul-instrumenter-loader 插件搭配webpack实现es6的modules

// require all test files that ends with .spec.js
const testsContext = require.context('./', true, /\.spec$/)
testsContext.keys().forEach(testsContext)

// require all src files for coverage.
const srcContext = require.context('../src', true, /index\.js$/)
srcContext.keys().forEach(srcContext)
  • 支持es6语法,babel套餐不能少。

在.babelrc 文件中加入以下配置:

1
2
3
4
5
{
"presets": [
"es2015"
]
}
  • 配置如上karma.conf.js的webpack内容。

挖坑问题:

在最开始测试覆盖和报告采用karma + karma-coverage-istanbul-reporter + karma-chrome-launcher + mocha + power-assert 生成, 每次npm run test 都会生成测试报告,并启动浏览器。

这种测试方式适合在本地执行,并且测试内容对浏览器兼容有要求。如果将这种模式放到Travis-CI 里构建是不行的。因为Travis-CI 无法启动浏览器,测试脚本永远执行不通过,导致3.4.2的Coveralls 测试覆盖率badge无法生成。

所以该项目里用了两套单元测试一套用来构建的Coveralls生成badge。一套用来构建本地测试。script 脚本里test是本地测试用,cover是用来放到Travis-CI构建的hook里。

  1. nyc + mocha + karma-mocha-reporter + power-assert + babel-core + babel-register + coveralls (生成测试覆盖率)
  2. karma + mocha + power-assert + webpack + babel(套装) + istanbul-instrumenter-loader + karma-power-assert + karma-coverage-istanbul-reporter + karma-chrome-launcher(本地)
1
2
3
4
5
6
7
8
9
10
11
"scripts": {
"test": "karma start karma.conf.js",
"precommit": "./node_modules/.bin/eslint --ext .jsx,.js ./",
"prepush": "./node_modules/.bin/eslint --ext .jsx,.js ./",
"eslint_fix": "./node_modules/.bin/eslint --fix --ext .jsx,.js ./",
"buildes5": "babel src -d lib",
"cover": "nyc --reporter=lcov --reporter=text-summary ./node_modules/mocha/bin/_mocha --reporter spec --require babel-core/register ./test/**/*.spec.js",
"coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"lint": "eslint --fix src",
"build": "babel src --out-file script-compiled.js"
},

3.4 持续集成

关于持续集成:持续集成的目的,就是让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成,持续集成并不能消除Bug,而是让它们非常容易发现和改正。

3.4.1 Travis-CI

github授权登陆,打开对应的库,如下图。

自动化构建工具类似jenkins,集成测试的相关命令,每次push后执行测试脚本,测试通过生成报告,配合Coveralls.io生成测试覆盖率。用github授权登陆。

Travis-CI

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
// .travis.yml  配置
# 使用的语言
language: node_js

# node的版本
node_js:
- 10.8.0

# 仓库的分支
branches:
only:
- master

# 执行install方法
install:
- npm install

# 执行脚本命令
script:
- npm run lint
- npm run buildes5
- npm run cover

# 成功后执行
after_success:
- npm run coveralls

3.4.2 Coveralls.io

Coveralls.io github授权登陆,然后打开所开发的内容库,如下图。

上面Travis-CI是持续集成的构建工具,Coveralls.io网站 + npm包(coveralls + nyc + mocha + power-assert)可以在travis-ci构建执行测试并生成测试覆盖率。

  1. 安装相关的npm包

  2. package.json 配置script 命令如下

1
2
3
4
5
6
7
8
9
10
11
12
// 生成测试报告(测试覆盖率)文件: nyc --reporter=lcov --reporter=text-summary

// 执行mocha测试 --reporter 是在termimal生成测试覆盖信息,--require babel-core/register 支持es6语法和es6的modules ./node_modules/mocha/bin/_mocha --reporter spec --require babel-core/register

// test下的是全部的测试用例格式 .spec.js

"cover": "nyc --reporter=lcov --reporter=text-summary ./node_modules/mocha/bin/_mocha --reporter spec --require babel-core/register ./test/**/*.spec.js"


// 通过coveralls工具将测试通过生成的lcov.info测试报告,关联到Coveralls.io,最后生成测试覆盖率的badge图标。

"coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
  1. 将npm run cover 命令配置到.travis.yml的 script 的hook 下面,每当git push代码,都会执行script下的命令。

  2. 将npm run coveralls 命令配置到.travis.yml 的 after_success的hook下面,每当script脚本都执行成功后,执行after_success下的脚本命令。

3.4.3 shields

shields 的网站下有已存在的很多类型。例如download, license ,star 等badge,例如: 点击 Download — 找到npm的图标 — 点击图标 — 配置选择(周,月,年)下载 - 输入自己的npm包名字 — 复制markdown url 然后拷贝到 README.md 里即可完成。

将自己的发布的npm包的名字拷贝上去即可,复制markdown url放到自己readme.md上。

四、发布及注意事项

4.1 发布

安装有cnpm

安装有cnpm时,需要注意切换到npm库在登陆发布。

操作步骤:

查看当前连接的库:npm config get registry

如果当前连接在淘宝镜像,设置连接到npm: npm config set registry=http://registry.npmjs.org

登陆npm : npm login

修改版本号(每次发布必须修改): npm version patchnpm version minornpm version major

版本号遵循:

1
2
3
4
5
6
7
// semantic versions 语义化版本

patch:小变动,比如修复bug等,版本号变动 v1.0.0->v1.0.1

minor:增加新功能,不影响现有功能,版本号变动 v1.0.0->v1.1.0

major:破坏模块对向后的兼容性,版本号变动 v1.0.0->v2.0.0

发布: npm publish

发布完成切回淘宝镜像: npm config set registry=https://registry.npm.taobao.org

没有cnpm

在上面的步骤去掉关于cnpm的切换操作即可。

Tips:

  1. npm publish 时,我们不希望把没用的内容发布上去。

   黑名单:可以建立.npmignore把不要的文件排除。

   白名单:可以在pakeage.json里的添加files字段指定要发布的文件。

参考资料:

package.json
package.json
持续集成
持续集成
单元测试
单元测试
发npm
发npm
发npm
发npm
git hook
git hook

返回
顶部