java 传参数报文给and_老沈 的动态 - SegmentFault 思否

我写过一些开源项目,在开源方面有一些经验,最近开到了阮老师的微博,深有感触,现在一个开源项目涉及的东西确实挺多的,特别是对于新手来说非常不友好

activities

最近我写了一个jslib-base,旨在从多方面快速帮大家搭建一个标准的js库,本文将已jslib-base为例,介绍写一个开源库的知识jslib-base 最好用的js第三方库脚手架,赋能js第三方库开源,让开发一个js库更简单,更专业

文档

所谓代码未动,文档先行,文档对于一个项目非常重要,一个项目的文档包括README.md

TODO.md

CHANGELOG.md

LICENSE

doc

README.md

README是一个项目的门面,应该简单明了的呈现用户最关心的问题,一个开源库的用户包括使用者和贡献者,所以一个文档应该包括项目简介,使用者指南,贡献者指南三部分

项目简介用该简单介绍项目功能,使用场景,兼容性的相关知识,这里重点介绍下徽章,相信大家都见过别人项目中的徽章,如下所示

activities

徽章通过更直观的方式,将更多的信息呈现出来,还能够提高颜值,有一个网站专门制作各种徽章,可以看这里

这里有一个README的完整的例子

TODO.md

TODO应该记录项目的未来计划,这对于贡献者和使用者都有很重要的意义,下面是TODO的例子- [X] 已完成

- [ ] 未完成

CHANGELOG.md

CHANGELOG记录项目的变更日志,对项目使用者非常重要,特别是在升级使用版本时,CHANGELOG需要记录项目的版本,发版时间和版本变更记录## 0.1.0 / 2018-10-6

- 新增xxx功能

- 删除xxx功能

- 更改xxx功能

LICENSE

开源项目必须要选择一个协议,因为没有协议的项目是没有人敢使用的,关于不同协议的区别可以看下面这张图(出自阮老师博客),我的建议是选择MIT或者BSD协议

activities

doc

开源项目还应该提供详细的使用文档,一份详细文档的每个函数介绍都应该包括如下信息:函数简单介绍

函数详细介绍

函数参数和返回值(要遵守下面的例子的规则)

- param {string} name1 name1描述

- return {string} 返回值描述

举个例子(要包含代码用例)

// 代码

特殊说明,比如特殊情况下会报错等

构建

理想的情况如下:库开发者美滋滋的写ES6+的代码;

库使用者能够运行在浏览器(ie6-11)和node(0.12-10)中

库使用者能够使用AMD或CMD模块方案

库使用者能够使用webpack、rollup或fis等预编译工具

理想很丰满,现实很。。。,如何才能够让开发者和使用者都能够开心呢,jslib-base通过babel+rollup提供了解决方案

activities

编译

通过babel可以把ES6+的代码编译成ES5的代码,babel经理了5到6的进化,下面一张图总结了babel使用方式的变迁

activities

本文不讨论babel的进化史(后面会单独开一片博文介绍),而是选择最现代化的babel-preset-env方案,babel-preset-env可以通过提供提供兼容环境,而决定要编译那些ES特性

其原理大概如下,首先通过ES的特性和[特性的兼容列表](http://kangax.github.io/compa...

)计算出每个特性的兼容性信息,再通过给定兼容性要求,计算出要使用的babel插件

activities

首先需要安装babel-preset-env$ npm i --save-dev babel-preset-env

然后新增一个.babelrc文件,添加下面的内容{

"presets": [

["env",

{

"targets": {

"browsers": "last 2 versions, > 1%, ie >= 6, Android >= 4, iOS >= 6, and_uc > 9",

"node": "0.10"

},

"modules": false,

"loose": false

}]

]

}

targets中配置需要兼容的环境,关于浏览器配置对应的浏览器列表,可以从browserl.ist上查看

modules表示编出输出的模块类型,支持"amd","umd","systemjs","commonjs",false这些选项,false表示不输出任何模块类型

loose代表松散模式,将loose设置为true,能够更好地兼容ie8以下环境,下面是一个例子(ie8不支持Object.defineProperty)// 源代码

const aaa = 1;

export default aaa;

// loose false

Object.defineProperty(exports, '__esModule', {

value: true

});

var aaa = 1;

exports.default = 1;

// loose true

exports.__esModule = true;

var aaa = 1;

exports.default = 1;

