bun icon indicating copy to clipboard operation
bun copied to clipboard

Generate type declarations during `bun build`

Open kynnyhsap opened this issue 2 years ago • 15 comments

What is the problem this feature would solve?

I'd like to build my library written in typescript with bun build (both for dev and prod), but there's no point in doing so if I or my library's users won't have type declarations. I am developing this library in the ecosystem of other projects, so often it looks like npm run dev:my-library && npm run dev:my-frontend. This is a common pattern for developing in monorepos. My current solution is tsc, which is slow in watch mode and has its drawbacks.

What is the feature you are proposing to solve the problem?

--type-declarations flag for build command to generate types along with js files.

What alternatives have you considered?

I see no workaround.

kynnyhsap avatar Sep 12 '23 19:09 kynnyhsap

This is out of scope for Bun. Currently tsc is the only tool in existence for reliably generating declaration files. Doing it right would require re-implementing the entire TypeScript compiler & type inference infrastructure unfortunately.

This may be possible in some limited cases eventually.

colinhacks avatar Sep 12 '23 23:09 colinhacks

@colinhacks I would have to disagree with you, the whole point of bun which is pointed out it to be a compiler, typescript, test runner and more. If you haven't implemented one of the main features of typescript which is types, how can you expect people to build a typescript library with it, as there will never be any type definitions.

And also Im pretty sure on https://bun.sh/blog/bun-v1.0 it litterally states that it is a replacement for tsc apart from typechecking, typechecking is fine but the actual usage of compiled types for development is a massive dealbreaker for me and probably will be for others.

Due to the reasons most people would want to move away from tsc is because speed but your just telling us to use it anyway.

Edit: because of this inability to generate types using bun, bun therefore isnt a drop in replacement for node and tsc, and actually breaks the functionality of libs.

The-Code-Monkey avatar Sep 14 '23 08:09 The-Code-Monkey

It seems like given this stance, library authors who want to provide types can't really use bun build for their libs/packages (every author except DHH) 🤷 .

Not a dealbreaker (can still use other parts of bun), but worth noting.

tomquirk avatar Sep 14 '23 10:09 tomquirk

My expectation is that Isolated Declarations will eventually ship in TypeScript and then some number of weeks or months later, we will ship support for emitting TypeScript types in bun build(for projects leveraging isolated declarations)

Jarred-Sumner avatar Sep 14 '23 10:09 Jarred-Sumner

👍 Thanks for clarification @Jarred-Sumner will this be something that bun will automatically do, if it notices the repo is using typescript

The-Code-Monkey avatar Sep 14 '23 14:09 The-Code-Monkey

When isolated declarations is stabilized, I think Bun should support that. It'll let us generate the d.ts files quickly, in exchange for some requirements on the source code (have to annotate each exported fn with a return type)

For now, a third-party tool i use on my own projects is https://github.com/timocov/dts-bundle-generator. Someone from the community even made a plugin for it: https://github.com/wobsoriano/bun-plugin-dts

To me, that's more of a workaround than a real solution (it's quite slow), but it works for now.

paperclover avatar Sep 19 '23 03:09 paperclover

As a workaround, you can generate the type declaration with just tsc in an additional command in your package.json.

Eg:

"scripts": {
  "build": "bun build ... && bun run build:declaration",
  "build:declaration": "tsc --emitDeclarationOnly --project tsconfig.types.json"
}

So you have the build followed by the generation of declarations that can be added to the build command or executed separately.

joaopaulomoraes avatar Sep 20 '23 11:09 joaopaulomoraes

As a workaround, you can generate the type declaration with just tsc in an additional command in your package.json.

Eg:

"scripts": {
  "build": "bun build ... && bun run build:declaration",
  "build:declaration": "tsc --emitDeclarationOnly --project tsconfig.types.json"
}

So you have the build followed by the generation of declarations that can be added to the build command or executed separately.

Is there any advantage to doing this as opposed to just using tsc for building? It seems like it would be slower to have two different process for building that have to run in sequence.

danielfalk avatar Dec 01 '23 17:12 danielfalk

https://github.com/wobsoriano/bun-plugin-dts

JacobWeisenburger avatar Jan 30 '24 00:01 JacobWeisenburger

https://github.com/ryoppippi/bun-plugin-isolated-decl

ryoppippi avatar Jul 04 '24 22:07 ryoppippi

Could bun operate tsc internally and build a declaration file of the bundle like tsup does?

https://tsup.egoist.dev/#generate-declaration-file

