JSDev 是老道(Douglas Crockford)最近搞的一個不太起眼的小玩意,他的大致思路是,對 JavaScript 做預先處理,選擇性地把注釋轉為相應的代碼。JSDev 處理的註解區塊有以下兩種形式:
- /*<tag> <stuff>*/
- /*<tag>(<condition>) <stuff>*/
然後你可以指定 JSDev 開啟哪些 tag,JSDev 就會把這塊注釋改成相應的代碼,而這些注釋在 JavaScript 正常啟動並執行時候是不會起作用的,在用 JSMin 等工具壓縮後也都會全被去掉,這樣就方便了 JavaScript 的開發。
一開始,老道只給出了 C 語言版本的程式,幾天后他給出了 JavaScript 版本,我覺得這開始變得有意思了,折騰了兩天,想試看能否用到今後的開發項目中去。下面我們先以 C 程式為例說明 JSDev 的用法,然後說說怎樣通過 JavaScript 版本把它用在 node 開發或者前端開發過程中。本文假定你在類 Unix 系統(Linux/FreeBSD/Mac OS)下工作並且對命令列、node 編程都有一定瞭解。
C 語言版本 和 JSDev 的用法
先從 github 上 clone 我的 fork,把 C 源碼編譯成可執行檔。
- git clone https://github.com/yuest/jsdev.git #clone github 上的原始碼到本地
- cd jsdev
- gcc jsdev.c #編譯 C 版本
- sudo mv a.out /usr/local/bin/jsdev #把編譯好的可執行檔移動到一個 $PATH 底下的目錄
現在我們就可以用 jsdev 來運行程式了。可是在命令列輸入 jsdev 並斷行符號,卻什麼都沒有,原來老道只做了這工具最核心的部分,而未在軟體介面下什麼功夫,所以連個命令用法提示也沒有。作為前置處理器,它只會從 stdin 吃進 JavaScript 代碼,然後把處理過的代碼從 stdout 拉出來,至於檔案操作嘛,用 unix 命令列的管道特性就可以啦。
普及一下,要把檔案,比如說 in.js 當 stdin 輸入到 jsdev 程式,使用小於符號 < 就行,如 jsdev <in.js,而輸出就是用大於符號 > 啦:jsdev <in.js >out.js 就可以。如果要嘗試 jsdev,只需要用 jsdev <in.js 就可以把結果輸出到螢幕上方便查看,或者你甚至可以配合使用 echo 命令來測試簡單的單行代碼。如 echo "/*tag 'stuff'*/" |jsdev tag
只運行 jsdev <in.js >out.js 這個命令不會對 in.js 有任何改動,我們需要以 tag 為參數調用 jsdev 命令來指定需要開啟的 tag,多個空格間用空格分開。以例子來說明,如果 in.js 代碼如下:
- /*hello console.log('Hello, World!')*/
- /*hi console.log('Hi, there.')*/
那麼 jsdev <in.js hello 會輸出如下代碼:
- {console.log('Hello, World!')}
- /*hi console.log('Hi, there.')*/
而 jsdev <in.js hello hi 則是:
- {console.log('Hello, World!')}
- {console.log('Hi, there.')}
這裡的花括弧是為了避免與程式中其他部分發生關聯而加的,主要問題是 JavaScript 有行尾自動插入分號的設計,如果不加花括弧把上下文隔開,可能會與前後行產生關聯,比如變成上一行的函數調用之類。花括弧實際上是把多個語句複合成一個語句,最常見的地方是用在 if 等僅接受一個語句的關鍵字後。JavaScript 裡面的花括弧不會產生一個命名空間。
你可以為 tag 指定一個函數名,於是此 tag 指定的註解區塊就會被這個函數名 wrap有點像 JSONP),如以下 in.js:
- /*log some_variable*/
用命令 jsdev <in.js log:console.log 輸出的是:
- {console.log(some_variable);}
這可以用於調試代碼時列印當時狀態下的某些變數值,會比較方便。
另外還可以為註解區塊指定條件,如下的 in.js:
- var name = 'Yuest'
- /*hello console.log('Hello, World!')*/
- /*hello(name === 'Celine') console.log('Hello, Celine!')*/
用命令 jsdev <in.js hello 輸出:
- var name = 'Yuest'
- {console.log('Hello, World!')}
- if (name === 'Celine') {console.log('Hello, Celine!')}
如果運行這段代碼,很顯然只會輸出 Hello, World!。
指定函數和條件也可以同時使用,如:
- var name = 'Yuest'
- /*log 'Hello, World!'*/
- /*log(name === 'Ling') 'Hello, Ling!'*/
用命令 jsdev <in.js log:console.log 輸出:
- var name = 'Yuest'
- {console.log('Hello, World!');}
- if (name === 'Ling') {console.log('Hello, Ling!');}
和前面幾乎是一樣的,但注釋短了不少。以上就是關於 JSDev 使用方法的一切了。其實最核心的是 /*<tag> <stuff>*/ 這種形式,我們可以完全只用這種形式來達到上面一樣的目的:
- var name = 'Yuest'
- /*hello console.log('Hello, World!')*/
- /*hello if (name === 'Ling') {console.log('Hello, Ling!')}*/
上面的代碼用命令 jsdev <in.js hello 輸出:
- var name = 'Yuest'
- {console.log('Hello, World!')}
- {if (name === 'Ling') {console.log('Hello, Ling!')}}
所以可以看出,為 tag 指定函數名和條件只是做了一些簡化而已。
哦對了,還忘了 jsdev 命令接受一個 -comment 參數為輸出的檔案頭部添加註釋,如對於上面的 in.js 檔案,運行一下命令:
- jsdev <in.js hello -comment "Author: Yuest" -comment "This is a temporary file generated by JSDev"
會獲得:
- // Author: Yuest
- // This is a temporary file generated by JSDev
- var name = 'Yuest'
- {console.log('Hello, World!')}
- {if (name === 'Ling') {console.log('Hello, Ling!')}}
嗯,以上就真的是關於 JSDev 用法的一切了,而關於要如何用好它,則需要他的使用者有一點想象力。比如,你可以寫一些 bash 指令碼什麼的。下面說說 JavaScript 版本的 JSDev 以及我想到的用在 node 項目的方法。
JavaScript 版本和 node 中用法
老道給出的 jsdev.js 裡面只定義了一個 JSDEV 函數,接受三個參數,第一個參數是要處理的原始碼,第二、三個參數是兩個數組,分別指定 tag 和注釋。如果上面最後一條命令用 JavaScript 版本來處理,就應該是:
- JSDEV( input
- , ['hello']
- , ['Author: Yuest', 'This is a temporary file generated by JSDev'])
我的 fork 為 node 加上了 exports.JSDEV = JSDEV,並 publish 到 npm 上了,你只需 npm install jsdev 或者在 package.json 裡面加上 jsdev 的依賴,並在源碼裡
- var JSDEV = require('jsdev').JSDEV
就可以使用 JavaScript 版本的 JSDEV() 函數了。
因為 node 可以通過修改 module.constructor.prototype._compile 來修改要載入的檔案,非常適合拿來應用 JSDev 這種前置處理器。我的想法是,一個檔案要開啟哪些 tag 下的註解區塊,應該在其自身也是用注釋)來描述。如果程式載入了 jsdev 並調用其 replaceRequire 方法require('jsdev').replaceRequire()),之後再 require 進的檔案有 //@jsdev tag1 tag2 這樣的注釋,就會用 jsdev 開啟 tag1、tag2 指定的註解區塊。
指定開啟 tags 的規則這樣寫:
- //@jsdev(test,production) tag1 tag2
- //@jsdev(development) tag3
以上注釋行表示在環境變數 NODE_ENV=test 或 NODE_ENV=production 的時候開啟本檔案的 tag1 tag2 註解區塊,在 NODE_ENV=development 時開啟 tag3 註解區塊。如果沒有 jsdev 後面的括弧,預設是 development,因此可以寫成
- //@jsdev(test,production) tag1 tag2
- //@jsdev tag3
例如有 a.js:
- require('jsdev').replaceRequire()
- require('./b')
b.js:
- //@jsdev(test,production) hello
- //@jsdev hi
- /*hello console.log('hello')*/
- /*hi console.log('hi')*/
則運行 node a.js 會輸出 hi,而運行NODE_ENV=test 則會輸出 hello
另外,對於前端開發也是一樣的,有 replaceStatic() 函數,可以替換 connect/express 的 static middleware。這樣就可以預先處理通過 static middleware 返回到瀏覽器的 js 檔案。檔案內 tag 開啟的規則和前面寫的一樣。需要注意的是要在使用 connect.static(root) 之前調用 replaceStatic()。
更多的例子可以參考我寫的測試,在遷出的代碼中 ./runtests 當然要確保 npm 依賴已經安裝,即在目錄下運行 npm install)就可以運行測試。我在這裡提供這個很通用的工具的用法,要怎樣用好它什麼環境設定什麼 tag 之類),就要靠使用者的想象力了吧。
有不足的地方歡迎指證,可以到 github 裡面去提 issue。
原文:http://club.cnodejs.org/topic/4f3b65a1b43c3c846a05518c