modules icon indicating copy to clipboard operation
modules copied to clipboard

Expose the default module resolver in a package

Open Jamesernator opened this issue 6 years ago • 3 comments

Currently we can access the default resolver when in a --loader loader. However we can't access this loader from a regular program. I'd like to propose exposing this function as a regular function that can be imported at runtime for use with vm.SourceTextModule to create in-process loaders.

For example:

import vm from 'vm';
import process from 'process';
import url from 'url';
import { resolver } from 'module';

const cwd = url.pathToFileURL(process.cwd() + '/');

class TestLoader {
  #cache = new Map();
  #realm = vm.createContext({});

  async #loadModule(url) {
    return new vm.SourceTextModule(await fsp.readFile(resolvedUrl, 'utf8'), {
      url: resolvedUrl,
      context: this.#realm,
    });
  }
  
  async #linker(module, specifier) {
    // --- Use node's resolution algorithm ---
    const resolvedUrl = resolver(module.url, specifier);
    if (this.#cache.has(resolvedUrl)) {
      return this.#cache.get(resolvedUrl);
    }
    const module = this.#loadModule(resolvedUrl);
    this.#cache.set(resolvedUrl, module);
    return module;
  }

  constructor() {
    // Setup test environment
    this.#realm.evaluate(`function deepEqual(a, b) { /* ... */ }`);
  }

  async runTest(file) {
    const module = this.#loadModule(resolver(cwd, file));
    await module.link((...args) => this.#linker(...args));
    module.instantiate();
    try {
      await module.evaluate();
      return { passed: true };
    } catch (error) {
      return { passed: false, error };
    }
  }
}

Jamesernator avatar Jul 29 '19 04:07 Jamesernator

Maybe worth noting, in some of my early --loader experiments, I was inclined to chain loaders by wrapping the resolvers and passing them as the defaultResolve argument…

So I think we might want to first verify if defaultResolve will not be specific to a loader or loader-chain in the newer loader design(s).

SMotaal avatar Aug 22 '19 16:08 SMotaal

The default resolver hook is sync, should this instead be async? If it's ever desired to support loader stacking node --loader=./loader1.js --loader=./loader2.js I think this would be a blocker as the 3rd argument to the resolve in loader2.js wouldn't be defaultResolve, it would be the resolve from loader1.js. Regardless of multiple loaders ever being supported or not I think the default resolver should return a Promise to be consistent but especially if it's going to be exposed in this way.

Another question, do you think the default resolver should be exposed, or should a function which wraps the current resolver be exposed?

coreyfarrell avatar Oct 02 '19 12:10 coreyfarrell

The default resolver hook is sync, should this instead be async?

I have no strong opinions on the matter, getting some more feedback from library authors for things like bundlers would probably be helpful. I would be fine with async for most my own use cases.

Another question, do you think the default resolver should be exposed, or should a function which wraps the current resolver be exposed?

Tools will probably want either depending on their use case.

Jamesernator avatar Oct 03 '19 03:10 Jamesernator