jiti icon indicating copy to clipboard operation
jiti copied to clipboard

`async import` of typescript file doesn't work from cjs

Open rchl opened this issue 2 years ago • 9 comments

Environment

Node 18 jiti 1.21.0

Reproduction

  1. Create two files

test.cjs

(async function foo() {
    await import('./typescript');
})();

typescript.ts

console.log('TYPESCRIPT FILE')
  1. Run npx jiti@latest test.cjs

Describe the bug

Trying to import *.ts file using async import from cjs file fails with:

node:internal/errors:496
    ErrorCaptureStackTrace(err);
    ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/.../typescript' imported from /.../test.cjs

It works if the test.cjs file is renamed to test.mjs

Additional context

Note that the actual case where I've noticed it is a lot less contrived. I found this issue by trying to configure @nuxtjs/sentry using a file that the module needed to import.

So I've set the serverConfig: '~/config/sentry.server' option which the Sentry module then tries to import using async import. The issue is that that sentry module is imported through the CJS export rather than MJS so then it fails. (note how exports are defined in the module: https://github.com/nuxt-community/sentry-module/blob/ad424f10763d5c08826e250f715ebde699d4edfa/package.json#L19-L27).

Logs

No response

rchl avatar Nov 10 '23 15:11 rchl

Thanks for reporting.

Minimal reproduction: https://stackblitz.com/edit/stackblitz-starters-3fj5rs?file=package.json

It seems transformation is not happening because the file has an explicit .cjs extension and also no ESM syntax (dynamic import is valid in Node.js CJS environment) and therefore we use native. (src)

I think we can also enable jiti transforms when in cjs, dynamic import syntax is detected but it adds overhead for all current .cjs files that go through and don't require it. Perhaps we can enable this behavior behind a flag so you can enable for sentry module importer?

pi0 avatar Nov 10 '23 15:11 pi0

How would I enable the flag in that case, given that the code looks like this?

https://github.com/nuxt-community/sentry-module/blob/977a74e7b28f360116514258adcf8cbcec2b5be1/src/options.ts#L243-L245

Note that this code is triggered by Nuxt when it loads the Sentry module (and uses CJS export by default).

rchl avatar Nov 10 '23 15:11 rchl

You need an explicit jiti instance in place of await import(). Also consider that it is only happening for stub mode when module is not transformed yet that uses implicit jiti for dynamic imports.

pi0 avatar Nov 10 '23 15:11 pi0

By "stub" do you mean the mode used during development only? Because that's not the case here.

Please have a look at dist/module.cjs at https://www.npmjs.com/package/@nuxtjs/sentry?activeTab=code . This is the file that Nuxt loads.

(Sorry if I'm saying obvious things. Just clarifying in case I misunderstood).

With all that said, I'm contemplating just forcing mjs for the module like:

diff --git a/package.json b/package.json
index 3fcd235..733a350 100755
--- a/package.json
+++ b/package.json
@@ -17,13 +17,7 @@
     "registry": "https://registry.npmjs.org"
   },
   "type": "module",
-  "exports": {
-    ".": {
-      "import": "./dist/module.mjs",
-      "require": "./dist/module.cjs"
-    }
-  },
-  "main": "./dist/module.cjs",
+  "main": "./dist/module.mjs",
   "types": "./dist/module.d.ts",
   "files": [
     "dist"

which I feel like would be perfectly fine. At least for versions of Nuxt that use jiti but that's probably reasonable to expect everyone using.

rchl avatar Nov 10 '23 15:11 rchl

Yep, I got it. Regardless I think it would be always much much safer if in this line you expect resolvedPath pointing to a possibly Typescript entry, to use an explicit jiti.import() and ensures doing transforms and i expect Nuxt uses native ESM for loading module (which in Node.js has no TS support)

Would love to hear your feedback about doing it :)

pi0 avatar Nov 10 '23 16:11 pi0

I've tried quickly and that seems to work too. Though I'm really tempted to just forcing Nuxt to load the module using MJS since that's so much cleaner...

rchl avatar Nov 10 '23 16:11 rchl

If nuxt uses MJS/Native ESM, i think this issue is irrelevant no? Because mainly we want typescript support from what i understand and it is possible when jiti transform happens. (we do transform dynamic import in native ESM files when passed to jiti only because we have do transform anyway as currently jiti is based on CJS. Is is a coincidence that works with ESM!)

Depending on Nuxt behavior that might use jiti implicitly for ESM files, is not sure assumption and can be broken in the future.

(update: reading above seems confusing explanation haha feel free to DM me if you have questions!)

pi0 avatar Nov 10 '23 16:11 pi0

If I'd want to do everything by the book then how would I initialize jiti in an ESM context?

jiti documentation says to use __filename:

const jiti = require("jiti")(__filename);

which would technically work when running in Nuxt since it seems to "polyfill" that but if I'd assume that I'm running in a native ESM context then it wouldn't be available.

rchl avatar Nov 12 '23 11:11 rchl

This should work in ESM context (PR welcome for docs!)

import initJiti from 'jiti'
const jiti = initJiti(import.meta.url)
console.log(jiti.resolve('.'))

pi0 avatar Nov 12 '23 19:11 pi0

(docs updated)

pi0 avatar Sep 12 '24 22:09 pi0