基于JavaScript編寫應用,JavaScript是我的主要開發語言。
CoffeeScript是編譯為JavaScript的程式設計語言。為什麼我們要用CoffeeScript來編寫一段可重用的代碼——
子產品呢?CoffeeScript是一個非常高階的語言,将JavaScript、Ruby和Python中我最愛的部分
結合在了一起。在本教程中,我将展示如何使用CoffeeScript為Node.js建立一個可複用的開源子產品。最近我在建立一個
播放清單分析子產品時get了這個新技能。重點在于如何将一個快速的開發變成一個結構良好的Node.js子產品。
步驟如下:
- 将創意放入git倉庫。
- 添加目錄結構。
- 從測試中分離庫函數。
- 添加建構腳本。
- 建立node子產品。
- 添加LICENSE 和 README。
- 釋出。
首先,我們需要一個創意。它不用是多麼革命性的創意。它隻需做一件事,并且将它做好。這是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
最後,我喜歡在别的地方建立一個新目錄,然後根據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子產品的建構步驟、測試、目錄結構有什麼想法?這個教程顯然不會是一個權威指南。我期待你們的評論。