Skip to content

Bun adapter: load hook cannot override the inferred Loader #600

@81reap

Description

@81reap

Environment

unplugin@3.0.0 + bun@1.3.13

Reproduction

import { createBunPlugin } from 'unplugin'

await Bun.write(
  './entry.ts',
  "import Hello from 'virtual:component'; console.log(<Hello />);\n",
)

const plugin = createBunPlugin(() => ({
  name: 'jsx-virtual',
  resolveId(id) {
    return id === 'virtual:component' ? id : null
  },
  load(id) {
    if (id === 'virtual:component') {
      return 'export default () => <h1>hi</h1>'
    }
    return null
  },
}))()

await Bun.build({
  entrypoints: ['./entry.ts'],
  plugins: [plugin],
})

Describe the bug

In the Bun adapter, the Loader for a load hook's returned contents is inferred from path.extname(id) via guessLoader (src/bun/utils.ts). The result is passed as the loader field on Bun's onLoad return.

This is fine for 'file' namespace loads where the id is a real file path, but for virtual modules the id has no meaningful extension (or carries a query suffix that confuses extname). Bun then falls back to js, which fails when the plugin's returned contents are JSX/TSX/CSS/etc.

Plugins have no way to tell unplugin "this content is tsx" — the existing LoadResult type only carries code and map. esbuild's own onLoad accepts { contents, loader }; unplugin's Bun adapter should let plugins pass the same hint.

Additional context

No response

Logs

$ bun run 02-loader.ts 
1 | export default () => <h1>hi</h1>
                         ^
error: Unexpected <
    at virtual:component:1:22

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions