Fork me on GitHub

tag-2021-08-30-tag
npm_modules.webp

编写代码只是我们整个软件开发流程中很小的一部分,一个完整的软件开发流程还包含其准备,测试,部署等其他阶段。在这些阶段,我们会用到除了编程语言之外的很多工具帮助我们搭起来一套工作流程。本这篇文章,通过一个实际项目,将这套流程辅助工具用到项目中,从代码开发-测试-部署-集成一套基础的规范做下来。

建库

单个人开发的项目可以任你自由发挥,基本上不需要各种版本管理工具,你把自己写的代码存在云上或者自己硬盘上就行。但是如今的软件应用,尤其是大型的软件应用都是多人合作的结晶。我们在开始项目之前要选择好的版本管理工具。

SVN依旧是很多企业,尤其是传统企业(例如我们公司)喜欢采用的版本控制工具。SVN作为一套版本管理工具我本人是非常喜欢它的,并且能够解决我们日常开发工作中的绝大对数需求。但是考虑它是一款相对封闭的管理系统,我们这次的项目就不打算使用它,而是使用git分布式管理系统来控制我们的版本;

git

git是目前最流行的版本管理工具,作为git的公共仓库,github也是非常受欢迎,你需要注册一个github帐号来创建你的仓库。

git私有仓库

很多时候我们的代码是不能放到公网上的,这就需要我们建立私有的仓库,github上的私有项目只能三个人同时开发,或者需要你交钱之后才能给你服务。作为全球公共的开源仓库,这种做法本身是没毛病的。如果你的团队不大,可以选择自己服务器创建仓库,如果你是在大型的公司,合作分工的人非常多,你可以使用gitlab。我们这里介绍如何搭建git私有仓库。

默认进入root权限sudo -i

#在linux上安装git
apt-get install git 
#添加git用户
adduser git
#进入git目录
cd /home/git/
#创建.ssh目录
mkdir .ssh
cd .ssh/
#新建存放公钥的文件 后续想里面添加开发人员的公钥
touch authorized_keys
#初始化一个git仓库
git init --bare /usr/local/sample.git
chown -R git:git /usr/local/sample.git

如果你的代码需要在服务器上进行备份保存,就需要新建目录用来保存大家提交的代码:

#在服务器上建立一个目录,用来同步提交的代码
mkdir /usr/local/ngnix/sync-folder
chown -R git:git /usr/local/ngnix/sync-folder
#添加钩子文件
cd /usr/local/sample.git/hooks
touch post-receive
#编辑
vim post-receive
#加入以下同步代码
#!/bin/bash
#git --work-tree=/usr/local/ngnix/sync-folder checkout -f

#最后修改权限
chmod +x post-receive

这样配置后代码一旦被远程提交,服务器会触发钩子函数,自动将提交的代码放到我们制定的目录下面。

verdaccio

我们经常编写npm的包放到公网上,但是有时候这些模块是不能面向公众的,例如一些涉及公司业务的组件或者内部代码,但是我们确实还是希望用到node_modules模块方式来组织我们的代码。这就需要在自己的服务器上搭建私有npm模块管理仓库,我们安装第三方的包来实现:verdaccio

#进入管理人员模式
sudo -i
#在linux上安装verdaccio和node程序管理器
npm install verdaccio pm2 -g
#退出管理员权限
exit

我们需要对verdaccio做一些修改,例如修改备用仓库以及内网地址:

#修改默认配置文件
cd /usr/local/lib/node_modules/verdaccio/conf/
vi default.yml

在最底部映射本地地址:

+ listen: 0.0.0.0:4873

你可以根据官方文档的说明对其他的配置进行个性化配置,例如代理,缓存等。不过一般来说,在加入最后一行的映射IP和端口之后就可以顺利跑起来了。

pm2 start verdaccio -i 1

在浏览器中打开 http://(你的.服务器.IP.端口):4873
30.png
接下来,只需要在发布/安装模块的时候指定下载路径,就可以把我们的包发布/下载。

