node icon indicating copy to clipboard operation
node copied to clipboard

resolve hook is not run for require

Open BadIdeaException opened this issue 1 year ago • 5 comments

Version

23.0.0

Platform

Linux hooks 6.8.0-47-generic #47~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Wed Oct  2 16:16:55 UTC 2 x86_64 x86_64 x86_64 GNU/Linux

Subsystem

customization hooks

What steps will reproduce the bug?

Set up a module resolution hook that replaces the core fs import with fake.js. Then try to import and require fs, respectively. When importing, fs is replaced as expected. When requiring, the hook is never run.

For the setup:

register.js:

import { register } from 'node:module';
register('./hook.js', import.meta.url);

hook.js:

import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

export async function resolve(specifier, context, nextResolve) {	
	const path = fileURLToPath(import.meta.url);
	const dir = dirname(path);
	
	if (/fs/.test(specifier)) specifier = join(dir, 'fake.js');

	return nextResolve(specifier, context);
};

fake.js:

export function readFileSync() { return 'foo'; }

Now we're ready for the money part:

index.js

import  {readFileSync} from 'fs';
import { fileURLToPath } from 'node:url';

console.log(readFileSync(fileURLToPath(import.meta.url), 'utf8')); // 'foo'

index.cjs:

const readFileSync = require('fs').readFileSync;

console.log(readFileSync(__filename, 'utf8')); // Prints out the source file

NB: type is set to module in package.json.

How often does it reproduce? Is there a required condition?

Can be reliably reproduced every time.

What is the expected behavior? Why is that the expected behavior?

Resolve hook should be run even for require, and replace fs with fake.js. The output should be foo.

I am basing this expectation off of the documentation (emphasis mine):

module#resolve

The resolve hook chain is responsible for telling Node.js where to find and how to cache a given import statement or expression, or require call.

section "enabling":

my-app.js can also be CommonJS. Customization hooks will run for any modules that it references via import (and optionally require).

What do you see instead?

node --import=./register.js index.js produces foo, as expected.

node --import=./register.js index.cjs prints out the source file - bad. Annotating the resolve hook with a console.log statement shows it is never run.

Additional information

I have asked about this on Stack Overflow but not received any answers.

BadIdeaException avatar Nov 16 '24 12:11 BadIdeaException

Hi, is there any news on this? Is there anything I can do to help?

It's completely blocking my project as is.

BadIdeaException avatar Jan 16 '25 09:01 BadIdeaException

They've recently added the registerHooks function. I tried replacing register with registerHooks in your example, and it worked. However, it still doesn't work with the register function.

mukkumayc avatar May 11 '25 17:05 mukkumayc

There is also one thing that behaves incorrectly in 22.14.0 but works as expected in 22.15.0. In 22.14.0, the register hook doesn't work not only for CommonJS modules but also for ES modules that are imported from a CommonJS module.

If you try to import index.js from index.cjs using require, here's what happens:

index.cjs:

const readFileSync = require('fs').readFileSync;

console.log(readFileSync(__filename, 'utf8')); // Prints out the source file
require('./index.js')

In 22.14.0, it prints this:

const readFileSync = require('fs').readFileSync;

console.log('index.cjs', readFileSync(__filename, 'utf8')); // Prints out the source file

require('./index.js');

import  {readFileSync} from 'fs';
import { fileURLToPath } from 'node:url';

console.log(readFileSync(fileURLToPath(import.meta.url), 'utf8')); // 'foo'

But in 22.15.0, it prints this:

const readFileSync = require('fs').readFileSync;

console.log('index.cjs', readFileSync(__filename, 'utf8')); // Prints out the source file

require('./index.js');

foo

mukkumayc avatar May 11 '25 17:05 mukkumayc

Hi, thanks for your answer and sorry for the delay. I can confirm that it works with registerHooks. Anyone know if that is intended behavior?

BadIdeaException avatar Jun 09 '25 18:06 BadIdeaException

I think you may have been hitting one of the documented caveats of module.register https://nodejs.org/api/module.html#caveat-in-the-asynchronous-load-hook - you'll need to override the source code & handle the null source for CommonJS modules for the asynchronous hooks to run for require() in child modules. If you switch to module.resiterHooks then there is not this caveat.

joyeecheung avatar Dec 06 '25 07:12 joyeecheung