Skip to content

fix(node): use Module.registerHooks instead of deprecated Module.register for Node v26+#19907

Closed
cyphercodes wants to merge 1 commit into
tailwindlabs:mainfrom
cyphercodes:fix-module-register-deprecation
Closed

fix(node): use Module.registerHooks instead of deprecated Module.register for Node v26+#19907
cyphercodes wants to merge 1 commit into
tailwindlabs:mainfrom
cyphercodes:fix-module-register-deprecation

Conversation

@cyphercodes
Copy link
Copy Markdown

Description

Node.js v26 deprecated Module.register() in favor of Module.registerHooks(), causing a deprecation warning when using Tailwind CSS with Node v26:

[DEP0205] DeprecationWarning: `module.register()` is deprecated. Use `module.registerHooks()` instead.

This change uses the new Module.registerHooks() API when available (Node v26+), falling back to the old Module.register() API for older Node versions to maintain backward compatibility.

Changes

  • Check for Module.registerHooks first (new API in Node v26+)
  • Fall back to Module.register for older Node versions
  • Added comment explaining the version requirements

Related Issue

Fixes #19893

…ster for Node v26+

Node.js v26 deprecated Module.register() in favor of Module.registerHooks().
This change uses the new API when available, falling back to the old API
for older Node versions.

Fixes tailwindlabs#19893
@cyphercodes cyphercodes requested a review from a team as a code owner April 5, 2026 13:27
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 5, 2026

Walkthrough

The Node module hook registration logic in the Tailwind CSS Node package was refactored to use a tiered approach. The code now attempts to use Module.registerHooks with a resolve option when available, and falls back to the previous Module.register method if the newer API is unavailable. The loader URL is extracted into a local variable before being passed to either registration method. This change introduces 7 lines and removes 1 line of code, maintaining backward compatibility while supporting newer Node.js module hook APIs.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: updating to use Module.registerHooks instead of the deprecated Module.register for Node v26+, which aligns with the actual code changes.
Description check ✅ Passed The description is well-detailed and directly related to the changeset, explaining the deprecation warning, the solution, and backward compatibility approach.
Linked Issues check ✅ Passed The PR implements the core requirement from issue #19893: using Module.registerHooks() for Node v26+ while maintaining backward compatibility with older versions, eliminating the DEP0205 deprecation warning.
Out of Scope Changes check ✅ Passed All changes in the PR are directly related to fixing the Module.register deprecation issue; no out-of-scope modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/`@tailwindcss-node/src/index.ts:
- Around line 21-24: The code incorrectly attempts to use Module.registerHooks
with the async esm-cache-loader; remove the synchronous registration path and
the conditional that calls Module.registerHooks, and rely solely on
Module.register() with the async loader (the esm-cache-loader / Module.register
usage). Locate the block referencing Module.registerHooks and loaderUrl (the
import/resolution of '@tailwindcss/node/esm-cache-loader' and the if
(Module.registerHooks) branch) and delete the branch that calls
Module.registerHooks (and any passing of loaderUrl), leaving only the async
Module.register() registration flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 853d3ae4-7c45-4501-8adf-d6e089ca28c4

📥 Commits

Reviewing files that changed from the base of the PR and between e01946f and 10edd5a.

📒 Files selected for processing (1)
  • packages/@tailwindcss-node/src/index.ts

Comment on lines +21 to +24
let loaderUrl = pathToFileURL(localRequire.resolve('@tailwindcss/node/esm-cache-loader'))
if (Module.registerHooks) {
Module.registerHooks({ resolve: loaderUrl })
} else {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In Node.js v26 node:module, what is the exact signature of module.registerHooks()and what type is required for theresolve option?

💡 Result:

In Node.js v26, the exact signature of module.registerHooks from the node:module module is: js module.registerHooks(options) Where options is an Object with the following properties: - load {Function|undefined} (optional) The synchronous load hook function. See load hook documentation. Default: undefined. - resolve {Function|undefined} (optional) The synchronous resolve hook function. See resolve hook documentation. Default: undefined. The function returns an object with a deregister method to remove the registered hooks. The resolve option requires a Function type: function resolve(specifier, context, nextResolve) { ... }. It must be synchronous for registerHooks (as opposed to the async version in module.register). It receives a specifier {string}, context {Object}, and nextResolve {Function}, and returns {Object|Promise} (but synchronous in practice for this API). This API registers synchronous, in-thread hooks for customizing module resolution and loading for both ESM and CJS, added in v23.5.0 / v22.15.0, currently at Stability: 1.2 - Release candidate (per v25 docs; stable or higher by v26). Use via --import or --require for early registration, e.g.: js // register-hooks.js import { registerHooks } from 'node:module'; registerHooks({ resolve(specifier, context, nextResolve) { // Custom logic return nextResolve(specifier, context); } }); node --import ./register-hooks.js app.js Official docs: https://nodejs.org/api/module.html#moduleregisterhooksoptions (v25; v26 unchanged per canary/preview). Implementation: nodejs/node@b2e44a8079. Tracking: nodejs/node#56241.

Citations:


🏁 Script executed:

find . -name "*esm-cache*" -type f | head -20

Repository: tailwindlabs/tailwindcss

Length of output: 121


🏁 Script executed:

cat -n ./packages/@tailwindcss-node/src/esm-cache.loader.mts

Repository: tailwindlabs/tailwindcss

Length of output: 784


🏁 Script executed:

cat -n ./packages/@tailwindcss-node/src/index.ts

Repository: tailwindlabs/tailwindcss

Length of output: 1240


🏁 Script executed:

# Check Node.js documentation to understand the API design difference
# between registerHooks (sync) and register (async)
cat -n ./packages/@tailwindcss-node/src/index.ts | head -27

Repository: tailwindlabs/tailwindcss

Length of output: 1240


Module.registerHooks cannot be used with the async loader approach; remove the sync registration path

The esm-cache.loader is designed as an async loader for Module.register() (line 25), not for Module.registerHooks(). These APIs are incompatible—registerHooks requires synchronous hook functions, but the exported resolve hook is async. Passing loaderUrl (line 23) is incorrect, but the proposed import-and-pass approach also fails because the function is async. Either create separate synchronous hooks for registerHooks or remove the conditional and rely on Module.register() alone.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@tailwindcss-node/src/index.ts around lines 21 - 24, The code
incorrectly attempts to use Module.registerHooks with the async
esm-cache-loader; remove the synchronous registration path and the conditional
that calls Module.registerHooks, and rely solely on Module.register() with the
async loader (the esm-cache-loader / Module.register usage). Locate the block
referencing Module.registerHooks and loaderUrl (the import/resolution of
'@tailwindcss/node/esm-cache-loader' and the if (Module.registerHooks) branch)
and delete the branch that calls Module.registerHooks (and any passing of
loaderUrl), leaving only the async Module.register() registration flow.

@afurm
Copy link
Copy Markdown

afurm commented Apr 11, 2026

Good migration to registerHooks. One thing to consider - if both registerHooks fails and register falls back, should we warn? Currently silent failure could be hard to debug. Also, any reason not to use feature detection for Node 26+ and skip register entirely?

Copy link
Copy Markdown
Contributor

@WarningImHack3r WarningImHack3r left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This AI-made PR doesn't compile. I made a PR that should work much better and be much more comprehensive at #20028

let localRequire = Module.createRequire(import.meta.url)

// `Module#register` was added in Node v18.19.0 and v20.6.0
// `Module#registerHooks` was added in Node v26.0.0 and is the preferred API
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// `Module#registerHooks` was added in Node v26.0.0 and is the preferred API
// `Module#registerHooks` was added in Node v23.5.0 and v22.15.0 and is the preferred API since v25.9.0,
// runtime-deprecating `Module#register` since v26

