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 {
Comment on lines +21 to +24
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.

Module.register?.(loaderUrl)
}
}