tsupbun
export default defineConfig({
  entry: ["src/index.ts"],
  format: ["esm", "cjs"],
  splitting: false,
  sourcemap: false,
  minify: true,
  clean: true,
  dts: true,
  platform: "browser",
});
await Bun.build({
  entrypoints: ["src/index.ts"],
  format: "esm", // CJS — missing
  splitting: false,
  sourcemap: "none",
  minify: true,
  outdir: "dist",
  // dts — missing
  target: "browser",
});

@JacobWeisenburger https://github.com/wobsoriano/bun-plugin-dts

That plugin does not work at all:

[local] ➜  merge-sx git:(try-bun) ✗ bun build.ts
354 | }
355 | exports.getRootSourceFile = getRootSourceFile;
356 | function getNodeOwnSymbol(node, typeChecker) {
357 |     const nodeSymbol = typeChecker.getSymbolAtLocation(node);
358 |     if (nodeSymbol === undefined) {
359 |         throw new Error(`Cannot find symbol for node "${node.getText()}" in "${node.parent.getText()}" from "${node.getSourceFile().fileName}"`);
                    ^
error: Cannot find symbol for node "window" in "window.matchMedia"

@ryoppippi https://github.com/ryoppippi/bun-plugin-isolated-decl

That one does work, with ~~minor issue~~ (fixed). And it's also worth it to note that it wipes JSDoc out at the moment.

RobinTail avatar Jul 05 '24 07:07 RobinTail

My expectation is that Isolated Declarations will eventually ship in TypeScript and then some number of weeks or months later, we will ship support for emitting TypeScript types in bun build(for projects leveraging isolated declarations)

It looks like that feature has been cancelled. Hopefully there's an updated plan for Bun?

It seems to me like the current intended audience for Bun is end-user app developers. If you're writing a server, service, task, etc, then it's amazing. But it's not currently particularly viable for library developers, since it can't emit types to be used down-stream and there's no built-in publish command.

So far, I see three options, each with a distinct disadvantage:

  • Add a tsc --emitDeclarationOnly to the build script. This is probably fine, but the whole reason to use Bun in a library is to avoid configuring and running tsc.
  • Use a community plugin like bun-plugin-dts or bun-plugin-isolated-decl. With absolutely no disrespect intended to the plugin authors, they're just too new and under-used to be trusted for anything sensitive yet. At <1200 and <200 downloads per week respectively, the "many eyes on" trust that open-source relies on just hasn't been built yet. Especially for something that directly modifies output code at publish time. The risk of supply-chain attacks is just too high.
  • Use NPM and TSC for library development and Bun only for end products. This misses out on a lot of benefits of Bun. It also introduces some risk and fragility for multi-component apps, since the dependency components will be tested in a different runtime.

My ideal solution for building libraries would be two-fold: an enhancement to bun build to automatically builds types (however it can, even if that means running tsc under the covers), and an bun publish command to replace NPM entirely. (The latter is in at least more than one other issue, so doesn't need to be discussed here.)

jftanner avatar Jul 11 '24 20:07 jftanner

It looks like that feature has been cancelled.

No, it was re-designed from the Pr we linked earlier, and is shipped in TypeScript 5.5.

https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#isolated-declarations

been keeping my eyes on this and the doors are open for us to start working on us since typescript has defined how they will work. i just currently have a bunch of other priorities before this one.

for now tsc --emitDeclarationOnly --isolatedDeclarations is the best choice. i kind of recommend having typescript configured to some degree regardless, as it is important to perform type checking (something only tsc can do). i recommend --isolatedDeclarations so that when bun build does types, it will be a drop-in replacement.

paperclover avatar Jul 11 '24 20:07 paperclover

No, it was re-designed from the Pr we linked earlier, and is shipped in TypeScript 5.5. Oh, fantastic!

This is all very good news. Thank you for the update. And thank you all, as well, for the project as a whole. It's a monumental undertaking, and what you've delivered already is very impressive.

jftanner avatar Jul 11 '24 20:07 jftanner

for now tsc --emitDeclarationOnly --isolatedDeclarations is the best choice.

can it bundle declarations by an entrypoint (make a single DTS file from many TS, similar to JS bundle), @paperdave ?

RobinTail avatar Jul 11 '24 21:07 RobinTail

When isolated declarations is stabilized, I think Bun should support that. It'll let us generate the d.ts files quickly, in exchange for some requirements on the source code (have to annotate each exported fn with a return type)

For now, a third-party tool i use on my own projects is https://github.com/timocov/dts-bundle-generator. Someone from the community even made a plugin for it: https://github.com/wobsoriano/bun-plugin-dts

To me, that's more of a workaround than a real solution (it's quite slow), but it works for now.

oxc supports that without having to add explicit return types on functions: https://github.com/unplugin/unplugin-isolated-decl though it removes comments sadly: image

huseeiin avatar Aug 12 '24 19:08 huseeiin

I use oxc-transform without additional packages to achieve this with the following plugin snippet for generating d.ts files with isolated declarations.

The downside is that you must specify both outdir and root in the build configuration to make it easier to replace paths and avoid unexpected behavior.

import type { BunPlugin } from "bun";
import { isolatedDeclaration } from "oxc-transform";

function getDtsBunPlugin(): BunPlugin {
	const wroteTrack = new Set<string>();
	return {
		name: "oxc-transform-dts",
		setup(builder) {
			if (builder.config.root && builder.config.outdir) {
				const rootPath = Bun.pathToFileURL(builder.config.root).pathname;
				const outPath = Bun.pathToFileURL(builder.config.outdir).pathname;
				builder.onStart(() => wroteTrack.clear());
				builder.onLoad({ filter: /\.ts$/ }, async (args) => {
					if (args.path.startsWith(rootPath) && !wroteTrack.has(args.path)) {
						wroteTrack.add(args.path);
						const { code } = isolatedDeclaration(
							args.path,
							await Bun.file(args.path).text(),
						);
						await Bun.write(
							args.path
								.replace(new RegExp(`^${rootPath}`), outPath)
								.replace(/\.ts$/, ".d.ts"),
							code,
						);
					}
					return undefined;
				});
			}
		},
	};
}

Here is how I use it with Bun.build:

await Bun.$`rm -rf dist`;
const result = await Bun.build({
	entrypoints: ["src/index.ts"],
	root: "src",
	outdir: "dist",
	minify: true,
	splitting: true,
	plugins: [getDtsBunPlugin()],
});
if (!result.success) {
	for (const log of result.logs) {
		console.error(log);
	}
	return;
}

binhtddev avatar Jan 16 '25 09:01 binhtddev

I wonder if bun maintainers could chime in on whether oxc-transform or tsc --emitDeclarationOnly could be shoehorned into a --type-declarations flag for the build command until something custom is developed (possibly typescript 5.5+ related).

After using bun for a few months, generating type declarations is the only feature I've found myself needing to rely on external libraries. On that note, much love to the Bun team for creating the world's first easy to use JavaScript experience.

Archmonger avatar Jan 17 '25 08:01 Archmonger

My expectation is that Isolated Declarations will eventually ship in TypeScript and then some number of weeks or months later, we will ship support for emitting TypeScript types in bun build(for projects leveraging isolated declarations)

isolatedDeclarations has already landed in TypeScript 5.5. Any progress on this, @Jarred-Sumner @paperclover?

arshad-yaseen avatar Apr 28 '25 08:04 arshad-yaseen

I'm a library writer and I'm writing libraries using bun. I admit that bun not producting type declaration files is a bummer, and I'm using tsc for this which is annoying because I have to deal with tsc's quirks.

That said, my toolchain is a LOT simpler this way.

Ultimately, IMO, the correct solution long term is for W3C to add types to Javascript proper so we can stop using Typescript which is an impedance mismatch for Javascript.

tonioloewald avatar Apr 28 '25 19:04 tonioloewald

I agree with your request but there is an obvious workaround, and that's to use tsc.

I'm already building my libraries with type declarations using bun. It's annoying because I need to use tsc to generate the type declarations but I already need to do quite a bit of work to produce multiple distributable versions and it's still simpler than using parcel (which is what I used previously) or rollup and uglify (which is what I used before that).

tonioloewald avatar Jun 02 '25 05:06 tonioloewald

it seems that isolated declarations has been abandoned, maybe bun could just add a CLI flag to the build command that simply uses tsc to generate type declaration files?

marcospgp avatar Jun 10 '25 11:06 marcospgp

one issue I've run into generating types with tsc is that unlike bun it won't rewrite import paths based on your aliases specified in tsconfig

for example having this in your tsconfig.json's compilerOptions:

    // Allow using @ to represent src folder.
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }

will cause bun to rewrite

import { something } from "@/components/something.tsx"

into

import { something } from "src/components/something.tsx"

but tsc will just ignore it and keep the alias format with the @, which will cause .d.ts files to be wrong

for context I'm generating types with

tsc --emitDeclarationOnly --project tsconfig.types.json --outdir dist

and building my project with

bun build src/index.ts --outdir dist

marcospgp avatar Jun 15 '25 14:06 marcospgp

If you need to install tsc to generate. d.ts files, I don't know the reason for using Bun. build.

Strongly recommend respecting tsconfig configuration and generating type files. This completely hinders the use of Bun. build in monorepo.

It can be very painful when we build shared packages for other apps without type files.

medz avatar Oct 13 '25 12:10 medz