使用CoffeeScript编写Node.js 模块

简介: Node.js 基于JavaScript编写应用,JavaScript是我的主要开发语言。CoffeeScript是编译为JavaScript的编程语言。为什么我们要用CoffeeScript来编写一段可重用的代码——模块呢?CoffeeScript是一个非常高阶的语言,将JavaScript、Ruby和Python中我最爱的部分结合在了一起。在本教程中,我将展示如何使用CoffeeScript为Node.js创建一个可复用的开源模块。最近我在创建一个播放列表分析模块时get了这个新技能。重点在于如何将一个快速的开发变成一个结构良好的Node.js模块。

Node.js 基于JavaScript编写应用,JavaScript是我的主要开发语言。CoffeeScript是编译为JavaScript的编程语言。为什么我们要用CoffeeScript来编写一段可重用的代码——模块呢?CoffeeScript是一个非常高阶的语言,将JavaScript、Ruby和Python中我最爱的部分结合在了一起。在本教程中,我将展示如何使用CoffeeScript为Node.js创建一个可复用的开源模块。最近我在创建一个播放列表分析模块时get了这个新技能。重点在于如何将一个快速的开发变成一个结构良好的Node.js模块。


步骤如下:

  1. 将创意放入git仓库。
  2. 添加目录结构。
  3. 从测试中分离库函数。
  4. 添加构建脚本。
  5. 创建node模块。
  6. 添加LICENSE 和 README。
  7. 发布。


首先,我们需要一个创意。它不用是多么革命性的创意。它只需做一件事,并且将它做好。这是UNIX饱受争议的哲学的第一条准则,在Node.js社区激起了共鸣。当我开发的时候,我从单一文件开始,进行一些探索。然后我渐进地改良代码直到我做出了可复用的东西。这样,我们可以复用它,别人也可以复用它,别人也可以从代码中得到启发,世界会因此更美好。


本教程中,我将展示如何为nanomsg创建一个绑定。nanomsg是ZeroMQ的创造者 Martin Sústrik最新开发的一个可伸锁性协议库。我以前曾经玩过ZeroMQ,感觉它非常棒。当我看到ZeroMQ的作者做出了一个基于C的新库的时候,我非常激动。因为我很喜欢他的博客《为什么我应该用C而不是C++编写 ZeroMQ》


为了快速地上手,我们首先确保node的版本够新。我喜欢使用nvm,以及最新的稳定minor版node(版本格式为major.minor.patch,稳定版的minor数字是偶数,所以v0.11.0是非稳定版)。

node -v

-> v0.10.17

接下来我需要下载我打算动态链接的库:

curl -O http://download.nanomsg.org/nanomsg-0.1-alpha.zip && \

unzip nanomsg-0.1-alpha.zip && \

cd nanomsg-0.1-alpha && \

mkdir build && \

cd build && \

../configure && \

make && \

make install

我们将使用node的FFI模块,以便和动态链接库交互。对于编写绑定而言,这比使用原生扩展要容易,而且V8的API最近的修改给原生扩展造成了一些麻烦

npm install ffi

我们将使用CoffeeScript编写代码:

npm install -g coffee-script

C++绑定样例的基础上我们创建一个main.coffee

ffi = require 'ffi'

assert = require 'assert'

AF_SP = 1

NN_PAIR = 16

nanomsg = ffi.Library 'libnanomsg',

 nn_socket: [ 'int', [ 'int', 'int' ]]

 nn_bind: [ 'int', [ 'int', 'string' ]]

 nn_connect: [ 'int', ['int', 'string' ]]

 nn_send: [ 'int', ['int', 'pointer', 'int', 'int']]

 nn_recv: [ 'int', ['int', 'pointer', 'int', 'int']]

 nn_errno: [ 'int', []]

# test

s1 = nanomsg.nn_socket AF_SP, NN_PAIR

assert s1 >= 0, 's1: ' + nanomsg.nn_errno()