babel-preset-env解决了语法新特性的兼容问题,如果想使用api新特性,在babel中一般通过babel-polyfill来解决,babel-polyfill通过引入一个polyfill文件来解决问题,这对于普通项目很实用,但对于库来说就不太友好了

babel给库开发者提供的方案是babel-transform-runtime,runtime提供类似程序运行时,可以将全局的polyfill沙盒化

首先需要安装babel-transform-runtime$ npm i --save-dev babel-plugin-transform-runtime

在.babelrc增加下面的配置"plugins": [

["transform-runtime", {

"helpers": false,

"polyfill": false,

"regenerator": false,

"moduleName": "babel-runtime"

}]

]

transform-runtime,支持三种运行时,下面是polyfill的例子// 源代码

var a = Promise.resolve(1);

// 编译后的代码

var _promise = require('babel-runtime/core-js/promise');

var a = _promise.resolve(1); // Promise被替换为_promise

虽然虽然可以优雅的解决问题,但是引入的文件非常之大,比如只用了ES6中数组的find功能,可能就会引入一个几千行的代码,我的建议对于库来说能不用最好不用

打包

编译解决了ES6到ES5的问题,打包可以把多个文件合并成一个文件,对外提供统一的文件入口,打包解决的是依赖引入的问题

rollup vs webpack

我选择的rollup作为打包工具,rollup号称下一代打包方案,其有如下功能依赖解析,打包构建

仅支持ES6模块

Tree shaking

webpack作为最流行的打包方案,rollup作为下一代打包方案,其实一句话就可以总结二者的区别:库使用rollup,其他场景使用webpack

为什么我会这么说呢?下面通过例子对比下webpack和rollup的区别

假设我们有两个文件,index.js和bar.js,其代码如下

bar.js对外暴漏一个函数barexport default function bar() {

console.log('bar')

}

index.js引用bar.jsimport bar from './bar';

bar()

下面是webpack的配置文件webpack.config.jsconst path = require('path');

module.exports = {

entry: './src/index.js',

output: {

path: path.resolve(__dirname, 'dist'),

filename: 'bundle.js'

}

};

下面来看一下webpack打包输出的内容,o(╯□╰)o,别着急,我们的代码在最下面的几行,上面这一大片代码其实是webpack生成的简易模块系统,webpack的方案问题在于会生成很多冗余代码,这对于业务代码来说没什么问题,但对于库来说就不太友好了

注意:下面的代码基于webpack3,webpack4增加了scope hoisting,已经把多个模块合并到一个匿名函数中/******/

(function(modules) { // webpackBootstrap

/******/ // The module cache

/******/

var installedModules = {};

/******/

/******/ // The require function

/******/

function __webpack_require__(moduleId) {

/******/

/******/ // Check if module is in cache

/******/

if (installedModules[moduleId]) {

/******/

return installedModules[moduleId].exports;

/******/

}

/******/ // Create a new module (and put it into the cache)

/******/

var module = installedModules[moduleId] = {

/******/

i: moduleId,

/******/

l: false,

/******/

exports: {}

/******/

};

/******/

/******/ // Execute the module function

/******/

modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/

/******/ // Flag the module as loaded

/******/

module.l = true;

/******/

/******/ // Return the exports of the module

/******/

return module.exports;

/******/

}

/******/

/******/

/******/ // expose the modules object (__webpack_modules__)

/******/

__webpack_require__.m = modules;

/******/

/******/ // expose the module cache

/******/

__webpack_require__.c = installedModules;

/******/

/******/ // define getter function for harmony exports

/******/

__webpack_require__.d = function(exports, name, getter) {

/******/

if (!__webpack_require__.o(exports, name)) {

/******/

Object.defineProperty(exports, name, {

/******/

configurable: false,

/******/

enumerable: true,

/******/

get: getter

/******/

});

/******/

}

/******/

};

/******/

/******/ // getDefaultExport function for compatibility with non-harmony modules

/******/

__webpack_require__.n = function(module) {

/******/

var getter = module && module.__esModule ?

/******/

function getDefault() { return module['default']; } :

/******/

function getModuleExports() { return module; };

/******/

__webpack_require__.d(getter, 'a', getter);

/******/

return getter;

/******/

};

/******/

/******/ // Object.prototype.hasOwnProperty.call

/******/

__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

/******/

/******/ // __webpack_public_path__

/******/

__webpack_require__.p = "";

/******/

/******/ // Load entry module and return exports

/******/

return __webpack_require__(__webpack_require__.s = 0);

/******/

})

