从内存中的字符串加载 node.js 模块

Posted

技术标签:

【中文标题】从内存中的字符串加载 node.js 模块【英文标题】:Load node.js module from string in memory 【发布时间】:2013-07-09 01:02:21 【问题描述】:

如果我将文件的内容作为字符串保存在内存中,而不将其写入磁盘,我将如何 require() 一个文件?这是一个例子:

// Load the file as a string
var strFileContents = fs.readFileSync( "./myUnalteredModule.js", 'utf8' );

// Do some stuff to the files contents
strFileContents[532] = '6';

// Load it as a node module (how would I do this?)
var loadedModule = require( doMagic(strFileContents) );

【问题讨论】:

我没有完整的答案,但您可以查看 Node.js 如何运行模块代码。 github.com/joyent/node/blob/…也看L813 【参考方案1】:
function requireFromString(src, filename) 
  var Module = module.constructor;
  var m = new Module();
  m._compile(src, filename);
  return m.exports;


console.log(requireFromString('module.exports =  test: 1', ''));

查看module.js中的_compile、_extensions和_load

【讨论】:

实际上,您还需要做一件事情才能使require 正常工作——在文件名上调用Module._nodeModulePaths(参见github.com/floatdrop/require-from-string)。 如果字符串有相对导入,是否可以设置基本路径? 你也可以用var Module = module.constructor;代替var Module = require("module"); 所以如果没有文件名参数,会发生什么?我试过了,这会引发错误,你需要传递一个文件名:requireFromString(code, filename)。我认为 uuid 会很好,因为传入真实的文件路径是不诚实的。 @AlexanderMills 我很确定文件名是作为 __filename 传递给模块包装函数的:nodejs.org/api/modules.html#modules_filename【参考方案2】:

Andrey 已经回答了这个问题,但我遇到了一个必须解决的缺点,其他人可能会对此感兴趣。

我希望记忆字符串中的模块能够通过require 加载其他模块,但上述解决方案破坏了模块路径(例如找不到针)。 我试图找到一个优雅的解决方案来维护路径,通过使用一些现有的功能,但我最终还是硬连接了路径:

function requireFromString(src, filename) 
  var m = new module.constructor();
  m.paths = module.paths;
  m._compile(src, filename);
  return m.exports;


var codeString = 'var needle = require(\'needle\');\n'
  + '[...]\n'
  + 'exports.myFunc = myFunc;';

var virtMod = requireFromString(codeString);
console.log('Available public functions: '+Object.keys(virtMod));

之后,我能够从字符串化模块加载所有现有模块。 任何 cmets 或更好的解决方案都非常感谢!

【讨论】:

感谢分享,为我节省了大量调试时间! 它适用于单文件模块!太糟糕了,它不适用于多文件模块。我深入研究了它,结果发现每次调用require(例如,当使用requireFromString 编译的“虚拟模块”需要另一个相对于自身的文件时)总是会在文件系统中以checking for a real file 结束。除非,我们可以以fs.statSyncfs.realpathSync 会选择它们的方式添加虚拟文件路径,否则多文件模块是遥不可及的。你可能会问为什么:我想通过网络发送模块... require.cache 条目丢失 我知道这是旧的,我还没有真正尝试过我在说什么,但是 require.resolving 你想要在你的字符串之外导入的模块并用完整的模块名称替换模块名称怎么样解决路径?可以工作!【参考方案3】:

require-from-string package 完成这项工作。

用法:

var requireFromString = require('require-from-string');

requireFromString('module.exports = 1');
//=> 1

【讨论】:

我建议您扩展您的答案,包括一个示例,说明您如何使用该模块来解决 OP 的问题。 这是一个例子:不使用 require-from-string 包: var test = require(pathToFile);使用 require-from-string 包: var test = requireFromString(string read from pathToFile);【参考方案4】:

在分析了piratesrequire-from-string等解决方案的源代码后,我得出的结论是,一个简单的模拟fsModule方法在支持方面不会差。而且在功能上会更好,因为它支持@babel/registerpirates等改变模块加载过程的模块。

你可以试试这个 npm 模块require-from-memory

import fs from 'fs'
import BuiltinModule from 'module'
const Module = module.constructor.length > 1 ? module.constructor : BuiltinModule

function requireFromString(code, filename) 
    if (!filename) 
        filename = ''
    

    if (typeof filename !== 'string') 
        throw new Error(`filename must be a string: $filename`)
    

    let buffer
    function getBuffer() 
        if (!buffer) 
            buffer = Buffer.from(code, 'utf8')
        
        return buffer
    

    const now = new Date()
    const nowMs = now.getTime()
    const size = Buffer.byteLength(code, 'utf8')
    const fileStat = 
        size,
        blksize    : 4096,
        blocks     : Math.ceil(size / 4096),
        atimeMs    : nowMs,
        mtimeMs    : nowMs,
        ctimeMs    : nowMs,
        birthtimeMs: nowMs,
        atime      : now,
        mtime      : now,
        ctime      : now,
        birthtime  : now
    

    const resolveFilename = Module._resolveFilename
    const readFileSync = fs.readFileSync
    const statSync = fs.statSync
    try 
        Module._resolveFilename = () => 
            Module._resolveFilename = resolveFilename
            return filename
        

        fs.readFileSync = (fname, options, ...other) => 
            if (fname === filename) 
                console.log(code)
                return typeof options === 'string'
                    ? code
                    : getBuffer()
            
            console.log(code)
            return readFileSync.apply(fs, [fname, options, ...other])
        

        fs.statSync = (fname, ...other) => 
            if (fname === filename) 
                return fileStat
            
            return statSync.apply(fs, [fname, ...other])
        

        return require(filename)
     finally 
        Module._resolveFilename = resolveFilename
        fs.readFileSync = readFileSync
        fs.statSync = statSync
    