#发布模块
npm publish --registry http://xxx.xxx.xx.xx:4873
#更新模块
npm i test_module --registry http://xxx.xxx.xx:4873

请使用一个核来跑verdaccio,verdaccio与pm2 据说还不兼容,起用多核跑程序会出现包显示不完整的问题。

构建

typescript

Javascript前端开发中的主要编程语言。因为它的动态和弱类型语言的特性,使得它很容易上手,也非常的灵活。这种特性同时也带来了格式混乱以及运行时错误频发,Typescript的大名大家都已经知道了,如果说它是一门语言更是一门代码检查/注释工具。ts的最大作用就是对JavaScript进行类型的检查,将潜在的类型错误在暴露编译阶段。你可甚至认为ts是一种编译规则的工具,按照自定义的type来保证我们的javascrit这种动态弱类型语言变成静态的强类型语言。Typescript安装并不难,本篇博文我们就从头开始来创建一个发布到公网上的node模块——panda-warrior

#创建&进入目录
mkdir panda-warrior & cd panda-warrior/
#初始化项目
npm init -y
#初始化残酷
git init
#创建忽略提交的文件
touch .gitignore
#建立远程的仓库链接
git remote add origin yourgithubrepoaddress@github.com
#开始安装typescript ts-node:node版本的ts, node-mon:nodejs实时编译运行的监听模块
npm install typescript node-ts nodemon -D
#如果你的typscript不是安装在全局的,需要在对应目录下的。bin中去运行该命令
node_modules/.bin/tsc --init

#创建入口文件
mkdir src && touch index.ts

编写入口文件代码:

import Speak from './speak';
import Jump from './jump';
import Sing from './sing';

class PandaWarrior {
  constructor() {
    console.log('hello world');
  }

  armour() {
    return true;
  }

  speak = Speak;

  jump = Jump;

  sing = Sing;

  weapon() {
    return 'bamboo';
  }

  furColor() {
    return 'black-and-white';
  }

  shout(type: string) {
    return type === 'frined' ? 'hi would like an banoo' : 'get out of here';
  }
}

export default PandaWarrior;

进入package.json文件中,编写scripts字段:

{
  "name": "panda-warrior",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon index.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "nodemon": "^2.0.7",
    "ts-node": "^9.1.1",
    "typescript": "^4.1.3"
  }
}

在package.json中的scripts字段中可以用npm 运行安装的脚本。一般来说它会去node_modules/.bin目录下面运行本地的脚本,我们将其配置在scripts中就可以方便的使用了,当然你也可以使用npx代替scripts配置,它们的作用基本上来说都是去执行本地命令,并无太大差别。现在我们在控制台执行npm start就可以看到我们的打印输出,说明我们已经搭建起来一个简单的typescript程序。

eslint

在编写代码的过程为了遵守规范,需要对的团队开发者的代码格式做规范,多人开发的团队尤其如此。因此我们选择一款检查代码的工具,来强制对我们的代码进行检查,eslint就是这样一种工具,它能按照既定的规则检查代码是否符合规范。配置eslint也非常简单,只需要几个命令就好,现在我们把eslint的代码规范导入到项目中来:

npm install eslint -D #安装eslint

安装好了eslint之后你需要配置一些eslint文件.eslintrc.js。从零开始配置一个eslint文件很让人抓狂,幸好这个工具为我们通过了类似脚手架的功能,你只需要运行初始化命令,然后选择你要的eslint选项即可。初始化eslint根据你安装的位置,大致有三种方式。

#如果你的eslint模块是全局安装的
eslint --init
#你的eslint只安装在本项目
npx eslint --init
#你的eslint只安装在本项目
node_modules/.bin/eslint --init

这三条命令的作用都是相同的,你安装的eslint的位置决定了你如何使用。接下来你会看到控制台出现了一些选项,只需要按照提示,一步步完成对应的选项选择:
28.png
eslint回自动为你生成基础的配置文件.eslintrc。文件生成之后,我们把eslint命令也加入到脚本中:

"script": {
  ...
  "check": "eslint . --ext .ts"
}

当我们运行npm run check命令时,eslint会分析所有ts文件的格式是否符合预期规范,并且将你的代码按照规范进行格式化,例如换行,空格,逗号等,但是有些代码规范的错误无法修复的格式,只会给你抛出错误,你需要手动的去修改。
27.png

Tslint团队已经声明不再维护Tslint了,因为Tslint和Eslint在很多方面的工作是重复的,因此一般我们也用Elinst来做代码格式检查的工作,即使你的项目是typescript编写的。

prettier

eslint在代码格检查方面做的很好,但我们每次跑npm run lint时会抛出一些无法解决的错误,为此我们需要引入一个更懂我们的修复代码规范的工具prettierprettier能够更加智能的补充你的代码:如加上逗号,去掉大括号,简而言之eslint给你抛出的错误大部分它都能够直接帮你解决。我们先安装prettier

npm install prettier -D #安装包
touch .prettierrc #创建配置文件
vi .prettierrc #编辑配置文件,将规则写入配置文件中
{
  "printWidth": 120, 
  "tabWidth": 2, 
  "useTabs": false, 
  "semi": true, 
  "singleQuote": true, 
  "jsxSingleQuote": false, 
  "trailingComma": "all", 
  "bracketSpacing": false, 
  "jsxBracketSameLine": false, 
  "arrowParens": "always" 
}

以上是一份基础的配置规则,你也可以在官方文档中查找对应的规则配置,然后我们把启动命令写入配置脚本:

"script":{
	"prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write"
}

在控制台执行npm run prettier-format后,你的代码会变得如丝般柔滑,这时候你用eslint去检查,不会抛出任何报错,很好得解决了代码格式问题。prettier同时也提供了插件供eslint使用的,将prettier的插件安装到eslint中,eslint会按照prettier的风格检查代码。安装插件以及配置即可。

npm install eslint-plugin-prettier eslint-config-prettier -D

然后在.eslintrc.js中加入扩展配置

extends: [
    ...
    "plugin:prettier/recommended",
    "prettier"
  ],
rules: {
    "prettier/prettier": 2
}

如果你不想频繁地执行格式化代码命令,你可以考虑在你的项目中引用文件监听模块,来减少你的重复工作,监听模块会监听你的文件然后自动格式化你的代码:

npm install --save-dev onchange #安装onchange文件监听包

然后在package.json中的脚本中加入:

"script": {
	...
	"prettier-watch": "onchange 'src/**/*.ts' -- prettier --write {{changed}}"
}

运行npm run prettier-watch,开启一个监听文件变化的目录,每当你保存文件,代码自动被格式化。

rollup

因为我们采用typscript编写的模块,这些模块最终需要最终编译成js,为此我们需要引入构建工具rollup来构建我们的工程。rollup帮助我们把指定的ts打包成js文件,它相较于webpack更轻量好用,非常适合打包单文件模块。我们首先来安装rollup以及必要的插件:

npm install rollup rollup-plugin-typescript2 rollup-plugin-commonjs -D

然后我们创建打包工具的配置文件.rollup.config.js

import typescript from 'rollup-plugin-typescript2';
import commonjs from 'rollup-plugin-commonjs';
export default {
  input: "src/index.ts", //入口文件
  output: [{
     file: 'dist/index.js', //目标路径
     format: 'cjs' //打包目标模式,我们编写的是node模块,请使用commonjs规范
  }
],
  plugins: [//打包插件
      commonjs(),
      typescript()
  ]
}

然后把编译命令写入scripts中:

"scripts": {
   ...
	"compile": "rollup -c" //rollup 通过配置文件编译项目
}

运行npm run compile rollup就会在将我们的应用构建成普通的javascript模块并且存放到dist目录下,dist目录结构如下所示:
Screen Shot 2021-01-21 at 4.50.34 PM.png

测试

Mocha

编写单元测试在大型的应用中非常普遍,代码除了在格式上符合规范,更需要在快速迭代的功能上保证稳定。单元测试的前提是代码的模块化,这样模块才能作为“单元”被测试。我们在构建大型多人协作时,也应该遵循编写模块的规范,做到各个模块独立,解耦,同时做到固定的输入有稳定的输出即无副作用。如今的单元测试框架不少,例如jest, mocha等。我们这里选用了较为流行的测试框架mocha进行单元测试,(如果你使用的是react,可以用jest来测试)把测试功能加入我们的项目当中。测试环境应该和断言库一起使用,我们这里采用chai断言库来配合测试的工作。

npm install mocha chai -D
mkdir test && cd test && touch index.test.js
const chai = require('chai');
//从打包出来的入口文件模块中引入js
const Panda = require('../dist/index');
var expect = chai.expect;
var p = new Panda;

describe('And Last sing a song', function () {
    it('listen to the panda\'s song', function () {
        expect(p.sing({ name: 'The song of bamboo', singer: 'Panda' })).to.equal('A song named The song of bamboo sang by Panda');
    });
});
describe('Say sth to the world', function () {
    it('What will you sing about', function () {
        expect(p.speak("save the world")).to.equal("save the world");
    });
});
describe('Now jump ahead', function () {
    it('How tall can panda jump(cm)', function () {
        expect(p.jump(1, 2)).to.equal(3);
    });
});
describe('What is your weapon? Panda', function () {
    it('should return an weapon', function () {
        expect(p.weapon()).to.equal('bamboo');
    });
});
describe('What is the color of your fur..? Panda', function () {
    it('should return fur color', function () {
        expect(p.furColor()).to.equal('black-and-white');
    });
});
describe('Do you have armour? Panda', function () {
    it('should return a boolean value', function () {
        expect(p.armour()).to.be.ok;
    });
});

然后执行命令,会得到单元测试的结果:

node_modules/.bin/mocha #启用测试,自动寻找test目录下被编译成的index.test.js

得到了一下结果:
31.png

我们接着把运行命令简化到scripts中, 后续只需要执行npm test,程序就会首先对项目进行构建,然后运行单元测试。

"scripts": {
	"test": "npm run compile && mocha"
}

npm test 是npm内置的命令,无需加上run关键字即可运行

nyc

模块单元测试提供给我们的是单个模块功能的测试情况,为更好的统计我们的单元测试结果,我们引入测试覆盖率。测试覆盖率则告诉我们项目中所有被纳入测试模块的运行状况,包括判断语句,函数,文件,甚至是分支的代码,为我们全面地提供了项目的测试状况。

npm install nyc -D #按照单元测试覆盖率模块

添加配置文件.nycrc

{
  "include": [
    "dist/*.js" //测试覆盖的范围
  ],
  "reporter": [
    "text", // 生成控制台报告
    "html" // 是否生成html界面的报告
  ],
  "lines": 60, //测试覆盖行数及格率
  "statements": 60, //测试覆条件分支及格率
  "functions": 60, //测试覆函数及格率
  "branches": 0, //这如果是去测试dist目录中,dist没被提交到任何分支中,就是0
  "all": true,
  "check-coverage": true,
  "sourceMap": true,
  "instrument": true
}

将其与测试框架mocha结合,我们可以在scripts中添加脚本并且写入部分配置:

"scripts": {
	"cover": "npm run compile && nyc mocha"
}

建议将你的代码测试覆盖率(函数,添加,分支等)设置为合理的值,不应该期待覆盖率达到100%的应用规模,并不是每个模块都需要被测试,只需要测试到具体的核心模块即可。

测试通过之后你可以直接在控制台查看对应的覆盖率情况,如果你在.nycrc中配置了html选项,那么程序会在根目录下生成coverage目录,里面包含了一个html文件,点击打开就可以看到网页版本的测试情况,非常直观。
Screen Shot 2021-01-21 at 5.12.43 PM.png

