exuanbo / module-from-string

Load module from string using require or import.
MIT License
47 stars 2 forks source link

TypeScript: Can't use relative import paths (cannot find module) #19

Closed jahilldev closed 1 year ago

jahilldev commented 1 year ago

First off, thanks for this great package! It's definitely saved me a lot of time 👍

I'm trying to import a TypeScript module that makes use of relative imports, but I'm running into what seems to be a resolution issue. Importing packages from NPM is working perfectly well, and relative imports when using JavaScript are also working correctly.

However, when using TypeScript, we receive the following when using relative import paths:

Error: Cannot find module './utility'
Require stack:
- /module-from-string-issue/input/handler.ts

You can see a minimal reproduction below: https://github.com/jahilldev/module-from-string-issue

Can you point me in the right direction here? I want this to be done in memory, so compiling the source TypeScript into JavaScript prior to using importFromString isn't really an option (or one I'd prefer is a last resort).

Many thanks in advance for any help!

jahilldev commented 1 year ago

I've actually found a solution to this; esbuild supports bundling / building in memory, so rather than passing the raw TypeScript into module-from-string, I'm running esbuild.build with writeFile: false, which returns an array of output files and their contents.

Roughly something like this:

import * as esbuild from 'esbuild';
import { importFromString } from 'module-from-string';

const { outputFiles: [{ text: contents }] } = await esbuild.build({
  entryPoints: [sourcePath],
  platform: 'node',
  loader: { '.ts*': 'ts' },
  bundle: true, // important
  write: false, // important
  format: 'esm',
  /*[...]*/
});

const { handler } = await importFromString(contents, {
  /*[...]*/
});

And voila, relative imports are bundled into one file. To keep packages in node_modules external, I'm loading the root package.json and passing the dependencies to the esbuild option "external", e.g:

const packageFile = await fs.readFile('./package.json', {
  encoding: 'utf8',
});

const project = JSON.parse(packageFile);

const bundle = await esbuild.build({
  /*[...]*/
  external: [...Object.keys(project.dependencies || {})],
});

Just FYI for anyone else interested in doing something similar 👍