Load node.js module from string in memory

How would I require() a file if I had the file's contents as a string in memory, without writing it out to disk? Here's an example:

// 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) );

Solution 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}', ''));

look at _compile, _extensions and _load in module.js

Solution 2:

The question is already answered by Andrey, but I ran into a shortcoming that I had to solve and which might be of interest for others.

I wanted the module in the memorized string to be able to load other modules via require, but the module path was broken with the above solution (so e.g. needle wasn't found). I tried to find an elegant solution to maintain the paths, by using some existing function but I ended up with hard wiring the paths:

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));

After that I was able to load all existing modules from the stringified module. Any comments or better solutions highly appreciated!

Solution 3:

The require-from-string package does the job.

Usage:

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

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

Solution 4:

After analyzing the source code of such solutions as pirates and require-from-string, I came to the conclusion that a simple mock of fs and Module methods would be no worse in terms of support. And in terms of functionality it will be better, because it supports @babel/register, pirates and other modules that changes the module loading process.

You can try this npm module 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
    }
}

Solution 5:

Based on Andrey Sidorov & Dominic solutions, I was saddened by the fact of not being able to require a stringified module then I suggest this version *.

Code:

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

Usage:

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

* as you can see, it contains a function formater which provides a way to create some modules from functions.