ret = nanomsg.nn_bind s1, 'inproc://a'

assert ret > 0, 'bind'

s2 = nanomsg.nn_socket AF_SP, NN_PAIR

assert s2 >= 0, 's2: ' + nanomsg.nn_errno()

ret = nanomsg.nn_connect s2, 'inproc://a'

assert ret > 0, 'connect'

msg = new Buffer 'hello'

ret = nanomsg.nn_send s2, msg, msg.length, 0

assert ret > 0, 'send'

recv = new Buffer msg.length

ret = nanomsg.nn_recv s1, recv, recv.length, 0

assert ret > 0, 'recv'

console.log recv.toString()

assert msg.toString() is recv.toString(), 'received message did not match sent'

coffee main.coffee

-> hello

这个快速编写的例子显示我们已经能做到一些事情了。目前我们的目录结构是这样的:

tree -L 2

.

├── main.coffee

└── node_modules

   └── ffi

2 directories, 1 file


将创意变为git仓库

接着我们使用git创建一个仓库,开始保存我们的工作。更早提交,更多提交

让我们加入一个.gitignore文件,这样不需要提交的文件就不会被加入到git仓库。node_modules文件夹是不必要提交的,因为当安装node模块的时候,它的依赖会被递归地安装,所以没有必要将它们提交到源代码管理系统。因为我使用vim,所以还需要排除vim的交换文件:

node_modules/

*.swp

好了,让我们创建git仓库吧。

git init && \

git add . && \

git commit -am "initial commit"

在github上创建一个未初始化的仓库,然后推送:

git remote add origin git@github.com:nickdesaulniers/node-nanomsg.git && \

git push -u origin master

现在我们的目录结构如下:

tree -L 2 -a

.

├── .gitignore

├── main.coffee

└── node_modules

   └── ffi

2 directories, 2 files

添加目录结构

既然我的代码已经处于git之下,让我们开始添加一些目录结构。我们需要创建src/lib/test/目录。src/放置我们的CoffeeScript,lib/放置编译的JavaScript文件,我们的测试代码会在test/

mkdir src lib test


从测试中分离库函数

现在我们把main.coffee移动到src/,并把它的一个副本移动到test/。我们将从测试逻辑中分离出库函数。

cp main.coffee test/test.coffee && \

git add test/test.coffee && \

git mv main.coffee src/nanomsg.coffee

git status告诉我们:

# On branch master

# Changes to be committed:

#   (use "git reset HEAD <file>..." to unstage)

#

# renamed:    main.coffee -> src/nanomsg.coffee

# new file:   test/test.coffee

#

让我们修改下 src/main.coffee

ffi = require 'ffi'

exports = module.exports = ffi.Library 'libnanomsg',

 nn_socket: [ 'int', [ 'int', 'int' ]]

 nn_bind: [ 'int', [ 'int', 'string' ]]

 nn_connect: [ 'int', ['int', 'string' ]]

 nn_send: [ 'int', ['int', 'pointer', 'int', 'int']]

 nn_recv: [ 'int', ['int', 'pointer', 'int', 'int']]

 nn_errno: [ 'int', []]

exports.AF_SP = 1

exports.NN_PAIR = 16

并且修改测试:

assert = require 'assert'

nanomsg = require '../lib/nanomsg.js'

{ AF_SP, NN_PAIR } = nanomsg

s1 = nanomsg.nn_socket AF_SP, NN_PAIR

assert s1 >= 0, 's1: ' + nanomsg.nn_errno()

ret = nanomsg.nn_bind s1, 'inproc://a'

assert ret > 0, 'bind'

s2 = nanomsg.nn_socket AF_SP, NN_PAIR

assert s2 >= 0, 's2: ' + nanomsg.nn_errno()

ret = nanomsg.nn_connect s2, 'inproc://a'

assert ret > 0, 'connect'

