Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/@tailwindcss-node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@ if (!process.versions.bun) {
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
//
// Not calling it means that while ESM dependencies don't get reloaded, the
// actual included files will because they cache bust directly via `?id=…`
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 })
} else {
Module.register?.(loaderUrl)
}
Comment on lines +22 to +26
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:

What is the correct API signature for Module.registerHooks in Node.js v26? Does it accept URLs/specifiers or function references?

💡 Result:

The correct API signature for Module.registerHooks in Node.js v26 is: module.registerHooks(options) where options is an Object with optional properties: - resolve {Function|undefined}: resolve hook function with signature resolve(specifier, context, nextResolve) - load {Function|undefined}: load hook function with signature load(url, context, nextLoad) It accepts direct function references for the hooks (synchronous, in-thread), not URLs or specifiers. URLs/specifiers are used by the deprecated module.register(specifier[, parentURL][, options]) API, which registers a module exporting async hooks run on a separate thread. This API was added in v23.5.0 / v22.15.0, reached release candidate status (1.2) by v25.4.0, and remains at that stability in v26 (still pre-release as of 2026-04-05). module.register is being deprecated in favor of registerHooks.

Citations:


🏁 Script executed:

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

Repository: tailwindlabs/tailwindcss

Length of output: 1240


🏁 Script executed:

fd -e mts -e mjs "esm-cache" packages/@tailwindcss-node/

Repository: tailwindlabs/tailwindcss

Length of output: 119


🏁 Script executed:

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

Repository: tailwindlabs/tailwindcss

Length of output: 784


🏁 Script executed:

cd packages/@tailwindcss-node && find . -name "esm-cache.loader*" -o -name "*.config.*" | grep -E "(mjs|mts|json|js)" | head -20

Repository: tailwindlabs/tailwindcss

Length of output: 94


🏁 Script executed:

cd packages/@tailwindcss-node && cat -n tsconfig.json package.json | head -60

Repository: tailwindlabs/tailwindcss

Length of output: 1722


Module.registerHooks does not support async hooks; use Module.register for both code paths.

Module.registerHooks (added in Node v26) accepts synchronous hook functions, whereas Module.register accepts a module specifier/URL that exports async hooks. The current code incorrectly passes a URL to registerHooks, but the actual problem runs deeper: the resolve hook in esm-cache.loader.mts is async and cannot be registered via registerHooks at all.

The correct fix is to use Module.register for both cases:

✅ Recommended fix: Use Module.register consistently
   let loaderUrl = pathToFileURL(localRequire.resolve('@tailwindcss/node/esm-cache-loader'))
-  if (Module.registerHooks) {
-    Module.registerHooks({ resolve: loaderUrl })
-  } else {
-    Module.register?.(loaderUrl)
-  }
+  Module.register?.(loaderUrl)

Module.register works in Node v18.19.0+ and remains available in v26 alongside the new registerHooks API, so this approach is forward-compatible.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (Module.registerHooks) {
Module.registerHooks({ resolve: loaderUrl })
} else {
Module.register?.(loaderUrl)
}
Module.register?.(loaderUrl)
🤖 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 22 - 26, The code
currently calls Module.registerHooks({ resolve: loaderUrl }) when
Module.registerHooks exists, but registerHooks only accepts synchronous hook
functions and cannot register the async resolve hook from esm-cache.loader.mts;
change the logic to always call Module.register(loaderUrl) (i.e., use
Module.register in both branches) so the async resolve hook exported by
esm-cache.loader.mts is registered correctly — update the conditional that
references Module.registerHooks to call Module.register(loaderUrl) instead,
keeping the loaderUrl and esm-cache.loader.mts export as-is.

}