4

Let's say we are creating a module called app by constructing a new vm.SourceTextModule object:

const context = {
  exports: {},
  console,  // custom console object
};
const sandbox = vm.createContext(context);

const app = new vm.SourceTextModule(
  `import path from 'path';
   console.log(path.resolve('./src'));`,
  {
    context: sandbox,
  }
);

According to the Node.js documentation to obtain the default export from path module we should "link" the imported dependencies of app module to it.
To achieve this we should pass linker callback to app.link method:

async function linker(specifier, referencingModule) {
    // the desired logic...
}

await app.link(linker);

How to implement linker function properly so that we could import path module in newly created app module and use it:

await app.evaluate();  // => /home/user/Documents/project/src

P.S. We are using TypeScript, so I checked if we have installed types for path package.

package.json:

"@types/node": "^17.0.31",
pospolitaki
  • 313
  • 2
  • 9

1 Answers1

6

I found https://github.com/nodejs/node/issues/35848 where someone posted a code snippet.

From there I've adapted the following linker callback:

const imports = new Map();

async function linker(specifier, referencingModule) {
  if (imports.has(specifier))
    return imports.get(specifier);
  
  const mod = await import(specifier);
  const exportNames = Object.keys(mod);
  const imported = new vm.SyntheticModule(
    exportNames,
    () => {
      // somehow called with this === undefined?
      exportNames.forEach(key => imported.setExport(key, mod[key]));
    },
    { identifier: specifier, context: referencingModule.context }
  );

  imports.set(specifier, imported);
  return imported;
}

The code snippet from the GitHub issue didn't work for me on Node 18.7.0 as is, because the evaluator callback passed to the constructor of SyntheticModule is somehow called with this set to undefined. This may be a Node bug.

I also cached the imported SyntheticModules in a Map because if they have internal state, creating a new SyntheticModule every time will reset that state.

ptomato
  • 56,175
  • 13
  • 112
  • 165
  • Thanks a lot for your time and attention, i really appreciate it. This stuff is a bit tricky for my current programming level. I tried to run your code snippet. The line `const mod = await import(specifier);` causes the `Error: Cannot find module 'path'` error. But outside of the `linker` body `await import('path')` - acts as expected. Maybe i'm missing something.. – pospolitaki Aug 11 '22 at 16:31
  • 1
    I'm not sure why this would be. – ptomato Aug 12 '22 at 00:37
  • If you have some time, please check out the [new question](https://stackoverflow.com/questions/73335799/using-the-dynamic-import-inside-the-module-link-callback-causes-an-error) caused by the above dynamic import problem. Thanks a lot anyway! – pospolitaki Aug 12 '22 at 14:48