問題出現場景
在項目中采用nodejs做中間層,做頁面的首屏渲染,同時采用express作為主web框架,其中express的router頁面路由我采用ts語言來編寫。如下:
//page.ts 文件
import request = require('request');
module.exports = function(router) {
router.get('/', function(req, resp) {
resp.render('xxx/page');
});
};
編寫完ts后運行tsc命令將相應的ts文件編譯為對應的js文件,如下:
//page.js 文件
var request = require('request');
module.exports = function(router) {
router.get('/', function(req, resp) {
resp.render('xxx/page');
});
};
其實這里只是import
變成了var
而已,但其意義在於在ts代碼中采用import
載入的模塊可以享用強類型檢查,以及代碼自動補全,預編譯檢查等。
但是在啟動node服務時卻報錯,錯誤信息如下:
Debugger listening on port 45864
/Users/WebSite/routes/xxx/page.ts:8
import request = require('request');
^^^^^^
SyntaxError: Unexpected reserved word
at exports.runInThisContext (vm.js:53:16)
at Module._compile (module.js:373:25)
at Object.Module._extensions..js (module.js:416:10)
at Module.load (module.js:343:32)
at Function.Module._load (module.js:300:12)
at Module.require (module.js:353:17)
at require (internal/module.js:12:17)
...
提示import
關鍵字非法,是保留字不能使用,在搜了Google以及Stackflow之后發現雖然提問的人很多,但幾乎沒人回答正確的解決方案,搜索無果后只能自己查為什么會出現這個問題。
原因分析
在受到stackoverflow上某一個回答(http://stackoverflow.com/a/23113558/6001468) 的啟發后,發現我的路由代碼的code.ts和編譯后的code.js文件都在express下的router文件夾下,而通過在這兩個文件分別輸出log發現均會輸出,而實際上我希望只有.js文件被識別,ts文件在編譯完成后就不再參與node的執行。
所以問題的原因就在於本不應該被node載入的.ts也被拉去進行執行。
通過斷點后發現,問題的原因在於核心模塊的module.js
文件里面的343行:
// Given a file name, pass it to the proper extension handler.
Module.prototype.load = function(filename) {
debug('load %j for module %j', filename, this.id);
assert(!this.loaded);
this.filename = filename;
this.paths = Module._nodeModulePaths(path.dirname(filename));
var extension = path.extname(filename) || '.js';
if (!Module._extensions[extension]) extension = '.js';
Module._extensions[extension](this, filename);
this.loaded = true;
};
在倒數第4行,如果某個文件的擴展名沒有在Module._extensions
字典內的文件,均被強制識別為.js文件,然后按照js代碼去執行。而_extensions
字典內只有js,json和node文件,如下圖:

所以問題找到了,但是因為module.js是核心模塊,不能修改其代碼,所以只能去更上層的Express的代碼去改。
解決方案
在node_modules/express-enrouten/lib/directory.js
中,有一個isFileModule
的函數,用來判斷當前文件是否一個模塊,從而來決定是否要用node去加載它,可以通過修改這個函數來達到目的。
/**
* Returns true if `require` is able to load the provided file
* or false if not.
* http://nodejs.org/api/modules.html#modules_file_modules
* @param file the file for which to determine module-ness.
* @returns {boolean}
*/
function isFileModule(file) {
var ext = path.extname(file);
// 新增:如果文件擴展名是.ts則不進行加載 --xxcanghai@博客園
if(ext === ".ts") {
return false;
}
// Omit dotfiles
// NOTE: Temporary fix in lieu of more complete (cross platform)
// fix using file flags/attributes.
if (path.basename(file, ext)[0] === '.') {
return false;
}
try {
// remove the file extension and use require.resolve to resolve known
// file types eg. CoffeeScript. Will throw if not found/loadable by node.
file = ext ? file.slice(0, -ext.length) : file;
require.resolve(file);
return true;
} catch (err) {
return false;
}
}
如果當前文件的擴展名是.ts則判斷為非模塊,而不去加載。
修改后問題解決了。
后記
雖然問題解決了,但是修改框架代碼這種事其實是有非常多的坑的,而且我本人也極力反感這種行為。
比如以后如何面臨框架升級,還有因為修改了源碼而帶來的其他bug怎么辦,等等。
但是目前現階段而言有沒有什么其他更好的辦法,所以只能先這樣了,如果有哪位博客園友知道能好的解決方案,還望不吝賜教。
完