59

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) );
ZECTBynmo
  • 2,838
  • 2
  • 20
  • 33
  • I don't have a full answer for you but you can checkout how Node.js runs the module code. https://github.com/joyent/node/blob/7373c4ddb7143fb7da75feda39c70788fb1bcfc7/src/node.js#L768 also look at at L813 – travis Jul 10 '13 at 23:19

6 Answers6

65
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

Tomasz Gandor
  • 5,982
  • 2
  • 45
  • 46
Andrey Sidorov
  • 22,962
  • 4
  • 58
  • 73
  • 9
    Actually you need one more thing to make `require` work properly - call `Module._nodeModulePaths` on filename (see https://github.com/floatdrop/require-from-string). – floatdrop Nov 06 '15 at 14:17
  • 1
    If the string has relative imports, is it possible to set a base path? – Dominic Sep 20 '18 at 20:44
  • 1
    Instead of `var Module = module.constructor;` you can also do `var Module = require("module");` – Lukas Oct 26 '18 at 11:52
  • so if there is no filename argument, what happens? I tried it, this throws an error, you need to pass a filename: `requireFromString(code, filename)`. I assume a uuid would be fine, since passing in a real filepath is disingenuous. – Alexander Mills Nov 22 '18 at 09:00
  • @AlexanderMills I'm pretty sure filename is what passed down to module wrapper function as __filename: https://nodejs.org/api/modules.html#modules_filename – Andrey Sidorov Nov 23 '18 at 01:14
  • @floatdrop if you still remember, can you elaborate on your comment? Because your repo doesn't talk about that at all in 2020. How do you make `Module` see all the other modules it should have access to? (`require()` code in the strings passed to `require-from-string` don't get found) – Mike 'Pomax' Kamermans Aug 05 '20 at 00:39
  • @Mike'Pomax'Kamermans doing `m.paths = Module._nodeModulePaths(path.dirname(filename))` before `m._compile` works – Aleksi Dec 09 '20 at 03:22
44

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!

Dominic
  • 825
  • 8
  • 13
  • Thanks for sharing, this saved me lots of time debugging! – Deleteman Aug 09 '14 at 07:29
  • 4
    It works great for single-file modules! Too bad, it won't work for multi-file modules. I digged into it, and turns out that every call to `require` (e.g. when a "virtual module" as compiled with `requireFromString` requires another file relative to itself) will always end up [checking for a real file](https://github.com/joyent/node/blob/master/lib/module.js#L134) in the file system. Unless, we can add virtual file paths in a way that `fs.statSync` and `fs.realpathSync` will pick them up, multi-file modules are out of reach. You might ask why: I want to send modules over the network... – Domi May 02 '15 at 13:27
  • require.cache entry is missing – xamiro Nov 22 '15 at 03:59
  • I know this is old and I haven't really tried what I'm talking about, but how about require.resolving modules you want to import outside of your string and replacing the module name with the full resolved path? Could work! – João Miguel Brandão Jul 26 '18 at 09:43
10

The require-from-string package does the job.

Usage:

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

requireFromString('module.exports = 1');
//=> 1
aymericbeaumet
  • 5,464
  • 1
  • 32
  • 48
hashchange
  • 5,971
  • 1
  • 40
  • 40
  • 2
    I'd recommend expanding your answer to include an example of how you'd use that module to solve the OP's problem. – brandonscript Aug 17 '16 at 19:39
  • this is an example: without using require-from-string package: var test = require(pathToFile); with using require-from-string package: var test = requireFromString(string read from pathToFile); – Huy - Logarit Aug 13 '20 at 08:00
3

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
    }
}
Nikolay Makhonin
  • 636
  • 7
  • 10
2

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.

Lcf.vs
  • 1,539
  • 1
  • 10
  • 14
-3

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
Borrey
  • 55
  • 7
  • I don't have that option. I have to be able to modify the module's contents as a string, and I would really like to avoid creating temp files. – ZECTBynmo Jul 10 '13 at 23:32