部署

husky

假设我们的编写代码阶段已经完成,我们还需要把代码提交到git仓库中去。统一提交格式规范不仅仅约束着我们的注释不会产生较大的奇异,便于代码检查时发现问题,同时也在我们归档或者建立里程碑的时候提供历史记录,呈现一致的UI展示和归类整理。husky为我们提供了git提交的一些生命周期钩子,在git提交过程中提供给我们干预的能力,例如提交之前就检查我们输入的注释的格式,代码的格式等。现在开始向我们的项目中安装对应的依赖模块:

npm i husky lint-staged @commitlint/cli @commitlint/config-angular -D
  • husky: git-hook模块
  • lint-staged: 将工作区的文件lint格式化 实现只检查提交到工作区的内容而非全部内容
  • @commitlint/cli: 提交命令依赖cli工具
  • @commitlint/config-angular: angular代码提交规范

尽量使用npm而非cnpm安装,否则,依赖会安装失败!!!!。

在根目录下创建commitlint.config.js写入我们指定提交格式规范:

module.exports = {extends: ['@commitlint/config-angular']};

angular团队的代码提交注释规范是是一种比较流行的注释规范,很多大项目的团队都在使用,我们的项目也按照这种规范进行提交注释的编写,下面是我们在git commit时遵循的规范:

<type>(<scope>): <subject>
<blank line>
<body>
<blank line>
<footer>
  • type: 本次提交的类型,具体的值如下表。(必填)
  • scope:代码影响的范围。(非必填)
  • subject:本次提交的主题。(必填)
  • body:提交的内容详细说明。(非必填)
  • footer:底部结尾说明。(非必填)
命令 描述
feat 新增二楼功能或者特性
fix 修复bug
docs 只修改文档
style 只修改了样式、空格、分号等
refactor 重构了某块业务模块
perf 优化了项目,例如性能
test 测试用例
build 改变构建流程、集成测试
revert 回滚到上一个版本
merge 合并一个版本进来
ci 构建相关基础流程

接着我们在package中加入**husky****lint-staged**配置,确保在提交代码之前强制规范我们的代码格式。

...
"husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", //首先检查调注释规范
      "pre-commit": "lint-staged", //提交之前执行的命令
    }
},
"lint-staged": {
    "*.ts": [ //校验的目标文件类型
      "npm run check", //规范所有的提交的代码,如果失败或报错
      "git add" // 自动将格式化代码提交到工作区
    ]
}

如果我们的提交规范或者代码格式不符合规范,提交命令会被打回,需要我们重新提交。
26.png

git commit -m "xxx" --no-verify 忽略提交格式强制提交。建议不这么做。

ignore

在提交我们的模块到远程仓库之前,需要对一些文件进行设置,告诉我们的项目,哪些文件需要被发布到npm上去,哪些又应该提交到git仓库中。我们以本项目为模板,说明哪些需要提交,以及这样做的原因。
首先决定哪些文件需要被提交到git, 创建忽略配置文件:
.gitignore

node_modules/ #这个无需解释
.vscode/ #如果你使用的是vscode编辑器,会产生一些相关文件,这些文件无需提交
.nyc_output/ # nyc模块产生的一些文件
coverage/ #测试覆盖率的静态资源也无需提交
dist/打包后的目录,不提交
*.d.ts #理由同上

.npmignore
需要发布到npm上的文件,应该是所有被编译成的js文件,以及.d.ts文件,除了这些其他的都是我们开发用的文件,无需提交。你可以在.npmignore中一一地把不需要的文件填入,但有个更简便的方法,在package.json中配置fileds字段,指定哪些目录需要被发布即可。一般生成的目录约定为dist目录,你只需要填写dist即可:

"files": ["dist"],//提到npm仓库的最终编译后的文件
"main": "dist/index.js",//引用入口文件

