Problem
The Bun adapter claims every TypeScript-shaped onLoad path and returns original source for paths the shared transform excludes. That prevents a later Bun plugin from handling declarations, node_modules sources, or another excluded match.
The adapter also has no build-boundary reset when it is used with Bun.build, even though that plugin builder exposes onStart. Bun runtime plugin builders in exact 1.3.14 do not expose the hook, so the lifecycle must remain feature-detected.
Evidence
packages/unplugin/src/bun.ts registers sourceFilePattern directly. Its loader then:
- reads the matched path;
- calls the raw Unplugin transform;
- returns the original contents when no transform result exists.
The raw plugin's transformInclude is the owner that excludes declarations and node_modules, but the Bun adapter does not ask it before reading or returning.
Exact Bun 1.3.14 build controls used a later loader that replaces a dependency with export default 42:
| Plugin order |
Result |
| Replacement plugin only |
42 |
Earlier broad loader returns undefined, then replacement |
42 |
| ttsc first with transforms disabled, then replacement |
1 |
The third result proves that ttsc completed the load with original contents and shadowed the later plugin.
The exact Bun build plugin builder exposed onStart; the exact runtime builder supplied to Bun.plugin did not. Calling onStart unconditionally in runtime setup throws.
A custom-namespace Bun build control remained composable with ttsc registered first. Namespace capture was investigated and rejected.
Consequence surface
- A downstream loader cannot transform an excluded dependency that happens to match the TypeScript extension filter.
- ttsc performs avoidable filesystem reads for files it will not transform.
Bun.build watch or repeated build sessions can retain a project cache across build boundaries, unlike the standard Unplugin adapters.
- Runtime registration must continue to work on Bun 1.3.14 without
onStart.
- Lazy option resolution in
bun-register must remain last-write-wins before the first load.
- Plugin-reported dependencies must remain harmless even though Bun runtime supplies no watch-registration channel.
Approach
Build the raw plugin lazily through one accessor. Before reading a path, call its transformInclude; return undefined when the file is excluded so Bun can continue to later loaders.
Feature-detect build.onStart. Where present, forward the build boundary to the raw plugin's buildStart hook so it clears the project cache. Where absent, register only onLoad.
Acceptance and verification
- An excluded
node_modules TypeScript path returns undefined without reading the file.
- A later Bun loader handles that path.
- An included source still receives the ttsc transform and the correct
ts or tsx loader.
- Bun runtime setup without
onStart remains valid.
- Bun build setup with
onStart clears a previously created raw cache at the next build boundary.
- Lazy explicit options in
bun-register retain current ordering and idempotence.
- Exact Bun 1.3.14 composition is rechecked in addition to stub tests.
- Run:
pnpm --filter @ttsc/unplugin build
pnpm --filter @ttsc/test-unplugin start -- --include=bun
pnpm --filter @ttsc/test-unplugin start
Coordination
Issue #969 reports Bun runtime startup, while #252 owns the shared validation amplification. This issue corrects Bun-specific composition and lifecycle behavior without changing the checker-backed project transform contract.
Problem
The Bun adapter claims every TypeScript-shaped
onLoadpath and returns original source for paths the shared transform excludes. That prevents a later Bun plugin from handling declarations,node_modulessources, or another excluded match.The adapter also has no build-boundary reset when it is used with
Bun.build, even though that plugin builder exposesonStart. Bun runtime plugin builders in exact 1.3.14 do not expose the hook, so the lifecycle must remain feature-detected.Evidence
packages/unplugin/src/bun.tsregisterssourceFilePatterndirectly. Its loader then:The raw plugin's
transformIncludeis the owner that excludes declarations andnode_modules, but the Bun adapter does not ask it before reading or returning.Exact Bun 1.3.14 build controls used a later loader that replaces a dependency with
export default 42:undefined, then replacementThe third result proves that ttsc completed the load with original contents and shadowed the later plugin.
The exact Bun build plugin builder exposed
onStart; the exact runtime builder supplied toBun.plugindid not. CallingonStartunconditionally in runtime setup throws.A custom-namespace Bun build control remained composable with ttsc registered first. Namespace capture was investigated and rejected.
Consequence surface
Bun.buildwatch or repeated build sessions can retain a project cache across build boundaries, unlike the standard Unplugin adapters.onStart.bun-registermust remain last-write-wins before the first load.Approach
Build the raw plugin lazily through one accessor. Before reading a path, call its
transformInclude; returnundefinedwhen the file is excluded so Bun can continue to later loaders.Feature-detect
build.onStart. Where present, forward the build boundary to the raw plugin'sbuildStarthook so it clears the project cache. Where absent, register onlyonLoad.Acceptance and verification
node_modulesTypeScript path returnsundefinedwithout reading the file.tsortsxloader.onStartremains valid.onStartclears a previously created raw cache at the next build boundary.bun-registerretain current ordering and idempotence.Coordination
Issue #969 reports Bun runtime startup, while #252 owns the shared validation amplification. This issue corrects Bun-specific composition and lifecycle behavior without changing the checker-backed project transform contract.