第 8 章 - 模組

設計自己的模組

模組定義與引用

當您用 javascript 寫出物件或函式庫時,可以提供給其他程式使用。

在 node.js 當中,模組的定義大致有兩種類型,一種是「匯出物件」的靜態模組,另一種是匯出「建構函數」的動態模組。

靜態模組:匯出物件

以下是一個匯出物件的靜態模組定義。

模組定義:math.js

var math = {
    PI:3.14,
    square:function(n) {
        return n*n;
    }
}

module.exports = math;

接著您可以使用 require 這個指令動態的引入該模組,注意 require 必須採用相對路徑,即使在同一個資料夾底下,也要加上 ./ 的前置符號,代表在目前資料夾之下。以下是一個引用上述模組的範例。

模組使用:mathTest.js

var m=require("./math");

console.log("PI=%d square(3)=%d", m.PI, m.square(3));

執行結果:

D:\Dropbox\Public\pmag\201306\code>node mathTest
PI=3.14 square(3)=9

亂數模組

檔案: randomLib.js

var o={};

o.random = function(a,b) { 
  return a+Math.random()*(b-a); 
}

o.randomInt = function(a,b) { 
  return Math.floor(o.random(a,b)); 
}

o.sample = function(array) { 
  return array[o.randomInt(0,array.length)]; 
}

module.exports = o;

自動產生小學數學問題的程式

var R=require("./randomLib");
/*
問題:   小華有6個蘋果
        給了大雄3個
        又給了小明2個
        請問小華還有幾個蘋果?

答案:   1個
*/
var peoples = ["小明", "小華", "小莉", "大雄"];
var objects = ["蘋果", "橘子", "柳丁", "番茄"];
var owner = People();
var object = Object();
var nOwn = R.randomInt(3, 20);

remove(peoples, owner);

function remove(array, item) {
  array.splice(array.indexOf(item), 1); 
}

function MathTest() {
  return "問題:\t"+Own()+"\n\t"+Give()
         +"\n\t又"+Give()+"\n\t"+Question();
}

function Own() {
  return owner+"有"+nOwn+"個"+object;
}

function Give() {
  var nGive = R.randomInt(1, nOwn);
  nOwn-=nGive;
  return "給了"+People()+nGive+"個";
}

function Question() {
  return "請問"+owner+"還有幾個"+object+"?";
}

function People() {
  return R.sample(peoples);
}

function Object() {
  return R.sample(objects);
}

console.log(MathTest());
console.log("\n答案:\t"+nOwn+"個");

執行結果

D:\Dropbox\cccwd2\file\jsh\code\module>node genMath.js
問題:   小華有10個橘子
        給了大雄9個
        又給了小明1個
        請問小華還有幾個橘子?

答案:   0個

D:\Dropbox\cccwd2\file\jsh\code\module>node genMath.js
問題:   小明有18個橘子
        給了大雄11個
        又給了小華4個
        請問小明還有幾個橘子?

答案:   3個

物件模組:匯出建構函數

第一種寫法

檔案: circleClass.js

var PI = 3.14;
class Circle {
    constructor(radius) {
        this.radius = radius
        this.area = function() {
            return PI * this.radius * this.radius;
        }
    }
}

module.exports = Circle;

主程式: circleClassTest.js

var Circle = require('./circleClass');  // 注意,./ 代表 circleClass 與此程式放在同一個資料夾底下。
var c = new Circle(5);
console.log("c.area()="+c.area());

執行結果

$ node circleClassTest
c.area()=78.5

第二種寫法

以下是一個定義圓形 circle 的物件。

模組定義:circle.js

var PI = 3.14;
Circle = function (radius) {
    this.radius = radius
    this.area = function() {
        return PI * this.radius * this.radius;
    }
};

module.exports = Circle;

在引用「匯出建構函數」的程式當中,由於取得的是建構函數,因此必須再使用 new 的方式建立物件之後才能使用 (例如以下的 c = new cir(5) 這個指令,就是在透過建構函數 cir() 建立物件。

模組使用:CircleTest.js

var cir = require('./circle');              // 注意,./ 代表 circle 與此程式放在同一個資料夾底下。
var c = new cir(5);
console.log("c.area()="+c.area());

執行結果

D:\code\node>node circleTest.js
c.area()=78.5

您現在應該可以理解為何我們要將 Circle 定義為一個函數了吧!這只不過 Circle 類別的建構函數而已, 當他被 module.exports = Circle 這個指令匯出時,就可以在 var cir = require('./circle') 這個指令 接收到建構函數,然後再利用像 var c = new cir(5) 這樣的指令,呼叫該建構函數,以建立出物件。

跨平台模組:定義各種平台均能使用的 JavaScript 模組

在很多開放原始碼的 JavaScript 專案模組中,我們會看到模組的最後有一段很複雜的匯出動作。舉例而言, 在 marked.js 這個將 Markdown 語法轉為 HTML 的模組最後,我們看到了下列這段感覺匪夷所思的匯出橋段, 這種寫法其實只是為了要讓這個模組能夠順利的在「瀏覽器、node.js、CommonJS 與其 Asynchronous Module Definition (AMD) 實作版的 RequireJS」等平台當中都能順利的使用這個模組而寫的程式碼而已。


/**
 * Expose
 */

marked.Parser = Parser;
marked.parser = Parser.parse;

marked.Lexer = Lexer;
marked.lexer = Lexer.lex;

marked.InlineLexer = InlineLexer;
marked.inlineLexer = InlineLexer.output;

marked.parse = marked;

if (typeof exports === 'object') {
  module.exports = marked;
} else if (typeof define === 'function' && define.amd) {
  define(function() { return marked; });
} else {
  this.marked = marked;
}

}).call(function() {
  return this || (typeof window !== 'undefined' ? window : global);
}());

對這個超複雜匯出程式有興趣的朋友可以看看以下的文章,應該就可以大致理解這種寫法的來龍去脈了。

用 npm 安裝外部套件

使用 npm install 可以安裝套件,舉例而言,假如我們有一個程式 main.js 用到 lodash 套件,其原始碼如下:

檔案: main.js

var _=require("lodash");

var set=_.intersection([1,3,7,9], [2,3,8,9]);
console.log("set=", set);

我們想要執行這個程式,於是切到 main.js 所在的資料夾後打入 node main.js ,結果卻發現下列情況。

nqu-192-168-61-142:package mac020$ ls
main.js
nqu-192-168-61-142:package mac020$ node main.js
module.js:338
    throw err;
    ^

Error: Cannot find module 'lodash'
    at Function.Module._resolveFilename (module.js:336:15)
    at Function.Module._load (module.js:286:25)
    at Module.require (module.js:365:17)
    at require (module.js:384:17)
    at Object.<anonymous> (/Users/mac020/Dropbox/cccwd/db/js1/code/package/main.js:1:69)
    at Module._compile (module.js:434:26)
    at Object.Module._extensions..js (module.js:452:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:475:10)

這代表我們沒有安裝 lodash 套件,因此我們必須要用下列 npm 指令先安裝 lodash 套件。

nqu-192-168-61-142:package mac020$ npm install lodash
[email protected] node_modules/lodash

nqu-192-168-61-142:package mac020$ ls
main.js        node_modules

nqu-192-168-61-142:package mac020$ ls node_modules
lodash

這樣就安裝好該套件了,您可以用 ls 列出目前資料夾內容,會發現多出的一個 node_modules 的資料夾,裡面會有 lodash 子資料夾,這代表您已經安裝好了該套件。

接著我們就可以用 node main.js 指令執行原本的程式,這樣就不會再產生錯誤了!

nqu-192-168-61-142:package mac020$ node main.js
set= [ 3, 9 ]