/************************************************************************/

/******/

([

/* 0 */

/***/

(function(module, __webpack_exports__, __webpack_require__) {

"use strict";

Object.defineProperty(__webpack_exports__, "__esModule", { value: true });

/* harmony import */

var __WEBPACK_IMPORTED_MODULE_0__bar__ = __webpack_require__(1);

Object(__WEBPACK_IMPORTED_MODULE_0__bar__["a" /* default */ ])()

/***/

}),

/* 1 */

/***/

(function(module, __webpack_exports__, __webpack_require__) {

"use strict";

/* harmony export (immutable) */

__webpack_exports__["a"] = bar;

function bar() {

//

console.log('bar')

}

/***/

})

/******/

]);

下面来看看rollup的结果,rollup的配置和webpack类似export default {

input: 'src/index.js',

output: {

file: 'dist/bundle2.js',

format: 'cjs'

}

};

下面看看rollup的产出,简直完美有没有,模块完全消失了,rollup通过顺序引入到同一个文件来解决模块依赖问题,rollup的方案如果要做拆包的话就会有问题,因为模块完全透明了,但这对于库开发者来说简直就是最完美的方案'use strict';

function bar() {

//

console.log('bar');

}

bar();

模块化方案

在ES6模块化之前,JS社区探索出了一些模块系统,比如node中的commonjs,浏览器中的AMD,还有可以同时兼容不同模块系统的UMD,如果对这部分内容感兴趣,可以看我之前的一篇文章《JavaScript模块的前世今生》

对于浏览器原生,预编译工具和node,不同环境中的模块化方案也不同;由于浏览器环境不能够解析第三方依赖,所以浏览器环境需要把依赖也进行打包处理;不同环境下引用的文件也不相同,下面通过一个表格对比下浏览器(script,AMD,CMD)预编译工具(webpack,rollup,fis)Node引用文件index.aio.jsindex.esm.jsindex.js

模块化方案UMDES Modulecommonjs

自身依赖打包打包打包

第三方依赖打包不打包不打包

*注意: legacy模式下的模块系统可以兼容ie6-8,但由于rollup的一个[bug](https://github.com/rollup/rol...

)(这个bug是我发现的,但rollup并不打算修复,╮(╯▽╰)╭哎),legacy模式下,不可同时使用 export 与 export default*

tree shaking

rollup是天然支持tree shaking,tree shaking可以提出依赖模块中没有被使用的部分,这对于第三方依赖非常有帮助,可以极大的降低包的体积

举个例子,假设index.js只是用了第三方包is.js中的一个函数isString,没有treeshaking会将is.js全部引用进来

activities

而使用了treeshaking的话则可以将is.js中的其他函数剔除,仅保留isString函数

activities

规范

无规矩不成方圆,特别是对于开源项目,由于会有多人参与,所以大家遵守一份规范会事半功倍

编辑器规范

首先可以通过.editorconfig来保证缩进、换行的一致性,目前绝大部分浏览器都已经支持,可以看这里

下面的配置设置在js,css和html中都用空格代替tab,tab为4个空格,使用unix换行符,使用utf8字符集,每个文件结尾添加一个空行root = true

[{*.js,*.css,*.html}]

indent_style = space

indent_size = 4

end_of_line = lf

charset = utf-8

insert_final_newline = true

[{package.json,.*rc,*.yml}]

indent_style = space

indent_size = 2

代码风格

其次可以通过eslint来保证代码风格一致,关于eslint的安装和配置这里不再展开解释了,在jslib-base中只需要运行下面的命令就可以进行代码校验了,eslint的配置文件位于config/.eslintrc.js$ npm run lint

设计规范

eslint只能够保证代码规范,却不能保证提供优秀的接口设计,关于函数接口设计有一些指导规则

参数数量函数的参数个数最多不要超过5个

可选参数可选参数应该放到后面

可选参数数量超过三个时,可以使用对象传入

可选参数,应该提供默认值

参数校验与类型转换必传参数,如果不传要报错

对下列类型要做强制检验,类型不对要报错(object, array, function)

对下列类型要做自动转换(number, string, boolean)

对于复合类型的内部数据,也要做上面的两个步骤

