Summary
@tailwindcss/vite@4.3.1 breaks Vite's tsconfig extends resolution when running under Deno with nodeModulesDir: "auto" and npm imports from deno.json.
The same reproduction repository:
- fails with
deno task build
- passes with Node via
./node_modules/.bin/vite build
- passes under Deno when Tailwind is pinned back to
4.3.0
Reproduction
Reproduction repository:
https://github.com/neunzehn86/tailwind-deno-registerhooks-repro.git
Steps:
git clone https://github.com/neunzehn86/tailwind-deno-registerhooks-repro.git
cd tailwind-deno-registerhooks-repro
deno task build
Actual Result
error during build:
[vite:build-html] failed to resolve "extends":"@vue/tsconfig/tsconfig.dom.json" in .../tsconfig.app.json
file: .../_dev/index.html
at resolveExtends (.../node_modules/.deno/vite@7.3.5/node_modules/vite/dist/node/chunks/config.js:5809:8)
at parseExtends (.../node_modules/.deno/vite@7.3.5/node_modules/vite/dist/node/chunks/config.js:5775:71)
...
In the original project, Vitest showed the nested cause more clearly:
Caused by: TypeError: Invalid URL: '/.../tsconfig.app.json'
❯ new URL ../ext:deno_web/00_url.js
❯ Pt ../node_modules/.deno/@tailwindcss+node@4.3.1/node_modules/@tailwindcss/node/dist/index.mjs
❯ De ../node_modules/.deno/@tailwindcss+node@4.3.1/node_modules/@tailwindcss/node/dist/index.mjs
❯ resolveExtends ../node_modules/.deno/vite@7.3.5/node_modules/vite/dist/node/chunks/config.js
Expected Result
Vite should resolve @vue/tsconfig/tsconfig.dom.json successfully under Deno, as it does:
- with
@tailwindcss/vite@4.3.0
- with Node using the same Deno-generated
node_modules
Environment
Observed locally:
deno 2.8.2
node v22.14.0
macOS arm64
Package versions in the reproduction:
@tailwindcss/vite 4.3.1
@tailwindcss/node 4.3.1
tailwindcss 4.3.1
vite 7.3.5
@vitejs/plugin-vue 6.0.7
@vue/tsconfig 0.8.1
typescript 5.9.3
vue 3.5.38
Node Comparison
After deno task build has generated node_modules, running Vite through Node succeeds:
./node_modules/.bin/vite build
Result:
vite v7.3.5 building client environment for production...
✓ built
Regression Boundary
Changing the reproduction to Tailwind 4.3.0 makes the Deno build pass:
"@tailwindcss/vite": "npm:@tailwindcss/vite@4.3.0",
"tailwindcss": "npm:tailwindcss@4.3.0"
This suggests the regression was introduced in 4.3.1, not the broader 4.3.x line.
Notes From Investigation
@tailwindcss/node@4.3.1 appears to introduce/use node:module.registerHooks when available:
if (!process.versions.bun) {
if (M.registerHooks) {
M.registerHooks({ resolve: De });
} else {
let e = M.createRequire(import.meta.url);
M.register?.(on(e.resolve("@tailwindcss/node/esm-cache-loader")));
}
}
In this local environment:
Deno 2.8.2: node:module.registerHooks exists
Node v22.14.0: node:module.registerHooks is undefined
So Tailwind takes the registerHooks path under Deno, but not under Node.
By contrast, @tailwindcss/node@4.3.0 still uses the older loader registration path:
let e = M.createRequire(import.meta.url);
M.register?.(on(e.resolve("@tailwindcss/node/esm-cache-loader")));
The registered resolve hook seems to receive a plain filesystem path as context.parentURL during Vite/tsconfck's createRequire(from).resolve(...) call. It then calls new URL(context.parentURL), which throws because context.parentURL is /.../tsconfig.app.json rather than a file://... URL.
Summary
@tailwindcss/vite@4.3.1breaks Vite's tsconfigextendsresolution when running under Deno withnodeModulesDir: "auto"and npm imports fromdeno.json.The same reproduction repository:
deno task build./node_modules/.bin/vite build4.3.0Reproduction
Reproduction repository:
Steps:
git clone https://github.com/neunzehn86/tailwind-deno-registerhooks-repro.git cd tailwind-deno-registerhooks-repro deno task buildActual Result
In the original project, Vitest showed the nested cause more clearly:
Expected Result
Vite should resolve
@vue/tsconfig/tsconfig.dom.jsonsuccessfully under Deno, as it does:@tailwindcss/vite@4.3.0node_modulesEnvironment
Observed locally:
Package versions in the reproduction:
Node Comparison
After
deno task buildhas generatednode_modules, running Vite through Node succeeds:Result:
Regression Boundary
Changing the reproduction to Tailwind
4.3.0makes the Deno build pass:This suggests the regression was introduced in
4.3.1, not the broader4.3.xline.Notes From Investigation
@tailwindcss/node@4.3.1appears to introduce/usenode:module.registerHookswhen available:In this local environment:
So Tailwind takes the
registerHookspath under Deno, but not under Node.By contrast,
@tailwindcss/node@4.3.0still uses the older loader registration path:The registered resolve hook seems to receive a plain filesystem path as
context.parentURLduring Vite/tsconfck'screateRequire(from).resolve(...)call. It then callsnew URL(context.parentURL), which throws becausecontext.parentURLis/.../tsconfig.app.jsonrather than afile://...URL.