以上就是在 node.js 裏用 npm 安裝外部套件的做法!

發佈自己的套件 - 以 rlab 為例

Node.js 中的 JavaScript 模組,可以透過 npm 進行管理與發佈,方法是你必須撰寫 package.json 這個檔案來描述你的專案。

記得先查查有沒有名稱衝突

沒有衝突才能發佈成功

接著您必須為該模組撰寫或產生 package.json 專案檔,以下是我所撰寫的一個 rlab 模組之 package.json 專案檔範例:

檔案: package.json

{
  "name": "rlab",
  "version": "0.0.1",
  "description": "Javascript scientific library like R based on lodash",
  "main": "rlab.js",
  "dependencies": {
    "lodash": "^4.6.1"
  },
  "devDependencies": {},
  "scripts": {},
  "repository": {},
  "keywords": [],
  "author": "ccckmit",
  "license": "BSD-3-Clause",
  "homepage": "https://github.com/ccckmit/rlab"
}

寫好後和你的程式放在同一個資料夾下,接著就可以發佈初版,以下是我的上傳發布範例:

$ npm set init.author.name "ccckmit"
$ npm set init.author.email "[email protected]"
$ npm set init.author.url "http://ccc.nqu.edu.tw"
$ npm adduser
$ ls
$ npm init
$ npm install -g pakmanager
$ sudo npm install -g pakmanager
$ npm publish ./

當您已經發布成功之後,可以上 npm 網站在檢查看看是否可以查到你的專案。

如果專案有任何修改要再次發布,就可以用 npm version 指令更新版本,然後再用 npm publish 指令發布新版:

$ npm version 0.0.2
v0.0.2
$ npm publish ./
+ [email protected]

最後你可以用下列指令安裝自己的套件,並開始使用此套件!

nqu-192-168-61-142:js mac020$ npm install rlab
[email protected] node_modules/rlab
└── [email protected]
nqu-192-168-61-142:js mac020$ node rtest
x=[1,3,2,6,3,2,5,4,4,3] max=6 min=1 mean=3.3
x= [ 20, 50, 10, 0, 80, 60, 70, 30, 100, 90 ]

您也可以在 package.json 當中加入像 github 專案的網址,這樣可以讓人家直接在網路上檢視你的原始碼,而且更方便多人一起共同合作開發:

以下是我使用 git 版本管理後上傳到 github 的一小段過程,更詳細的 git 使用請參考相關文獻。

nqu-192-168-61-142:rlab mac020$ git commit -am "add jStat functions"
[master 41cdba8] add jStat functions
 2 files changed, 24 insertions(+), 0 deletions(-)
nqu-192-168-61-142:rlab mac020$ git push -u origin master
Username: 
Password: 
Counting objects: 7, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 835 bytes, done.
Total 4 (delta 2), reused 0 (delta 0)
To https://github.com/ccckmit/rlab.git
   5a4adbf..41cdba8  master -> master
Branch master set up to track remote branch master from origin.
nqu-192-168-61-142:rlab mac020$ npm version 0.0.3
v0.0.3

如果要將 Node.js 的程式轉成可以在瀏覽器中使用的模組,可以利用 browserify 這個工具,browserify會將你的程式打包後變成單一的 javascript 檔案,並且去除 require 這類 node.js 專有的語法,讓你的程式可以順利的在瀏覽器當中執行。(當然、你的程式裡不可以呼叫那些不能在瀏覽器當中使用的函數,像是存取檔案系統等等)

以下《十分鐘系列》投影片詳細介紹了如何用 npm 創建並發布模組的方法,請您進一步閱讀以便學習這套非常方便且重要的方法。

習題

  1. 請設計一個自己寫的模組,並上傳到 npm 官網上。(如果你不知道該設計甚麼,那就把本章的 randomLib.js 拿來上傳到 npm 官網上好了)

results matching ""

    No results matching ""