天天看點

使用CoffeeScript編寫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 [email protected]:nickdesaulniers/node-nanomsg.git && \

git push -u origin master

現在我們的

目錄結構

如下:

tree -L 2 -a

├── .gitignore

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

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

exports.AF_SP = 1

exports.NN_PAIR = 16

并且修改測試:

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

{ AF_SP, NN_PAIR } = nanomsg

注意到了我們在

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'

├── 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

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

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'

    msg = new Buffer 'hello'

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

    recv = new Buffer msg.length

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

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

然後編輯我們的

gruntfile

,加入測試步驟:

    mochaTest:

      options:

        reporter: 'nyan'

      src: ['test/test.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.

又到了送出的時候了。

├── Gruntfile.coffee

│   ├── ffi

│   ├── grunt

│   └── grunt-contrib-coffee

7 directories, 5 files

建立node子產品

現在我們的設計已經比較子產品化了,内置了建構和測試的邏輯,讓我們使這個子產品容易再分發吧。首先,我們讨論忽略的檔案。建立一個

.npmignore

檔案,它将指定哪些檔案不會被包含在下載下傳中。Node包管理程式,

npm

,預設會

忽略一組檔案檔案

Gruntfile.coffee

src/

test/

預設忽略了

src/

目錄,在我們的

.gitignore

中則忽略了

lib/

lib/

為何如此?老實說,在嚴格意義上而言,忽略這兩個目錄不是必需的。但是我認為這很有用。當有人擷取代碼的時候,他不需要編譯的結果,畢竟他們可以進行修改,而這需要重新編譯。添加

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

來運作我們的測試了。在釋出子產品之前,先

├── .npmignore

│   ├── .bin

│   ├── chai

│   ├── grunt-contrib-coffee

│   └── grunt-mocha-test

├── package.json

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):

 * <[email protected]> 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

├── LICENSE

├── README.md

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

+ [email protected]

最後,我喜歡在别的地方建立一個新目錄,然後根據readme中的步驟跑一遍,以確定包确實可以複用。這很有用,因為在readme中我

不小心遺漏了

errno 和 recv 前的

nn_

字首!

更新readme中的例子之後,讓我們

修改版本号

并重新釋出。使用

npm version

檢視目前版本,然後使用

npm version patch

來修改。在此之前你需要送出readme的改動。最後,别忘了再次運作

npm publish

我們

最終的目錄結構

看起來是這樣的:

最後,我會

聯系下

Martin Sústrik,讓他知道nanomsg有一個新綁定了。

這個綁定遠遠不夠完整,測試覆寫率可以更高,API非常像C,可以使用一些OO文法糖的,但是我們已經有了一個良好的起點,可以進一步改進了。如果你有意幫忙,請派生

https://github.com/nickdesaulniers/node-nanomsg.git

你對node子產品的建構步驟、測試、目錄結構有什麼想法?這個教程顯然不會是一個權威指南。我期待你們的評論。