Skip to content
This repository has been archived by the owner on Jan 1, 2025. It is now read-only.

Documentation for customRequire option #461

Closed
JosephDenman opened this issue Aug 15, 2022 · 6 comments · Fixed by #489
Closed

Documentation for customRequire option #461

JosephDenman opened this issue Aug 15, 2022 · 6 comments · Fixed by #489

Comments

@JosephDenman
Copy link

JosephDenman commented Aug 15, 2022

Looking for some clarification around the customRequire option in NodeVMOptions. From what I gather, the customRequire option of type (id: string) => any takes the name of the module being required and returns an object containing the functions that the require is intended to import. But, for some reason, my provided customRequire function is never being executed.

My simplified use case is as follows. I have strings representing two typescript source files representing the API producer:

export function produce() {
  return 10
}

and the API consumer

import { produce } from 'producer'

export function consume() {
  return produce()
}

Then I'm transpiling them to JS using the TS compiler API, which produces

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.produce = void 0;
function produce() {
    return 10;
}
exports.produce = produce;

and

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.consume = void 0;
const producer_1 = require("producer");
function consume() {
    return (0, producer_1.produce)();
}
exports.consume = consume;

respectively. But when I run

const producer = `export function produce() { return 10 }`
const consumer = `import { produce } from 'producer' ; export function consume() { return produce() }`
const producerjs = ts.transpile(producer)
const producerFuncs = NodeVM.code(producerjs)
const consumerjs = ts.transpile(consumer)
new NodeVM({
  require: {
    customRequire: (id: string) => {
      if (id === 'producer') {
        // eslint-disable-next-line @typescript-eslint/no-unsafe-return
        return producerFuncs
      }
    }
  }
}).run(consumerjs)

I get VMError: Cannot find module 'producer'. Any idea why this might be happening?

@XmiliaH
Copy link
Collaborator

XmiliaH commented Aug 15, 2022

Like the comment says Custom require to require host and built-in modules. this option can be used to require host or built-in modules. In your case the module is not found to be a built-in or host module. Therefore the customRequire function is not called. This can be fixed by using:

new NodeVM({
  require: {
    builtin: ['producer'],
    customRequire: (id: string) => {
      if (id === 'producer') {
        // eslint-disable-next-line @typescript-eslint/no-unsafe-return
        return producerFuncs
      }
    }
  }
})

@JosephDenman
Copy link
Author

I implemented what you suggested, but still getting the same error.

@XmiliaH
Copy link
Collaborator

XmiliaH commented Aug 15, 2022

Sorry, I was wrong here. Only actual built-ins are allowed in the builtin option.
Since customRequire is not really documented (only for typescript, not in the readme) you could use mocks instead.

new NodeVM({
  require: {
    mock: {
      producer: producerFuncs
    }
  }
})

@JosephDenman
Copy link
Author

Thanks! Would you consider this an idiomatic way to perform dependency injection? My hope was that there was a way to directly specify the source code of the producer as a module dependency, but it doesn't seem like that's possible without writing it to a file and listing it as a require options.

@XmiliaH
Copy link
Collaborator

XmiliaH commented Aug 15, 2022

Since it is currently not possible to give strings as sources instead of loading from files I would do it like this:

const { NodeVM, VMScript } = require('vm2');

const kVM = Symbol('vm');

const inject = {
	producer: new VMScript('exports.x = 1;'),
};

function makeInjectionProto(inject) {
	const proto = { __proto__: null };
	for (const key of Object.keys(inject)) {
		proto[key] = {
			configurable: true,
			enumerable: true,
			get: function () {
				return this[kVM].run(inject[key]);
			},
		};
	}
	return proto;
}

const proto = makeInjectionProto(inject);


function onNewInstance() {
	const mock = Object.defineProperties({}, proto);
	const vm = new NodeVM({
		require: {
			mock
		}
	});
	mock[kVM] = vm;
	console.log(vm.run('exports.x = require("producer");'));
}

onNewInstance();
onNewInstance();

@JosephDenman
Copy link
Author

Got it! Thanks.

@XmiliaH XmiliaH linked a pull request Nov 22, 2022 that will close this issue
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants