這幾年 NodeJS 發展的速度的很快,許多產品的後端 (Backend Server) 均採用 NodeJS 進行設計,NodeJS 所採用的 Module 是遵循 CommonJS API 規範,在實現上最後會透過 module.exports 變數將模組導出。然而前端 (Frontend) 常用的 RequireJS 在實作上卻有些不同 (用的是 define 方法),如果可以將前後端共用的程式碼,改寫為可以同時被 RequireJS 與 NodeJS 引用的程式,那麼就可以實現 DRY 原則 (Don't Repeat Yourself),這樣一來再好不過了。
以下是程式碼樣板:
'use strict'; /** * Universal JavaScript package * JavaScript, NodeJS Module and RequireJS Module Supported */ var Universal = function () { var privateVar = 'I_AM_PRIVATE_VAR'; var privateFn = function () { console.log('I am private function.'); }; // constructor var constructor = function () { console.log(privateVar); privateFn(); }; var publicFn = function () { console.log('I am public function.'); }; constructor(); return { /** * Public defined */ publicVar: 'I_AM_PUBLIC_VAR', /** * Public function */ publicFn: publicFn }; }; // Hook into NodeJS and RequireJS module systems if (typeof module !== 'undefined' && 'exports' in module) { module.exports = Universal; } else if (typeof(define) === 'function') { define(Universal); }
上述程式碼 (GitHub) 最後在結束的時候,判斷是否存在 module.exports 或 define 變數來決定導出或宣告,基本上在 NodeJS 與 RequireJS 都可以運作,當然 HTML 透過 <Script> 直接引用也是可以的。
NodeJS 測試
var universal = require('./src/foxdrive/universal')(); console.log(universal); universal.publicFn();
執行結果 STD OUT 如下:
I_AM_PRIVATE_VAR
I am private function.
{ publicVar: 'I_AM_PUBLIC_VAR', publicFn: [Function] }
I am public function.
RequireJS 測試
<script src="./js/require.js"></script> <script> require([ './js/universal.js' ], function (universal) { console.log(universal); universal.publicFn(); }); </script>
Chrome 執行 Console 顯示如下:
Browser JavaScript Tag 直接引用
<script src="./js/universal.js"></script> <script> var universal = Universal(); console.log(universal); universal.publicFn(); </script>
Chrome 執行 Console 顯示如下:
後來看書後發現有一個更棒更漂亮的版本 (GitHub),上面的程式整個 Low 掉,經過整理與改良的版本如下:
'use strict'; /** * Universal JavaScript package PLUS! * JavaScript, NodeJS Module and RequireJS Module Supported */ (function (name, definition) { if (typeof(module) !== 'undefined' && module.exports) { module.exports = definition; } else if (typeof(define) === 'function') { define(definition); } else { this[name] = definition(); } }('Universal', function () { var privateVar = 'I_AM_PRIVATE_VAR'; var privateFn = function () { console.log('I am private function.'); }; // constructor var constructor = function () { console.log(privateVar); privateFn(); }(); var publicFn = function () { console.log('I am public function.'); }; return { /** * Public defined */ publicVar: 'I_AM_PUBLIC_VAR', /** * Public function */ publicFn: publicFn }; }));