最近在写一个类似于“计划任务”之类的程序,需要动态载入js文件来运行,但是NodeJs中的require函数加载文件后会将其缓存,而且无法访问这些缓存,只得自己写了个require函数来代替。
基本功能
- 载入指定文件名的NodeJs模块,可自动搜索路径(与require函数差不多);
- 自动缓存载入的模块,当模块文件被修改时,自动清除该缓存;
- 沙箱功能允许在在模块里面调用自己扩展的函数;
实现原理
- 使用vm模块来编译并运行JavaScript代码
- 利用vm.runInNewContext()的沙箱参数来获取被载入模块文件中的module.exports
- 使用fs.watchFile()来监视文件是否被改动
程序代码
/**
* require函数
* 兼容 NodeJs的 require 函数
*
* $Id$
* @author 雷宗民 <leizongmin@gmail.com>
* [[[[[[[[[[[[[[[[[[@version](/user/version)](/user/version)](/user/version)](/user/version)](/user/version)](/user/version)](/user/version)](/user/version)](/user/version)](/user/version)](/user/version)](/user/version)](/user/version)](/user/version)](/user/version)](/user/version)](/user/version)](/user/version) 0.23
* [[[[[[[[[[[[[[[[[[[@date](/user/date)](/user/date)](/user/date)](/user/date)](/user/date)](/user/date)](/user/date)](/user/date)](/user/date)](/user/date)](/user/date)](/user/date)](/user/date)](/user/date)](/user/date)](/user/date)](/user/date)](/user/date)](/user/date) 2011-07-22 10:10:36
/
// 使用说明:
// ExtModule.require(‘文件名’);
// ExtModule.require('文件名’, {ExtFunc: function () {…}});
// ExtModule.require('文件名’, {沙箱}, ‘目录’);
// 1、如果文件名不是以 ‘.’、’/’ 开头,则认为是系统内置模块,直接返回 require(‘模块名’);
// 2、沙箱可以加入扩展的函数、变量等,以便在被载入的模块中使用
// 3、会自动匹配 .js、.node 后缀的文件,以及该目录的 node_modules 子目录、父目录的 node_modules 等,直至回溯到根目录
// 4、如果载入模块出错,则返回 string 类型的出错信息 if (typeof (m = ExtModule.require(‘xxx’)) == ‘string’) console.log('Error: ' + m);
// 5、首次载入文件,自动将其缓存;当该文件被改动时,自动删除该文件的缓存
var vm = require(‘vm’),
path = require(‘path’),
fs = require(‘fs’);
ExtModule = {};
CACHE = {};
ExtModule.debug = function (x) {
console.log('[ExtModule] ' + x.toString());
}
ExtModule.wrap = function © {
return '(function () { \n ' +
//’var require = ' +
c + '\n });’;
}
// 加载程序 n=名称, d=所在目录, sandbox=沙箱
ExtModule.require = function () {
if (arguments.length < 1) {
return 'Not enough arguments!’;
}
if (arguments.length == 1) {
var n = arguments[0], d = __dirname, sandbox = {};
}
else if (arguments.length == 2) {
var n = arguments[0], d = __dirname, sandbox = arguments[1];
}
else {
var n = arguments[0], d = arguments[1], sandbox = arguments[2];
}
//ExtModule.debug('require dir: ' + d);
try {
// 如果是系统模块
var _c = n.charAt(0);
if (_c != ‘.’ && _c != ‘/’) {
return require(n);
}
// 取得实际文件名
if (n.charAt(0) != ‘/’) {
n = path.resolve(d, n);
}
var filename = ExtModule.findModule(n);
// 没有找到模块文件
if (!filename) {
ExtModule.debug(‘Cannot find the module "’ + n + ‘".’);
return ‘Cannot find the module "’ + n + '".’;
}
// 检查是否在缓存中
if (!(filename in CACHE)) {
// 首次编译
ExtModule.debug('compile ' + filename);
var c = fs.readFileSync(filename);
var s = vm.createScript(ExtModule.wrap©, filename);
for (var k in global) {
sandbox[k] = global[k];
}
var requireModule = { exports: {}, parent: module};
sandbox.exports = requireModule.exports;
sandbox.__filename = filename;
sandbox.__dirname = path.dirname(filename);
sandbox.module = requireModule;
sandbox.global = sandbox;
sandbox.root = sandbox;
sandbox.require = function (n) { return ExtModule.require(n, sandbox.__dirname, {}); };
var f = s.runInNewContext(sandbox);
f.apply();
CACHE[filename] = {
filename: filename,
script: s,
exports: requireModule.exports
}
// 监视文件是否改动
fs.unwatchFile(filename);
fs.watchFile(filename, function (curr, prev) {
ExtModule.debug('removeCache ' + filename);
delete CACHE[filename];
});
}
return CACHE[filename].exports;
}
catch (err) {
ExtModule.debug(‘compile error: \n’ + err.stack);
return err.stack;
// throw err;
}
}
// 查找模块路径 n = 文件名, 必须以/开头
ExtModule.findModule = function(n) {
var paths = [];
var pdir = n;
var basename = path.basename(n);
var notParentDir = true;
do {
pdir = path.dirname(pdir);
if (notParentDir) {
paths.push(pdir);
notParentDir = false;
}
if (pdir == ‘/’) {
paths.push(pdir + ‘node_modules’);
break;
}
else {
paths.push(pdir + ‘/node_modules’);
}
} while (true);
for (i in paths) {
if (path.extname(basename) == ‘’) {
var files = [basename + '.node’, basename + '.js’, basename + ‘/index.js’ ];
}
else {
var files = [basename];
}
for (j in files) {
var filename = path.resolve(paths[i], files[j]);
try {
//console.log('test ' + filename);
var stat = fs.statSync(filename);
if (stat.isFile()) {
return filename;
}
}
catch (err) {}
}
}
}
/* 模块输出 */
exports.require = ExtModule.require;