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

问题描述 投票:42回答:6

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

// 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) );
string node.js file require
6个回答
52
投票
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


38
投票

安德烈已经回答了这个问题,但是我遇到了一个我必须解决的缺点,这可能是其他人感兴趣的。

我希望记忆字符串中的模块能够通过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));

之后,我能够从stringified模块加载所有现有模块。任何评论或更好的解决方案高度赞赏


6
投票

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

用法:

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

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

2
投票

基于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());
}();

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


1
投票

在分析了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
    }
}

-3
投票

我认为更好的方法是获得一个你可以在之后设置的参数......

比如文件里面:myUnalteredModule.js

exports.setChanges = function( args )...

然后你可以这样做:

 var loadedModule = require( 'myUnalteredModule' );
loadedModule
© www.soinside.com 2019 - 2024. All rights reserved.