Repository navigation
ERR_INVALID_RETURN_PROPERTY_VALUE with load hook and commonjs #50435
Description
Version
v20.9.0
Platform
Linux abulia 6.5.0-2-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.5.6-1 (2023-10-07) x86_64 GNU/Linux
Subsystem
No response
What steps will reproduce the bug?
-
Create the following files:
register-hooks.mjs
import { register } from 'node:module'; register( './hooks.mjs', import.meta.url );hooks.mjs
import { readFileSync } from 'node:fs'; export async function load(url, context, nextLoad) { if ( context.format !== 'commonjs' && context.format !== 'module' ) { return nextLoad(url, context); } // This might in practice be a check for `node_modules`. if ( url.endsWith( '/bar.cjs' ) ) { return nextLoad(url, context); } // Do some transformation of the source here. const source = readFileSync(new URL(url)); return { format: context.format, source, shortCircuit: true, }; }foo.cjs
const bar = require( './bar.cjs' ); bar.hello();bar.cjs
function hello() { console.log( "hello, world" ); } module.exports = { hello }; -
Run
node --import ./register-hooks.mjs foo.cjs
How often does it reproduce? Is there a required condition?
Always.
What is the expected behavior? Why is that the expected behavior?
Script runs, printing "hello, world".
Because that's what this very simple code does. 🤷
What do you see instead?
node:internal/errors:497
ErrorCaptureStackTrace(err);
^
TypeError [ERR_INVALID_RETURN_PROPERTY_VALUE]: Expected array buffer, or typed array to be returned for the "source" from the "transformSource" function but got null.
at new NodeError (node:internal/errors:406:5)
at assertBufferSource (node:internal/modules/esm/translators:84:9)
at stringify (node:internal/modules/esm/translators:94:3)
at createCJSModuleWrap (node:internal/modules/esm/translators:219:12)
at ModuleLoader.<anonymous> (node:internal/modules/esm/translators:267:10)
at callTranslator (node:internal/modules/esm/loader:273:14)
at ModuleLoader.<anonymous> (node:internal/modules/esm/loader:277:24)
at new ModuleJob (node:internal/modules/esm/module_job:65:26)
at #createModuleJob (node:internal/modules/esm/loader:290:17)
at ModuleLoader.getJobFromResolveResult (node:internal/modules/esm/loader:248:34) {
code: 'ERR_INVALID_RETURN_PROPERTY_VALUE'
}
Node.js v20.9.0
Additional information
This seems to have been introduced in v20.6.0, as v20.5.1 is not affected. Likely it's part of the enhancements to the load hook added in that version.
What seems to be happening is that once one commonjs file returns a non-null source, any sub-requires of that file will fail if null is returned for source despite that being the default behavior for the passed-in nextLoad function.
I ran into this while trying to figure out why playwright started giving me this error when I tried to update from Node 18 to Node 20. The loader can be found here, and the bug was being triggered by a mjs config file that imports a cjs file that requires something out of node_modules. They've also got some other stuff going on in there, if I hack that loader to always fill in source then it winds up hanging eventually at this line (it seems that sends a message on the MessagePort that the other side never reacts to?).
Considering the comment I see in the doc that "This behavior for nullish source is temporary — in the future, nullish source will not be supported", I suppose you might decide this isn't a bug, saying that any loader that "opts in" to this behavior should carry it all the way through.
Activity
aduh95 commentedon Nov 5, 2023
Before 20.6.0, the
sourcevalue was completely ignored whenformatwas'commonjs'. As of 20.6.0, this is no longer the case, but one of the trade offs is that you need to supply the source for all the CJS modules. I agree it’s unfortunate that the default load has to returnnullfor backward compatibility; if someone had an idea how to improve the DX, please share./cc @nodejs/loaders
anomiex commentedon Nov 6, 2023
This statement does not seem to match the documentation for 20.x.
In the column for "Acceptable types for
sourcereturned byload" for "commonjs" it saysThere's also extended discussion below about the behavior:
If nothing else, perhaps it should mention in there that there's no mixing-and-matching, a
loadhook must returnnullorundefinedfor all'commonjs'modules or for none of them? And possibly explicitly call out that in the latter casenextLoad's return value needs to have asourcefilled in?GeoffreyBooth commentedon Nov 6, 2023
As a stopgap, yes, the docs should at least document the current state of affairs until we can improve it.
mfucci-medable commentedon Nov 8, 2023
I am running into the same issue for the same reasons: Playwright config and upgrading to node v20.9
The app is using esm app and the Playwright config references a commonjs common config.
Per this thread, I am unclear what is the suggested stopgap:
=> I fail to see how updating the doc is a stopgap for the issue unless I don't understand something.
I am not clear as well if this is a bug or not that needs to be addressed in Playwright, or if by any chance I can modify something in my code..
Right now, downgrading node seems to be my only option...
mfucci-medable commentedon Nov 8, 2023
Nevermind: I turned the common config into a hybrid ESM / CommonJs lib and now the problem above is resolved for the ESM app and the common config is still working for all other CommonJs apps...
=> seems like the issue mentioned here only happens when ESM and CommonJs is mixed
anomiex commentedon Nov 11, 2023
The issue happens if the load hook returns a non-null/undefined
sourcefor one commonjs file, which thenrequires another commonjs file for which the load hook tries to return a null/undefinedsource.Playwright's current loader does this when the first commonjs file is not inside
node_modulesand the second is.lizthegrey commentedon Nov 12, 2023
Thanks for the minimal repro, I was wondering why Yarn PnP chained to Datadog IITM (wrapped by OTel-JS) was not working correctly post Node 20.6.
esm: fallback to `getSource` when `load` returns nullish `source`
esm: fallback to `getSource` when `load` returns nullish `source`
esm: fallback to `getSource` when `load` returns nullish `source`
esm: fallback to `getSource` when `load` returns nullish `source`
lizthegrey commentedon Dec 14, 2023
Appears this will be fixed in 20.11.0 per #51124 and fbdf291 making it into the 20.11 candidate
esm: fallback to `getSource` when `load` returns nullish `source`
esm: fallback to `getSource` when `load` returns nullish `source`
lizthegrey commentedon Jan 13, 2024
Confirmed fixed by the backport. 20.11 works flawlessly now with yarn pnp and with datadog iitm