msg = new Buffer 'hello'

ret = nanomsg.nn_send s2, msg, msg.length, 0

assert ret > 0, 'send'

recv = new Buffer msg.length

ret = nanomsg.nn_recv s1, recv, recv.length, 0

assert ret > 0, 'recv'

assert msg.toString() is recv.toString(), 'received message did not match sent'

注意到了我们在test中包含了尚不存在的lib/下的JavaScript文件?如果我们尝试运行coffee test/test.coffee,它会崩溃。让我们先编译一下。coffee -o lib -c src/nanomsg.coffee

编译完成之后,使用coffee test/test.coffee运行我们的测试。

现在让我们提交一下吧。注意不要把lib/加入版本控制,我下面会解释为什么。

tree -L 2 -C -a -I '.git'

.

├── .gitignore

├── lib

│   └── nanomsg.js

├── node_modules

│   └── ffi

├── src

│   └── nanomsg.coffee

└── test

   └── test.coffee

5 directories, 4 files

就目前而言,如果我们添加了特性并打算运行测试,我们需要执行:

coffee -o lib -c src/nanomsg.coffee && coffee test/test.coffee

虽然这个命令很简单,也不难理解,但是任何贡献代码给你的项目的人需要知道这个命令才能运行测试。让我们使用Grunt,JavaScript任务自动化工具来自动化我们的构建和测试过程。


添加一个构建脚本

npm install -g grunt-cli && \

npm install grunt-contrib-coffee

用CoffeeScript创建一个简单的Gruntfile:

module.exports = (grunt) ->

 grunt.initConfig

   coffee:

     compile:

       files:

         'lib/nanomsg.js': ['src/*.coffee']

 grunt.loadNpmTasks 'grunt-contrib-coffee'

 grunt.registerTask 'default', ['coffee']

运行grunt将创建我们的库,让我们提交一下

但是grunt不会运行我们的测试。我们的测试输出也不好看。让我们改变这一点:

npm install -g mocha && \

npm install chai grunt-mocha-test

编辑test/test.coffee

assert = require 'assert'

should = require('chai').should()

nanomsg = require '../lib/nanomsg.js'

describe 'nanomsg', ->

 it 'should at least work', ->

   { AF_SP, NN_PAIR } = nanomsg

   s1 = nanomsg.nn_socket AF_SP, NN_PAIR

   s1.should.be.at.least 0

   ret = nanomsg.nn_bind s1, 'inproc://a'

   ret.should.be.above 0

   s2 = nanomsg.nn_socket AF_SP, NN_PAIR

   s2.should.be.at.least 0

   ret = nanomsg.nn_connect s2, 'inproc://a'

   ret.should.be.above 0

   msg = new Buffer 'hello'

   ret = nanomsg.nn_send s2, msg, msg.length, 0

   ret.should.be.above 0

   recv = new Buffer msg.length

   ret = nanomsg.nn_recv s1, recv, recv.length, 0

   ret.should.be.above 0

   msg.toString().should.equal recv.toString()

然后编辑我们的gruntfile,加入测试步骤:

module.exports = (grunt) ->

 grunt.initConfig

   coffee:

     compile:

       files:

         'lib/nanomsg.js': ['src/*.coffee']

   mochaTest:

     options:

       reporter: 'nyan'

     src: ['test/test.coffee']

 grunt.loadNpmTasks 'grunt-contrib-coffee'

 grunt.loadNpmTasks 'grunt-mocha-test'

 grunt.registerTask 'default', ['coffee', 'mochaTest']

现在,当我们运行grunt的时候,将构建我们的程序,然后运行测试,然后我们可以看到开心死了的彩虹猫彩虹猫mocha测试报告差不多是人类心智所能达到的最高成就。

grunt

Running "coffee:compile" (coffee) task

File lib/nanomsg.js created.

Running "mochaTest:src" (mochaTest) task

1   -__,------,

0   -__|  /\_/\

