[written w/ claude]
Summary
@kobalte/solidbase@0.6.3's published dist/default-theme/ ships .jsx files whose internal imports reference .js extensions. Vite ≤ 7 (esbuild resolver) silently substituted .js → .jsx; Vite 8 + Rolldown does not. Consumers that install SolidBase from npm and use default-theme cannot build on the stack SolidBase itself targets.
Reproduction
Install @kobalte/solidbase@0.6.3, vite@8, the pkg.pr.new SolidStart@2080 build, then:
// vite.config.ts
import { createSolidBase } from "@kobalte/solidbase/config"
import defaultTheme from "@kobalte/solidbase/default-theme"
const solidBase = createSolidBase(defaultTheme)
export default defineConfig({ plugins: [solidBase.plugin({ /* … */ }), /* … */] })
Build fails:
[UNRESOLVED_IMPORT] Could not resolve './components/Preview.js' in
node_modules/@kobalte/solidbase/dist/default-theme/mdx-components.jsx
[UNRESOLVED_IMPORT] Could not resolve './context.js' in
node_modules/@kobalte/solidbase/dist/default-theme/mdx-components.jsx
[UNRESOLVED_IMPORT] Could not resolve '../client/index.js' in
node_modules/@kobalte/solidbase/dist/default-theme/Layout.jsx
The referenced files exist on disk as Preview.jsx, context.jsx, ../client/index.jsx — only the import specifiers say .js. resolve.extensionAlias: { ".js": [".js", ".jsx"] } doesn't help on Vite 8 / Rolldown.
Root cause
The mismatch is a side effect of two tsconfig choices interacting.
The TS convention. With "moduleResolution": "node16", TypeScript requires source files to write the import path as it will exist at runtime. So in src/default-theme/mdx-components.tsx, when it imports its neighbor src/default-theme/context.tsx, the import is written:
import { ... } from "./context.js"
That .js isn't referring to anything in dist/ — the source is referring to its own future post-build name. TS type-checks by looking next door at context.tsx; the .js is purely about what the file will be called after compilation.
The twist. The convention assumes context.tsx compiles to context.js. But SolidBase sets "jsx": "preserve" so TS deliberately leaves JSX intact for the downstream Solid plugin to handle. The emitted file is context.jsx, not context.js. TS doesn't rewrite import strings, so in dist/:
- file on disk:
context.jsx
- import inside its sibling:
./context.js
Who used to paper over it. Vite ≤ 7's esbuild resolver tried .jsx automatically when .js didn't resolve, hiding the bug. Vite 8 + Rolldown doesn't, so the unresolved ./context.js becomes a hard build error.
Why this wasn't caught
There is no integration test that builds a consumer app against the published package. Every existing path bypasses dist/:
tests/default-theme/ has two unit specs (badges, preview) that import from source.
docs/ imports default-theme from ../src/default-theme (workspace .tsx).
examples/solid-docs is a submodule of solid-docs, which ships its own theme without extends: defaultTheme — so it never loads dist/default-theme/*.jsx.
We could prevent this class of regression by adding a smoke test that packs the tarball, installs it into a minimal Vite app fixture, and runs vite build.
Suggested fix
Switch tsconfig.json to "moduleResolution": "bundler" and update source imports from ./foo.js → ./foo.jsx. Under bundler, TS accepts the actual on-disk extension, so dist/ becomes self-consistent (.jsx files importing .jsx) with no codemod and no reliance on bundler extension fallback.
Workaround for downstream consumers
A small Vite plugin that intercepts .js imports originating inside @kobalte/solidbase and falls back to the sibling .jsx file:
import { existsSync } from "node:fs"
import { dirname, extname, resolve as resolvePath } from "node:path"
import { fileURLToPath } from "node:url"
import type { Plugin } from "vite"
export function solidbaseJsxFallback(): Plugin {
return {
name: "solidbase-jsx-fallback",
enforce: "pre",
resolveId(source, importer) {
if (!importer) return null
if (!importer.includes("@kobalte/solidbase")) return null
if (extname(source) !== ".js") return null
const importerPath = importer.startsWith("file://")
? fileURLToPath(importer)
: importer
const absolute = resolvePath(dirname(importerPath), source)
const jsxCandidate = absolute.replace(/\.js$/, ".jsx")
if (existsSync(jsxCandidate)) return jsxCandidate
return null
},
}
}
Add solidbaseJsxFallback() first in your plugins array; it'll patch the resolver until the upstream tsconfig change ships.
[written w/ claude]
Summary
@kobalte/solidbase@0.6.3's publisheddist/default-theme/ships.jsxfiles whose internal imports reference.jsextensions. Vite ≤ 7 (esbuild resolver) silently substituted.js → .jsx; Vite 8 + Rolldown does not. Consumers that install SolidBase from npm and usedefault-themecannot build on the stack SolidBase itself targets.Reproduction
Install
@kobalte/solidbase@0.6.3,vite@8, the pkg.pr.new SolidStart@2080 build, then:Build fails:
The referenced files exist on disk as
Preview.jsx,context.jsx,../client/index.jsx— only the import specifiers say.js.resolve.extensionAlias: { ".js": [".js", ".jsx"] }doesn't help on Vite 8 / Rolldown.Root cause
The mismatch is a side effect of two tsconfig choices interacting.
The TS convention. With
"moduleResolution": "node16", TypeScript requires source files to write the import path as it will exist at runtime. So insrc/default-theme/mdx-components.tsx, when it imports its neighborsrc/default-theme/context.tsx, the import is written:That
.jsisn't referring to anything indist/— the source is referring to its own future post-build name. TS type-checks by looking next door atcontext.tsx; the.jsis purely about what the file will be called after compilation.The twist. The convention assumes
context.tsxcompiles tocontext.js. But SolidBase sets"jsx": "preserve"so TS deliberately leaves JSX intact for the downstream Solid plugin to handle. The emitted file iscontext.jsx, notcontext.js. TS doesn't rewrite import strings, so indist/:context.jsx./context.jsWho used to paper over it. Vite ≤ 7's esbuild resolver tried
.jsxautomatically when.jsdidn't resolve, hiding the bug. Vite 8 + Rolldown doesn't, so the unresolved./context.jsbecomes a hard build error.Why this wasn't caught
There is no integration test that builds a consumer app against the published package. Every existing path bypasses
dist/:tests/default-theme/has two unit specs (badges,preview) that import from source.docs/imports default-theme from../src/default-theme(workspace.tsx).examples/solid-docsis a submodule of solid-docs, which ships its own theme withoutextends: defaultTheme— so it never loadsdist/default-theme/*.jsx.We could prevent this class of regression by adding a smoke test that packs the tarball, installs it into a minimal Vite app fixture, and runs
vite build.Suggested fix
Switch
tsconfig.jsonto"moduleResolution": "bundler"and update source imports from./foo.js→./foo.jsx. Underbundler, TS accepts the actual on-disk extension, sodist/becomes self-consistent (.jsxfiles importing.jsx) with no codemod and no reliance on bundler extension fallback.Workaround for downstream consumers
A small Vite plugin that intercepts
.jsimports originating inside@kobalte/solidbaseand falls back to the sibling.jsxfile:Add
solidbaseJsxFallback()first in yourpluginsarray; it'll patch the resolver until the upstream tsconfig change ships.