当你运行npm publish时,dist目录下的所有文件会被发布到npm库中,其他文件没有带上。当然你也可以结合两者一起,设置files的同时,也写入.npmignore文件在dis目录中那些是不需要写入。

  • ts项目,只需要在tsconfig.js中设置编译后js存放的的路径为dist即可。
  • 好的工具一定不能缺少文档,请一并创建README.md 文件上传到github。

持续测试/集成

Travis cli

目前位置,我们的开发任务、测试、发布任务已经基本接近完成,软件开发周期也到此为止。如果新的功能继续迭代,我们只需按照业务编写代码即可。持续集成的概念在软件领域早已存在很久,只是在近几年随着前端工程的复杂化,为了保证产品快速稳定迭代而被引入了到了前端领域。我们需要持续集成基于以下两点考虑:

  1. 快速发现错误。每完成一点更新,就集成到主干,可以快速发现错误,定位错误也比较容易。
  2. 防止分支大幅偏离主干。如果不是经常集成,主干又在不断更新,会导致以后集成的难度变大,甚至难以集成。

持续集成已经有很多平台,例如jekinstravis cli等平台都支持我们的集成工作。我这里使用与git配套的travis cli来集成部署集成我们的代码。首先我们提交代码到git仓库中:

git push origin master

接着,我们用github授权登陆Travis Cli上找到激活仓库集成测试的按钮,选择需要集成的仓库:
29.png

然后我们在项目中创建.travis.yml文件,告诉travis我们的集成测试流程:

language: node_js

sudo: false

cache:
  	apt: true
  	directories:
    	- node_modules

node_js: stable

install:
  - npm install -D

scripts:
  - npm run cover

deploy:
  provider: npm
  email: your-email-address
  api_key: "$NPM_TOKEN"
  skip_cleanup: true

$NPM_TOKEN是你本地的npm auth_key值,cat ~/.npmrc 可以查看,从中拷贝然后把这个值放到travis cli里面去,发布的时候就自动用你的信息对包进行发布处理操作。我们用github的权限对travis-cli进行授权验证,然后导入你的仓库代码,点击进入,再点击右上角的more option->setting->Environment variables ,将你本地的auth_key添加到变量环境当中:

34.png
随后我们把代码提交上去,travis会按照我们编写的yml文件对项目进行集成测试,如果成功,它会继续发布到npm上去,如果报错则停止构建流程:
39.png
这样,每当我们把代码推送到远程,集成框架就会帮我们自动测试校验,并且发布到npm上去。减少了我们的人工测试和发布的流程处理,从而完成一个软件的完整发布流程。

由于我们是在master分支开发的项目,不需要合并多个分支。所以到此为止,我们的模块开发流程就基本完成了一个闭环。

36.png

puppy-cli

本篇从头到尾讲解了一个npm 包的编写过程,创建仓库-开发-测试-部署-集成。可以看到,这样的一个流程很长,而且里面的每个工具如果每次都从头开始配置的话会较为繁琐。为了简化流程,我们需要把所有这些步骤统一成一套SOP,并且尽量地简化每个工具的安装,配置细节。正是因此,很多cli工具才应运而生。用cli工具,只需要几行命令,就能够帮助我们快速创建模板,自动测试发布,集成操作,从而快速提高我们的工作效率,让开发人员只需要关注在业务逻辑即可。我把工作中的业务功能就抽象成了一个工具,puppy-cli

总结

本篇博文通过介绍编写一个npm模块的大致流程,介绍了前端开发领域中应用开发的大致通用规范。不同的业务形态可能在不同的阶段有所差异:例如编写前端业务项目时我们较多的使用webpack构建工具,并且还需要自动化UI测试,和业务测试介入。在发布部署环节上也会因为公司环境不同而表现不一。无论如何了解这些工具和发布的流程,有助于你了解整个前端工程,而不仅仅局限在编写业务代码的环境上😂。

posted on 2021-10-31 15:48  chen·yan  阅读(482)  评论(0编辑  收藏  举报