0   -_~|_( ^ .^)

    -_ ""  ""

 1 passing (5 ms)

Done, without errors.

又到了提交的时候了。

tree -L 2 -C -a -I '.git'

.

├── .gitignore

├── Gruntfile.coffee

├── lib

│   └── nanomsg.js

├── node_modules

│   ├── ffi

│   ├── grunt

│   └── grunt-contrib-coffee

├── src

│   └── nanomsg.coffee

└── test

   └── test.coffee

7 directories, 5 files

创建node模块

现在我们的设计已经比较模块化了,内置了构建和测试的逻辑,让我们使这个模块容易再分发吧。首先,我们讨论忽略的文件。创建一个.npmignore文件,它将指定哪些文件不会被包含在下载中。Node包管理程序,npm,默认会忽略一组文件文件

Gruntfile.coffee

src/

test/

默认忽略了src/目录,在我们的.gitignore中则忽略了lib/

node_modules/

lib/

*.swp

为何如此?老实说,在严格意义上而言,忽略这两个目录不是必需的。但是我认为这很有用。当有人获取代码的时候,他不需要编译的结果,毕竟他们可以进行修改,而这需要重新编译。添加lib/nanomsg.js将增加下载的文件(当然它的大小相对而言无关紧要)。同理,当有人下载模块的时候,他多半只想要编译好的文件,而不是源代码、构建脚本或测试。如果我希望让浏览器可以访问编译好的JavaScript,我可能不会在.gitignore中包含lib/,这样就可以通过github的raw URL引用它了。当然,这些只是一般情况下的经验,并不总是正确的。为了弥补不把整个代码放进模块的缺憾,我们将在manifest中添加一个指向仓库的链接。不过在此之前,让我们先提交一下!

现在该是创建manifest文件的时候,其中包含了我们的应用的基本信息。预先使用npm search <packagename>看看打算使用的包名是否可用是个很好的注意。由于我们已经安装了所有依赖,让我们运行npm init吧。

This utility will walk you through creating a package.json file.

It only covers the most common items, and tries to guess sane defaults.

See `npm help json` for definitive documentation on these fields

and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and

save it as a dependency in the package.json file.

Press ^C at any time to quit.

name: (nanomsg)

version: (0.0.0)

description: nanomsg bindings

entry point: (index.js) lib/nanomsg.js

test command: grunt