【讨论】:

我收到“模块未定义”错误。你能帮忙吗? 是的,如果您在此处描述错误:github.com/NikolayMakhonin/require-from-memory/issues【参考方案5】:

基于 Andrey Sidorov 和 Dominic 的解决方案,我对无法要求字符串化模块这一事实感到难过,因此我建议使用此版本 *。

代码:

void function() 
    'use strict';

    const EXTENSIONS = ['.js', '.json', '.node'];

    var Module,
        path,
        cache,
        resolveFilename,
        demethodize,
        hasOwnProperty,
        dirname,
        parse,
        resolve,
        stringify,
        virtual;

    Module = require('module');
    path = require('path');

    cache = Module._cache;
    resolveFilename = Module._resolveFilename;

    dirname = path.dirname;
    parse = path.parse;
    resolve = path.resolve;
    demethodize = Function.bind.bind(Function.call);
    hasOwnProperty = demethodize(Object.prototype.hasOwnProperty);

    Module._resolveFilename = function(request, parent) 
        var filename;

        // Pre-resolution
        filename = resolve(parse(parent.filename).dir, request);

        // Adding extension, if needed
        if (EXTENSIONS.indexOf(parse(filename).ext) === -1) 
            filename += '.js';
        

        // If the module exists or is virtual, return the filename
        if (virtual || hasOwnProperty(cache, filename)) 
            return filename;
        

        // Preserving the native behavior
        return resolveFilename.apply(Module, arguments);
    ;

    Module._register = function(request, parent, src) 
        var filename,
            module;

        // Enabling virtual resolution
        virtual = true;

        filename = Module._resolveFilename(request, parent);

        // Disabling virtual resolution
        virtual = false;

        // Conflicts management
        if (hasOwnProperty(cache, filename)) 
            error = new Error('Existing module "' + request + '"');
            error.code = 'MODULE_EXISTS';

            throw error;
        

        // Module loading
        cache[filename] = module = new Module(filename, parent);
        module.filename = filename;
        module.paths = Module._nodeModulePaths(dirname(filename));
        module._compile(stringify(src), filename);
        module.loaded = true;

        return module;
    ;

    stringify = function(src) 
        // If src is a function, turning to IIFE src
        return typeof src === 'function'
            ? 'void ' + src.toString() + '();'
            : src;
    ;
();

void function() 
    var Module,
        parentModule,
        child;

    Module = require('module');

    // Creating a parent module from string
    parentModule = Module._register('parent', process.mainModule, `
        module.exports = 
            name: module.filename,
            getChild: function() 
                return require('child');
            
        ;
    `);

    // Creating a child module from function
    Module._register('child', parentModule, function() 
        module.exports = 
            name: module.filename,
            getParent: function() 
                return module.parent.exports;
            
        ;
    );

    child = require('child');

    console.log(child === child.getParent().getChild());
();

用法:

void function() 
    var Module,
        parentModule,
        child;

    Module = require('module');

    // Creating a parent module from string
    parentModule = Module._register('parent', process.mainModule, `
        module.exports = 
            name: module.filename,
            getChild: function() 
                return require('child');
            
        ;
    `);

    // Creating a child module from function
    Module._register('child', parentModule, function() 
        module.exports = 
            name: module.filename,
            getParent: function() 
                return module.parent.exports;
            
        ;
    );

    child = require('child');

    console.log(child === child.getParent().getChild());
();

* 如您所见,它包含一个函数格式化程序,它提供了一种从函数创建一些模块的方法。

【讨论】:

您能在其中添加一些 cmets 吗? 不清楚你想在这里做什么,也许把它作为一个 github 要点并以某种方式缩短示例 这只是一种替代解决方案,可以通过虚拟路径创建一些虚拟模块,来自其他模块的可调用对象。 ;)【参考方案6】:

我认为解决这个问题的更好方法是有一个可以在之后设置的参数...

比如里面的文件:myUnalteredModule.js

exports.setChanges = function( args )...

那么你可以这样做:

 var loadedModule = require( 'myUnalteredModule' );
loadedModule

【讨论】:

我没有那个选项。我必须能够将模块的内容修改为字符串,并且我真的很想避免创建临时文件。

以上是关于从内存中的字符串加载 node.js 模块的主要内容,如果未能解决你的问题,请参考以下文章

如何从Node.js中的URL加载外部js脚本

路径必须是一个字符串(需要带有node.js http模块的url)

Node.js,需要文件夹中的所有模块并直接使用加载的模块

模块的加载机制 丨Node.js模块化

Node.js中模块加载机制

Node.js中模块加载机制