node
node copied to clipboard
configure CJS named export within ESM loader
What is the problem this feature will solve?
When ESM loader resolve and load a virtual CJS module, named export not found.
Reproduce Description
-
use ESM loader to load a virtual CJS module
-
the virtual CJS name is
virtual:reproduce
-
the virtual CJS code is
module.exports.foo = () => 42;
-
Expect: OK
-
Actual: got
Named export 'foo' not found
Workaround
Add virtual ESM module using code export const foo = CJSExports.foo
But it will
-
at least double the complexity of CJS part of
importHook.resolve
-
at least double the complexity of CJS part of
importHook.load
So, I think better to introduce
- return { format: 'commonjs', shortCircuit: true, source: undefined }
+ return { format: 'commonjs', shortCircuit: true, source: undefined, namedExports: ['foo'] }
Reproduce
reproduce.mjs
import { foo } from "virtual:reproduce";
// import mod from 'virtual:reproduce'
// const { foo } = mod
console.log({ foo })
import-hook.mjs
const VIRTUAL_FILEURL = 'file:///virtual/reproduce'
export async function resolve(specifier, context, nextResolve) {
if (specifier === 'virtual:reproduce') {
return { format: 'commonjs', shortCircuit: true, url: VIRTUAL_FILEURL }
}
return await nextResolve(specifier, context)
}
export async function load(url, context, nextLoad) {
if (url === VIRTUAL_FILEURL) {
return { format: 'commonjs', shortCircuit: true, source: undefined }
}
return await nextLoad(url, context)
}
require-hook.cjs
const { Module } = require('node:module')
// const { syncBuiltinESMExports } = require("node:module");
const $resolveFilename = Module._resolveFilename
const $handler = Module._extensions['.js']
const VIRTUAL_PATH = '/virtual/reproduce'
const VIRTUAL_CODE = `module.exports.foo = () => 42;`
Module._resolveFilename = function (request, parent, isMain, options) {
if (request === VIRTUAL_PATH) {
return VIRTUAL_PATH
}
return $resolveFilename(request, parent, isMain, options)
}
Module._extensions['.js'] = function (module, filename) {
if (filename === VIRTUAL_PATH) {
module._compile(VIRTUAL_CODE, filename)
// syncBuiltinESMExports();
return
}
$handler(module, filename)
}
Actual
$ cd `mktemp -d`
$ tree
.
βββ reproduce.mjs
βββ import-hook.mjs
βββ require-hook.cjs
1 directory, 3 files
$ node --require ./require-hook.cjs --loader ./import-hook.mjs ./reproduce.mjs
(node:687343) ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
file:///path/to/reproduce.mjs:1
import { foo } from "virtual:reproduce";
^^^
SyntaxError: Named export 'foo' not found. The requested module 'virtual:reproduce' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from 'virtual:reproduce';
const { foo } = pkg;
at ModuleJob._instantiate (node:internal/modules/esm/module_job:124:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:190:5)
Node.js v18.15.0
Expected
No error.
What is the feature you are proposing to solve the problem?
Introduce .namedExports
when format is commonjs
diff --git a/import-hook.mjs b/import-hook.mjs
index c454228..63b2d12 100644
--- a/import-hook.mjs
+++ b/import-hook.mjs
@@ -10,7 +10,7 @@ export async function resolve(specifier, context, nextResolve) {
export async function load(url, context, nextLoad) {
if (url === VIRTUAL_FILEURL) {
- return { format: 'commonjs', shortCircuit: true, source: undefined }
+ return { format: 'commonjs', shortCircuit: true, source: undefined, namedExports: ['foo'] }
}
return await nextLoad(url, context)
What alternatives have you considered?
No response
@nodejs/loaders
Are you sure itβs not just that named exports arenβt found, but rather that the source is undefined? See https://github.com/nodejs/loaders#milestone-2-stability:~:text=Support%20loading%20source%20when%20the%20return%20value%20of%20load%20has%20format%3A%20%27commonjs%27
I've also run afoul of this, but in my case the source very much is there, it's just that I can't explicitly define in the __esModule way because they aren't known at load time (I'm doing dependency injection for tests). Is there any way to disable the static analysis?
There has been no activity on this feature request for 5 months. To help maintain relevant open issues, please add the https://github.com/nodejs/node/labels/never-stale label or close this issue if it should be closed. If not, the issue will be automatically closed 6 months after the last non-automated comment. For more information on how the project manages feature requests, please consult the feature request management document.
There has been no activity on this feature request and it is being closed. If you feel closing this issue is not the right thing to do, please leave a comment.
For more information on how the project manages feature requests, please consult the feature request management document.