git repository: (git://github.com/nickdesaulniers/node-nanomsg.git)

keywords: nanomsg

author: Nick Desaulniers

license: (BSD-2-Clause) Beerware

About to write to /Users/Nicholas/code/c/nanomsg/package.json:

{

 "name": "nanomsg",

 "version": "0.0.0",

 "description": "nanomsg bindings",

 "main": "lib/nanomsg.js",

 "directories": {

   "test": "test"

 },

 "dependencies": {

   "chai": "~1.7.2",

   "ffi": "~1.2.5",

   "grunt": "~0.4.1",

   "grunt-mocha-test": "~0.6.3",

   "grunt-contrib-coffee": "~0.7.0"

 },

 "devDependencies": {},

 "scripts": {

   "test": "grunt"

 },

 "repository": {

   "type": "git",

   "url": "git://github.com/nickdesaulniers/node-nanomsg.git"

 },

 "keywords": [

   "nanomsg"

 ],

 "author": "Nick Desaulniers",

 "license": "Beerware",

 "bugs": {

   "url": "https://github.com/nickdesaulniers/node-nanomsg/issues"

 }

}

Is this ok? (yes)

这将为npm创建一个package.json manifest。

现在,除了使用grunt之外,我们也可以通过npm test来运行我们的测试了。在发布模块之前,先提交一下

tree -L 2 -C -a -I '.git'

.

├── .gitignore

├── .npmignore

├── Gruntfile.coffee

├── lib

│   └── nanomsg.js

├── node_modules

│   ├── .bin

│   ├── chai

│   ├── ffi

│   ├── grunt

│   ├── grunt-contrib-coffee

│   └── grunt-mocha-test

├── package.json

├── src

│   └── nanomsg.coffee

└── test

   └── test.coffee

10 directories, 7 files

添加 LICENSE 和 README

现在我们差不多已经完成了。但是开发者如何知道该如何复用这些代码呢?不管我有多么喜欢直接查看源代码,npm会抱怨我们的模块没有一个readme文件。而且有readme的话,github仓库会比较好看。

# Node-NanoMSG

Node.js binding for [nanomsg](http://nanomsg.org/index.html).

## Usage

`npm install nanomsg`

```javascript

var nanomsg = require('nanomsg');

var assert = require('assert');

var AF_SP = nanomsg.AF_SP;

var NN_PAIR = nanomsg.NN_PAIR;

var msg = new Buffer('hello');

var recv = new Buffer(msg.length);

var s1, s2, ret;

s1 = nanomsg.nn_socket(AF_SP, NN_PAIR);

assert(s1 >= 0, 's1: ' + nanomsg.errno());

ret = nanomsg.nn_bind(s1, 'inproc://a');

assert(ret > 0, 'bind');

s2 = nanomsg.nn_socket(AF_SP, NN_PAIR);

assert(s2 >= 0, 's2: ' + nanomsg.errno());

ret = nanomsg.nn_connect(s2, 'inproc://a');

assert(ret > 0, 'connect');

ret = nanomsg.nn_send(s2, msg, msg.length, 0);

assert(ret > 0, 'send');

ret = nanomsg.recv(s1, recv, recv.length, 0);

assert(ret > 0, 'recv');

assert(msg.toString() === recv.toString(), "didn't receive sent message");

console.log(recv.toString());

发布之前,我们需要创建一个许可文件,因为我们将公开我们的代码,没有明确许可的公开代码仍然处于版权保护之下,不可复用

/*

* ----------------------------------------------------------------------------

* "THE BEER-WARE LICENSE" (Revision 42):

* <nick@mozilla.com> wrote this file. As long as you retain this notice you

* can do whatever you want with this stuff. If we meet some day, and you think

* this stuff is worth it, you can buy me a beer in return. Nick Desaulniers

* ----------------------------------------------------------------------------

*/

```

如果你希望正经一点,你可以使用MIT或BSD风格的许可,如果你不在乎你的仓库会被如何使用。如果你在乎,可以使用GPL风格的许可。[TLDRLegal](http://www.tldrlegal.com/)对常见的许可协议都有简要的说明。

```sh

tree -L 2 -C -a -I '.git'

.

├── .gitignore

├── .npmignore

├── Gruntfile.coffee

├── LICENSE

├── README.md

├── lib

│   └── nanomsg.js

├── node_modules

│   ├── .bin

│   ├── chai

│   ├── ffi

│   ├── grunt

│   ├── grunt-contrib-coffee

│   └── grunt-mocha-test

├── package.json

├── src

│   └── nanomsg.coffee

└── test

   └── test.coffee

10 directories, 9 files

发布

npm publish

npm http PUT https://registry.npmjs.org/nanomsg

npm http 201 https://registry.npmjs.org/nanomsg

npm http GET https://registry.npmjs.org/nanomsg

npm http 200 https://registry.npmjs.org/nanomsg

npm http PUT https://registry.npmjs.org/nanomsg/-/nanomsg-0.0.0.tgz/-rev/1-20f1ec5ca2eed51e840feff22479bb5d

npm http 201 https://registry.npmjs.org/nanomsg/-/nanomsg-0.0.0.tgz/-rev/1-20f1ec5ca2eed51e840feff22479bb5d

npm http PUT https://registry.npmjs.org/nanomsg/0.0.0/-tag/latest

npm http 201 https://registry.npmjs.org/nanomsg/0.0.0/-tag/latest

+ nanomsg@0.0.0

最后,我喜欢在别的地方创建一个新目录,然后根据readme中的步骤跑一遍,以确保包确实可以复用。这很有用,因为在readme中我不小心遗漏了 errno 和 recv 前的nn_前缀!

更新readme中的例子之后,让我们修改版本号并重新发布。使用npm version查看当前版本,然后使用npm version patch来修改。在此之前你需要提交readme的改动。最后,别忘了再次运行npm publish

我们最终的目录结构看起来是这样的:

tree -L 2 -C -a -I '.git'

.

├── .gitignore

├── .npmignore

├── Gruntfile.coffee

├── LICENSE

├── README.md

├── lib

│   └── nanomsg.js

├── node_modules

│   ├── .bin

│   ├── chai

│   ├── ffi

│   ├── grunt

│   ├── grunt-contrib-coffee

│   └── grunt-mocha-test

├── package.json

├── src

│   └── nanomsg.coffee

└── test

   └── test.coffee

10 directories, 9 files

最后,我会联系下Martin Sústrik,让他知道nanomsg有一个新绑定了。

这个绑定远远不够完整,测试覆盖率可以更高,API非常像C,可以使用一些OO语法糖的,但是我们已经有了一个良好的起点,可以进一步改进了。如果你有意帮忙,请派生 https://github.com/nickdesaulniers/node-nanomsg.git

你对node模块的构建步骤、测试、目录结构有什么想法?这个教程显然不会是一个权威指南。我期待你们的评论。

相关文章
|
JavaScript 前端开发
在Node.js中,如何合理使用模块来避免全局变量的问题?
在Node.js中,如何合理使用模块来避免全局变量的问题?
596 167
|
JavaScript 前端开发 开发者
Node学习笔记:HTTP模块
总的来说,Node.js的HTTP模块是一个强大的工具,可以帮助你处理HTTP协议的各种需求。无论你是想开设自己的餐厅(创建服务器),还是想去别的餐厅点菜(发出请求),HTTP模块都能满足你的需求。
360 18
|
缓存 JavaScript 安全
nodejs里面的http模块介绍和使用
综上所述,Node.js的http模块是构建Web服务的基础,其灵活性和强大功能,结合Node.js异步非阻塞的特点,为现代Web应用开发提供了坚实的基础。
528 62
|
JavaScript 前端开发
Vue、ElementUI配合Node、multiparty模块实现图片上传并反显_小demo
如何使用Vue和Element UI配合Node.js及multiparty模块实现图片上传并反显的功能,包括前端的Element UI组件配置和后端的Node.js服务端代码实现。
306 1
Vue、ElementUI配合Node、multiparty模块实现图片上传并反显_小demo
|
JavaScript 数据可视化
JS如何优雅的实现模块自动滚动展示
【8月更文挑战第22天】JS如何优雅的实现模块自动滚动展示
471 1
JS如何优雅的实现模块自动滚动展示
|
缓存 JSON JavaScript
Node.js模块系统
10月更文挑战第4天
163 2
|
缓存 JavaScript 前端开发
JavaScript模块化开发:ES6模块与CommonJs的对比与应用
JavaScript模块化开发:ES6模块与CommonJs的对比与应用
383 2
|
算法 JavaScript 前端开发
国标非对称加密:RSA算法、非对称特征、js还原、jsencrypt和rsa模块解析
国标非对称加密:RSA算法、非对称特征、js还原、jsencrypt和rsa模块解析
1408 1
|
存储 缓存 JSON
Node.js有哪些模块系统
【8月更文挑战第12天】Node.js有哪些模块系统
306 3
|
算法 JavaScript 前端开发
对称加密算法解析:DES、AES及其在`pycryptodome` 和 `crypto-js` 模块中的应用
对称加密算法解析:DES、AES及其在`pycryptodome` 和 `crypto-js` 模块中的应用
760 1