实现自己的require函数
发布于 4年前 作者 leizongmin 3147 次浏览 最后一次编辑是 3年前

最近在写一个类似于“计划任务”之类的程序,需要动态载入js文件来运行,但是NodeJs中的require函数加载文件后会将其缓存,而且无法访问这些缓存,只得自己写了个require函数来代替。

 基本功能



  1. 载入指定文件名的NodeJs模块,可自动搜索路径(与require函数差不多);

  2. 自动缓存载入的模块,当模块文件被修改时,自动清除该缓存;

  3. 沙箱功能允许在在模块里面调用自己扩展的函数;


实现原理



  1.  使用vm模块来编译并运行JavaScript代码

  2. 利用vm.runInNewContext()的沙箱参数来获取被载入模块文件中的module.exports

  3. 使用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;

3 回复

just for a notifying. a new feature: ‘require.cache’ was added to ‘require’ to let you manipulate the module cache since v0.4.9. (it’s a little hard to use pinyin for me so i type in english for this comment. sorry.)

node中require也是用JS实现的么?

回到顶部