Source: https://nodejs.org/docs/latest/api/module.html#moduleregisterhooksoptions

Module.register?.(pathToFileURL(localRequire.resolve('@tailwindcss/node/esm-cache-loader')))
let loaderUrl = pathToFileURL(localRequire.resolve('@tailwindcss/node/esm-cache-loader'))
if (Module.registerHooks) {
Module.registerHooks({ resolve: loaderUrl })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect usage. As per the docs, the exported resolve function from esm-cache.loader.mts can be used as-is here, no need for a loader URL anymore.

RobinMalfait added a commit that referenced this pull request May 11, 2026
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

Fix this warning when building apps with TailwindCSS with Node 26+:
```
(node:25346) [DEP0205] DeprecationWarning: `module.register()` is deprecated. Use `module.registerHooks()` instead.
    at node:internal/util:129:11
    at Module.register (node:internal/modules/esm/loader:969:3)
    at file:///Users/antoine/Developer/my-app/node_modules/.pnpm/@tailwindcss+node@4.3.0/node_modules/@tailwindcss/node/dist/index.mjs:18:214
    at ...
```

This PR:
- Correctly prefers using `Module#registerHooks` instead of
`Module#register` by checking its availability at runtime
- Adjusts the exports of the hooks’ file by creating a synchronous
version for the new API
- Remove now unused exports from the `package.json`, relying on the
[recommended usage in the
docs](https://nodejs.org/docs/latest/api/module.html#registration-of-asynchronous-customization-hooks)
for the `Module#register` calls

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->

I'd be happy to ensure this works correctly on my end before merging
this, but it's not as trivial to test locally as a logic fix. Can you
guide me through what I need to do to test this?

I extensively based my change on the docs, following those guides:
- [Fixing imports for
`Module#register`](https://nodejs.org/docs/latest/api/module.html#registration-of-asynchronous-customization-hooks)
- [Using the new `Module#registerHooks`
function](https://nodejs.org/docs/latest/api/module.html#registration-of-synchronous-customization-hooks)

(and other more detailed sections of the same docs page)

---

Fixes #19893
Closes #19907

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DEP0205] DeprecationWarning: module.register() is deprecated. Use module.registerHooks() instead.

3 participants