对于number转换后如果为NaN,要做特殊处理(有默认值的赋值为默认值,无默认值的要报错)

参数类型参数尽量使用值类型(简单类型)

参数尽量不要使用复杂类型(避免副作用)

使用复杂类型时,层级不要过深

使用复杂数据类型时,应该进行深拷贝(避免副作用)

函数返回值返回值可返回操作结果(获取接口),操作是否成功(保存接口)

返回值的类型要保持一致

返回值尽量使用值类型(简单类型)

返回值尽量不要使用复杂类型(避免副作用)

版本规范

版本号格式:x.y.zx 主版本号,不兼容的改动

y 次版本号,兼容的改动

z 修订版本号,bug修复

Git commit规范

代码的提交应该遵守规范,这里推荐一个我的规范

测试

没有单元测试的库都是耍流氓,单元测试能够保证每次交付都是有质量保证的,业务代码由于一次性和时间成本可以不做单元测试,但开源库由于需要反复迭代,对质量要求又极高,所以单元测试是必不可少的

关于单元测试有很多技术方案,其中一种选择是[mocha](https://mochajs.org/

)+[chai](http://www.chaijs.com/

),mocha是一个单元测试框架,用来组织、运行单元测试,并输出测试报告;chai是一个断言库,用来做单元测试的断言功能

由于chai不能够兼容ie6-8,所以选择了另一个断言库——expect.js,expect是一个BDD断言库,兼容性非常好,所以我选择的是mocha+expect.js

关于BDD与TDD的区别这里不再赘述,感兴趣的同学可以自行查阅相关资料

有了测试的框架,还需要写单元测试的代码,下面是一个例子var expect = require('expect.js');

var base = require('../dist/index.js');

describe('单元测试', function() {

describe('功能1', function() {

it('相等', function() {

expect(1).to.equal(1);

});

});

});

然后只需运行下面的命令,mocha会自动运行test目录下面的js文件$ mocha

mocha支持在node和浏览器中测试,但上面的框架在浏览器下有一个问题,浏览器没法支持require('expect.js'),我用了一个比较hack的方法解决问题,早浏览器中重新定义了require的含义

var libs = {

'expect.js': expect,

'../dist/index.js': jslib_base

};

var require = function(path) {

return libs[path];

}

下面是用mocha生成测试报告的例子,左边是在node中,右边是在浏览器中

activities

可持续集成

没有可持续集成的库都是原始人,如果每次push都能够自动运行单元测试就好了,这样就省去了手动运行的繁琐,好在[travis-ci](https://www.travis-ci.org/

)已经为我们提供了这个功能

用GitHub登录travis-ci,就可以看到自己在GitHub上的项目了,然后需要打开下项目的开关,才能够打开自动集成功能

activities

第二步,还需要在项目中添加一个文件.travis.yml,内容如下,这样就可以在每次push时自动在node 4 6 8版本下运行npm test命令,从而实现自动测试的目的language: node_js

node_js:

- "8"

- "6"

- "4"

其他内容

开源库希望得到用户的反馈,如果对用户提的issue有要求,可以设置一个模版,用来规范github上用户反馈的issue需要制定一些信息

通过提供.github/ISSUE_TEMPLATE文件可以给issue提供模版,下面是一个例子,用户提issue时会自动带上如下的提示信息### 问题是什么

问题的具体描述,尽量详细

### 环境

- 手机: 小米6

- 系统:安卓7.1.1

- 浏览器:chrome 61

- jslib-base版本:0.2.0

- 其他版本信息

### 在线例子

如果有请提供在线例子

### 其他

其他信息

jsmini

jsmini是基于jslib-base的一系列库,jsmini的理念是小而美,并且无第三方依赖,开源了很多能力,能够

助力库开发者

总结

五年弹指一挥间,本文总结了自己做开源项目的一些经验,希望能够帮助大家,所有介绍的内容都可以在jslib-base里面找到

jslib-base是一个拿来即用脚手架,赋能js第三方库开源,快速开源一个标准的js库

最后再送给大家一句话,开源一个项目,重在开始,贵在坚持

最后推荐下我的新书《React状态管理与同构实战》,深入解读前沿同构技术,感谢大家支持

最后最后招聘前端,后端,客户端啦!地点:北京+上海+成都,感兴趣的同学,可以把简历发到我的邮箱: yanhaijing@yeah.net查看原文

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值