登录
注册
node.js 学习社区
实现自己的require函数

卷卷儿

2014-12-09 01:13

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

基本功能

载入指定文件名的NodeJs模块,可自动搜索路径(与require函数差不多);
自动缓存载入的模块,当模块文件被修改时,自动清除该缓存;
沙箱功能允许在在模块里面调用自己扩展的函数;

实现原理

使用vm模块来编译并运行JavaScript代码
利用vm.runInNewContext()的沙箱参数来获取被载入模块文件中的module.exports
使用fs.watchFile()来监视文件是否被改动

程序代码


/**
 * require函数
 * 兼容 NodeJs的 require 函数
 *
 * $Id$
 * @author 雷宗民 
 * @version 0.23
 * @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 (c) {
	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(c), 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;

原文引自:http://cnodejs.org/topic/4f16442ccae1f4aa270010cd

回复 · 0

发表回复

你可以在回复中 @ 其他人