Load node.js module from string in memory

Stringnode.jsFileRequire

String Problem Overview


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

String Solutions


Solution 1 - String

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 - String

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 - String

The require-from-string package does the job.

Usage:

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

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

Solution 4 - String

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 - String

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.

Solution 6 - String

I think the better way to approach this would be to have a parameter that you could set afterwards...

such as inside the file: myUnalteredModule.js

exports.setChanges = function( args )...

Then you could do:

 var loadedModule = require( 'myUnalteredModule' );
loadedModule

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionZECTBynmoView Question on Stackoverflow
Solution 1 - StringAndrey SidorovView Answer on Stackoverflow
Solution 2 - StringDominicView Answer on Stackoverflow
Solution 3 - StringhashchangeView Answer on Stackoverflow
Solution 4 - StringNikolay MakhoninView Answer on Stackoverflow
Solution 5 - StringLcf.vsView Answer on Stackoverflow
Solution 6 - StringBorreyView Answer on Stackoverflow